一、引言及介绍

近期在开发中用到了metadata-extractor-xxx.jar 和 xmpcore-xxx.jar这个玩意, 索性查阅大量文章了解学习,来分享分享。

本身工作也是常常和处理大图片打交道,摸索摸索也是多多益善。

首先介绍一下什么是EXIF。EXIF是 Exchangeable Image File 的缩写,这是一种专门为数码相机照片设定的格式。这样的格式能够用来记录数字照片的属性信息,如相机的品牌及型号、相片的拍摄时间、拍摄时所设置的光圈大小、快门速度、ISO等信息。除此之外它还能够记录拍摄数据,以及图片格式化方式。这样就能够输出到兼容EXIF格式的外设上。如照片打印机等。

眼下最常见的支持EXIF信息的图片格式是JPG。非常多的图像工具都能够直接显示图片的EXIF信息,包括如今的一些著名的相冊站点也提供页面用于显示照片的EXIF信息。本文介绍Java怎样读取图像的EXIF信息,包括怎样依据EXIF信息对图像进行调整以便适合用户浏览。

用BufferedImage类来读的时候。过大的图片时常会抛出OutOfMemoryException异常,挺蛋疼的。

BufferedImage image = ImageIO.read(File file);

眼下最简单易用的EXIF信息处理的Java包是 Drew Noakes 写的 metadata-extractor。

这是一个能够从图像文件里读取元数据(Exif, IPTC, XMP, ICC等)的简单的Java库,使用简单:

Metadata metadata = ImageMetadataReader.readMetadata(imagePath);

该库能了解多种格式的元数据,当中很多能够存在于单个图像:
Exif、IPTC、XMP、JFIF / JFXX、ICC Profiles、Photoshop fields、PNG properties、BMP properties、GIF properties

它能处理类型的文件:JPEG、TIFF、PSD、PNG、BMP、GIF、Camera Raw (NEF/CR2/ORF/ARW/RW2/...)

注:并非每一个JPG图像文件都包括有EXIF信息,你能够在Windows资源管理器单击选中图片后。假设该图片包括EXIF信息,则在窗体状态栏会显示出相机的型号。

二、演示样例代码及描写叙述

以下我们给出一些代码将含有EXIF的图片信息所有打印出来。

演示样例1):

import java.io.File;
import java.util.Iterator; import com.drew.imaging.jpeg.JpegMetadataReader;
import com.drew.metadata.Directory;
import com.drew.metadata.Metadata;
import com.drew.metadata.Tag;
import com.drew.metadata.exif.ExifDirectory; /**
* 读取图片的EXIF信息
*/
public class ExifTest { public static void main(String[] args) throws Exception { //包括EXIF信息的图片地址
File jpegFile = new File("D:\\XXXX\\XXXX\\XXXX.JPG"); Metadata metadata = JpegMetadataReader.readMetadata(jpegFile); Directory exif = metadata.getDirectory(ExifDirectory.class); Iterator tags = exif.getTagIterator(); while (tags.hasNext()) { Tag tag = (Tag)tags.next(); System.out.println(tag); }
}
}

演示样例2:)

