クリッピング処理
イメージの加工を行う場合、「イメージを切り抜く」という処理が必要となることも多々あります。単に原型のまま拡大縮小するなら簡単ですが、イメージの中から一部分だけを切り抜いて描画するようなことになると、「クリッピング」と呼ばれる処理を理解しておく必要があります。
クリッピングとは、あらかじめ描画を行う領域を指定しておき、その領域内だけに描画を行う処理です。これは、Graphics2Dを利用することで比較的簡単に行うことができます。Graphics2Dではシェイプ(Shape)を利用して描画を行いますが、このシェイプを使ってクリッピングの領域を設定できる仕組みになっているのです。
実際に、簡単なクリッピング処理の例を挙げておきましょう。
// import java.awt.geom.*; を追加しておく public BufferedImage getModifiedImage(BufferedImage im){ int w1 = im.getWidth(),h1 = im.getHeight(); BufferedImage im2 = new BufferedImage(w1,h1,BufferedImage.TYPE_INT_ARGB); Graphics2D g2 = im2.createGraphics(); g2.setColor(Color.PINK); g2.fillRect(0,0,w1,h1); Ellipse2D shape = new Ellipse2D.Double(w1 / 4,h1 / 4,w1 / 2,h1 / 2); g2.clip(shape); g2.drawImage(im, 0, 0, null); g2.dispose(); return im2; }

これは、円形のクリッピングを設定してイメージを描画させるサンプルです。背景は淡いピンクで塗りつぶしてあります。イメージが切り抜かれて描画されているのが分かりますね。
クリッピングの処理をしているのは、以下の部分です。
Ellipse2D shape
= new Ellipse2D.Double(w1 / 4,h1 / 4,w1 / 2,h1 / 2);
g2.clip(shape);
Ellipse2Dインスタンスを作成し、Graphics2Dのclipでクリッピングに設定している、これだけです。これで、以後の描画はすべてクリッピング設定したシェイプの内部にのみ描かれるようになります。ここではdrawImageしていますが、基本的にはすべての描画処理はクリッピングの影響を受けます。
clipは、連続して実行することでクリップする領域を合算できます。あるシェイプをclipした後に別のシェイプをclipすると、2つのシェイプの重なる領域がクリップされます。
クリッピングを解除する場合は、setClipという現在のクリップ設定を変更するメソッドでnull設定します。
クリップの演算
ただし、単純にシェイプをクリップするだけでは、描ける図形の形状も単純なものに限られてしまいます。繰り返しclipを使うことである程度の形状は作れますが、クリップの領域を追加したり削ったりをもっと柔軟に行いたい、という場合には別の方法をとった方が良いでしょう。
「java.awt.geom」パッケージには、任意の形状を示すためのAreaというクラスが用意されています。これを利用することで、複数のシェイプを演算して領域を作成することができ、この機能を利用すれば、複雑な形でイメージを切り抜き描画できるようになります。簡単な例を挙げておきましょう。
public BufferedImage getModifiedImage(BufferedImage im){ int w1 = im.getWidth(),h1 = im.getHeight(); BufferedImage im2 = new BufferedImage(w1,h1,BufferedImage.TYPE_INT_ARGB); Graphics2D g2 = im2.createGraphics(); g2.setColor(Color.PINK); g2.fillRect(0,0,w1,h1); Area area = new Area(); Ellipse2D shape = new Ellipse2D.Double(im.getWidth() / 2 - 35, im.getHeight() / 2 - 35,70,70); area.add(new Area(shape)); shape = new Ellipse2D.Double(im.getWidth() / 2 - 20, im.getHeight() / 2 - 20,40,40); area.subtract(new Area(shape)); for(int i = 0;i < 16;i++){ double x = (im.getWidth() / 2) + Math.sin(Math.PI / 8 * i) * 80; double y = (im.getHeight() / 2) + Math.cos(Math.PI / 8 * i) * 80; shape = new Ellipse2D.Double(x - 15,y - 15,30,30); area.add(new Area(shape)); } g2.clip(area); g2.drawImage(im,0,0,null); g2.dispose(); return im2; }

多数の円を組み合わせた形で図形をクリッピングして描画していますね。ここでは、Areaを複数作成してクリッピングの領域を作っています。Areaのインスタンス作成は、次のような形で行います。
new Area(); new Area(Shape shape);
単純にnewするだけの他、Shapeインスタンスを引数にすることで、そのシェイプの形状のAreaを作成することができます。こうして作成したAreaは、Area同士で演算していくことが可能です。ここでは、以下の2通りの演算処理を行っています。
[Area].add(Area area);
[Area].subtract(Area area);
加算すると、そのAreaの領域に引数で指定したAreaの領域を区分けた形状になります。また減算すると、そのAreaの領域から引数指定したAreaの領域を取り除いた形状となります。
このように、Shapeを引数指定して作成した(形状情報を持っている)Areaを用意し、それらを演算することで複雑な形状を作成できます。また、ここではクリッピング領域にAreaを使いましたが、Areaはそのままfill/drawで描画に用いることもできます。複雑な図形を描くときには重宝するでしょう。
まとめ
今回は、イメージファイルの読み書きから描画、クリッピング処理までイメージの利用全般について説明しました。単にイメージを読み込み、拡大縮小したり、切抜いて描画するだけであれば、これで十分できるようになるでしょう。
ただし、イメージの処理には、こうした形状に関する処理だけでなく、イメージの状態をさまざまに変更する、いわゆる「フィルタ処理」と呼ばれるものもあります。本格的なイメージ処理にはこれらの知識は不可欠でしょう。
ということで、次回は「イメージのフィルタ処理」を中心に説明を行う予定です。
