Convert image to 1 bpp bitmap in .net compact framework

后端 未结 3 2014
攒了一身酷
攒了一身酷 2020-12-03 09:05

I have an image of a signature I am trying to save as 1 bpp bitmap to save file space. The full .NET Framework has the enum PixelFormat.Format1bppIndexed, but

相关标签:
3条回答
  • 2020-12-03 09:29

    I had to do this in the past for generating black & white reports printed via Bluetooth (color or greyscale images were too large for the printer's buffer). Turned out I had to create the images using native code.

    Here's a snippet:

    private void CreateUnmanagedResources()
    {
        // for safety, clean up anything that was already allocated
        ReleaseUnmanagedResources();
    
        bih = new BITMAPINFOHEADER();
        bih.biBitCount = 1;
        bih.biClrImportant = 0;
        bih.biClrUsed = 0;
        bih.biCompression = 0;
        bih.biHeight = m_cy;
        bih.biPlanes = 1;
        bih.biSize = (uint)(Marshal.SizeOf(typeof(BITMAPINFOHEADER)) - 8); 
        bih.biSizeImage = 0;
        bih.biWidth = m_cx;
        bih.biXPelsPerMeter = 0;
        bih.biYPelsPerMeter = 0;
        bih.clr2 = 0xffffff;
        bih.clr1 = 0x0;
    
        hDC = Win32.CreateCompatibleDC(IntPtr.Zero);
        pBits = IntPtr.Zero;
        hBitmap = Win32.CreateDIBSection(hDC, bih, 1, ref pBits, IntPtr.Zero, 0);
        hbmOld = Win32.SelectObject(hDC, hBitmap);
    }
    
    private void ReleaseUnmanagedResources()
    {
        if (hbmOld != IntPtr.Zero)
            Win32.SelectObject(hDC, hbmOld);
    
        if(hBitmap != IntPtr.Zero)
            Win32.DeleteObject(hBitmap);
    
        if (hDC != IntPtr.Zero)
            Win32.DeleteDC(hDC);
    }
    

    I then used Graphics.FromHdc to get a managed graphics object that I could paint the report onto.

    I did saving with a BinaryWriter, but that was in CF 1.0 days when the Bitmap class didn't have a Save, so you're free and clear there.

    0 讨论(0)
  • 2020-12-03 09:37

    Thanks for pointing me in the right direction, ctacke. I was unable to use the Bitmap class to save the image data. It continually threw an OutOfMemoryException. I resorted to writing the bitmap out using a BinaryWriter, like you suggested.

    My end solution returns a byte array, with which you can choose to write to disk, save to a database, transmit, etc.

    class ImageHelper
    {
        [StructLayout(LayoutKind.Sequential)]
        public struct BITMAPINFOHEADER
        {
            public BITMAPINFOHEADER(ushort bpp, int height, int width)
            {
                biBitCount = bpp;
                biWidth = width;
                biHeight = height;
    
                biSize = (uint)Marshal.SizeOf(typeof(BITMAPINFOHEADER));
                biPlanes = 1; // must be 1
                biCompression = 0; // no compression
                biSizeImage = 0; // no compression, so can be 0
                biXPelsPerMeter = 0;
                biYPelsPerMeter = 0;
                biClrUsed = 0;
                biClrImportant = 0;
            }
    
            public void Store(BinaryWriter bw)
            {
                Store(bw, null);
            }
    
            public void Store(BinaryWriter bw, uint[] colorPalette)
            {
                // Must maintain order for file writing
                bw.Write(biSize);
                bw.Write(biWidth);
                bw.Write(biHeight);
                bw.Write(biPlanes);
                bw.Write(biBitCount);
                bw.Write(biCompression);
                bw.Write(biSizeImage);
                bw.Write(biXPelsPerMeter);
                bw.Write(biYPelsPerMeter);
                bw.Write(biClrUsed);
                bw.Write(biClrImportant);
    
                // write color palette if 8 bpp or less
                if (biBitCount <= 8)
                {
                    if (colorPalette == null)
                        throw new ArgumentNullException("bpp is 8 or less, color palette is required");
    
                    uint paletteCount = BITMAPFILEHEADER.CalcPaletteSize(biBitCount) / 4;
                    if (colorPalette.Length < paletteCount)
                        throw new ArgumentException(string.Format("bpp is 8 or less, color palette must contain {0} colors", paletteCount));
    
                    foreach (uint color in colorPalette)
                        bw.Write(color);
                }
            }
    
            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;
        }
    
        [StructLayout(LayoutKind.Sequential)]
        public struct BITMAPFILEHEADER
        {
            public BITMAPFILEHEADER(BITMAPINFOHEADER info, out uint sizeOfImageData)
            {
                bfType = 0x4D42;  // Microsoft supplied value to indicate Bitmap 'BM'
                bfReserved1 = 0;
                bfReserved2 = 0;
    
                // calculate amount of space needed for color palette
                uint paletteSize = CalcPaletteSize(info.biBitCount);
    
                bfOffBits = 54 + paletteSize; // default value + paletteSize
    
                // calculate size of image
                sizeOfImageData = (uint)(CalcRowSize(info.biWidth * info.biBitCount) * info.biHeight);
                bfSize = sizeOfImageData + bfOffBits;
            }
    
            private static int CalcRowSize(int bits)
            {
                return ((((bits) + 31) / 32) * 4);
            }
    
            public static uint CalcPaletteSize(int bpp)
            {
                // 8 bpp or less, needs an uint per color
                if (bpp <= 8)
                    return 4 * (uint)Math.Pow(2, bpp);
    
                // no palette needed for 16bpp or higher
                return 0;
            }
    
            public void Store(BinaryWriter bw)
            {
                // Must maintain order for file writing
                bw.Write(bfType);
                bw.Write(bfSize);
                bw.Write(bfReserved1);
                bw.Write(bfReserved2);
                bw.Write(bfOffBits);
            }
    
            public ushort bfType;
            public uint bfSize;
            public short bfReserved1;
            public short bfReserved2;
            public uint bfOffBits;
        }
    
        public static byte[] GetByteArray(Bitmap image)
        {
            IntPtr hbmOld;
            IntPtr hBitmap;
            IntPtr hDC;
    
            // create infoheader
            BITMAPINFOHEADER bih = new BITMAPINFOHEADER(1, image.Height, image.Width);
            // set black and white for 1 bit color palette
    
            // create fileheader and get data size
            uint sizeOfImageData;
            BITMAPFILEHEADER bfh = new BITMAPFILEHEADER(bih, out sizeOfImageData);
    
            // create device context in memory
            hDC = Win32.CreateCompatibleDC(IntPtr.Zero);
    
            // create a 1 bpp DIB
            IntPtr pBits = IntPtr.Zero;
            hBitmap = Win32.CreateDIBSection(hDC, ref bih, 1, ref pBits, IntPtr.Zero, 0);
    
            // selet DIB into device context
            hbmOld = Win32.SelectObject(hDC, hBitmap);
    
            using (Graphics g = Graphics.FromHdc(hDC))
            {
                 g.DrawImage(image, 0, 0);
            }
    
            byte[] imageData = new byte[sizeOfImageData];
            byte[] fileData;
    
            using (MemoryStream ms = new MemoryStream((int)bfh.bfSize))
            {
                using (BinaryWriter w = new BinaryWriter(ms))
                {
                    bfh.Store(w);
                    // store bitmapinfoheader with 1 bpp color palette for black and white
                    bih.Store(w, new uint[] { (uint)0x0, (uint)0xffffff });
    
                    // copy image data into imageData buffer
                    Marshal.Copy(pBits, imageData, 0, imageData.Length);
    
                    // write imageData to stream
                    w.Write(imageData);
    
                    w.Close();
                }
    
                fileData = ms.GetBuffer();
                ms.Close();
            }
    
            // select old object
            if (hbmOld != IntPtr.Zero)
                Win32.SelectObject(hDC, hbmOld);
    
            // delete memory bitmap
            if (hBitmap != IntPtr.Zero)
                Win32.DeleteObject(hBitmap);
    
            // delete memory device context
            if (hDC != IntPtr.Zero)
                Win32.DeleteDC(hDC);
    
            return fileData;
        }
    }
    
    0 讨论(0)
  • 2020-12-03 09:46

    Creating and saving a bitonal bitmap is problematic even in the full framework.

    I previously authored an article on this issues involved.

    http://www.codeproject.com/KB/GDI-plus/BitonalImageConverter.aspx

    I revisited this code in the context of the compact framework and discovered as you did that the enum value does not exist, so you can't create a bitonal image from scratch.

    I'd be interested to know if you can load pre-existing bitonal images in the compact framework. If you can load pre-existing bitonal bitmaps, then it might be possible to go lower level and write the bitmap image format to disk or a memory stream directly, rather than use the GDI+ objects, but it would be non-trivial to do so.

    0 讨论(0)
提交回复
热议问题