views:

1089

answers:

2

I need to create a Bitmap object using raw bytes representing monochrome bitmap data. On the full framework, I am doing the following:

Bitmap bmp = new Bitmap(width, height, PixelFormat.Format8bppIndexed)
BitmapData bmpData = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.WriteOnly, bmp.PixelFormat);
// Write my data into bmpData.Scan0
bmp.UnlockBits(bmpData);

Unfortunately, the Compact Framework doesn't have the PixelFormat.Format8bppIndexed enum value. So how can I accomplish this on the CF? The only thing I can think of is to manually create the bitmap file header myself and write it along with the data into a Stream and then construct a Bitmap object with that Stream.

Ideas?

A: 

In the OpenNetCf Smart Device Framework library (there is a free version), they have their own Bitmap class (BitmapEx). While I haven't tried what you are trying to do, you might check out their implementation. (note: I do have to do this in the next week, so I will probably update this later)

Chris Brandsma
I looked at OpenNETCF but it relies on the same enum. Looking at how they implement some of the Bitmap class will probably be useful as I'm pretty sure I'll need to manually build the header myself. :(
Jason
+1  A: 
  const int XPelsPerMeter = 0xb12; // 72 ppi, 96 would work well too
  const int YPelsPerMeter = 0xb12;
  const int Gptr = 0x40;
  const int Srccopy = 0x00CC0020;


  struct BITMAPFILEHEADER
  {
     public ushort  bfType;
     public uint    bfSize;
     public ushort  bfReserved1;
     public ushort  bfReserved2;
     public uint    bfOffBits;
  }

  struct BITMAPINFOHEADER
  {
     public uint  biSize;
     public int   biWidth;
     public int   biHeight;
     public ushort   biPlanes;
     public ushort   biBitCount;
     public uint  biCompression;
     public uint  biSizeImage;
     public int   biXPelsPerMeter;
     public int   biYPelsPerMeter;
     public uint  biClrUsed;
     public uint  biClrImportant;
  }

  public static byte[] GetByteArray(Bitmap bitmap)
  {
     IntPtr hbm = bitmap.GetHbitmap(); // this is step (1)
     IntPtr sdc = GetDC( IntPtr.Zero );       // First we obtain the DC for the screen
     // Next, create a DC for the original hbitmap
     IntPtr hdc = CreateCompatibleDC( sdc );
     SelectObject( hdc, hbm );

     byte[] arrayBytes = CreateBinary(hdc, bitmap.Height, bitmap.Width);

     // Finally some cleanup.
     DeleteDC( hdc );
     ReleaseDC( IntPtr.Zero, sdc );
     DeleteObject( hbm );

     return arrayBytes;
  }

  static int WIDTHBYTES( int bits )
  {
     return ( ( ( ( bits ) + 31 ) / 32 ) * 4 );
  }

  private static byte[] CreateBinary( IntPtr hDc, int height, int width )
  {
     IntPtr hMemDc = CreateCompatibleDC( hDc );

     int cb = 0;

     BITMAPINFOHEADER bi = new BITMAPINFOHEADER();
     bi.biSize = ( uint )Marshal.SizeOf( bi );
     bi.biBitCount = 1; // Creating RGB bitmap. The following three members don't matter
     bi.biClrUsed = 2;
     bi.biClrImportant = 2;
     bi.biCompression = 0;
     bi.biHeight = height;
     bi.biWidth = width;
     bi.biPlanes = 1;
     cb = WIDTHBYTES( bi.biWidth * bi.biBitCount ) * bi.biHeight;
     bi.biSizeImage = ( uint )cb;
     bi.biXPelsPerMeter = XPelsPerMeter;
     bi.biYPelsPerMeter = YPelsPerMeter;

     IntPtr pBits = IntPtr.Zero;
     //Allocate memory for bitmap bits
     IntPtr pBi = LocalAlloc( Gptr, bi.biSize );
     // Not sure if this needed - simply trying to keep marshaller happy
     Marshal.StructureToPtr( bi, pBi, false );
     //This will return IntPtr to actual DIB bits in pBits
     IntPtr hBmp = CreateDIBSection( hDc, pBi, 0, ref pBits, IntPtr.Zero, 0 );
     //Marshall back - now we have BITMAPINFOHEADER correctly filled in
     //Marshal.PtrToStructure(pBI, bi);
     BITMAPINFOHEADER biNew = ( BITMAPINFOHEADER )Marshal.PtrToStructure( pBi, typeof( BITMAPINFOHEADER ) );

     //Usual stuff
     IntPtr hOldBitmap = SelectObject( hMemDc, hBmp );
     //Grab bitmap
     BitBlt( hMemDc, 0, 0, bi.biWidth, bi.biHeight, hDc, 0, 0, Srccopy );
     // Allocate memory for a copy of bitmap bits
     byte[] RealBits = new byte[cb];
     // And grab bits from DIBSestion data
     Marshal.Copy( pBits, RealBits, 0, cb );

     // This simply creates valid bitmap file header, so it can be saved to disk
     BITMAPFILEHEADER bfh = new BITMAPFILEHEADER();
     uint colorSize = 2 * 4;//2 colors for B&W, 4 bytes (RGBQUAD)
     uint sizeofBinfo = 0x36 + colorSize;//original
     //sizeofBINFO = (uint)Marshal.SizeOf(bi);//sorin
     //bfh.bfSize = ( uint )cb + 0x36; // Size of header + size of BITMAPINFOHEADER size of bitmap bits
     bfh.bfSize = ( uint )( cb + sizeofBinfo );
     bfh.bfType = 0x4d42; //BM
     bfh.bfOffBits = sizeofBinfo; // 
     int HdrSize = 14;
     byte[] header = new byte[HdrSize];
     BitConverter.GetBytes( bfh.bfType ).CopyTo( header, 0 );
     BitConverter.GetBytes( bfh.bfSize ).CopyTo( header, 2 );
     BitConverter.GetBytes( bfh.bfOffBits ).CopyTo( header, 10 );

     //Allocate enough memory for complete bitmap file
     byte[] data = new byte[cb + bfh.bfOffBits];
     //BITMAPFILEHEADER
     header.CopyTo( data, 0 );

     //BITMAPINFOHEADER
     header = new byte[Marshal.SizeOf( bi )];
     IntPtr pHeader = LocalAlloc( Gptr, ( uint )Marshal.SizeOf( bi ) );
     Marshal.StructureToPtr( biNew, pHeader, false );
     Marshal.Copy( pHeader, header, 0, Marshal.SizeOf( bi ) );
     LocalFree( pHeader );

     header.CopyTo( data, HdrSize );

     //set black color as second color from color table
     byte[] colors = new byte[10];
     colors[4] = 255;
     colors[5] = 255;
     colors[6] = 255;

     colors.CopyTo( data, ( int )bfh.bfOffBits - ( int )colorSize );

     //Bitmap bits
     RealBits.CopyTo( data, ( int )bfh.bfOffBits );

     DeleteObject( SelectObject( hMemDc, hOldBitmap ) );
     DeleteDC( hMemDc );

     return data;
  }

  [DllImport( "coredll.dll" )]
  public static extern bool DeleteObject( IntPtr hObject );

  [DllImport( "coredll.dll" )]
  public static extern int InvalidateRect( IntPtr hwnd, IntPtr rect, int bErase );

  [DllImport( "coredll.dll" )]
  public static extern IntPtr GetDC( IntPtr hwnd );

  [DllImport( "coredll.dll" )]
  public static extern IntPtr CreateCompatibleDC( IntPtr hdc );

  [DllImport( "coredll.dll" )]
  public static extern int ReleaseDC( IntPtr hwnd, IntPtr hdc );

  [DllImport( "coredll.dll" )]
  public static extern int DeleteDC( IntPtr hdc );

  [DllImport( "coredll.dll" )]
  public static extern IntPtr SelectObject( IntPtr hdc, IntPtr hgdiobj );

  [DllImport( "coredll.dll" )]
  public static extern int BitBlt( IntPtr hdcDst, int xDst, int yDst, int w, int h, IntPtr hdcSrc, int xSrc, int ySrc, int rop );

  [DllImport( "coredll.dll" )]
  private static extern IntPtr LocalAlloc( uint flags, uint cb );

  [DllImport( "coredll.dll" )]
  private static extern IntPtr LocalFree( IntPtr hMem );

  [DllImport( "coredll.dll" )]
  private static extern IntPtr CreateDIBSection( IntPtr hdc, IntPtr hdr, uint colors, ref IntPtr pBits, IntPtr hFile, uint offset );

} You can use it, to save on a file: byte[] data = BWImage.GetByteArray(bitmap); FileStream fs = new FileStream( "BW.bmp", FileMode.Create ); fs.Write( data, 0, data.Length ); fs.Flush(); fs.Close();

Wow, what a pain in the butt. I'm not sure this will exactly work for what I need since I can't call GetByteArray(bitmap) (I don't have a Bitmap yet, I need to create one). I think I need to call CreateDIBSection and then write my data into pBits...
Jason