如何根据方向元数据旋转JPEG图像?
我有一些服务器代码,当图片上传时产生缩略图。 问题在于,当图像被拍摄并且相机/设备被旋转时,即使全尺寸图像本身在任何图像查看软件中以正确的方向被显示,缩略图也被旋转。 这只是与jpgs发生。
在OSX上使用预览,我可以看到jpgs中embedded了方向元数据。 当我使用ImageTools(Grails插件)生成缩略图时,EXIF元数据不在缩略图中,这就是缩略图旋转的原因。
通过离线对话,我了解到虽然读取EXIF元数据相对比较容易,但没有简单的方法来编写它,这就是生成jpg缩略图时数据丢失的原因。
所以看来我有两个select:
- 使用ImageMagick生成缩略图。 缺点是它需要在我们的服务器上安装更多的软件。
- 读取EXIF方向数据是代码并适当地旋转缩略图。
有谁知道任何其他的select?
如果你想旋转你的图片,我会build议使用元数据提取库http://code.google.com/p/metadata-extractor/ 。 您可以使用以下代码获取图像信息:
// Inner class containing image information public static class ImageInformation { public final int orientation; public final int width; public final int height; public ImageInformation(int orientation, int width, int height) { this.orientation = orientation; this.width = width; this.height = height; } public String toString() { return String.format("%dx%d,%d", this.width, this.height, this.orientation); } } public static ImageInformation readImageInformation(File imageFile) throws IOException, MetadataException, ImageProcessingException { Metadata metadata = ImageMetadataReader.readMetadata(imageFile); Directory directory = metadata.getFirstDirectoryOfType(ExifIFD0Directory.class); JpegDirectory jpegDirectory = metadata.getFirstDirectoryOfType(JpegDirectory.class); int orientation = 1; try { orientation = directory.getInt(ExifIFD0Directory.TAG_ORIENTATION); } catch (MetadataException me) { logger.warn("Could not get orientation"); } int width = jpegDirectory.getImageWidth(); int height = jpegDirectory.getImageHeight(); return new ImageInformation(orientation, width, height); }
然后根据您检索的方向,您可以旋转和/或将图像翻转到正确的方向。 EXIF方向的仿射变换由以下方法给出:
// Look at http://chunter.tistory.com/143 for information public static AffineTransform getExifTransformation(ImageInformation info) { AffineTransform t = new AffineTransform(); switch (info.orientation) { case 1: break; case 2: // Flip X t.scale(-1.0, 1.0); t.translate(-info.width, 0); break; case 3: // PI rotation t.translate(info.width, info.height); t.rotate(Math.PI); break; case 4: // Flip Y t.scale(1.0, -1.0); t.translate(0, -info.height); break; case 5: // - PI/2 and Flip X t.rotate(-Math.PI / 2); t.scale(-1.0, 1.0); break; case 6: // -PI/2 and -width t.translate(info.height, 0); t.rotate(Math.PI / 2); break; case 7: // PI/2 and Flip t.scale(-1.0, 1.0); t.translate(-info.height, 0); t.translate(0, info.width); t.rotate( 3 * Math.PI / 2); break; case 8: // PI / 2 t.translate(0, info.width); t.rotate( 3 * Math.PI / 2); break; } return t; }
图像的旋转将通过以下方法完成:
public static BufferedImage transformImage(BufferedImage image, AffineTransform transform) throws Exception { AffineTransformOp op = new AffineTransformOp(transform, AffineTransformOp.TYPE_BICUBIC); BufferedImage destinationImage = op.createCompatibleDestImage(image, (image.getType() == BufferedImage.TYPE_BYTE_GRAY) ? image.getColorModel() : null ); Graphics2D g = destinationImage.createGraphics(); g.setBackground(Color.WHITE); g.clearRect(0, 0, destinationImage.getWidth(), destinationImage.getHeight()); destinationImage = op.filter(image, destinationImage); return destinationImage; }
在服务器环境中,不要忘记使用-Djava.awt.headless=true
运行
缩略词库赞成EXIF方向标志。 要以正确方向阅读全尺寸图像,请执行以下操作:
BufferedImage image = Thumbnails.of(inputStream).scale(1).asBufferedImage();
这可以通过使用JavaXT核心库的图像部分令人惊讶地完成:
// Browsers today can't handle images with Exif Orientation tag Image image = new Image(uploadedFilename); // Auto-rotate based on Exif Orientation tag, and remove all Exif tags image.rotate(); image.saveAs(permanentFilename);
而已!
我已经尝试了Apache Commons Imaging,但是那是一团糟。 JavaXT是更优雅的方式。
Exif似乎很难写,因为它的专有的东西。 但是,你可以考虑另一种select
阅读原文,但只写方向标签缩略图。
Apache Sanselan似乎有很好的收集工具来做到这一点。
http://commons.apache.org/proper/commons-imaging/
例如,看一下ExifRewriter类。
如果你只是想要它看起来正确。 根据您已经提取的“方向”,您可以根据需要添加“旋转”-PI / 2(-90度),PI / 2(90度)或PI(+180度)。 浏览器或任何其他程序将正确显示图像,因为方向已经被应用,元数据从缩略图输出中被剥离。
基于Antoine Martin的回答,我创build了一个自己的类,用于根据图像的exif信息来纠正给定的jpeg图像(在我的情况下,作为inputstream)的方向。 在他的解决scheme中,我遇到了这个问题,即所得图像的颜色是错误的,所以我创build了这个图像。 为了检索我使用元数据提取器库的图像的元数据 。
我希望这会帮助一些人。
public class ImageOrientationUtil { /** * Checks the orientation of the image and corrects it if necessary. * <p>If the orientation of the image does not need to be corrected, no operation will be performed.</p> * @param inputStream * @return * @throws ImageProcessingException * @throws IOException * @throws MetadataException */ public static BufferedImage correctOrientation(InputStream inputStream) throws ImageProcessingException, IOException, MetadataException { Metadata metadata = ImageMetadataReader.readMetadata(inputStream); if(metadata != null) { if(metadata.containsDirectoryOfType(ExifIFD0Directory.class)) { // Get the current orientation of the image Directory directory = metadata.getFirstDirectoryOfType(ExifIFD0Directory.class); int orientation = directory.getInt(ExifIFD0Directory.TAG_ORIENTATION); // Create a buffered image from the input stream BufferedImage bimg = ImageIO.read(inputStream); // Get the current width and height of the image int[] imageSize = {bimg.getWidth(), bimg.getHeight()}; int width = imageSize[0]; int height = imageSize[1]; // Determine which correction is needed AffineTransform t = new AffineTransform(); switch(orientation) { case 1: // no correction necessary skip and return the image return bimg; case 2: // Flip X t.scale(-1.0, 1.0); t.translate(-width, 0); return transform(bimg, t); case 3: // PI rotation t.translate(width, height); t.rotate(Math.PI); return transform(bimg, t); case 4: // Flip Y t.scale(1.0, -1.0); t.translate(0, -height); return transform(bimg, t); case 5: // - PI/2 and Flip X t.rotate(-Math.PI / 2); t.scale(-1.0, 1.0); return transform(bimg, t); case 6: // -PI/2 and -width t.translate(height, 0); t.rotate(Math.PI / 2); return transform(bimg, t); case 7: // PI/2 and Flip t.scale(-1.0, 1.0); t.translate(height, 0); t.translate(0, width); t.rotate( 3 * Math.PI / 2); return transform(bimg, t); case 8: // PI / 2 t.translate(0, width); t.rotate( 3 * Math.PI / 2); return transform(bimg, t); } } } return null; } /** * Performs the tranformation * @param bimage * @param transform * @return * @throws IOException */ private static BufferedImage transform(BufferedImage bimage, AffineTransform transform) throws IOException { // Create an transformation operation AffineTransformOp op = new AffineTransformOp(transform, AffineTransformOp.TYPE_BICUBIC); // Create an instance of the resulting image, with the same width, height and image type than the referenced one BufferedImage destinationImage = new BufferedImage( bimage.getWidth(), bimage.getHeight(), bimage.getType() ); op.filter(bimage, destinationImage); return destinationImage; } }