java.awt.Image を BufferedImage や byte 配列に変換する方法
Java ImageIO で Flickr 並みのサムネイル画像をつくるという記事で、
java.awt.Image を BufferedImage や byte 配列に変換する方法は次回。
と書いたにも関わらず、そのまま放置になっていた(コメントで指摘されているのに気づいたのも今日だ。すまん)。
概要
まずは java.awt.image.BufferedImage を byte 配列に変換する方法を紹介する。
そのあと、java.awt.Image を java.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 のインスタンスであるとして、順を追って説明する。
まず、image が BufferedImage のインスタンスなら、以降は何もする必要がない。
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();
ここからが実際の変換処理になるが、基本的に、
- 新規に BufferedImage を生成
- 生成した BufferedImage に Image のピクセルをコピー
という処理になる。
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 を使っている。
あとは、ここで生成した BufferedImage を byte 配列に変換してやればよい。
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/
libjpeg や libpng がないと 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 のサムネイルはこんな感じだが、

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.Image を BufferedImage や byte 配列に変換する方法は次回。
こんな感じになる。

格段にキレイだ。