public static void main(String[] args) throws Exception {

        File mFile = new File("F:/XXX.JPG");
Metadata metadata = ImageMetadataReader.readMetadata(mFile);
for (Directory directory : metadata.getDirectories()) { if("ExifSubIFDDirectory".equalsIgnoreCase( directory.getClass().getSimpleName() )){ //光圈F值=镜头的焦距/镜头光圈的直径
System.out.println("光圈值: f/" + directory.getString(ExifSubIFDDirectory.TAG_FNUMBER) ); System.out.println("曝光时间: " + directory.getString(ExifSubIFDDirectory.TAG_EXPOSURE_TIME)+ "秒" );
System.out.println("ISO速度: " + directory.getString(ExifSubIFDDirectory.TAG_ISO_EQUIVALENT) );
System.out.println("焦距: " + directory.getString(ExifSubIFDDirectory.TAG_FOCAL_LENGTH) + "毫米" ); System.out.println("拍照时间: " + directory.getString(ExifSubIFDDirectory.TAG_DATETIME_ORIGINAL) );
System.out.println("宽: " + directory.getString(ExifSubIFDDirectory.TAG_EXIF_IMAGE_WIDTH) );
System.out.println("高: " + directory.getString(ExifSubIFDDirectory.TAG_EXIF_IMAGE_HEIGHT) ); } if("ExifIFD0Directory".equalsIgnoreCase( directory.getClass().getSimpleName() )){
System.out.println("照相机制造商: " + directory.getString(ExifIFD0Directory.TAG_MAKE) );
System.out.println("照相机型号: " + directory.getString(ExifIFD0Directory.TAG_MODEL) );
System.out.println("水平分辨率: " + directory.getString(ExifIFD0Directory.TAG_X_RESOLUTION) );
System.out.println("垂直分辨率: " + directory.getString(ExifIFD0Directory.TAG_Y_RESOLUTION) );
}
}
}

演示样例3):

File mFilePath="C://XXX.jpg";

    Metadata metadata = com.drew.imaging.jpeg.JpegMetadataReader.readMetadata(mFilePath);

    JpegDirectory jd = (JpegDirectory)metadata.getDirectory(JpegDirectory.class);
System.out.println("------------" + jd.getImageHeight()); //图片的高
System.out.println("------------" + jd.getImageWidth()); //图片的宽 //因为仅仅是读取图片的头信息,所以不管多大的图片都能读取,并且速度非常快.

从运行的中能够看到照片的具体拍摄时间。拍摄用的相机型号,曝光时间,光圈值。焦距,ISO值 等等。

你也能够直接指定读取当中随意參数的值。ExifDirectory 类中定义了非常多以 TAG_ 开头的整数常量。这些常量代表特定的一个參数值,比如要读取相机的型号,能够用以下代码来获取。

Metadata metadata = JpegMetadataReader.readMetadata(jpegFile);

Directory exif = metadata.getDirectory(ExifDirectory.class);

String model = exif.getString(ExifDirectory.TAG_MODEL);

上述提到的是怎样获取照片的EXIF信息,当中包括一个非常重要的信息就是——拍摄方向。

比如所用的图片拍摄方向是:Orientation - Top, left side (Horizontal / normal)。我们在拍照的时候常常会依据场景的不同来选择相机的方向,比如拍摄一颗高树。我们会把相机竖着拍摄,使景物刚好适合整个取景框。可是这样得到的图片假设用普通的图片浏览器看便是倒着的,须要调整角度才干得到一个正常的图像。

通过读取图片的EXIF信息,能够得到关于拍摄方向的这样一个结果:Orientation - Left side, bottom (Rotate 270 CW)

而直接读取 ExitDirectory.TAG_ORIENTATION 标签的值是8。

来看下这个项目是怎样来定义这些返回值的,打开源代码包中的ExifDescriptor类的getOrientationDescription(),该方法代码例如以下:

public String getOrientationDescription() throws MetadataException{

        if (!_directory.containsTag(ExifDirectory.TAG_ORIENTATION)) return null;

        int orientation = _directory.getInt(ExifDirectory.TAG_ORIENTATION);

        switch (orientation) {

            case 1: return "Top, left side (Horizontal / normal)";
case 2: return "Top, right side (Mirror horizontal)";
case 3: return "Bottom, right side (Rotate 180)";
case 4: return "Bottom, left side (Mirror vertical)";
case 5: return "Left side, top (Mirror horizontal and rotate 270 CW)";
case 6: return "Right side, top (Rotate 90 CW)";
case 7: return "Right side, bottom (Mirror horizontal and rotate 90 CW)";
case 8: return "Left side, bottom (Rotate 270 CW)";
default:
return String.valueOf(orientation);
}
}

从这种方法能够清楚看到各个返回值的意思,如此我们便能够依据实际的返回值来对图像进行旋转或者是镜像处理了。

以下给出代码用以旋转图片,其它的关于图片的镜像等处理读者能够依此类推:

