HEXA BLOG
ヘキサブログ
プログラム
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);
その結果作成されたのが、↓です。
つぶつぶによってグラデーションが再現されていますね
今回もUnityの減色機能で結果を比較してみましょう。
左が今回生成した画像で、右がフルカラーの元画像をUnityの設定で減色したものです。
グラデーションがだいぶ維持されたまま減色することが出来ました
というわけで、今回はここまでです。
次は別の手法も試してみます。
では
CATEGORY
- about ヘキサ (166)
- 部活動 (6)
- CG (18)
- プロジェクトマネジメント (1)
- 研修 (5)
- 美学 (1)
- いいモノづくり道 (230)
- 採用 -お役立ち情報も- (149)
- プログラム (188)
- デザイン (99)
- ゲーム (274)
- 日記 (1,104)
- 書籍紹介 (113)
- その他 (875)
- 就活アドバイス (20)
- ラーメン (3)
- ライフハック (25)
- イベント紹介 (10)
- 料理 (23)
- TIPS (7)
- 怖い話 (3)
- サウンド (5)
- 子育て (1)
- 筋トレ (1)
- 商品紹介 (21)
- アプリ紹介 (31)
- ソフトウェア紹介 (33)
- ガジェット紹介 (12)
- サイト紹介 (10)
- 研究・開発 (34)
- 回路図 (4)
- アナログゲーム (40)
- 交流会 (21)
- 報告会 (3)
- インフラ (25)
- グリとブラン (6)
- カメラ (9)
- クラフト (27)
- 部活 (14)
- 画伯 (15)
- カレー (6)
- 音楽(洋楽) (6)
- 映画・舞台鑑賞 (43)
- 飼育 (5)
- いぬ (8)
- ねこ (19)
ARCHIVE
- 2024年
- 2023年
- 2022年
- 2021年
- 2020年
- 2019年
- 2018年
- 2017年
- 2016年
- 2015年
- 2014年
- 2013年
- 2012年
- 2011年
- 2010年
- 2009年
- 2008年
- 2007年