|
C#(厳密には.NET Framework と言うべきか)でのビットマップ操作
画像ファイルをロードして表示するためのAPIやクラスの解説はよく見かけるのだけど、プログラム中でピクセルを操作した結果を表示する方法についてはあまり見かけないので、メモ。
大まかに言うと、
・Bitmap オブジェクトを生成。
・Bitmap オブジェクトから BitmapDataをロック
・ロックしたBitmapDataに設定
・BitmapDataのロックを解除
・Bitmapオブジェクトを描画
で描画できる。
ここでは、8bitグレイスケールのデータ(byte配列)を画像として描画するためのサンプルコードを示す。
まず、描画の元となるデータとしての8bitデータ配列を用意。
// BMP_WIDTH, BMP_HEIGHT は、データの幅と高さ
private byte[] img_data_ = new byte[BMP_WIDTH * BMP_HEIGHT];
描画元としてのBitmapオブジェクトを用意。8bitグレイスケールなので、ピクセルフォーマットはパレット付8ビットを選択する。
private Bitmap img_ = new Bitmap(BMP_WIDTH, BMP_HEIGHT, PixelFormat.Format8bppIndexed);
8bitグレイスケールなので、パレットを設定。
// パレットの設定は、img_ に対して 1 回だけおこなえばよい。
ColorPalette pal = img_.Palette;
for (int i = 0; i < 256; ++i)
{
pal.Entries[i] = Color.FromArgb(i, i, i);
}
img_.Palette = pal; // なぜかこうする。
ColorPalette オブジェクトを Bitmap から取得して、設定した後、Bitmap オブジェクトへ設定し直す事が必要。Bitmap から取得した ColorPalette オブジェクトが、取得元の Bitmap のカラーテーブルそのものを指すのではなく、複製したオブジェクトが返っているのだろうか。
ここまでが、言わば準備の段階。次からが、8bit配列を画像として描画するための本質的な部分。
BitmapDataをロックして、設定する。
BitmapData bmpdata = null;
try
{
bmpdata = img_.LockBits(new Rectangle(0, 0, BMP_WIDTH, BMP_HEIGHT),
ImageLockMode.WriteOnly, PixelFormat.Format8bppIndexed);
for (int j = 0; j < BMP_HEIGHT; ++j)
{
IntPtr dst_line = (IntPtr)((Int64)bmpdata.Scan0 + j * bmpdata.Stride);
Marshal.Copy(img_data_, j * BMP_WIDTH, dst_line, BMP_WIDTH);
}
}
finally
{
if (bmpdata != null)
{
img_.UnlockBits(bmpdata);
}
}
ロックしたBitmapDataは必ずしもロック範囲で連続したデータになっているわけではないので、1行単位でStrideメンバ分のオフセットを加えてアドレスを計算する。
また、生メモリ配列に等しいメモリに配列のデータをコピーするために、System.Runtime.InteropServices.Marshal.Copy()メソッドを利用する。
最後に描画する。
// Form上に配置した Panel に描画している。
e.Graphics.DrawImage(img_, new Rectangle(panel1.Location, panel1.Size));
自分が調べてた中で、一番の肝は、配列からrawメモリへコピーするためのMarshal.Copy()メソッドの存在。BitmapDataのロックについては、DirectX のテクスチャメモリ(Surfaceメモリ)のロックと同様だし、1行単位のオフセットをStrideメンバで計算しなければならないことも同様。
|