String mPath = "D:\\XXX.JPG";

    File img = new File(mPath);
BufferedImage old_img = (BufferedImage)ImageIO.read(img);
int w = old_img.getWidth();
int h = old_img.getHeight(); BufferedImage new_img = new BufferedImage(h,w,BufferedImage.TYPE_INT_BGR);
Graphics2D g2d =new_img.createGraphics(); AffineTransform origXform = g2d.getTransform();
AffineTransform newXform = (AffineTransform)(origXform.clone());
// center of rotation is center of the panel double xRot = w/2.0;
newXform.rotate(Math.toRadians(270.0), xRot, xRot); //旋转270度 g2d.setTransform(newXform);
// draw image centered in panel
g2d.drawImage(old_img, 0, 0, null);
// Reset to Original
g2d.setTransform(origXform); //写到新的文件
FileOutputStream out = new FileOutputStream("D:\\XXX2.jpg");
try{
ImageIO.write(new_img, "JPG", out);
}finally{
out.close();
}

注:利用上面的代码旋转照片后。原有照片包括的EXIF信息就不存在了。

关于该问题须要在照片旋转之前先把EXIF信息读出。然后再在旋转后写入新的照片中,能够使用 MediaUtil 包来写EXIF信息到图片文件里,关于这个包的使用可參考最后的链接。

照片的镜面翻转能够直接利用Graphic2D 的 drawImage 方法来实现:

public abstract boolean drawImage(Image img,
int dx1,int dy1,
int dx2,int dy2,
int sx1,int sy1,
int sx2,int sy2,
ImageObserver observer);

三、补充说明

解释部分參数的实际含义:

Make 生产者 指产品生产厂家

Model 型号 指设备型号

Orientation  方向 有的相机支持,有的不支持

X Resolution/Y Resolution X/Y方向分辨率 本栏目已有专门条目解释此问题

ResolutionUnit  分辨率单位 一般为PPI

Software  软件 显示固件Firmware版本号

DateTime  日期和时间

YCbCrPositioning  色相定位

ExifOffsetExif  信息位置。定义Exif在信息在文件里的写入。有些软件不显示。

ExposureTime 曝光时间 即快门速度

FNumber  光圈系数

ISO speed ratings  感光度

ExifVersionExif  版本号

DateTimeOriginal  创建时间

DateTimeDigitized  数字化时间

ComponentsConfiguration  图像构造(多指色彩组合方案)

CompressedBitsPerPixel(BPP)  压缩时每像素色彩位 指压缩程度

ExposureBiasValue  曝光补偿。

MaxApertureValue  最大光圈

MeteringMode  測光方式, 平均式測光、中央重点測光、点測光等。

Lightsource  光源 指白平衡设置

Flash  是否使用闪光灯。

FocalLength  焦距。一般显示镜头物理焦距。有些软件能够定义一个系数。显示相当于35mm相机的焦距 MakerNote(User Comment)作者标记、说明、记录

FlashPixVersionFlashPix  版本号 (个别机型支持)

ColorSpace 色域、色彩空间

ExifImageWidth(Pixel X Dimension)  图像宽度 指横向像素数

ExifImageLength(Pixel Y Dimension)  图像高度 指纵向像素数

四、參考资料及资源下载

项目的最新版本号及其源代码下载:

GitHub主页:https://github.com/drewnoakes/metadata-extractor

http://www.drewnoakes.com/code/exif/

MediaUtil   http://mediachest.sourceforge.net/mediautil/

