如何旋转 JPEG 图像的基础上的方向元数据?

我有一些服务器代码,这是生成缩略图时,一个图像上传。问题在于,当拍摄图像并旋转摄像机/设备时,缩略图会旋转,即使在任何图像浏览软件中,全尺寸图像本身显示的方向都是正确的。只有 JPG 才会这样。

通过在 OSX 上使用 Preview,我可以看到 jpgs 中嵌入了方向元数据。当我使用 ImageTools (Grails Plugin)生成缩略图时,EXIF 元数据不在缩略图中,这就是缩略图显示为旋转的原因。

通过离线对话,我了解到虽然读取 EXIF 元数据相对容易,但是没有简单的方法来编写它,这就是为什么在生成 jpg 缩略图时数据会丢失的原因。

看来我有两个选择:

  1. 使用 ImageMagick 生成缩略图。缺点是它需要在我们的服务器上安装更多的软件。
  2. 读取 EXIF 方向数据为代码并适当旋转缩略图。

有人知道其他选择吗?

61743 次浏览

如果你只是想让它看起来正确的话。您可以根据已经提取的“方向”,根据需要添加“旋转”-PI/2(-90度)、 PI/2(90度)或 PI (+ 180度)。浏览器或任何其他程序将正确地显示图像,因为方向将被应用和元数据从缩略图输出剥离。

Exif 看起来很难写,因为里面有专有的东西。 但是,你可以考虑另一种选择吗

阅读原文,但只写方向标签的缩略图。

ApacheSanselan 似乎有很多工具可以做到这一点。

Http://commons.apache.org/proper/commons-imaging/

例如,查看 ExifRewriter 类。

如果您想旋转您的图像,我建议使用元数据提取器库 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运行

通过使用 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);

就是这样!

我已经试过 ApacheCommons Image,但是一团糟,JavaXT 更优雅。

缩略图库使用 EXIF 方向标志。要读取具有正确方向的全尺寸图像:

BufferedImage image = Thumbnails.of(inputStream).scale(1).asBufferedImage();

基于 Antoine Martin 的答案,我创建了一个自己的类,用于根据图像的 exif 信息校正给定 jpeg 图像的方向(在我的例子中是作为输入流)。在他的解决方案中,我遇到了一个问题,那就是产生的图像的颜色是错误的,因此我创建了这个图像。 为了检索图像的元数据,我使用了 元数据提取器库。

我希望它能帮助一些人。

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;
}
}

正如前面的评论中提到的 Dnault指示器库解决了这个问题。但是,您应该使用正确的输入/输出格式,以避免颜色的变化,在这个自动旋转。

ByteArrayOutputStream baos = new ByteArrayOutputStream();
ByteArrayInputStream in = new ByteArrayInputStream(file.getContents());
Thumbnails.of(in)
.scale(1)
.toOutputStream(baos);
byte[] bytes = baos.toByteArray();

我的解决方案是结合@PerLindberg 的回答和@AntoineMartin 的回答。我在 Windows10上用 Java8尝试了其他的答案,但似乎都没有效果。@ AntoinMartin 的 com.drew.成像解决方案很慢,图像变成了黑白的,到处都是艺术品。@ PerLindberg 的 JavaXT 解决方案没有读取 Exif 2.2数据。

1)使用 com.drew.image 阅读 exif 信息:

// 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 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;
if (directory != null) {
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);
} else {
return null;
}
}

2)使用 JavaXT 实现基于 Exif 数据的旋转。

public void rotateMyImage(String imageDownloadFilenme);
File imageDownloadFile =  new File(imgageDownloadFilenme);
Image image = new Image(imgageDownloadFilenme);
ImageInformation imageInformation = readImageInformation(imageDownloadFile);
if (imageInformation != null) {
rotate(imageInformation, image);
}
image.saveAs(imgageDownloadFilenme);
}


public void rotate(ImageInformation info, Image image) {


switch(info.orientation) {
case 1:
return;
case 2:
image.flip();
break;
case 3:
image.rotate(180.0D);
break;
case 4:
image.flip();
image.rotate(180.0D);
break;
case 5:
image.flip();
image.rotate(270.0D);
break;
case 6:
image.rotate(90.0D);
break;
case 7:
image.flip();
image.rotate(90.0D);
break;
case 8:
image.rotate(270.0D);
}


}

上面的旋转函数旋转图像数据。还有一件事要做。必须设置 BufferedImage 的宽度和高度。当旋转90度和270度时,宽度和高度必须互换。

    BufferedImage transformed;
switch (orientation) {
case 5:
case 6:
case 7:
case 8:
transformed = new BufferedImage(image.getHeight(), image.getWidth(), image.getType());
break;
default:
transformed = new BufferedImage(image.getWidth(), image.getHeight(), image.getType());
}