我有一些服务器代码在上传图片时生成缩略图。问题在于,当拍摄图像并旋转相机/设备时,即使在任何图像查看软件中以正确的方向显示全尺寸图像本身,也会旋转缩略图。这只发生在jpgs上。
在OSX上使用Preview,我可以看到jpgs中嵌入了方向元数据。当我使用ImageTools(Grails插件)生成缩略图时,EXIF元数据不在缩略图中,这就是缩略图显示为旋转的原因。
通过离线对话,我了解到虽然读取EXIF元数据相对容易,但没有简单的方法来编写它,这就是生成jpg缩略图时数据丢失的原因。
所以看来我有两个选择:
有没有人知道其他任何选择吗?
答案 0 :(得分:56)
如果您想旋转图片,我建议您使用元数据提取器库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
答案 1 :(得分:14)
Thumbnailator库尊重EXIF方向标志。要以正确的方向读取完整尺寸的图像:
BufferedImage image = Thumbnails.of(inputStream).scale(1).asBufferedImage();
答案 2 :(得分:7)
使用image part of JavaXT core library:
可以非常轻松地完成此操作// 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更优雅。
答案 3 :(得分:3)
读取原始但只能将方向标记写入缩略图。
Apache Sanselan似乎拥有很好的工具集。
http://commons.apache.org/proper/commons-imaging/
例如,查看ExifRewriter类。
答案 4 :(得分:1)
如果你只是想让它看起来正确。您可以根据需要添加“旋转”-PI / 2(-90度),PI / 2(90度)或PI(+180度),具体取决于您已经提取的“方向”。浏览器或任何其他程序将正确显示图像,因为将应用方向并从缩略图输出中删除元数据。
答案 5 :(得分:0)
如先前评论中提到的 dnault 一样,Thumbnaliator库解决了该问题。但是,您应该使用正确的输入/输出格式,以避免在此自动旋转时颜色发生变化。
disabled
答案 6 :(得分:0)
我的解决方案是@PerLindberg的答案和@AntoineMartin的答案的组合。我在Windows 10上使用Java 8尝试了其他答案,但似乎没有一个解决之道。 @AntoinMartin的com.drew.imaging解决方案速度很慢,并且图像变成了黑白且充满了伪影。 @PerLindberg的JavaXT解决方案未读取Exif 2.2数据。
1)使用com.drew.imaging读取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);
}
}
答案 7 :(得分:0)
上面的旋转函数旋转图像数据。还有一件事要做。您必须设置 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());
}
答案 8 :(得分:-1)
基于Antoine Martin的答案,我创建了一个自己的类,用于根据图像的exif信息校正给定jpeg图像的方向(在我的情况下作为输入流)。有了他的解决方案我遇到了问题,结果图像的颜色是错误的,因此我创建了这个。 为了检索图像的元数据,我使用了metadata-extractor库。
我希望它会帮助一些人。
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;
}
}