24bitsデータをビットマップに書き込む
カラーの画像データは、Blue と Green と Red の成分がそれぞれ 0..255 の 8bits 深さデータをもっています。
たとえばカラーカメラから送られてくる画像データは、BGR, BGR, BGR, BGR.... のように連続したカラー画素のデータが隙間なくシステムメモリに配置される場合が多いです。このようなカラー画素のデータ構造は C# には定義されていないので、自分で struct を使ってパックドな24bits画素データを宣言してやる必要があります。
このような画像データの形は、画像処理業界では B8G8R8 と称されます。
下記のコードは、あらかじめ準備しておいた B8G8R8のデータを、Bitmap の中に書き込んで、Bitmap のメソッドを使って、BMP形式で保存します。
下記コードのコンパイルするためには、VisualStudio のビルドオプションで「アンセーフコードの許可」を実施してください。ビルドオプションの設定方法は下記の記事をごらんください。
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
// この using 追加を忘れずに. Don't forget to add this sentence.
using System.Drawing.Imaging;
using System.Runtime.InteropServices;
using System.Diagnostics;
namespace aaa
{
public partial class Form1 : Form
{
// WindowsAPI を定義する、memcpy() と同じ動作をする.
[DllImport("Kernel32.Dll")]
public static unsafe extern void CopyMemory( byte* dst, byte* src, int bytes );
// 24ビットパックのデータ構造体の宣言.
[StructLayout( LayoutKind.Explicit)]
public struct GY_IMG_BPP24
{
[FieldOffset(0)] public byte B;
[FieldOffset(1)] public byte G;
[FieldOffset(2)] public byte R;
}
const int INI_W = 512;
const int INI_H = 256;
GY_IMG_BPP24 [] Data000;
Bitmap Bmp000;
public Form1()
{
InitializeComponent();
}
private void menuDebugExec000_Click( object sender, EventArgs e )
{
int w = INI_W;
int h = INI_H;
int numpix = w * h;
Data000 = new GY_IMG_BPP24[ numpix ];
int quarter_w = w >> 2;
// 左から順に、Gray, Blue, Green, Red のグラデーションを作成したい.
int xs0 = quarter_w * 0;
int xs1 = quarter_w * 1;
int xs2 = quarter_w * 2;
int xs3 = quarter_w * 3;
int xe0 = xs0 + quarter_w - 1;
int xe1 = xs1 + quarter_w - 1;
int xe2 = xs2 + quarter_w - 1;
int xe3 = xs3 + quarter_w - 1;
for ( int j = 0; j < h; j++ )
{
byte level = (byte)(j);
// Gray のグラデーション.
for ( int i = xs0; i <= xe0; i++ )
{
Data000[ i + w * j ].B = level;
Data000[ i + w * j ].G = level;
Data000[ i + w * j ].R = level;
}
// Blue のグラデーション.
for ( int i = xs1; i <= xe1; i++ )
{
Data000[ i + w * j ].B = level;
Data000[ i + w * j ].G = 0x00;
Data000[ i + w * j ].R = 0x00;
}
// Green のグラデーション.
for ( int i = xs2; i <= xe2; i++ )
{
Data000[ i + w * j ].B = 0x00;
Data000[ i + w * j ].G = level;
Data000[ i + w * j ].R = 0x00;
}
// Red のグラデーション.
for ( int i = xs3; i <= xe3; i++ )
{
Data000[ i + w * j ].B = 0x00;
Data000[ i + w * j ].G = 0x00;
Data000[ i + w * j ].R = level;
}
}
// 24bpp のビットマップを作成する.
PixelFormat pixfmt = PixelFormat.Format24bppRgb;
Bmp000 = new Bitmap( w, h, pixfmt );
// グラデーションデータをビットマップにコピーする.
unsafe
{
BitmapData bmdt = new BitmapData();
Rectangle rct = new Rectangle( 0, 0, w, h );
ImageLockMode mode = ImageLockMode.ReadWrite;
// ビットマップをロックする.
bmdt = Bmp000.LockBits( rct, mode, pixfmt );
// ワンスキャンぶんのバイト数を計算する.
// ビットマップの幅ピクセル数が4の倍数でなかったときは、
// one_scan_bytes_dst と one_scan_bytes_src は、
// 違う値になる可能性が高いので別々に扱う.
int one_scan_bytes_src = w * sizeof( GY_IMG_BPP24 );
int one_scan_bytes_dst = bmdt.Stride;
// ここで24bitsパックドデータを Bitmap に書き込む.
fixed( void* src = &( Data000[0] ))
{
byte* pt_src = (byte*)( src );
byte* pt_dst = (byte*)( bmdt.Scan0 );
for ( int j = 0; j < h; j++ )
{
CopyMemory( pt_dst, pt_src, one_scan_bytes_src );
pt_src += one_scan_bytes_src;
pt_dst += one_scan_bytes_dst;
}
}
// ビットマップをアンロックする.
Bmp000.UnlockBits( bmdt );
}
// ビットマップファイルを保存する.
String fp_bmp = "c:/tmp/bpp24.bmp";
ImageFormat imgfmt = ImageFormat.Bmp;
Bmp000.Save( fp_bmp, imgfmt );
// ビットマップは生成したら Dispose しなければならない.
Bmp000.Dispose();
Bmp000 = null;
// 「フォト」や「ペイントブラシ」などで保存した画像ファイルを開く.
Process.Start( fp_bmp );
}
}
}
画像データは、Bitmap を24bppで作成したので、そのフォーマットのまま保存されています。
下記にビットマップファイルサイズの計算方法を示しておきます。
- BITMAPFILEHEADER が 14バイト
- BITMAPINFOHEADER が 40バイト
- RGBQUADカラーパレットは使わないので 0バイト
- 512×256画素でB8G8R8なので ( 131072 × 3 ) → 393216 バイト
14 + 40 + 0 + 393216 を全部足すと 393270 バイトになります。出来上がったファイルのプロパティを見て確認してみてください。