こんにちは。
シュンスケです
先日、デザイナー視点で「画像圧縮」について書かれていましたが、
少し似た話で、減色の話です。
近頃は、モバイル端末でもフルHDディスプレイが搭載されていますが、
画像データのサイズは小さく抑えたい。
そんな時に出てくるのが、16bitカラーフォーマットです
16bitカラーの中には、赤緑青それぞれに振り分けるビット数を5bit、
最後1bitを透明度(1bitだと透明か、そうでないかのみ)にするもの等、
bitの振り分けで工夫するフォーマットもありますが、
今回は4444、 赤、緑、青、透明度それぞれ4bitずつのフォーマットの話です。
32bitカラーが、いわゆるフルカラー(透明度含む)なので、容量にして
ちょうど半分です
容量もさることながら、もう一つの利点は、対応ハードウェアの多さです。
サポートされていると、メモリ展開時もフルカラーにならずにそのままと
なるので、そこの削減にも繋がります
しかしながら、そう旨い話でもありません。
16bitになると使用できる色数はなんと4096色にまで減ります。
32bitで16777216色なため、4096分の1に減色されるということです
そうするとどうなるかというと、↓こうなります。
せっかくのグラデーションがガタガタです。
さて、前置きが長くなりましたが、これをツール作って何とかしようというのが始まりです。
基本の考えはまずこうです。
・4096色だけで、綺麗に見えるようにする。
→1ピクセルで元の色が表現出来ないなら数ピクセルで元の色っぽく見えるようにする。
が、ここで1つ断っておきます。今回ここまでいきません。
すみません、時間の関係上次回以降に続きます。
では、気を取り直してはじめましょう。 まずは言語選択。
Windows環境、作りやすさ、実行速度で、今回はC#です。
流れとしてはこんな感じでしょうか。
1.変換元ファイルパス指定
2.ファイル読み込み
3.内容をバッファに読み込んでファイルは閉じる
4.件の綺麗になるような変換をかける
5.新規ファイルとして保存
処理としては、↓です。
using System; using System.IO; using System.Drawing; using System.Drawing.Imaging; using System.Runtime.InteropServices; namespace Pic32bitTo16bit { class Program { private const int PIXEL_BYTES = 4; static void Main(string[] args) { // 入力受付 Console.WriteLine("please input picture path..."); var srcPath = Console.ReadLine(); // ファイル存在確認 if( !File.Exists(srcPath) ) { Console.WriteLine("file not exists."); return; } // 画像読み込み byte[] buf = null; int width = 0; int height = 0; using( var img = Image.FromFile(srcPath, true) as Bitmap ) { if( img == null ) { Console.WriteLine("file type is not support."); return; } // メモリに保持 var dat = img.LockBits(new Rectangle(Point.Empty, img.Size), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb); width = img.Width; height = img.Height; buf = new byte[width * height * PIXEL_BYTES]; Marshal.Copy(dat.Scan0, buf, 0, buf.Length); // 解放 img.UnlockBits(dat); } if( buf == null ) { Console.WriteLine("buf load failed."); return; } // 画像変換 filter(ref buf, width, height); // 出力画像作成 var newImg = new Bitmap(width, height); var newDat = newImg.LockBits(new Rectangle(Point.Empty, newImg.Size), ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb); Marshal.Copy(buf, 0, newDat.Scan0, buf.Length); newImg.UnlockBits(newDat); newDat = null; // 保存 var dstPath = srcPath.Substring(0, srcPath.Length - Path.GetFileName(srcPath).Length) + Path.GetFileNameWithoutExtension(srcPath) + "_new" + Path.GetExtension(srcPath); newImg.Save(dstPath); newImg.Dispose(); newImg = null; } static void filter(ref byte[] bmp, int w, int h) { for( int y = 0; y < h; ++y ) { for( int x = 0; x < w; ++x ) { var index = (y * w + x) * PIXEL_BYTES; int r = bmp[index + 0]; int g = bmp[index + 1]; int b = bmp[index + 2]; int a = bmp[index + 3]; // テスト的に半透明にしてみる a = 128; var color = Color.FromArgb(a, r, g, b); bmp[index + 0] = color.R; bmp[index + 1] = color.G; bmp[index + 2] = color.B; bmp[index + 3] = color.A; } } } } }
System.Drawingを使うためには、参照設定に追加する必要があるので忘れずに。
こういったツール類を作る際には、まずはこうやって処理の流れ、周りを固め、
工夫部分に後から注力できるようにするのが、オススメです。
これで動かすと、↓こうなります。
半透明グラデーションで消えていた背景がちゃんと一律になっています。
というわけで、今回はここまでです。
次回は画像処理部分でいくつか試してみようと思います
ちなみに、Unity用途であれば、↓こんなのもあって良さそうです。
https://github.com/keijiro/unity-dither4444/
では