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

カラーの画像データは以前は 24bits が主流だったのですが、近年は 32bits が主流になりつつあります。

画像データは、BGRA, BGRA, BGRA, BGRA.... のように連続したカラー画素のデータが隙間なくシステムメモリに配置されます。

このような画像データの形は、画像処理業界では B8G8R8A8 と称されます。ここで A は Alpha を意味し透明度を意味する場合が多いです。CGの分野でアルファブレンディングという用語がありますがそのアルファのことです。

なぜ「...透明度を意味する場合が多い...」とあいまいな表現をしたかというと、この A にあたる1バイトはユーザの定義次第で透明度以外の意味で使っている事例もあるからです。

例えば画像入力ボードなどで、A になんの意味を持たせずにメモリデータアクセスの高速化を期待した DWORD アライメントのためだけに使っているという事例も見られます。

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

下記コードのコンパイルするためには、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 );

		// 32ビットパックのデータ構造体の宣言.
		[StructLayout( LayoutKind.Explicit)]
		public struct GY_IMG_BPP32
		{
			[FieldOffset(0)] public byte B;
			[FieldOffset(1)] public byte G;
			[FieldOffset(2)] public byte R;
			[FieldOffset(3)] public byte A;
		}

		const int INI_W = 512;
		const int INI_H = 256;

		GY_IMG_BPP32 [] 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_BPP32[ 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;

			// 0xff で不透明、0x80 で半透明、0x00 で透明.
			const byte LEVEL_ALPHA = 0xff;

			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;
					Data000[ i + w * j ].A = LEVEL_ALPHA;
				}

				// 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;
					Data000[ i + w * j ].A = LEVEL_ALPHA;
				}

				// 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;
					Data000[ i + w * j ].A = LEVEL_ALPHA;
				}

				// 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;
					Data000[ i + w * j ].A = LEVEL_ALPHA;
				}

			}

			// 32bpp のビットマップを作成する.
			PixelFormat pixfmt = PixelFormat.Format32bppArgb;
			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_BPP32 );
				int one_scan_bytes_dst = bmdt.Stride;

				// ここで32bitsパックドデータを 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 );

			}

			// BMPファイルは透明度の定義がないのでPNGファイルで保存する.
			String filepath = "c:/tmp/bpp32.png";
			ImageFormat imgfmt = ImageFormat.Png;
			Bmp000.Save( filepath, imgfmt );

			// ビットマップは生成したら Dispose しなければならない.
			Bmp000.Dispose();
			Bmp000 = null;

			// 「フォト」や「ペイントブラシ」などで保存した画像ファイルを開く.
			Process.Start( filepath );

		}

	}
}

70行目、LEVEL_ALPHA の値を 0xff, 0x80, 0x00 など、いろいろな値に変えてみることによって透明度の効き目が実感できるでしょう。

下記に、透明度を変えるとどのようなPNGが作成されるか示します。

透明度 0xff のとき
透明度 0x80 のとき
透明度 0x00 のとき