java.awt.Image を BufferedImage や byte 配列に変換する方法

Java ImageIO で Flickr 並みのサムネイル画像をつくるという記事で、

java.awt.Image を BufferedImage や byte 配列に変換する方法は次回。

と書いたにも関わらず、そのまま放置になっていた(コメントで指摘されているのに気づいたのも今日だ。すまん)。

概要

まずは java.awt.image.BufferedImagebyte 配列に変換する方法を紹介する。

そのあと、java.awt.Imagejava.awt.image.BufferedImage に変換する方法を紹介するので、このふたつを組み合わせれば、

Image --> BufferedImage --> byte[]

が実現できるわけだ。

BufferedImage を byte[] に変換する

BufferedImage から byte 配列の変換は、javax.imageio.ImageWriter でわりかし素直に実装できる。

BufferedImage image = ...;
ByteArrayOutputStream bytesOut = new ByteArrayOutputStream();
ImageWriter writer = ImageIOKit.getImageWritersByFormatName(formatName);
ImageOutputStream stream = ImageIO.createImageOutputStream(bytesOut);

try {
  writer.setOutput(stream);
  writer.write(image);
  stream.flush();
} finally {
  writer.dispose();
  if (stream != null) try { stream.close(); } catch (Throwable t) {;}
}

byte[] bytes = bytesOut.toByteArray();
...

ImageWriteParam も組み合わせれば、圧縮率などの設定ができる。

Image を BufferedImage をに変換する

一筋縄ではいかないのが、Image を BufferedImage をに変換する方法だ。

以下では、image 変数が java.awt.Image のインスタンスであるとして、順を追って説明する。

まず、imageBufferedImage のインスタンスなら、以降は何もする必要がない。

if (image instanceof BufferedImage) {
  ...
}

次に、java.awt.Image のインスタンスは、実際のデータが非同期でロードされているかもしれないので、変換処理を行う前に、java.awt.MediaTracker でロードが完了するまで待機する。

// java.awt.MediaTracker でロードを待機
MediaTracker tracker = new MediaTracker(new Component(){});
tracker.addImage(image, 0);
tracker.waitForAll();

ここからが実際の変換処理になるが、基本的に、

  1. 新規に BufferedImage を生成
  2. 生成した BufferedImageImage のピクセルをコピー

という処理になる。

PixelGrabber pixelGrabber = new PixelGrabber(image, 0, 0, -1, -1, false);
pixelGrabber.grabPixels();
ColorModel cm = pixelGrabber.getColorModel();

final int w = pixelGrabber.getWidth();
final int h = pixelGrabber.getHeight();
WritableRaster raster = cm.createCompatibleWritableRaster(w, h);
BufferedImage renderedImage =
  new BufferedImage(
    cm,
    raster,
    cm.isAlphaPremultiplied(),
    new Hashtable());
renderedImage.getRaster().setDataElements(0, 0, w, h, pixelGrabber.getPixels());

上記のコードでは、元の ColorModel もサポートするために、PixelGrabber を使っている。

あとは、ここで生成した BufferedImagebyte 配列に変換してやればよい。

OpenCV 1.0.0 を Mac OS X 10.4 にインストール

物体認識もできる画像処理ライブラリ OpenCV 1.0.0 を Mac OS X 10.4 にてコンパイルしてみた。

OpenCV のインストール

まずは、SourceForge から opencv-1.0.0.tar.gz をダウンロードして解凍。

% tar xvzf opencv-1.0.0.tar.gz
% mkdir build
% cd build/

libjpeglibpng がないと JPEG, PNG 画像を扱えないので、DarwanPorts でいれたものを使うように configure

% ../configure CPPFLAGS="-I/opt/local/include" LDFLAGS="-L/opt/local/lib"
% make

案の定、エラーがでる。

collect2: ld returned 1 exit status
make[4]: *** [_cv.la] Error 1
make[3]: *** [all-recursive] Error 1
make[2]: *** [all-recursive] Error 1
make[1]: *** [all-recursive] Error 1
make: *** [all] Error 2

なんか、python のラッパー周辺が原因っぽいので外してコンパイル。

% ../configure --without-swig --without-python CPPFLAGS="-I/opt/local/include" LDFLAGS="-L/opt/local/lib"
% make
% sudo make install

無事成功。

サンプル・プログラムを試す

サンプル・プログラムをコンパイルするために、pkg-config の探索パスを追加しておく。

export PKG_CONFIG_PATH=${PKG_CONFIG_PATH}:/usr/local/lib/pkgconfig

あとは samples/c ディレクトリで build_all.sh を実行すれば、すべてのサンプル・プログラムをコンパイルしてくれる。

% cd samples/c
% chmod u+x ./build_all.sh
% ./build_all.sh

Mac OS X 向けに Carbon で作られているので、動作も良好。

Java ImageIO で Flickr 並みのサムネイル画像をつくる

まあ、記事のタイトルがアレですけど...

Java の Image I/O で画像の縮小を行う方法として、AffineTransformOp を利用する方法がある。

final double scale = 0.5;
AffineTransformOp transformOp = new AffineTransformOp(AffineTransform.getScaleInstance(scale, scale), null);
BufferedImage dest1 = new BufferedImage(
    (int)(originalImage.getWidth() * scale),
    (int)(originalImage.getHeight() * scale),
    originalImage.getType());
transformOp.filter(originalImage, dest1);

しかし、この方法だとジャギーが目立ってしまい、Flickr ほどキレイなサムネイルにはならない。

たとえば、Flickr で見つけたこの写真。Flickr のサムネイルはこんな感じだが、

Bark with Monument Under Rennovation

AffineTransformOp を用いた方法だと、こうなってしまう。

Java ImageIO AffineTransformOp

で、どうするかというと、AWT の AreaAveragingScaleFilter を使うのがいいようだ。以下、API リファレンスより抜粋:

最近接点アルゴリズムよりもなめらかな結果が得られる、簡単な領域平均化アルゴリズムを使用してイメージをスケーリングする ImageFilter クラスです。

では、試してみる。

ImageFilter filter = new AreaAveragingScaleFilter(w, h);
ImageProducer im = new FilteredImageSource(image.getSource(), filter);
Image newImage = Toolkit.getDefaultToolkit().createImage(im);

フィルタした結果の java.awt.ImageBufferedImagebyte 配列に変換する方法は次回。

こんな感じになる。

Java ImageIO AreaAveragingScaleFilter

格段にキレイだ。

Want fries with that?

Open Source Projects