HEXA BLOG
ヘキサブログ
プログラム
フルカラー画像をRGB565に減色
ラーメン屋でスタンプカード出すのを忘れがちな某(なにがし)です
過去にシュンスケさんが32bitカラー画像を16bitカラーに減色する方法を紹介してくれました。
32とか16とかその5
↑今回関係するのはこのへん
「組織的ディザ法」と「誤差分散法」を試して、誤差分散法が結構イイカンジという話でした。
私のチームで、シュンスケさんの実装を借用してみたところ、
それなりにイイカンジではありますが、元画像からの劣化がやっぱり気にはなりました。
※画像は「魔法パスワード1111」で使われている背景画像です。
減色による画質劣化がわかりやすいものを選びました。
画像全体にノイズのようなドットが目立ちます
そもそもこの元画像、見ての通り不透明な背景画像です。
つまり、アルファチャンネルを使っていないので、
RGBA各チャンネルを4bitずつ表現しているRGBA4444形式では、
アルファチャンネルの4bitがすべて無駄になっているということになります。
この無駄な4bitをうまく利用してやれば、さらにイイカンジにできそうです
16bitカラーと言っても、RGBA4444だけでなく、RGB565という形式もあります。
名前の通り、RGBがそれぞれ5、6、5bitで構成されて16bitとなります。
アルファチャンネルがないので、不透明画像に適しています。
緑(G)が1bitだけ他のチャンネルより大きいのが面白いですね。
1bit増えると表現可能な色数が倍になりますので、大きな差になります。
それでは、シュンスケさんの実装をRGBA4444からRGB565に対応させてみます
特定の桁数から、任意の桁数に収まるよう端数を切る処理が必要なので、
例えば、10桁から6桁に落とすなら、上位6桁を持ってきて、下位4桁を捨てる考えでいいでしょう。
32bitフルカラーからの減色の場合、1チャンネルあたり8bit(1byte)なので、
Nビットに落とすなら、上位Nビットを取ってくればOKです。
// 1ピクセルあたりのバイト数 private const int PIXEL_BYTES = 4; // 1バイトあたりのビット数 private const int BYTE_BITS = 8; // 減色モード private enum Mode { RGBA4444, RGB565, } // チャネル private enum Channel { R, G, B, A, } private static uint quantizeChannel(uint value, Mode mode, Channel channel) { var bitCount = BYTE_BITS; switch(mode) { case Mode.RGBA4444: bitCount = 4; break; case Mode.RGB565: if(channel == Channel.A) { bitCount = 0; } else if(channel == Channel.G) { bitCount = 6; } else { bitCount = 5; } break; default: break; } if(bitCount >= BYTE_BITS) return value; if(bitCount <= 0) return 0xFF; // 上位bitCountビット分を返す var shiftBits = BYTE_BITS - bitCount; return value & (~0u << shiftBits); } private static uint channelFilter(uint org, int x, int y, ref double[,] err, Mode mode, Channel channel) { // 蓄積された誤差を加えて近似 var tmp = (err[x, y] + org); if(tmp < 0) { tmp = 0; } if(tmp > 255) { tmp = 255; } var c = quantizeChannel((uint)Math.Round(tmp), mode, channel); // 誤差を分散して蓄積 double e = tmp - c; for(int i = 0; i < DISPERSION_TBL.Length; ++i) { var p = DISPERSION_TBL[i]; var xIdx = x + p.offsetX; var yIdx = y + p.offsetY; var xLen = err.GetLength(0); var yLen = err.GetLength(1); if(xIdx < 0 || xIdx >= xLen || yIdx < 0 || yIdx >= yLen) continue; err[xIdx, yIdx] += (p.rate * e); } return c; } private static void filter(ref byte[] bmp, int w, int h, Mode mode) { // 誤差格納用の領域を確保 var errR = new double[w, h]; var errG = new double[w, h]; var errB = new double[w, h]; var errA = new double[w, h]; for(int i = 0; i < w; ++i) { for(int j = 0; j < h; ++j) { errR[i, j] = 0; errG[i, j] = 0; errB[i, j] = 0; errA[i, j] = 0; } } for(int y = 0; y < h; ++y) { for(int x = 0; x < w; ++x) { var index = (y * w + x) * PIXEL_BYTES; uint r = bmp[index + 0]; uint g = bmp[index + 1]; uint b = bmp[index + 2]; uint a = bmp[index + 3]; r = channelFilter(r, x, y, ref errR, mode, Channel.R); g = channelFilter(g, x, y, ref errG, mode, Channel.G); b = channelFilter(b, x, y, ref errB, mode, Channel.B); a = channelFilter(a, x, y, ref errA, mode, Channel.A); var color = Color.FromArgb((int)a, (int)r, (int)g, (int)b); bmp[index + 0] = color.R; bmp[index + 1] = color.G; bmp[index + 2] = color.B; bmp[index + 3] = color.A; } } }
では、出力を比較してみましょう。
全然違いますね。RGB565なら、ぱっと見では元画像と同じに見えます
ところで、Unity(5.4以前)でテクスチャフォーマットを16bit Colorで指定したとき、
元画像が不透明だと自動でRGB565に変換されます(※)が精度は良くないです・・・
ここで今回の手法を適用した画像を使うと、Unityの自動変換による劣化をかなり防げます
※Unity 5.5からテクスチャフォーマットの直接指定はプラットフォーム別にしかできなくなりました。
旧バージョンからアップグレードした時には、metaに中途半端な設定が残ることもあるので注意しましょう。
エディタ上で「RGB 16 bit」と表示されるものが「RGB565」に相当します。
(参考:Unity – Manual: Texture compression formats for platform-specific overrides)
こういう減色ツールが手軽に使えるものとしてなかったりしますが、
原理は簡単なので作ってしまえば何かと融通がききそうです
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年