Javaでの画像ファイルの読み書きにBufferedImageというクラスを用いているのですが
このクラスで画像のピクセルデータを読み出すのがとても遅いことに気づきました。
ちょっとピクセルごとに処理しようとすると、とてもじゃないけど待ってられないくらい遅くなります。
この問題は、こことかにあるように、BufferedImageの中身であるDataBufferクラスを取り出して
処理すれば早くなるはずです。また、データをintやbyteの配列にまで取り出してやっても早くなると思います。
そこで、画像のネガポジ反転処理(1ピクセルごとに、赤青緑データを255から引いた値にする)を例に、
以下の条件でどの程度早く出来るかを試してみました。
ただし、処理後の出力はBufferedImageクラスとします。
①1ピクセルごとにBufferedImage.getRGB(), setRGB()を実行して処理
②最初にBufferedImage.getRGBでデータをint型に変換してから処理
③BufferedImageをDataBufferByteに変換してから処理
④BufferedImageをbyteに変換してから処理 ※1
※3:ソースコードは最後に書いておきます。
1600×900のサイズのpngデータを用い、それぞれ100回ループした時の平均時間を計測しました。
結果、
①171.29ms
②158.88ms
③72.35ms
④85.04ms
となり、DataBuffereByteに変換して処理するのが最も高速となりました。
本来、byteに変換する④が高速になるはずなのですが、
再度BufferedImageに変換し直す処理がネックになっているようで、今回は多少遅くなってしまいました。
例えば出力がBufferedImageで無くても良い処理(画像マッチング等)などでは、
④の方が処理が早いのではないかと思います。※2
まあBufferedImageは読み書きに便利ですし、ARGBやABGRと言った並び順、alpha値の有無等の
画像ごとのデータ・タイプの差異を吸収してくれるため、遅くても仕方ない部分はあるかと思いますが、
ピクセル処理をする場合には少なくともDataBufferに変換してやったほうが、速く処理できるという結果となりました。
まあそれでもあまり速いとは言い難いですが、そこを求めるならそもそもjava使うなよということで…
※1
④のbyte配列だと処理速度は速いはずなのですが、データ範囲が0~255ではなく-128~127
(unsigned→signed)になってしまうため、少し工夫をしないと処理結果が他の関数と異なる場合があります。
まあめんどくさいので、多少処理速度が落ちてもintの配列にするのが楽で良いでしょうか。
※2
実際に同じ処理を、BufferedImageで返さないで10回ほどループさせた結果、
③1183ms
④1072ms
となり、byte配列にしたほうが処理速度は高速になりました。
※3
最後に時間計測に用いたソースを添付します。
①1ピクセルごとにBufferedImage.getRGB(), setRGB()を実行して処理
public static void negaposi_bufferedImageEach(BufferedImage image)
{
int height = image.getHeight();
int width = image.getWidth();
int color = 0;
int red, blue, green, alpha;
//反転する
for(int h=0; h<height; h++){
for(int w=0; w<width; w++){
color = image.getRGB(w, h);//色の読込
//反転
alpha = ImageUtility.alpha(color);
red = 255-ImageUtility.red(color);
blue = 255-ImageUtility.blue(color);
green = 255-ImageUtility.green(color);
image.setRGB(w, h, ImageUtility.argb(alpha, red, green, blue));
}
}
}
②最初にBufferedImage.getRGBでデータをint[]型に変換してから処理
public static void negaposi_bufferedImagePicelArray(BufferedImage image)
{
int height = image.getHeight();
int width = image.getWidth();
int color = 0;
int red, blue, green, alpha;
//ピクセルデータをgetRGBを使ってint配列で取得
int[] pixel = image.getRGB(0, 0, width, height, null, 0, width);
//反転する
for(int h=0; h<height; h++){
for(int w=0; w<width; w++){
color = pixel[h*width+w];//色の読込
//反転
alpha = ImageUtility.alpha(color);
red = 255-ImageUtility.red(color);
blue = 255-ImageUtility.blue(color);
green = 255-ImageUtility.green(color);
pixel[h*width+w] = ImageUtility.argb(alpha, red, green, blue);
}
}
image.setRGB(0, 0, width, height, pixel, 0, width);
}
③BufferedImageをDataBufferByteに変換してから処理
public static void negaposi_dataBufferByte(BufferedImage image)
{
int height = image.getHeight();
int width = image.getWidth();
int[] ints = new int[width * height];
int red, blue, green, alpha, count=0;
//ピクセルデータをDataBufferByteで取得
DataBufferByte buf = ImageUtility.convertARGB(image);
DataBuffer iBuf = new DataBufferInt(ints, width*height);
//反転する
for(int i=0; i<buf.getSize(); i+=4, count++){
alpha = buf.getElem(i);
red = 255-buf.getElem(i+1);
green = 255-buf.getElem(i+2);
blue = 255-buf.getElem(i+3);
iBuf.setElem(count, ImageUtility.argb(alpha, red, green, blue));
}
//BufferedImageに戻す
int[] bandMasks = new int[] { 0x00ff0000, 0x0000ff00, 0x000000ff };
WritableRaster writableRaster = Raster.createPackedRaster(iBuf, width, height, width, bandMasks, null);
image.setData(writableRaster);
}
※以下のBufferedImageをDataBufferByteに変換するコードは省略してますが、
実際はBufferedImageのtypeごとに別個の処理をすることになります。
public static DataBufferByte convertARGB(BufferedImage image)
{
int alpha=1, count=0, color=0;
DataBufferByte rBuf=null;
DataBuffer iBuf = image.getRaster().getDataBuffer();
switch(image.getType()){
case BufferedImage.TYPE_INT_ARGB:
rBuf = new DataBufferByte(iBuf.getSize()*4);
for(int i=0; i<iBuf.getSize(); i++){
color = iBuf.getElem(i);
rBuf.setElem( i*4+0, color>>24&0xff );
rBuf.setElem( i*4+1, color>>16&0xff );
rBuf.setElem( i*4+2, color>>8&0xff );
rBuf.setElem( i*4+3, color&0xff );
}
break;
}
return rBuf;
}
④BufferedImageをbyte[]に変換してから処理
public static void negaposi_Array(BufferedImage image)
{
int height = image.getHeight();
int width = image.getWidth();
int red, blue, green, alpha, count=0;
//ピクセルデータをByte配列で取得
byte[] bufa = ImageUtility.convertByteARGB(image);
int[] iBuf = new int[width*height];
//反転する
for(int i=0; i<bufa.length; i+=4, count++){
alpha = bufa[i];
red = 255-bufa[i+1];
green = 255-bufa[i+2];
blue = 255-bufa[i+3];
iBuf[count] = ImageUtility.argb(alpha, red, green, blue);
}
//BufferedImageに戻す
int[] bandMasks = new int[] { 0x00ff0000, 0x0000ff00, 0x000000ff };
WritableRaster writableRaster = Raster.createPackedRaster(new DataBufferInt(iBuf, width*height), width, height, width, bandMasks, null);
image.setData(writableRaster);
}
※以下の処理も③同様省略してます。
public static byte[] convertByteARGB(BufferedImage image)
{
int alpha=1, count=0, color=0;
byte[] rBuf=null;
DataBuffer iBuf = image.getRaster().getDataBuffer();
switch(image.getType()){
case BufferedImage.TYPE_INT_ARGB:
rBuf = new byte[iBuf.getSize()*4];
for(int i=0; i<iBuf.getSize(); i++){
color = iBuf.getElem(i);
rBuf[ i*4+0] = (byte)(color>>24&0xff );
rBuf[ i*4+1] = (byte)(color>>16&0xff );
rBuf[ i*4+2] = (byte)(color>>8&0xff );
rBuf[ i*4+3] = (byte)(color&0xff );
}
break;
}
return rBuf;
}