なんとなく

なんとなく書きます

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);
   }
}