24bitsデータをビットマップに書き込む

カラーの画像データは、Blue と Green と Red の成分がそれぞれ 0..255 の 8bits 深さデータをもっています。

たとえばカラーカメラから送られてくる画像データは、BGR, BGR, BGR, BGR.... のように連続したカラー画素のデータが隙間なくシステムメモリに配置される場合が多いです。このようなカラー画素のデータ構造は C# には定義されていないので、自分で struct を使ってパックドな24bits画素データを宣言してやる必要があります。

このような画像データの形は、画像処理業界では B8G8R8 と称されます。

下記のコードは、あらかじめ準備しておいた B8G8R8のデータを、Bitmap の中に書き込んで、Bitmap のメソッドを使って、BMP形式で保存します。

下記コードのコンパイルするためには、VisualStudio のビルドオプションで「アンセーフコードの許可」を実施してください。ビルドオプションの設定方法は下記の記事をごらんください。

アンセーフunsafeコードを許可する

データのコピーやポインタ操作などアンセーフ unsafe なコードのビルドを実施する方法を紹介します。

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 バイトになります。出来上がったファイルのプロパティを見て確認してみてください。