Search code examples
c#imageimage-processingmonochrome

Getting pixels' colors of a monochrome image using C#


As the subject says, I have a .bmp image and I need to write a code which will be able to get the color of any pixel of the image. It is a 1bpp (indexed) image, so the colour will be either black or white. Here is the code I currently have:

    //This method locks the bits of line of pixels
    private BitmapData LockLine(Bitmap bmp, int y)
    {
        Rectangle lineRect = new Rectangle(0, y, bmp.Width, 1);
        BitmapData line = bmp.LockBits(lineRect,
                                       ImageLockMode.ReadWrite,
                                       bmp.PixelFormat);
        return line;
    }
    //This method takes the BitmapData of a line of pixels
    //and returns the color of one which has the needed x coordinate
    private Color GetPixelColor(BitmapData data, int x)
    {
        //I am not sure if this line is correct
        IntPtr pPixel = data.Scan0 + x; 
        //The following code works for the 24bpp image:
        byte[] rgbValues = new byte[3];
        System.Runtime.InteropServices.Marshal.Copy(pPixel, rgbValues, 0, 3);
        return Color.FromArgb(rgbValues[2], rgbValues[1], rgbValues[0]);
    }

But how can I make it work for a 1bpp image? If I read only one byte from the pointer it always has the 255 value, so I assume, I am doing something wrong.
Please, do not suggest to use the System.Drawing.Bitmap.GetPixel method, because it works too slow and I want the code to work as fast as possible. Thanks in advance.

EDIT: Here is the code that works fine, just in case someone needs this:

    private Color GetPixelColor(BitmapData data, int x)
    {
        int byteIndex = x / 8;
        int bitIndex = x % 8;
        IntPtr pFirstPixel = data.Scan0+byteIndex;
        byte[] color = new byte[1];
        System.Runtime.InteropServices.Marshal.Copy(pFirstPixel, color, 0, 1);
        BitArray bits = new BitArray(color);
        return bits.Get(bitIndex) ? Color.Black : Color.White;
    }

Solution

  • Ok, got it! You need to read the bits from the BitmapData and apply a mask to the bit you want to extract the colour:

    var bm = new Bitmap...
    
    //lock all image bits
    var bitmapData = bm.LockBits(new Rectangle(0, 0, bm.Width, bm.Height), ImageLockMode.ReadWrite, PixelFormat.Format1bppIndexed);
    
    // this  will return the pixel index in the color pallete
    // since is 1bpp it will return 0 or 1
    int pixelColorIndex = GetIndexedPixel(50, 30, bitmapData);
    
    // read the color from pallete
    Color pixelColor = bm.Pallete.Entries[pixelColorIndex];
    

    And here is the method:

    // x, y relative to the locked area
    private int GetIndexedPixel(int x, int y, BitmapData bitmapData)
    {
        var index = y * bitmapData.Stride + (x >> 3);
        var chunk = Marshal.ReadByte(bitmapData.Scan0, index);
    
        var mask = (byte)(0x80 >> (x & 0x7));
        return (chunk & mask) == mask ? 1 : 0;
    }
    

    The pixel position is calculated in 2 rounds:

    1. Find the byte where pixel in 'x' is (x / 8): each byte holds 8 pixels, to find the byte divide x/8 rounding down: 58 >> 3 = 7, the pixel is on the byte 7 of the current row (stride)

    2. Find the bit on the current byte (x % 8): Do x & 0x7 to get only the 3 leftmost bits (x % 8)

    Example:

    x = 58 
    // x / 8 - the pixel is on byte 7
    byte = 58 >> 3 = 58 / 8 = 7 
    
    // x % 8 - byte 7, bit 2
    bitPosition = 58 & 0x7 = 2 
    
    // the pixels are read from left to right, so we start with 0x80 and then shift right. 
    mask = 0x80 >> bitPosition = 1000 0000b >> 2 =  0010 0000b