リスケール・フィルタを使う
本格的なフィルタ処理を行う場合には、システムライブラリに用意されているフィルタ処理用のクラスを利用するのが一般的です。これには、フィルタ処理の内容に応じていくつかのものが用意されています。これらは、java.awt.imageパッケージにまとめられています。
まずは、イメージのスケール(輝度)を一律に調整するためのRescaleOpクラスから使ってみましょう。これは、各ビットの輝度に、あらかじめ指定した係数の積を求め、それを新たな輝度として設定します。これにより、イメージ全体の輝度を明るくしたり、暗くしたりすることができます。
では、このRescaleOpを使ったサンプルをgetModifiedImageに用意してみましょう。
public BufferedImage getModifiedImage(BufferedImage im){ float f = 0.5f; // 係数 float d = 0f; // オフセット int w1 = im.getWidth(),h1 = im.getHeight(); BufferedImage im2 = new BufferedImage(w1,h1,BufferedImage.TYPE_INT_RGB); RescaleOp op = new RescaleOp(f,d,null); op.filter(im,im2); return im2; }

ここでは、イメージを暗くする処理を用意してみました。係数(f)に、0.5fを指定してあります。この値は1が現在と同じになり、それより小さければ暗く、大きければ明るくなります(なお、使用するイメージファイルによっては、うまくフィルタが機能しない場合があります。これについては後述します)。
では、フィルタ処理の流れを整理しましょう。フィルタ処理は、基本的に2つのイメージ(BufferedImage)を必要とします。1つが元のイメージ、もう1つがフィルタ処理により変換されたイメージです。
このとき、注意するのは、「両者は同じタイプのイメージでなければいけない」という点です。BufferedImageを作成する際、3番目のパラメータにイメージタイプを指定していますね。このタイプが同じでないと、うまくフィルタ処理されないので注意が必要です(これについては後述)。
さて、肝心のRescaleOpの使い方ですが、これは意外に簡単です。引数に、係数となるfloat値、オフセット値、そして「レンダリングヒント(RenderingHints)」と呼ばれる値をそれぞれ指定します。スケール値の変換は、基本的に「元の値×係数+オフセット値=新しい値」という形で計算されます。
レンダリングヒントは、イメージ描画の際に必要となる値で、例えばアンチエイリアスやアルファ補間などに使われます。が、とりあえずフィルタ処理をするだけなら、これはnullでかまいません。
こうしてRescaleOpインスタンスが用意できたら、そのfilterメソッドを呼び出してフィルタ処理を行います。これは2つのBufferedImageを引数に持ちます。第1引数に指定したイメージをフィルタ処理し、第2引数のイメージに描画します。
実際に動作を確認できたら、係数fの値をいろいろと変更してイメージがどう変わるか確かめてみてください。
RGBそれぞれリスケーリングする
RescaleOpでは、単純に全輝度を一律操作する他に、RGB(A)のそれぞれの輝度を個別に変換することも可能です。この場合には、係数とオフセットとしてfloatの配列を用意します。
public BufferedImage getModifiedImage(BufferedImage im){ float[] f = {0.5f,2.0f,0.5f}; // 係数 float[] d = {10f,0f,0f}; // オフセット int w1 = im.getWidth(),h1 = im.getHeight(); BufferedImage im2 = new BufferedImage(w1,h1,BufferedImage.TYPE_INT_RGB); RescaleOp op = new RescaleOp(f,d,null); op.filter(im,im2); return im2; }

これは、緑の輝度をあげ、赤と青の輝度を半分にするサンプルです。最初に用意されている係数とオフセット値に、それぞれfloat配列が用意されていることが分かります。ここに用意される値は、それぞれRGBの各輝度を変換するのに用いられます。
それぞれの値の並び順は、このBufferedImageのタイプに倣います。ここではTYPE_INT_RGBとしてありますので、各値はRGBとなるわけです。また配列に用意される値の個数も、やはりタイプにあわせる必要があります。TYPE_INT_RGBでは3つの項目が必要となりますし、TYPE_INT_RGBAならば4項目用意する必要があります。
RescaleOpインスタンスの作成およびfilterの利用については、先の例とまったく同じですので説明の要はないでしょう。
イメージタイプをそろえるには?
次のフィルタ処理に進む前に、ここで「フィルタ処理に必要な2つのイメージのタイプ」について触れておきましょう。フィルタ処理をする場合は、処理する対象のBufferedImageと、処理済のイメージを描画するBufferedImageでタイプをそろえる必要がある、と説明しました。
実際に試してみると、イメージファイルによっては先のサンプルでうまくフィルタ処理がなされない場合もあったのではないでしょうか。ここでは、処理後のイメージを描画するBufferedImageのタイプとして、TYPE_INT_RGBを指定しています。このため、読み込み元のイメージがこれとは違うタイプだったりすると、フィルタ処理がうまくいかないことがあるのです。
イメージファイルによって、読み込まれたBufferedImageのタイプは変わってきます。従って、元にあるイメージのタイプに揃えて描画するBufferedImageを用意してやる必要があるでしょう。
一般に、イメージファイルをそろえる場合には、BufferedImageのgetTypeを使ってタイプを取得し利用します。例えば、先の例でim2を作成する際に、
BufferedImage im2 = new BufferedImage(w1,h1,im.getType());
このようにしてimのタイプをgetTypeで取得し作成すればよいわけです。が、実際にさまざまなファイルで試してみると、これではうまくいかないケースにぶつかるはずです。イメージファイルから読み込んで生成されたBufferedImageをそのまま利用すると、こうした問題が発生することがあるのです。
そこで、フィルタ処理を行う場合には、新たにnew BufferedImageでインスタンスを用意し、そこにイメージを描画して利用するのが最も確実でしょう。
File f = new File(dir + fname); if (f.exists()) { try { BufferedImage im0 = ImageIO.read(f); im = new BufferedImage(im0.getWidth(),im0.getHeight(), BufferedImage.TYPE_INT_RGB); Graphics g = im.getGraphics(); g.drawImage(im0,0,0,null); g.dispose(); } catch (IOException e) { // 略 } } else { // 略 }
フィルタ処理をするイメージを、例えばこのようにして新たにBufferedImageを用意してやれば、確実にフィルタ処理することができます。フィルタ処理に関しては、「ImageIO.readで生成したBufferedImageをそのまま利用しない」のが基本と考えた方が良いようです。
