さて、前回(2/2)に引き続き、Imageクラスのビットマップの読み込みについて説明をしたいと思います。
前回ではビットマップファイルの読み込みを行ったところまで説明しましたが、この状態では描画したくない部分(キャラクターの周りの背景部分)まで描画の対象となってしまうという現象が発生してしまいます。
この状態を改善する方法がマスク処理となります。マスク処理はビットマップ内の指定の色を描画しないという情報に書き換える処理の事を表します。
JavaのImageクラスではイメージフィルタという方法を用いると、ビットマップデータを容易に変更することが出来ます。
(他にも色々な方法がありますが、ここではこの方法で説明したいと思います)
さて、目的である「ビットマップ内の描画をしたくない部分を作成する」にはビットマップイメージをRGBの形式で操作するRGBImageFilterクラスを用いると、比較的容易に実現が行えます。
まずはサンプルソースにて実装例を挙げてみます。
【単純なコーディング例】
/** * マスク用イメージフィルタ */ public class MaskFilter extends RGBImageFilter { // パレット(派生先で配列を指定します) protected int[][] m_palette; // マスクカラー(RGB) public int m_mask = 0xff00ff00; // 透明レベル(0 ~ 255) public int m_transparent = 0xffffffff; // 輝度モード public boolean m_brightMode = false; // 輝度レベル(0 ~ 255) public int m_bright = 0; /** * コンストラクタ */ MaskFilter() { canFilterIndexColorModel = true; } /** * コンストラクタ * @param mask マスク値(RGB) * @param transparent 透過レベル(0 ~ 255) */ public MaskFilter(int mask, int transparent) { canFilterIndexColorModel = true; m_mask = mask; m_transparent = (transparent << 24) | 0x00ffffff; } /** * ピクセルフィルターコールバック<br> * 詳細は JDK ImageFilter クラスを参照 * @param x ピクセル X 座標 * @param y ピクセル Y 座標 * @param rgb RGB 値 */ public int filterRGB(int x, int y, int rgb) { // マスクチェック if(rgb == m_mask) { return 0; } // パレットが無い場合は明るさのみをフィルタリング if(m_palette == null) { // 輝度モードの場合は 1 ピクセルごとに輝度を設定します if(m_brightMode == true) { int r = (((0x00ff0000 & rgb) >> 16) + m_bright); if(r > 256) {r = 255;} if(r < 0) {r = 0;} r &= 0x000000ff; int g = (((0x0000ff00 & rgb) >> 8) + m_bright); if(g > 256) {g = 255;} if(g < 0) {g = 0;} g &= 0x000000ff; int b = ((0x000000ff & rgb) + m_bright); if(b > 256) {b = 255;} if(b < 0) {b = 0;} b &= 0x000000ff; rgb = (rgb & 0xff000000) | ((r << 16) | (g << 8) | b); } // 明るさを考慮して配色を return (rgb & m_transparent); } // 一致したパレットを置き換えます int count = m_palette.length; for(int i = 0; i < count; i++) { if((m_palette[i][0] & 0x00ffffff) == (rgb & 0x00ffffff)) { rgb = m_palette[i][1]; break; } } // 輝度モードの場合は 1 ピクセルごとに輝度を設定します if(m_brightMode == true) { int r = (((0x00ff0000 & rgb) >> 16) + m_bright); if(r > 256) {r = 255;} r &= 0x000000ff; int g = (((0x0000ff00 & rgb) >> 8) + m_bright); if(g > 256) {g = 255;} g &= 0x000000ff; int b = ((0x000000ff & rgb) + m_bright); if(b > 256) {b = 255;} b &= 0x000000ff; rgb = (rgb & 0x00ffffff) | ((r << 16) | (g << 8) | b); } // 明るさを考慮して配色を return (rgb & m_transparent); } } |
少し長いコードですが、要点だけを抑えればおおよそシンプルに考えることができます。また、「パレット」というキーワードが記述されていますが、とりあえずこの部分は次回に説明したいと思います。
まずMaskFilterクラスはRGBImageFilterより派生した、「マスクカラー」「透明度」「明るさ」を持つクラスとなります。
コンストラクタにて引数を指定しない場合は、緑色(RGBと透明度で表現すると 0xff00ff00)をマスクカラー、透明度を完全不透明(0xff。逆に透明は0xffとなります)としています。
コンピューターにおける古くからマスクカラーにはよくマゼンダ色がマクスカラー(クロマキーとも言ったりします)にされたりするのですが、色が沈みやすく個人的に見辛いと判断したので、ここでは緑色にしました。
次にfilterRGB関数ですが、この関数はFilteredImageSourceクラスがクラスを使用して、更にComponentクラスがFilteredImageSourceを用いてこのImageクラスを生成すると、この際に1ピクセル毎に呼び出されるコールバック関数となります。
…文章ですと複雑なので、サンプルソースとイメージで説明すると以下のとおりです。
(ソースは前回のGlobal, ImageToolクラスを流用して記述します)
【呼び出し例のサンプルコード】
/** * システムを管理するクラス * システム唯一の情報などを管理します */ public class Global { // 描画コンポーネント public static JPanel m_component; // ルートパス(ClassLoader) public static Class m_rootPath; // 透過用のイメージフィルタ public static MaskFilter m_maskFilter; /** * コンストラクタ */ Global() { // ルートパスを設定します m_rootPath = getClass().getClassLoader().getClass(); // マスク用のイメージフィルタを作成します m_maskFilter = new MaskFilter(); } /** * 指定のファイルから URL を取得します * @param filename ファイル名 * @return URL */ public static URL getURL(String filename) { return m_rootPath.getResource(filename); } } /** * 画像ツール */ public class ImageTool { /** * イメージを取得します * @param fileName 画像ファイル名 * @param imageFilter イメージフィルター * @param component コンポーネント */ public static Image loadImage( String fileName, ImageFilter imageFilter, Component component) { // イメージを読み込みます Toolkit toolkit = Toolkit.getDefaultToolkit(); Image srcImage = null; URL url = Global.getURL(fileName); if(url != null) { srcImage = toolkit.getImage(url); } if(srcImage == null) { return null; } MediaTracker mt = new MediaTracker(component); mt.addImage(srcImage, 0); try { mt.waitForID(0); } catch(Exception e) {} // ImageFilter を用いてフィルター処理イメージを作成します Image destImage = component.createImage( new FilteredImageSource( srcImage.getSource(), imageFilter)); if(destImage == null) { return null; } mt.addImage(destImage, 1); try { mt.waitForID(1); } catch(Exception e) {} return destImage; } /** * イメージを取得します * @param fileName 画像ファイル名 */ public static Image loadImage(String fileName) { return loadImage(fileName, Global.m_maskFilter, Global.m_component); } } |
この方法を用いると、Component.createImage が呼び出された時点で、MaskFilterクラスのfilterRGB関数がビットマップの1ピクセル毎に対して呼び出されます。
そして、filterRGB関数の先頭にて
public int filterRGB(int x, int y, int rgb) { // マスクチェック if(rgb == m_mask) { return 0; } : |
と判定している処理がマスク処理となります。
マスクカラーと一致している色の場合、"return 0"として戻り値を返しているといった具合です。
この関数で0を返すとそのピクセルは透明になります。
それ以降の処理は有効となるビットマップのピクセルをどのようにするかという判定になります。次回はこの有効ピクセルの情報を変化させるためのパレット処理について説明したいと思います。

これでImageクラスで読み込んだビットマップを描画処理にかけると緑の枠が表示されない状態になります。
(描画処理自体についてはまだまだ先となりますが、現時点では「まぁ、ゲームとしては表現しやすいビットマップを作成した。」という概念で話を進める事とします)