WriteableBitmapの保存方法その2
bitmapのヘッダー情報を自分で作り出して保存する方法。
自分の環境ではBmpBitmapEncoderを使う方法よりも早くできた。
かかる時間はだいたいの場合半分以下。
8bitのグレースケール専用になっている。
構造体の初期化や画像のサイズはカラー時には変更する必要あり。
処理は大きく4段階
1.ストリームの作成
2.ヘッダー情報の書き込み
3.カラーテーブルの書き込み
4.ピクセルデータの書き込み。
各関数は、下記に記載。
public void SaveBitmap(WriteableBitmap bitmap, string fileName) { //ストリームの作成 using (FileStream stream=new FileStream(fileName,FileMode.Create,FileAccess.Write)) { //ヘッダー情報の書き込み BitmapHeaderSave(stream, bitmap); //カラーテーブルの書き込み ColorTableSave(stream); //ピクセルデータの書き込み ImageDataSaveR(stream, bitmap); } }
まずは、ヘッダー情報の構造体
あるかもしれないが探すのが面倒だったので作った。
[System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Explicit)] public struct BitmapHeader { //BITMAPFILEHEADER /// <summary>BM(値固定)</summary> [System.Runtime.InteropServices.FieldOffset(0)] public Int16 bfType; /// <summary>ファイルサイズ</summary> [System.Runtime.InteropServices.FieldOffset(2)] public Int32 bfSize; /// <summary>予約値1(値固定)</summary> [System.Runtime.InteropServices.FieldOffset(6)] public Int16 bfReserved1; /// <summary>予約値2(値固定)</summary> [System.Runtime.InteropServices.FieldOffset(8)] public Int16 bfReserved2; /// <summary>画像データまでのオフセット</summary> [System.Runtime.InteropServices.FieldOffset(10)] public Int32 bfOffBits; //BITMAPINFOHEADER /// <summary>BITMAPINFOHEADERサイズ(値固定)</summary> [System.Runtime.InteropServices.FieldOffset(14)] public Int32 biSize; /// <summary>画像の幅</summary> [System.Runtime.InteropServices.FieldOffset(18)] public Int32 biWidth; /// <summary>画像の高さ</summary> [System.Runtime.InteropServices.FieldOffset(22)] public Int32 biHeight; /// <summary>プレーン数(値固定)</summary> [System.Runtime.InteropServices.FieldOffset(26)] public Int16 biPlanes; /// <summary>色ビット数(今回は固定)</summary> [System.Runtime.InteropServices.FieldOffset(28)] public Int32 biBitCount; /// <summary>圧縮形式(今回は固定)</summary> [System.Runtime.InteropServices.FieldOffset(30)] public Int32 biCompression; /// <summary>画像データサイズ</summary> [System.Runtime.InteropServices.FieldOffset(34)] public Int32 biSizeImage; /// <summary>水平解像度(今回は固定)</summary> [System.Runtime.InteropServices.FieldOffset(38)] public Int32 biXPixPerMeter; /// <summary>垂直解像度(今回は固定)</summary> [System.Runtime.InteropServices.FieldOffset(42)] public Int32 biYPixPerMeter; /// <summary>パレット使用色数(今回は固定)</summary> [System.Runtime.InteropServices.FieldOffset(46)] public Int32 biClrUsed; /// <summary>重要色数(今回は固定)</summary> [System.Runtime.InteropServices.FieldOffset(50)] public Int32 biCirImportant; }
次にカラーテーブル
[System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Explicit)] public struct ColorTable { //RGBQUAD /// <summary>B</summary> [System.Runtime.InteropServices.FieldOffset(0)] public byte rgbBlue; /// <summary>G</summary> [System.Runtime.InteropServices.FieldOffset(1)] public byte rgbGreen; /// <summary>R</summary> [System.Runtime.InteropServices.FieldOffset(2)] public byte rgbRed; /// <summary>予約領域</summary> [System.Runtime.InteropServices.FieldOffset(3)] public byte rgbReserved; }
ストリームにヘッダー情報を書き込む方法
public void BitmapHeaderSave(Stream stream, WriteableBitmap bitmap) { BitmapHeader header = new BitmapHeader() { bfType = 0x4D42, bfSize = bfSize = bitmap.PixelHeight * bitmap.BackBufferStride, bfReserved1 = 0x0, bfReserved2 = 0x0, bfOffBits = 54 + 256 * 4, biSize = 40, biHeight = bitmap.PixelHeight, biWidth = bitmap.PixelWidth, biPlanes = 0x1, biBitCount = 0x8, biCompression = 0x0, biSizeImage = bitmap.PixelHeight * bitmap.PixelWidth + 54 + 256 * 4, biXPixPerMeter = 0x00, biYPixPerMeter = 0x00, biClrUsed = 256, biCirImportant = 0x0, }; //54がほしいのに56が帰ってくるから-2する。 int headerSize = System.Runtime.InteropServices.Marshal.SizeOf(header) - 2; byte[] headerBytes = new byte[headerSize]; System.Runtime.InteropServices.GCHandle gch = System.Runtime.InteropServices.GCHandle.Alloc(headerBytes, System.Runtime.InteropServices.GCHandleType.Pinned); System.Runtime.InteropServices.Marshal.StructureToPtr(header, gch.AddrOfPinnedObject(), false); stream.Write(headerBytes, 0, headerBytes.Length); gch.Free(); }
ストリームにカラーテーブル情報を書き込む方法
public void ColorTableSave(Stream stream) { int colorNum = 256; byte[] colorTableSizeBytes = new byte[colorNum * 4]; byte val = 0; for (int i = 0; i < colorNum; i++) { val = Convert.ToByte(i); colorTableSizeBytes[4 * i] = val; colorTableSizeBytes[4 * i + 1] = val; colorTableSizeBytes[4 * i + 2] = val; } stream.Write(colorTableSizeBytes, 0, colorTableSizeBytes.Length); }
ストリームにピクセルデータを書き込む方法
public void ImageDataSaveR(Stream stream, WriteableBitmap bitmap) { byte[] rawData = new byte[bitmap.PixelHeight * bitmap.BackBufferStride]; System.Runtime.InteropServices.Marshal.Copy(bitmap.BackBuffer, rawData, 0, rawData.Length); for (int i = 0; i < bitmap.PixelHeight; i++) { //WriteableBitmapは、左下原点なので左上原点になるように調整。 stream.Write(rawData, bitmap.PixelHeight * bitmap.BackBufferStride - (i + 1) * bitmap.BackBufferStride, bitmap.BackBufferStride); } }