JPEG画像をリサイズして保存したいことはたまにあると思います。ImageMagickを使っていいなら話は早いのですが、どうしてもJavaだけで完結させたい場合は、ImageIOを使うことになります。
ただし、一番シンプルな実装方法ではJPEG画像の保存品質が自動的に「75」にされてしまうようなので注意が必要です(Java 8で確認)。見事にハマりました。
保存品質を指定できない方法
実装自体はシンプルだけど、保存品質を変更したい場合には NG です。
public static byte[] resize(final byte[] src, final double scale) throws IOException {
try (ByteArrayInputStream is = new ByteArrayInputStream(src);
ByteArrayOutputStream os = new ByteArrayOutputStream()) {
BufferedImage srcImage = ImageIO.read(is);
BufferedImage destImage = resizeImage(srcImage, scale);
// 保存品質は「75」になる
ImageIO.write(destImage, "jpeg", os);
return os.toByteArray();
}
}
保存品質を指定できる方法
こちらが正解。保存品質 quality
を 0 〜 1.0 で指定できます。1.0 が品質 100 相当になります。
public static byte[] resize(final byte[] src, final double scale, final float quality) throws IOException {
try (ByteArrayInputStream is = new ByteArrayInputStream(src);
ByteArrayOutputStream os = new ByteArrayOutputStream();
ImageOutputStream ios = ImageIO.createImageOutputStream(os)) {
BufferedImage srcImage = ImageIO.read(is);
BufferedImage destImage = resizeImage(srcImage, scale);
// 保存品質はユーザー指定に従う
ImageWriter writer = ImageIO.getImageWritersByFormatName("jpeg").next();
ImageWriteParam param = writer.getDefaultWriteParam();
param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
param.setCompressionQuality(quality);
writer.setOutput(ios);
writer.write(null, new IIOImage(destImage, null, null), param);
writer.dispose();
return os.toByteArray();
}
}
補足
先の2つの例で出てきたリサイズ用のメソッド resizeImage
の実装は次のようになります。画像のリサイズもデフォルトでは画質がダメダメですが、アフィン変換するときに TYPE_BICUBIC
を指定することで、画質を高めることができます。
public static BufferedImage resizeImage(final BufferedImage image, final double scale) throws IOException {
int width = (int) (image.getWidth() * scale);
int height = (int) (image.getHeight() * scale);
BufferedImage resizedImage = new BufferedImage(width, height, image.getType());
// アフィン変換でリサイズ(画質優先)
AffineTransform transform = AffineTransform.getScaleInstance(scale, scale);
AffineTransformOp op = new AffineTransformOp(transform, AffineTransformOp.TYPE_BICUBIC);
op.filter(image, resizedImage);
return resizedImage;
}
Javaでのリサイズは画像データが大きくなるとメモリ&CPU負荷が許容できないくらい大きくなるので、使えるシーンはかなり限定的だと思います。