さて、前回
(2/5)に引き続き、マスク処理の応用でImageクラスのパレットについて説明をしたいと思います。
まず「パレット」とは何かというと、ビットマップの各ピクセルにおけるカラー情報(RGB)の配列となります。
(配列を絵の具のパレットと同じようなイメージで見たてます)
例えば7段階のグレースケールのパレットを用意したとします。
これをカラー情報配列を作成すると以下のようになります。
配列 | 色 | RGB | 16進 | 色 |
0 | 白 | R:255, G:255, B:255 | 0xffffff | ■ |
1 | : | R:212, G:212, B:212 | 0xd4d4d4 | ■ |
2 | : | R:170, G:170, B:170 | 0xaaaaaa | ■ |
3 | 灰色 | R:128, G:128, B:128 | 0x808080 | ■ |
4 | : | R:85, G:85, B:85 | 0x555555 | ■ |
5 | : | R:43, G:43, B:43 | 0x2b2b2b | ■ |
6 | 黒 | R:0, G:0, B:0 | 0x000000 | ■ |
これを虹の配色に変化させるとした場合
配列 | 色 | RGB | 16進 | 色 |
0 | 赤 | R:255, G:0, B:0 | 0xff0000 | ■ |
1 | 橙 | R:255, G:128, B:0 | 0xff8000 | ■ |
2 | 黄 | R:255, G:255, B:0 | 0xffff00 | ■ |
3 | 緑 | R:0, G:255, B:0 | 0x00ff00 | ■ |
4 | シアン | R:0, G:255, B:255 | 0x00ffff | ■ |
5 | 青 | R:0, G:0, B:255 | 0x0000ff | ■ |
6 | 紫 | R:255, G:0, B:255 | 0xff00ff | ■ |
のような配列を用意します。
前回MaskFilterクラスのfilterRGB関数にて
マスクカラーと一致している色の場合、"return 0"として戻り値を返しているといった具合です。 この関数で0を返すとそのピクセルは透明になります。 それ以降の処理は有効となるビットマップのピクセルをどのようにするかという判定になります。
|
ということを説明しましたが、この"return 0"を行わなかった場合以降の処理にパレット処理を行います。
具体的にコードで表わすと、以下の手順でコーディングを行います。
1.MaskFilterクラスから、パレットを持つクラスを派生させる。
2.派生させたクラスのコンストラクタにて、基底クラス(MaskFilter)の
メンバ変数m_paletteにパレット配列を設定する。
といった手順となります。
サンプルコードでは以下のような感じです。(前回のMaskFilterクラスも記述します)
【単純なコーディング例】
/** * マスク用イメージフィルタ */ 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); } } /** * 虹パレット */ public class RainbowPalette extends MaskFilter { // パレット(ベース配色→変換色) protected int[][] palette = { { 0xffffffff, 0xfff00000 }, // 0 白→赤 { 0xffd4d4d4, 0xffff8000 }, // 1 :→橙 { 0xffaaaaaa, 0xffffff00 }, // 2 :→黄 { 0xff808080, 0xff00ff00 }, // 3 灰色→緑 { 0xff555555, 0xff00ffff }, // 4 :→シアン { 0xff2b2b2b, 0xff0000ff }, // 5 :→青 { 0xff000000, 0xffff00ff }, // 6 黒→紫 }; /** * コンストラクタ */ public RainbowPalette() { super(); // パレットをアタッチします m_palette = palette; } /** * コンストラクタ * @param mask マスク値(RGB) * @param transparent 透過レベル(0 ~ 255) */ public RainbowPalette(int mask, int transparent) { super(mask, transparent); // パレットをアタッチします m_palette = palette; } } |
この派生したクラスにより、グレースケールのビットマップを虹色のビットマップに変換するこができます。
そしてパレットでカラーを置き換えた処理の後、またはパレット処理を行わなくても、この有効ピクルに対して輝度の設定も行いつつ、更にそれに対して透明度との積を取った値を戻り値として決定しています。

そして、前々回
(2/2)のImageクラスのビットマップファイル読み込み処理と併せて実現すると、
【単純なコーディング例】
/** * システムを管理するクラス * システム唯一の情報などを管理します */ 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); } /** * イメージを取得します * @param fileName 画像ファイル名 * @param imageFilter イメージフィルター */ public static Image loadImage(String fileName, MaskFilter imageFilter) { return loadImage(fileName, imageFilter, Global.m_component); } } |
と前回の関数に更に任意のパレット用フィルターを引数に渡せるloadImge関数を最後に追加して完成です。
実際に呼び出すコードは至極単純で、文頭のグレースケールのピクセルで構成されたビットマップファイルを用意(ここでは"gray.png"とします)して、
【単純なコーディング例】
// グレースケールのビットマップの読み込み Image grayImage = ImageTool("gray.png"); // 虹色のビットマップの読み込み Image rainbowImage = ImageTool("gray.png", new RainbowPalette()); |
というプログラムコードを記述すれば、グレースケールと、虹色のImageクラスを生成することができます。
更に色を暗くしたり半透明にしたい場合は、
【単純なコーディング例】
// 虹色を少し暗い色に RainbowPalette palette = new RaindowPalette(); palette.m_brightMode = true; palette.m_bright = -50; Image grayImage = ImageTool("gray.png", palette); // 更に半透明に palette.m_transparent = 128; Image rainbowImage = ImageTool("gray.png", palette); |
といった具合で実現できます。
今回説明したパレット処理では指定したビットマップファイルにつき、一つのImageクラスが生成されてしまいますが、一つのImageクラスに対してリアルタイムにパレット処理を行いたい場合は、ImageToolクラスを改良して実現することも可能です。
(ただし、パレット数が多ければ多い程、オーバーヘッドが大きくなるので、この辺はゲーム内容との兼ね合いとなるかと思います)
昔のビデオゲームはハードウェアに依存した情報として色管理を行っていたものが多く、ハードウェアの色情報を変化させると、それに付随して自動的にキャラクターの色情報が高速且つ容易に変更できたのですが(逆にハードウェアの色情報を誤って設定してしまうと全ての色が変化してしまいます)、時代はパレットではなく直接ビットマップピクセルカラーを変更する方式に流れたので、このようなとても面倒な処理を実装する必要となってしまう傾向にあります。
次回は画面にImageクラスを描画する処理に入りたいと思います。