BufferedImageが遅い

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;

}