Java

【Java】JPEGのEXIFを取得・更新する方法

今回記載するのは、JavaでJPEGのEXIFを操作する方法。
デジカメやスマホ、タブレットで撮影した写真には、EXIFという撮影時の情報が付加されている。EXIFとは簡単にいうとJPEGに付加されるメタ情報で、撮影した画像の向きや、サムネイルの情報、位置情報などが格納されている。

 
JavaでEXIFを操作するために使用するライブラリは↓の2つがメジャーで情報を集めやすい。

  1. Metadata Extractor
  2. Apache-Commons-Imaging

ただしMetadata Extractorは読み取ることはできるが、書き込みができないので注意が必要。(Ver2.15.0時点)
Apache-Commons-Imagingについては、古いバージョン(Apache Sanselan時代)で脆弱性の報告があったので、最新版を使用することを推奨する。
※ただし後述するサンプルはJavaのバージョンの整合上古いものを使用している。


読み取りサンプル

今回はサンプルとして、撮影した画像のバイト配列から画像の向きを取得する処理を書いてみる。画像の向きは、EXIFデータのORIENTATIONという項目で値を保持しており、設定値上8通りの値がある。

Orientation 説明
1 無回転
2 左右反転
3 180度回転
4 上下反転
5 90度+左右反転
6 90度
7 270度+左右反転
8 270度回転

上記の1~8の値を取得する。

Metadata Extractor

    /**
     *
     * EXIFの画像方向を読み取って返す。
     *
     * @param src 画像のバイト配列
     * @return 画像方向
     */
    private int readExifOrientation(byte[] src) throws Exception {

        // 画像方向 初期値=1:無回転
        int orientation = 1;
        Metadata metadata = ImageMetadataReader.readMetadata(new ByteArrayInputStream(src));
        Directory directory = metadata.getFirstDirectoryOfType(ExifIFD0Directory.class);
        if (directory != null && directory.containsTag(ExifIFD0Directory.TAG_ORIENTATION)){
            // EXIFありかつ、画像方向ありの場合は取得する
            orientation = directory.getInt(ExifIFD0Directory.TAG_ORIENTATION);
        }

        return orientation;
    }

Apache-Commons-Imaging

    /**
     *
     * EXIFの画像方向を読み取って返す。
     *
     * @param src 画像のバイト配列
     * @return 画像方向
     */
    private int readExifOrientation(byte[] src) throws Exception {

        TiffImageMetadata exif;
        exif = readExifMetadata(src);
        if (exif == null) {
            //画像にEXIF情報がない場合は1:無回転を返す
            return 1;
        }

        TiffField field = exif.findField(TiffTagConstants.TIFF_TAG_ORIENTATION);
        if (field == null) {
            //EXIFに画像方向がない場合は1:無回転を返す
            return 1;
        } else {
            return field.getIntValue();
        }
    }

    /**
     *
     * 画像のバイト配列からEXIFを読み取って返す。
     *
     * @param jpegData 画像のバイト配列
     * @return EXIF情報
     */
    private TiffImageMetadata readExifMetadata(byte[] jpegData) throws ImageReadException, IOException {
        IImageMetadata imageMetadata = Imaging.getMetadata(jpegData);
        if (imageMetadata == null) {
            return null;
        }
        JpegImageMetadata jpegMetadata = (JpegImageMetadata)imageMetadata;
        TiffImageMetadata exif = jpegMetadata.getExif();
        if (exif == null) {
            return null;
        }
        return exif;
     }

書き込みサンプル

冒頭で記載したとおり、Metadata Extractorは書き込みには対応していないので、Imagingのみサンプルソースを記載する。

Apache-Commons-Imaging

下記のサンプルは、上で記載した読み取りサンプルのApache-Commons-Imagingで記載した元画像のEXIFを取得した後に、画像の向きだけ変更して更新する処理。

    /**
      * EXIFの画像方向の設定値(1:無回転)
      */
    private final int ORIENTATION_NOROTATION = 1;

    /**
      * 画像方向のTAG_INFO(Imagingが古い場合に定義が必要(※1))
      */
    //private static final TagInfo EXIF_TAG_ORIENTATION = new TagInfo(
    //       "Orientation", 0x0112, FieldType.SHORT, 1, TiffDirectoryType.EXIF_DIRECTORY_IFD0);

    /**
     *
     * 引数で受け取った画像のバイト配列にEXIFを書き込み返す。
     *
     * @param exif オリジナル画像のEXIF情報
     * @param jpegData 更新対象の画像のバイト配列
     * @return 更新した画像のバイト配列
     * @throws ImageWriteException
     * @throws ImageReadException
     * @throws IOException
     */
    private byte[] writeExifMetadata(TiffImageMetadata exif, byte[] jpegData) throws ImageWriteException, ImageReadException, IOException {
        byte[] outByte = null;
        if (exif != null){
            try(ByteArrayOutputStream outStream = new ByteArrayOutputStream()){
                TiffOutputSet outputSet = exif.getOutputSet();
                 if (outputSet != null){
                     // 元画像のMeta情報が取得できた場合は更新する
                     updateExifOrientation(outputSet);

                     // EXIF更新実施
                     new ExifRewriter().updateExifMetadataLossless(jpegData, outStream, outputSet);
                 }
                 outByte = outStream.toByteArray();
            }
         }
         return outByte;
    }

    /**
     *
     * ROOTディレクトリ(0th IFD)の画像方向を更新する。
     *
     * @param outputSet EXIF書き込み用メタデータ
     * @throws ImageWriteException
     */
    private void updateExifOrientation(TiffOutputSet outputSet) throws ImageWriteException{
        TiffOutputDirectory rootDir = outputSet.findDirectory(TiffDirectoryConstants.DIRECTORY_TYPE_ROOT);
        List fields = rootDir.getFields();
        for (int fieldIdx = 0; fieldIdx < fields.size(); fieldIdx++) {
            TiffOutputField field = fields.get(fieldIdx);
            if (field.tagInfo.name.equals("Orientation")) {
                // 画像方向がある場合は事前に削除する
                rootDir.removeField(field.tagInfo);
                break;
            }
        }

        // 更新する画像の向き
        int updOrientation = 6;
        TiffOutputField orientationField = new TiffOutputField(TiffConstants.EXIF_TAG_ORIENTATION, FieldType.SHORT, 1, new byte[]{0, updOrientation}); //※1
        rootDir.add(orientationField);
    }

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です