HEXA BLOG

プログラム

HEXA BLOGプログラム2015.12.9

32とか16とかその4

こんにちは。

 

シュンスケです

 

前回からさらに引き続き減色の話です。

 

前回は無事に1チャンネル当たり4bitで使用できる色のみに減色することが出来ました

ただし、このままでは、単純な均等分割で近い色に置き換えただけなので、

グラデーションの部分が滑らかではなく美しくありません

 

ここで遂に、初回時に基本の考えとして記載した、

「1ピクセルで元の色が表現出来ないなら数ピクセルで元の色っぽく見えるようにする。」

に取り掛かります

 

減色について某検索エンジンで調べてみると、いくつかキーワードが見つかります

・メディアンカット法

・k-means法

・組織的ディザ法

・誤差分散法

 :

 :

 

今回は使用できる色は決まっているので、はじめの2つは除外されます。

で、まずは変化値が少なくシンプルな、「組織的ディザ法」を使ってみます

「組織的ディザ法」について、ぱっと調べて出てくるのは、2値化に関する説明で、

ざっくり説明すると、以下の様な流れです。

 

変換したい画像を、4×4や8×8など、ブロック単位に分割する

単純な閾値固定の2値化であれば、0~255を真ん中の128より大きいかで

 0とするか255とするかを決定するところを、ブロック単位で閾値の平均が

 128になるように、ブロック内の閾値は場所によってバラつかせる

バラつかせた閾値を元に、0か255かを各ピクセル決定していく

※ブロック内の閾値のバラつかせ方を固定で定義したものを「ディザ行列」と呼び、

 代表的なものがいくつか考案されています。

 

さて、RGBA各チャンネルについては同じ処理にするとして、例によってまた

1チャンネルについて考えます。

1チャンネルについて16段階が使用できるので、2値化する処理を利用する部分としては、

各段階の補間です

 

例えば、0x05という数値が1チャンネルにあった際に、全てを0x00にするのではなく、

ディザ行列の閾値に従って、0x11に持ち上げてあげる事もあるといった具合に、

使用できる色の間に組織的ディザ法による2値化を利用します

 

結果、今回用いた処理の流れは以下の通りです。

 

チャンネルの値について16段階の間を1ブロックとするので、15分割するために17で割る

割った結果はベースのインデックスとして保持。15を超えると元の色のまま終了

 (今回の範囲なら255のみ)

ピクセルの位置からディザ行列の閾値をピックアップ

チャンネルの値が、ブロック内でどの位置かを算出

閾値とチャンネルの値の単位を揃えて比較

閾値を超えたかによって、インデックスをそのまま使うか、1つ上を使うかで

 色テーブルにアクセスして色を返す

 

定数は以下の通り(ディザ行列はBayer型を使用しました)

// 1ピクセルあたりのバイト数
private const int PIXEL_BYTES			= 4;

// ディザ行列幅
private const int DITHER_BLOCK_W		= 4;

// ディザ行列高さ
private const int DITHER_BLOCK_H		= 4;

// ブロック分割数
private const int BLOCK_COUNT			= 15;

// 1ブロックあたりの元色数
private const int BLOCK_COLOR_COUNT		= 255 / BLOCK_COUNT;

// 1ブロックあたりのピクセル数
private const int DITHER_GROUP_PIXEL_COUNT	= DITHER_BLOCK_W * DITHER_BLOCK_H;

// 16bitカラーテーブル
private static readonly uint[] RGBA4444_COLORS = new uint[] {
	0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff
};

// ディザ行列
private static readonly uint[,] DITHER_GROUP_MTX = new uint[DITHER_BLOCK_H, DITHER_BLOCK_W] {
	{ 0,  8,  2, 10},
	{12,  4, 14,  6},
	{ 3, 11,  1,  9},
	{15,  7, 13,  5}
};

 

1チャンネルを処理するメソッドを用意して、

static uint channelFilter(uint org, int x, int y)
{
	uint index = org / BLOCK_COLOR_COUNT;
	if( index >= BLOCK_COUNT ) return org;

	uint t = DITHER_GROUP_MTX[y % DITHER_BLOCK_H, x % DITHER_BLOCK_W];
	uint c = (org % BLOCK_COLOR_COUNT);
	t *= BLOCK_COLOR_COUNT;
	t += DITHER_GROUP_PIXEL_COUNT / 2;
	c *= DITHER_GROUP_PIXEL_COUNT;
	if( t > c ) return RGBA4444_COLORS[index];

	return RGBA4444_COLORS[index + 1];
}

 

減色処理部分は、前回の

r = RGBA4444_COLORS[r / 16];
g = RGBA4444_COLORS[g / 16];
b = RGBA4444_COLORS[b / 16];
a = RGBA4444_COLORS[a / 16];

を↓に置換えます。

r = channelFilter(r, x, y);
g = channelFilter(g, x, y);
b = channelFilter(b, x, y);
a = channelFilter(a, x, y);

 

その結果作成されたのが、↓です。

20151209_test_logo_new

つぶつぶによってグラデーションが再現されていますね

 

今回もUnityの減色機能で結果を比較してみましょう。

20151209_compar

左が今回生成した画像で、右がフルカラーの元画像をUnityの設定で減色したものです。

グラデーションがだいぶ維持されたまま減色することが出来ました

 

というわけで、今回はここまでです。

次は別の手法も試してみます。

 

では

RECRUIT

大阪・東京共にスタッフを募集しています。
特にキャリア採用のプログラマー・アーティストに興味がある方は下のボタンをクリックしてください

RECRUIT SITE