Android中读取图片EXIF元数据之metadata-extractor的使用的更多相关文章

  1. Android中显示照片的Exif信息

    package com.hyzhou.pngexifdemo; import android.media.ExifInterface; import android.os.Bundle; import ...

  2. Android 实现对图片 Exif 的修改(Android 自带的方法)

    很多时候我们都要对我们的图片信息进行一些处理,比如向图片中写入经纬度,拍摄时间,设备信息,作者等等. 这个时候我们就要对我们的图片Exif进行写入信息的操作,当然,我们想知道图片的Exif信息,也可以 ...

  3. Android 中对于图片的内存优化方法

    Android 中对于图片的内存优化方法,需要的朋友可以参考一下     1. 对图片本身进行操作 尽量不要使用 setImageBitmap.setImageResource. BitmapFact ...

  4. 利用Python读取图片exif敏感信息

    众所周知,现在很多的照相机等软件,拍摄会有选项,是否包含位置信息等. 当然有的人会说,我在微信中查看图片exif信息并没有啊,这是因为你发送到微信服务器的时候,微信帮你完成了保密工作. 常见的图片中包 ...

  5. Servlet从本地文件中读取图片,并显示在页面中

    import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.http.HttpSer ...

  6. [转]asp.net mvc 从数据库中读取图片

    本文转自:http://www.cnblogs.com/mayt/archive/2010/05/20/1740358.html 首先是创建一个类,继承于ActionResult,记住要引用Syste ...

  7. Android中读取短信信息

    Android中读取的短信文件有 ? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 /**  * 所有的短信  */ public static final Strin ...

  8. Android中的图片压缩

    1.android中计算图片占用堆内存的kB大小跟图片本身的kB大小无关,而是根据图片的尺寸来计算的. 比如一张 480*320大小的图片占用的堆内存大小为: 480*320*4/1024=600kB ...

  9. asp.net mvc 从数据库中读取图片的实现代码

    首先是创建一个类,继承于ActionResult,记住要引用System.Web.Mvc命名空间,如下: public class ImageResult : ActionResult { publi ...

随机推荐

  1. 洛谷 [P3620] 数据备份

    贪心神题 首先我们发现一个显然的贪心策略,连接相邻两个写字楼总是更优. 所以本题就变成了数轴上一堆点,要选 k 个彼此不相邻的区间,使得区间长度最小 对于 10000 的数据来说,我们可以用 DP 解 ...

  2. Java之Jenkins工具【转】

    1.1 前言 Jenkins是一个用Java编写的开源的持续集成工具.在与Oracle发生争执后,项目从Hudson项目独立. Jenkins提供了软件开发的持续集成服务.它运行在Servlet容器中 ...

  3. SQL游标的使用与语法

    原文发布时间为:2010-08-07 -- 来源于本人的百度文章 [由搬家工具导入] 以[master].[dbo].[spt_values] 这个表为例子===declare @name nvarc ...

  4. 图表插件echars的使用案例

    <%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding= ...

  5. Docker(五):镜像

    一,什么是镜像? Docker的镜像文件是由文件系统叠加而成的.最底端是一个引导文件系统,即bootfs.Docker用户几乎永远没有机会和引导文件有什么交互,实际上,当一个容器启动之后,容器就会被移 ...

  6. 从无序序列中求这个序列排序后邻点间最大差值的O(n)算法

    标题可能比较绕口,简单点说就是给你一个无序数列A={a1,a2,a3……an},如果你把这个序列排序后变成序列B,求序列B中相邻两个元素之间相差数值的最大值. 注意:序列A的元素的大小在[1,2^31 ...

  7. 只用一次循环开销 将类似 1 A 、1 B 的数据返回成为 1 A,B 的拼接形式

    /// <summary> ///将类似 1 A .1 B 的数据返回成为 1 A,B 的拼接形式 /// </summary> /// <param name=&quo ...

  8. AC日记——[HAOI2007]理想的正方形 P2216

    [HAOI2007] 理想的正方形 思路: 正解多个单调队列: 但是我用树套树水了过去: 来,上代码: #include <cstdio> #include <cstring> ...

  9. Linux系统日常运维-修改IP地址

    分享下高手写的很好的文章 IP地址.子网掩码.网络号.主机号.网络地址.主机地址 step 0: check the iptables.selinux service iptables iptable ...

  10. luogu P1880 石子合并

    题目描述 在一个园形操场的四周摆放N堆石子,现要将石子有次序地合并成一堆.规定每次只能选相邻的2堆合并成新的一堆,并将新的一堆的石子数,记为该次合并的得分. 试设计出1个算法,计算出将N堆石子合并成1 ...