DATE : 2006/09/12 (Tue)
(前回の記事)
メソッドの抽出 (1)
前回の記事では、処理手順の通りに処理を並び替えました。
そこで、並び替えた処理をメソッドに抽出します。
まずは、「1 : アルファチャンネル付きのカラーモデルの生成」の部分
// 対象画像のカラースペース ColorSpace sourceColorSpace = image.getColorModel().getColorSpace(); // アルファチャンネルのあるカラーモデル ColorModel destColorModel = new ComponentColorModel( sourceColorSpace, true, false, Transparency.BITMASK, DataBuffer.TYPE_BYTE);
を、createColorModelWithAlpha メソッドとして抽出します。
/** * アルファチャンネルを追加したカラーモデルを生成します。 * * @param src アルファチャンネルを追加する元となるカラーモデル * @return アルファチャンネルを追加したカラーモデル * @throws NullPointerException src が null の場合 */ private static ColorModel createColorModelWithAlpha(ColorModel src) { return new ComponentColorModel(src.getColorSpace(), true, src.isAlphaPremultiplied(), Transparency.BITMASK, src.getTransferType()); }
抽出前のコードには、ColorSpace を格納するローカル変数がありましたが、オブジェクトに問い合わせる形(src.getColorSpace())に変更しました。
また、アルファが直前に乗算されているかどうかやデータの転送型は、引数のカラーモデルに従うように修正しました。
この部分は、メソッドに分けて初めて気が付いた部分です。メソッドに分けると、こういった細かい部分に気が向くので便利ですね( ´∀`)b
すると、「1 : アルファチャンネル付きのカラーモデルの生成」の部分は次のようになります。
ColorModel destColorModel = createColorModelWithAlpha(image.getColorModel());
とてもシンプルになりました。
ちなみに、「2 : 1のカラーモデルを扱える WritableRaster の生成」の部分は、「1のカラーモデルを扱える WritableRaster の生成」という意味がコードそのものに表れているので、メソッドには抽出しません。
WritableRaster destRaster = destColorModel. createCompatibleWritableRaster(width, height);
ただし、width, height という変数の中身はコードの上の方に定義されているので読みづらく感じます。そこで、これもオブジェクトに問い合わせる形に変更します。
WritableRaster destRaster = destColorModel. createCompatibleWritableRaster( image.getWidth(), image.getHeight());
(つづきます)
DATE : 2006/09/10 (Sun)
(前回の記事)
処理の順序を整理する
まず手始めに、addAlpha メソッドの処理の流れを整理します。
とりあえず、「アルファチャンネルを追加した BufferedImage を作成する - 処理手順」に従ってコードを整理します。
整理した addAlpha メソッドは、次の通りです(javadoc コメントは省略しています)
private static RenderedImage addAlphaChannel(RenderedImage image) { // 対象画像の大きさ int width = image.getWidth(); int height = image.getHeight(); // 1 : アルファチャンネル付きのカラーモデルの生成 // 対象画像のカラースペース ColorSpace sourceColorSpace = image.getColorModel(). getColorSpace(); // アルファチャンネルのあるカラーモデル ColorModel destColorModel = new ComponentColorModel( sourceColorSpace, true, false, Transparency.BITMASK, DataBuffer.TYPE_BYTE); // 2 : 1のカラーモデルを扱える WritableRaster の生成 WritableRaster destRaster = destColorModel. createCompatibleWritableRaster(width, height); // 3 : 2の WritableRaster の色成分を設定 // 色成分のバンド番号の配列 int[] colorBandList = getColorBandList(destRaster.getNumBands()); // 色成分のみの WritableRaster WritableRaster colorRaster = destRaster. createWritableChild(0, 0, width, height, 0, 0, colorBandList); colorRaster.setRect(image.getData()); // 4 : 2の WritableRaster のアルファ成分を設定 WritableRaster alphaRaster = destRaster. createWritableChild(0, 0, width, height, 0, 0, new int[]{destRaster.getNumBands() - 1}); // 可視領域を表すアルファ成分のみの Raster WritableRaster visibleMaskRaster = createVisibleAlphaRaster(image); alphaRaster.setRect(visibleMaskRaster); // 5 : BufferedImage の生成 return new BufferedImage(destColorModel, destRaster, false, null); }
番号の振ってあるコメントは、「アルファチャンネルを追加した BufferedImage を作成する - 処理手順」の各手順を表しています。
具体的には、次のようにして処理の整理を行いました。
- 1 : アルファチャンネル付きのカラーモデルの生成
- アルファチャンネルのあるカラーモデルを生成しているところで処理を区切りました。
- 2 : 1のカラーモデルを扱える WritableRaster の生成
- 1の直後にある、WritableRaster を生成している処理で区切りました。
- 3 : 2の WritableRaster の色成分を設定
- アルファ成分を表す WritableRaster にデータを設定している部分の直前で一旦区切りました(具体的には、
alphaRaster.setRect(visibleMaskRaster);
の直前)。しかし、この部分には、アルファ成分に関係する処理も含まれています。そこで、アルファ成分に関係する処理を4に移しました(具体的には、WritableRaster alphaRaster = destRaster. createWritableChild(0, 0, width, height, 0, 0, new int[]{destRaster.getNumBands() - 1});
// 可視領域を表すアルファ成分のみの Raster WritableRaster visibleMaskRaster = createVisibleAlphaRaster(image);
の処理です) - 4 : 2の WritableRaster のアルファ成分を設定
- 3で移された処理を含めて、アルファ成分のデータを設定しているところで区切りました。
- 5 : BufferedImage の生成
- 最後に BufferedImage を生成しているので、この部分を5としました。
これで順序ごとに処理が分けられたので、ある程度は見やすくなりました。しかし、全体が長いためか、あまり分かりやすいようには思えません。
そこで、順序ごとに分けた部分を、メソッドとして抽出していきたいと思います。
(つづきます)
DATE : 2006/09/05 (Tue)
「アルファチャンネルを追加した BufferedImage を作成する」で掲載したコードは、私が開発していたアプリケーションに組み込まれていたコードをリファクタリングしたものです。
初めは、それほど長いコードでもなかったのでリファクタリングせずに掲載しようと思っていたのですが、リファクタリングすると「処理手順」に沿ったコードになったので、とても面白く感じました。
そこで、リファクタリングを行った記録を書き残しておこうと思い立ちました。できれば、掲載したコードよりもさらに再利用しやすい形にリファクタリングを進めていこうと思います。
ちなみに、リファクタリング前のコードは次の通りです(Subversion のリポジトリから引っ張りだしてきました)。
なお、一応動作はしますが、リファクタリング後のコードと比べると、細かい部分で間違いが含まれています。
なお、import 文などは省略しています。また、「...」は省略を表します。
/** * 指定の画像にアルファチャンネルを加えます。 * * @param image アルファチャンネルを加える画像 * @return アルファチャンネルを加えた画画像 */ private static RenderedImage addAlphaChannel(RenderedImage image) { // 対象画像の大きさ int width = image.getWidth(); int height = image.getHeight(); // 対象画像のカラースペース ColorSpace sourceColorSpace = image.getColorModel(). getColorSpace(); // アルファチャンネルのあるカラーモデル ColorModel destColorModel = new ComponentColorModel( sourceColorSpace, true, false, Transparency.BITMASK, DataBuffer.TYPE_BYTE); WritableRaster destRaster = destColorModel. createCompatibleWritableRaster(width, height); // 色成分のバンド番号の配列 int[] colorBandList = getColorBandList(destRaster.getNumBands()); // 色成分のみの WritableRaster WritableRaster colorRaster = destRaster. createWritableChild(0, 0, width, height, 0, 0, colorBandList); WritableRaster alphaRaster = destRaster. createWritableChild(0, 0, width, height, 0, 0, new int[]{destRaster.getNumBands() - 1}); // 可視領域を表すアルファ成分のみの Raster WritableRaster visibleMaskRaster = createVisibleAlphaRaster(image); colorRaster.setRect(image.getData()); alphaRaster.setRect(visibleMaskRaster); return new BufferedImage(destColorModel, destRaster, false, null); } /** * 可視部分のアルファ成分のみを持つ WritableRaster を生成します。 * * @param source 生成対象の画像 * @return 可視部分のアルファ成分のみを持つ WritableRaster */ private static WritableRaster createVisibleAlphaRaster( RenderedImage source) { // 画像の大きさ int width = source.getWidth(); int height = source.getHeight(); // SampleModel SampleModel sampleModel = new BandedSampleModel( DataBuffer.TYPE_BYTE, width, height, 1); WritableRaster raster = Raster.createWritableRaster( sampleModel, new Point(0, 0)); int[] alphaSample = new int[width * height]; // データの最大値を設定(アルファ成分を最大にする) Arrays.fill(alphaSample, 0xff); raster.setSamples(0, 0, width, height, 0, alphaSample); return raster; } /** * 指定のバンド数から、アルファ成分を除いたバンド番号の配列を * 返します。 * * @param numBands バンド数 * @return アルファ成分を除いたバンド番号の配列 */ private static int[] getColorBandList(int numBands) { int[] bands = new int[numBands - 1]; for (int i = 0; i < bands.length; i++) { bands[i] = i; } return bands; }
上のメソッド群は、 ImageProcessor クラスに含まれているメソッドで、ImageProcessor 内部で使用されています。
(;^ω^)正直なところ、あまり見直したくないコードですね。あちこちにコメントがある割には、解読にはそれなりの時間がかかりそうです。
次回から、このコードをリファクタリングしていきます。
(つづきます)
DATE : 2006/09/02 (Sat)
参考文献
- JDK 5.0 ドキュメント
- Java Advanced Imaging API FAQ - "Add a transparency mask (alpha channel) to an image." こちらには、3つのバンドからなる BufferedImage にアルファチャンネルを追加するサンプルコードが掲載されています。
DATE : 2006/09/01 (Fri)
(前回の記事)
3. BufferedImage 用の WritableRaster の色成分(アルファチャンネルを除いた成分)部分に、RenderedImage の画素値をコピーする
setColorSamples(destRaster, image.getData());
ここでは、対象の画像の Raster と、BufferedImage 用の WritableRaster を使って setColorSamples メソッドを呼び出しています。
setColorSamples の内容は、次の通りです。
private static void setColorSamples(WritableRaster dest, Raster src) { WritableRaster colorRaster = dest.createWritableChild( 0, 0, dest.getWidth(), dest.getHeight(), 0, 0, getBandListExceptAlpha(dest)); colorRaster.setRect(src); }
まず、 java.awt.image.WritableRaster#createWritableChild(int, int, int, int, int, int, int[]) を使って、色成分のみを表す WritableRaster を取り出します。このメソッドの最後の引数は、取り出すバンドの番号を並べた配列です。例えば、0, 1, 2 番のバンドを取り出す場合は、0, 1, 2 と格納された int 型の配列となります。ここでは、1つのバンドが色成分ひとつに相当すると考えても良いと思います。
なお、アルファ値は、最後のバンドがアルファ値のバンドになります(java.awt.image.ColorModel#getAlphaRaster(java.awt.image.WritableRaster) 参照。実際に、java.awt.image.ComponentColorModel のソースコードを調べると、確かに最後のバンドがアルファ値のバンドとして扱われていました)。
そこで、 getBandListExceptAlpha メソッドを使用して、アルファ値以外のバンド番号を取得します。このメソッドの内容は、次のようになります。
private static int[] getBandListExceptAlpha(Raster raster) { int[] bandList = new int[raster.getNumBands() - 1]; for (int i = 0; i < bandList.length; i++) { bandList[i] = i; } return bandList; }
ただ単に、全バンドから最後のバンド番号を除いた配列番号を生成しているだけです。
ただし、このメソッドは、アルファ値を持たない Raster を渡すとおかしな結果を返すことになります。今回は必ずアルファ値のバンドを持っているので、このような簡単な形になっています。
4. BufferedImage 用の WritableRaster にアルファチャンネル部分の値を設定する
setAlphaSamples(destRaster);
BufferedImage 用の WritableRaster を使って setAlphaSamples メソッドを呼び出しています。このメソッドの内容は、次の通りです。
private static final int ALPHA_VALUE = Integer.MAX_VALUE; private static void setAlphaSamples(WritableRaster raster) { int alphaBand = raster.getNumBands() - 1; for (int y = 0; y < raster.getHeight(); y++) { for (int x = 0; x < raster.getWidth(); x++) { raster.setSample(x, y, alphaBand, ALPHA_VALUE); } } }
引数の WritableRaster のアルファ値を表すバンドに、int 型の最大値を設定しています。この int 型の最大値は、アルファ値の最大値を表します。つまり、このメソッドでは、完全に不透明を表す値を設定していることになります。
なお、このメソッドもアルファチャンネルを持たない WritableRaster を渡すとおかしな結果になります。
5. BufferedImage 用の WritableRaster にアルファチャンネル部分の値を設定する
return new BufferedImage(destColorModel, destRaster, destColorModel.isAlphaPremultiplied(), null);
これまでの手順で BufferedImage 用の ColorModel や WritableRaster ができたので、これらを使った java.awt.image.BufferedImage のコンストラクタで BufferedImage を生成します。アルファの乗算状態は特にこだわらないので、ColorModel と同じ状態に設定しています。
以上で、アルファチャンネルを持った BufferedImage を生成することができました。
(了)