1

Here is a function that takes a transparent and white image and attempts to turn it into a bool array.

My unit test code gives me 2 images that I would expect (see below) but the "numberOfMasked" is always higher then I expect. For example if the "maskBuffer" has one pixel marked (see mask_image_test.bmp below) then although "mask_bool_test70.bmp") creates an image with one pixel marked. For some reason the actual number of bools marked as true is much higher and seemingly random. I have seen it range from 25 - > 70.

public static bool[] ConvertImageToBoolAray(Bitmap maskbuffer)
    {
        bool[] mask = null;
        int w = maskbuffer.Width;
        int h = maskbuffer.Height;

        #region unit_test
            maskbuffer.Save("mask_image_test.bmp");
        #endregion

        lock (maskbuffer)
        {
            BitmapData bmpData = maskbuffer.LockBits(new Rectangle(0, 0, w, h),
                ImageLockMode.ReadOnly,
                PixelFormat.Format8bppIndexed);

            const int numBmpChannel = 1;
            int maskIndex = 0;
            int bmpIndex = 0;

            unsafe
            {

                byte* pixels = (byte*) bmpData.Scan0;
                int numPixels = w*h;

                mask = new bool[numPixels];

                for (;
                    maskIndex < numPixels;
                    bmpIndex += numBmpChannel, maskIndex++)
                {
                    byte red = pixels[bmpIndex];
                    bool masked = red != 0;
                    mask[maskIndex] = masked;
                }

            }
            maskbuffer.UnlockBits(bmpData);
        }

        #region unit_test
            byte[] boolAsByte = Array.ConvertAll(mask, b => b ? (byte)1 : (byte)0);
            Bitmap maskBitmap = GLImageConvertor.ConvertByteBufferToBitmap(boolAsByte, w, h, PixelFormat.Format8bppIndexed);
            int numberOfMasked = mask.Count(b => b);
            maskBitmap.Save("mask_bool_test" + numberOfMasked + ".bmp");
        #endregion


        return mask;
    }

Anyone got any ideas for this strange behavior?

Debugging shows that in the "pixels" memory, there is splots of bytes that I would not expect, I would expect to see one byte set to "FF" but instead I have random parts of data.

mask_image_test.bmp:

mask_image_test.bmp

mask_bool_test70.bmp:

mask_bool_test70.bmp

chrispepper1989
  • 2,100
  • 2
  • 23
  • 48

2 Answers2

1

There's a difference between Stride and Width which you need to consider in your algorithm.

The stride is the width of a single row of pixels (a scan line), rounded up to a four-byte boundary.

Since 70 is not divisable by 4, there are some spare pixels, probably filled with random data.

You need something like

int count = 0;
int stride = bmpData.Stride;

for (int column = 0; column < bmpData.Height; column++)
{
    for (int row = 0; row < bmpData.Width; row++)
    {
        red = pixels[(column * stride) + (row * 3) + 2]);
    }
}
Thomas Weller
  • 55,411
  • 20
  • 125
  • 222
  • damn it! I knew it was something simple! I completely forgot about stride! – chrispepper1989 Sep 08 '15 at 13:27
  • I added my version below, out of interest why do you add 2? I am assuming you *3 to account for possible RGB images? in my case its a single channel image – chrispepper1989 Sep 09 '15 at 11:02
  • 1
    @chrispepper1989: yes, that's right, I assumed an RGB image - probably I didn't read your code thoroughly enough. Red is at +2, G at +1 and B at +0 then. Thanks for posting your own answer as well. It may help others. – Thomas Weller Sep 09 '15 at 11:13
  • thanks for that, so C# bitmaps are stored in BGR format by default? – chrispepper1989 Sep 09 '15 at 13:41
1

As Thomas pointed out I was not taking into account stride! Didnt think for a second that C# would pack out the extra bytes in the bitmap. But sure enough in this case width != stride (width was 111 but the stride was 112). I decided to write a simpler version that avoids the raw buffer completely to avoid sticky problems (and I couldn't be bothered with the raw buffer anymore)

 public static bool[] ConvertImageToBoolAray(Bitmap maskbuffer)
    {
        bool[] mask = null;
        int w = maskbuffer.Width;
        int h = maskbuffer.Height;

        #region unit_test
        //maskbuffer.Save("mask_image_test.bmp");
        #endregion

        lock (maskbuffer)
        {
            int numPixels = w * h;
            mask = new bool[numPixels];

            for (int y = 0; y < maskbuffer.Height; ++y)
            {
                for (int x = 0; x < maskbuffer.Width; ++x)
                {
                    Color color = maskbuffer.GetPixel(x, y);
                    int index = x + (y*maskbuffer.Width);
                    mask[index] = color.A != 0;
                }
            }
        }

        #region unit_test
        //byte[] boolAsByte = Array.ConvertAll(mask, b => b ? (byte)1 : (byte)0);
       // Bitmap maskBitmap = GLImageConvertor.ConvertByteBufferToBitmap(boolAsByte, w, h, PixelFormat.Format8bppIndexed);
       // int numberOfMasked = mask.Count(b => b);
       // maskBitmap.Save("mask_bool_test" + numberOfMasked + ".bmp");
        #endregion


        return mask;
    }

If anyone wants to post the correct raw buffer version, please go ahead and I will mark yours as best answer.

--- Update --- added the reworked version to use the buffer directly

   private static bool[] ConvertImageToBoolArayUnsafe(Bitmap maskbuffer)
    {
        bool[] mask = null;
        int w = maskbuffer.Width;
        int h = maskbuffer.Height;

        #region unit_test
            //maskbuffer.Save("mask_image_test.bmp");
        #endregion

        lock (maskbuffer)
        {
            BitmapData bmpData = maskbuffer.LockBits(new Rectangle(0, 0, w, h),
                ImageLockMode.ReadOnly,
                PixelFormat.Format8bppIndexed);


            int stride = bmpData.Stride;
            unsafe
            {

                byte* pixels = (byte*) bmpData.Scan0;


                mask = new bool[w * h];

                for (int y = 0; y < h; ++y)
                {
                    for (int x = 0; x < w; ++x)
                    {
                        int imageIndex = x + (y * stride);
                        byte color = pixels[imageIndex];
                        int maskIndex = x + (y * w);
                        mask[maskIndex] = color != 0;
                    }
                }

            }
            maskbuffer.UnlockBits(bmpData);
        }

        #region unit_test
           // byte[] boolAsByte = Array.ConvertAll(mask, b => b ? (byte)1 : (byte)0);
          //  Bitmap maskBitmap = GLImageConvertor.ConvertByteBufferToBitmap(boolAsByte, w, h, PixelFormat.Format8bppIndexed);
          //  int numberOfMasked = mask.Count(b => b);
          //  maskBitmap.Save("mask_bool_test" + numberOfMasked + ".bmp");
        //check equality to safe version (as that is correct)
        #endregion


        return mask;
    }
chrispepper1989
  • 2,100
  • 2
  • 23
  • 48