如何基于方向元数据旋转JPEG图像?
image-processing
java
5
0

我有一些服务器代码,当上载图像时会生成缩略图。问题在于,即使在任何图像查看软件中以正确的方向显示完整尺寸的图像本身时,在拍摄图像并旋转相机/设备时,缩略图也会旋转。只有jpg会发生这种情况。

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

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

所以看来我有两个选择:

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

有人知道其他选择吗?

参考资料:
Stack Overflow
收藏
评论
共 6 个回答
高赞 时间 活跃

由于其中包含专有内容,Exif似乎很难编写。但是,您可以考虑其他选择

读取原始文件,但仅将方向标签写入缩略图。

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

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

例如,看一下ExifRewriter类。

收藏
评论

通过使用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更优雅。

收藏
评论

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

收藏
评论

dnault在先前的评论中所述, Thumbaliator库解决了该问题。但是您应该使用正确的输入/输出格式,以避免在此自动旋转时颜色发生变化。

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

Thumbnailator库支持EXIF方向标志。要以正确的方向读取完整尺寸的图像:

BufferedImage image = Thumbnails.of(inputStream).scale(1).asBufferedImage();
收藏
评论

我的解决方案是@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);
    }

}
收藏
评论
新手导航
  • 社区规范
  • 提出问题
  • 进行投票
  • 个人资料
  • 优化问题
  • 回答问题

关于我们

常见问题

内容许可

联系我们

@2020 AskGo
京ICP备20001863号