web-dev-qa-db-ja.com

方向メタデータに基づいてJPEG画像を回転させる方法は?

画像のアップロード時にサムネイルを生成するサーバーコードがあります。問題は、画像表示ソフトウェアでフルサイズの画像自体が正しい向きで表示されていても、画像が撮影され、カメラ/デバイスが回転すると、サムネイルが回転することです。これは、jpgでのみ発生します。

OSXでプレビューを使用すると、jpgに方向メタデータが埋め込まれていることがわかります。 ImageTools(Grailsプラグイン)を使用してサムネイルを生成すると、EXIFメタデータがサムネイルに含まれないため、サムネイルが回転して表示されます。

オフラインの会話を介して、EXIFメタデータは比較的簡単に読み取ることができますが、簡単に書き込む方法がないため、jpgサムネイルを生成するとデータが失われることがわかりました。

だから、私には2つのオプションがあるようです:

  1. ImageMagickを使用して、サムネイルを生成します。欠点は、サーバーにインストールするソフトウェアが増えることです。
  2. EXIFオリエンテーションデータをコードで読み取り、サムネイルを適切に回転させます。

誰か他のオプションを知っていますか?

59
hvgotcodes

画像を回転する場合は、メタデータ抽出ライブラリ 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方向のAffine変換は、次の方法で提供されます。

// 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で実行することを忘れないでください

58
Antoine Martin

Thumbnailator ライブラリはEXIF方向フラグを尊重します。正しい向きでフルサイズで画像を読み取るには:

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

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はよりエレガントです。

7
Per Lindberg

Exifは、プロプライエタリなものがあるため、書くのが難しいようです。ただし、別のオプションを検討できます

オリジナルを読み取りますが、サムネイルには方向タグのみを書き込みます。

Apache Sanselanには、それを行うためのツールの素晴らしいコレクションがあるようです。

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

たとえば、ExifRewriterクラスを見てください。

3
Alex Gitelman

あなたがちょうどそれが正しく見えるようにしたい場合。既に抽出した「方向」に応じて、必要に応じて「回転」-PI/2(-90度)、PI/2(90度)、またはPI(+180度)を追加できます。ブラウザまたは他のプログラムは、向きが適用され、メタデータがサムネイル出力から削除されるため、画像を正しく表示します。

1
karmakaze

前のコメントで言及したdnaultのように、 Thumbnaliator ライブラリは問題を解決します。ただし、この自動回転で色が変化しないように、正しい入力/出力形式を使用する必要があります。

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

私の解決策は、@ PerLindbergの回答と@AntoineMartinの回答を組み合わせたものです。 Windows 10でJava 8を使って他の答えを試してみましたが、だれもトリックを実行できませんでした。 @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);
    }

}
0
Ted Gulesserian