Java で ImageIO を使用して jpg画像 を保存すると品質劣化する問題への対応

三国志大戦の個人的な開発でTwitterにある画像をJavaのプログラム処理でローカルPCへ保存していた。
保存した画像を見てみると、明らかに画質の劣化が見受けられたので色々と調べてみました。

画質劣化はパターンマッチングにおいて致命的になりそうなので、妥協できない点です。

結論から書くと、ImageIOを使用せずにファイルとして取り扱えとなります。

以下、ImageIOを使用してTwitterの画像(mediaUrl)をローカルPCに保存(outputPath)している。

ImageIO.write(ImageIO.read(mediaUrl), "jpg", new File(outputPath));

ブラウザで保存した画像とImageIOを使用して保存した画像をWinMergeで比較してみる。
WinMergeは、いつから画像比較もできるようになったんだ。
WinMergeの進化が止まらない。
f:id:kameya_takefumi:20171116151524j:plain

差異がある部分が黄色になっている。
やっぱ画質劣化してるやん、詐欺やん。

調べてみると、画像保存時の品質設定がMAXではないらしい。

以下、品質設定MAXで行う処理。

try (FileImageOutputStream output = new FileImageOutputStream(new File(outputPath))) {

    BufferedImage readImage = ImageIO.read(mediaUrl);

    ImageWriter writeImage = ImageIO.getImageWritersByFormatName("jpeg").next();
    ImageWriteParam writeParam = writeImage.getDefaultWriteParam();
    writeParam.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
    writeParam.setCompressionQuality(1.0f);
    writeImage.setOutput(output);
    writeImage.write(null, new IIOImage(readImage, null, null), writeParam);
    writeImage.dispose();
}

その結果。
f:id:kameya_takefumi:20171116151638j:plain

たいして変わってねぇ。
むしろ差異が増えている?
そもそもJpg画像として保存する事が自体がダメな気がしてきました。

以下、ファイルとして保存する処理。

try (ReadableByteChannel rbc = Channels.newChannel(mediaUrl.openStream());
        FileOutputStream fos = new FileOutputStream(outputPath)) {
    fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE);
}

その結果。
f:id:kameya_takefumi:20171116151746j:plain

いけた。
Jpg画像を読み込んでJpg画像を出力すると品質劣化しますね。
Jpg自体、画像を圧縮する技術なので、圧縮処理されたものを、さらに圧縮処理する事になっているのか。
劣化するのは当たり前っちゃ当たり前ですね。

考えればわかりそうな事ですが、パッと思いつかなかったなぁ。
画像といえど、そのまま保存するならファイルとして扱うのがいいですね。
今更ながらの学び、でも大事。