Link to home
Start Free TrialLog in
Avatar of rockyaldrich
rockyaldrich

asked on

A true monochrome bitmap from C#

Is there a way to create a true monochrome bitmap from C#?  Or convert one from a 32bit or 24bit bitmap.  I got some good code from this post:
https://www.experts-exchange.com/questions/21824554/Convert-bitmap-from-24-bit-to-monochrome.html

However it behaves differently than going to MS Paint and saving as "Monochrome Bitmap".  Could this be because the conversion code uses PixelFormat.Format8bppIndexed instead of PixelFormat.Format1bppIndexed?  I tried modifiying it to use PixelFormat.Format1bppIndexed but all i get is a completely blacked out picture.  I have some picky receipt printers and they demand that the bitmap be a monochrome one.  I've looked all over and been through as much documentation as I can stand but nothing really addresses this issue.  Can anyone help??

Avatar of vo1d
vo1d
Flag of Germany image

what happens if you convert a black /white picture in your function? do the whole picture appears black?
Avatar of rockyaldrich
rockyaldrich

ASKER

Yes the whole thing is a black block.  And all the bitmaps I am creating are all pure white and pure black.  What I'm doing is drawing a signature captured from a pin pad device.  The sig is just a bunch of x, y coordinates and I draw that with a black pen on a white background then I need to save it as a monochrome and from what I gather using indexed colors will not work.  What I need is an unindexed 1bpp bitmap.
       Bitmap ConvertRGB24ToMonochrome(Bitmap source)
        {
            if ( source.PixelFormat != PixelFormat.Format24bppRgb )
            {
                return null;
            }

            Byte[] colorBytes = BitmapToBytesRGB24(source);

            if ( colorBytes == null )
            {
                return null;
            }

            bool[] monoBytes = new bool[source.Width * source.Height];

            int j = 0;

            for ( int i = 0; i < source.Width * source.Height; i++ )
            {
                // Apply threshold to average
//                monoBytes[i] = (((colorBytes[j] + colorBytes[j + 1] + colorBytes[j + 2]) / 3) >= 128);

                // Apply threshold to grayscale pixel
                byte b = (Byte)(0.3f * (float)colorBytes[j + 2] + 0.59f * (float)colorBytes[j + 1] + 0.11f * (float)colorBytes[j]);
                monoBytes[i] = (b >= 128);

                j += 3;
            }

            return BitsToBitmapMonochrome(monoBytes, source.Width, source.Height);
        }

        // Create monochrome bitmap from bool array
        Bitmap BitsToBitmapMonochrome(bool[] pixels, int width, int height)
        {
            if (pixels.GetLength(0) < width * height)
            {
                return null;
            }

            Bitmap bmp = new Bitmap(width, height, PixelFormat.Format1bppIndexed);

            bmp.Palette = GetMonochromePalette();
           

            BitmapData data = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height),
                ImageLockMode.WriteOnly, bmp.PixelFormat);


            int pixelNumber = 0;

            for (int y = 0; y < bmp.Height; y++)
            {
                IntPtr linePtr = new IntPtr(data.Scan0.ToInt32() + data.Stride * y);

                for (int x = 0; x < bmp.Width; x++)
                {
                    SetBit(linePtr, x, pixels[pixelNumber++]);
                }
            }

            bmp.UnlockBits(data);

            return bmp;
        }

        void SetBit(IntPtr ptr, int pixelNumber, bool pixelValue)
        {
            int byteNumber = pixelNumber / 8;
            int bitNumber = pixelNumber % 8;

            byte b = Marshal.ReadByte(ptr, byteNumber);

            if ( pixelValue )
            {
                b |= (byte)(1 << bitNumber);
            }
            else
            {
                b &= (byte)(~(1 << bitNumber));
            }

            Marshal.WriteByte(ptr, byteNumber, b);
        }


        // Create Byte array from RGB24 bitmap
        Byte[] BitmapToBytesRGB24(Bitmap bmp)
        {
            if ( bmp.PixelFormat != PixelFormat.Format24bppRgb )
            {
                return null;
            }

            int i;
            int length = bmp.Width * bmp.Height * 3;

            Byte[] bytes = new Byte[length];

            BitmapData data = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height),
                ImageLockMode.ReadOnly, bmp.PixelFormat);

            if (data.Stride == bmp.Width * 3)
            {
                Marshal.Copy(data.Scan0, bytes, 0, length);
            }
            else
            {
                for ( i = 0; i < bmp.Height; i++ )
                {
                    IntPtr p = new IntPtr(data.Scan0.ToInt32() + data.Stride * i);
                    Marshal.Copy(p, bytes, i * bmp.Width * 3, bmp.Width * 3);
                }
            }

            bmp.UnlockBits(data);

            return bytes;
        }


        ColorPalette GetMonochromePalette()
        {
            Bitmap bmp = new Bitmap(1, 1, PixelFormat.Format1bppIndexed);

            ColorPalette monoPalette = bmp.Palette;

            Color[] entries = monoPalette.Entries;

            entries[0] = Color.FromArgb(0, 0, 0);
            entries[1] = Color.FromArgb(255, 255, 255);

            return monoPalette;
        }
    }
I implemented here simplest threshold approach, take a look at the following code:

                // Apply threshold to average
//                monoBytes[i] = (((colorBytes[j] + colorBytes[j + 1] + colorBytes[j + 2]) / 3) >= 128);

                // Apply threshold to grayscale pixel
                byte b = (Byte)(0.3f * (float)colorBytes[j + 2] + 0.59f * (float)colorBytes[j + 1] + 0.11f * (float)colorBytes[j]);
                monoBytes[i] = (b >= 128);

Both versions are working, and program gives result close to Paint. I don't know exactly what algorithm is using in the Paint program, I don't think that this is threshold, it is something more complicated. If results are not sufficient, find better algorithm and implement in instead of threshold. This is called image binarization. If you want, I can post later link to free book which contains such algorithm.
I think this is still an indexed bmp.  The printer says it is a bad format, plus it's a lot smaller than when I save it with Paint.  Also the sig comes out flawed, it is broken up into wierd lines.  I wish I could attach it so you could see, it's actually pretty cool looking.  Anyways I went ahead and wrote a Win32 dll to draw the sig and flush it to disk, then I just use DllImport in my C#.  Could you please post that link to the book? I'd like to take a look at some of those algorithms and then I'll go ahead and give you the points.  Thanks for the help.
ASKER CERTIFIED SOLUTION
Avatar of AlexFM
AlexFM

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial