画像処理のプログラムを作成するときは、GUIはC#、画像処理はC、C++で書かれたライブラリで作成するというのが、私の定番となっているのですが、C言語で処理された画像データをC#へ渡す場合は、画像データのコピーや画像ファイルを介せずとも、画像データのポインタをC#側へ渡す事で画像データを扱うことが可能となります。
これを可能にするのには、Bitmapクラスのコンストラクタに
public Bitmap(
int width,
int height,
int stride,
PixelFormat format,
IntPtr scan0
)
というのがあるので、scan0の部分にC言語で扱っている画像データのポインタを渡す事で、C#のビットマップオブジェクトとして扱うことが可能となります。
ただし、画像データのメモリ構造はトップダウンで画像1行あたりのサイズが4バイト単位となります。
(参考)
scan0に渡されたポインタはBitmapクラス内ではメモリを参照するだけ(Bitmapクラス内でメモリを確保していない)なので、BitmapオブジェクトをDisposeするまでscan0で示されたメモリは解放しないように注意が必要です。
逆にBitmapオブジェクトをDisposeしてもメモリは解放されないので、メモリの解放も忘れずに。
メモリが参照されているだけかどうか?を評価するのに、下記のようなプログラムを作成してみました。
// 元のBitmap
var bmp1 = new Bitmap(@"C:\Temp\Lenna.bmp");
// ロックしてポインタ参照できるようにする
var bmpData1 = bmp1.LockBits(new Rectangle(0, 0, bmp1.Width, bmp1.Height),
System.Drawing.Imaging.ImageLockMode.ReadWrite, bmp1.PixelFormat);
// ポインタを渡してBitmapクラスを作成
var bmp2 = new Bitmap(
bmp1.Width,
bmp1.Height,
bmpData1.Stride,
bmp1.PixelFormat,
bmpData1.Scan0 // bmp1の画像データのポインタ
);
// アンロック
bmp1.UnlockBits(bmpData1);
// ロックしてポインタ参照できるようにする
var bmpData2 = bmp2.LockBits(new Rectangle(0, 0, bmp2.Width, bmp2.Height),
System.Drawing.Imaging.ImageLockMode.ReadWrite, bmp2.PixelFormat);
var data2 = new byte[Math.Abs(bmpData2.Stride) * bmp2.Height];
// bmpData2の輝度値を配列へコピーする
System.Runtime.InteropServices.Marshal.Copy(
bmpData2.Scan0,
data2,
0,
data2.Length
);
// 画像の輝度値を+50する
for (int i = 0; i < data2.Length; i++)
{
data2[i] = (byte)Math.Min(data2[i] + 50, 255);
}
// 元のポインタへコピーして、データを戻す
System.Runtime.InteropServices.Marshal.Copy(
data2,
0,
bmpData2.Scan0,
data2.Length
);
// アンロック
bmp2.UnlockBits(bmpData2);
//
bmp2.Dispose();
// bmp1を保存(※bmp1は直接輝度値の変更はしていない)
bmp1.Save(@"C:\Temp\Lenna1.bmp", System.Drawing.Imaging.ImageFormat.Bmp);
// 解放
bmp1.Dispose();
上記のプログラムではbmp1でBitmapオブジェクトを作成(画像のメモリを確保)し、bmp1の画像データのポインタをbmp2へ渡しています。
この時点でbmp1とbmp2の画像データのメモリは同じをメモリを参照しているはずなので、bmp2の画像の輝度値を変更すると、bmp1の画像も変更されているか?を確認しています。
元の画像(bmp1の画像)
bmp2を処理した後のbmp1の画像
以上のようになり、思惑通りでした!
←画像処理のためのC#テクニックへ戻る
コメント