2012年10月19日金曜日

Java による画像縮小 JDK 編

忙しい日々が続いて、色々嵌っているが書けていない・・・
また同じ事で嵌まることになるだろう・・・

半年以上前だが、Java で画像を縮小してサムネイルを作成する際に、なるべく画質を落としたくなかったので色々試したのでその記録を。
結局ブログ書くために再調査したわけだけ・・・

元となる画像(1024 x 768)は、
これから 120 x 90 のサムネイルを作成することとする。


共通処理として、

画像読み込み

public static BufferedImage loadImage(String filename) {
  try (FileInputStream in = new FileInputStream(filename)) {
    return ImageIO.read(in);
  }
}

画像出力

// 品質 95 Jpeg
public static void outputImage(String filename, BufferedImage image) throws IOException {
  JPEGImageWriteParam param = new JPEGImageWriteParam(Locale.getDefault());
  param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
  param.setCompressionQuality(0.95f);

  ImageWriter writer = ImageIO.getImageWritersByFormatName("jpg").next();
  writer.setOutput(ImageIO.createImageOutputStream(new File(filename)));
  writer.write(null, new IIOImage(image, null, null), param);
  writer.dispose();
}
の 2 つを用意。

縮小処理は、2 パターン。

パターン1 BufferedImage#getScaledInstance() を使用

public static BufferedImage reduce1(BufferedImage image, int dw, int dh) {
  BufferedImage thumb = new BufferedImage(dw, dh, image.getType());
  thumb.getGraphics().drawImage(image.getScaledInstance(dw, dh, Image.SCALE_AREA_AVERAGING), 0, 0, dw, dh, null);
  return thumb;
}

パターン 2 Graphics2D#drawImage() を使用

public static BufferedImage reduce2(BufferedImage image, int dw, int dh) {
  BufferedImage thumb = new BufferedImage(dw, dh, image.getType());
  Graphics2D g2d = thumb.createGraphics();
  g2d.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
  g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
  g2d.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
  g2d.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_ENABLE);
  g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
  g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
  g2d.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_NORMALIZE);
  g2d.drawImage(image, 0, 0, dw, dh, null);
  return thumb;
}

パターン1、パターン2 をそれぞれ呼び出し

try {
  BufferedImage base = loadImage("base.jpg");
  int dw = 120, dh= 90;

  long start1_1 = System.currentTimeMillis();
  BufferedImage reduce1_1 = reduce1(base, dw, dh);
  System.out.println("reduce1_1: " + (System.currentTimeMillis() - start1_1));
  outputImage("reduce1_1.jpg", reduce1_1);

  long start2_1 = System.currentTimeMillis();
  BufferedImage reduce2_1 = reduce2(base, dw, dh);
  System.out.println("reduce2_1: " + (System.currentTimeMillis() - start2_1));
  outputImage("reduce2_1.jpg", reduce2_1);
} catch (Exception e) { e.printStackTrace(); } 

パターン1はぼやけた感じ、パターン2はざらついた感じ。
連続で 4 回実行し、1 回目を除いた 3 回の平均時間を計測。
速度はパターン2が早いが、今回は作成した画像をキャッシュするので速度より画質。

パターン1:  80.3ms


パターン2: 0.7ms


段階的に縮小

近傍縮小法だと、大きな画像から一気に縮小すると色情報の欠落が大きくなるので、段階的に縮小してみる。
  • 1024x768 -> 640x480 -> 120x90 の 2 段階
  • 1024x768 -> 800x600 -> 480x360 -> 120x90 の 3 段階
の 2 つそれぞれのパターンで実行。
int dw2_1 = 640, dh2_1 = 480;
int dw3_1 = 800, dh3_1 = 600, dw3_2 = 480, dh3_2 = 360;

long start1_2 = System.currentTimeMillis();
outputImage("reduce1_2.jpg", reduce1(reduce1(base, dw2_1, dh2_1), dw, dh));
System.out.println("reduce1_2: " + (System.currentTimeMillis() - start1_2));

long start2_2 = System.currentTimeMillis();
outputImage("reduce2_2.jpg", reduce2(reduce2(base, dw2_1, dh2_1), dw, dh));
System.out.println("reduce2_2: " + (System.currentTimeMillis() - start2_2));

long start1_3 = System.currentTimeMillis();
outputImage("reduce1_3.jpg", reduce1(reduce1(reduce1(base, dw3_1, dh3_1), dw3_2, dh3_2), dw, dh));
System.out.println("reduce1_3: " + (System.currentTimeMillis() - start1_3));

long start2_3 = System.currentTimeMillis();
outputImage("reduce2_3.jpg", reduce2(reduce2(reduce2(base, dw3_1, dh3_1), dw3_2, dh3_2), dw, dh));
System.out.println("reduce2_3: " + (System.currentTimeMillis() - start2_3));
パターン1 2段階: 141.7ms


パターン1 3段階: 215ms


パターン2 2段階: 7.3ms


パターン2 3段階: 16.3ms


多少マシになったが、ちょっと納得いかないレベル・・・
続く

0 件のコメント:

コメントを投稿