最近在开发中用到了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();