1

I am trying to learn LockBitmap class for image processing and I came across this code posted below. Basically it returns the color of a x-y coordinate.

Of course this method only works after I perform source.LockBits() and Marshal.Copy() / unsafe context.

public Color GetPixel(int x, int y, Bitmap source)
{
    int Depth = System.Drawing.Bitmap.GetPixelFormatSize(source.PixelFormat);
    int Width = source.Width;
    int Height = source.Height;
    Color clr = Color.Empty;

    // Get color components count
    int cCount = Depth / 8;
    int PixelCounts = Width * Height;
    byte[] Pixels = new byte[cCount * PixelCounts];

    // Get start index of the specified pixel
    int i = ((y * Width) + x) * cCount;

    byte b = Pixels[i];
    byte g = Pixels[i + 1];
    byte r = Pixels[i + 2];
    byte a = Pixels[i + 3]; // a
    clr = Color.FromArgb(a, r, g, b);

    return clr;
}
  1. What is cCount, why is it always Depth / 8?
  2. int i = ((y * Width) + x) * cCount, is this a fixed formula to convert from (x,y) coordinate to Pixels[i]? Why?
Liren Yeo
  • 3,215
  • 2
  • 20
  • 41
  • 1
    This is very simplistic, and will only really work properly for simple 32-bit bitmaps (ignoring that `Pixels` never gets any data in the first place). It doesn't handle palette, alignment, bit alignment (not all bitmaps are "byte aligned") etc. If you want to get around the safe GDI+ methods, you'll need to do a bit of research - the key to fast code is often ignoring edge cases that don't concern you, but that means knowing what the edge cases are in the first place, and understanding if you can safely ignore them. – Luaan Mar 22 '16 at 09:30
  • @Luaan, thanks for the respond. I really want to do more research regarding this. Do you have any tutorial/website to recommend? I don't even know what keyword to google with. – Liren Yeo Mar 22 '16 at 09:34
  • The thing is, all of this is *ancient*, low-level stuff. It's mostly knowledge you have to piece together from many different sources, and it most likely simply isn't worth it. Even games are using 32-bit ARGB for everything nowadays (well, apart from those moving on to HDR :P). For example, if you can assume a certain pixel format, you can ignore pretty much all the complexities - and a possible way to ensure this is to simply pick the most common format (MSPaint's 32-bit is the most common on Windows nowadays), and fallback on GDI+ to convert from the other formats. – Luaan Mar 22 '16 at 09:39

2 Answers2

1

First of all, cCount is how many bytes one pixel is (bytes per pixel), Depth is how many bits one pixel is (bits per pixel). Division by 8 is done to convert from bits to bytes. (Though, Depth / 8 is inefficient, use (int)Math.Ceiling(Depth / 8d) instead)

To answer your second question, pixels are layed out row-by-row. Since each row is width pixels, then the size of each row would width * cCount bytes. If you want to get the position of, let's say, the second row, you would use ((2-1) * width) * cCount. If you want to get the position of the fourth pixel in that row, you would use ((2-1) * width + (4-1)) * cCount. Therefore, to get the position of the pixel with the coordinates, (x, y), you would use the formula, (y * width + x) * cCount.

AboodXD
  • 148
  • 9
0
  1. It isn't always, check out this. This might be a superior implementation to yours.

    It can be 8, 16, 24, 32, etc. because 8 (or 16, etc.) bytes are required for color information.

Example code from the posted source:

 // Get start index of the specified pixel
        int i = ((y * Width) + x) * cCount;

        if (i > Pixels.Length - cCount)
            throw new IndexOutOfRangeException();

        if (Depth == 32) // For 32 bpp get Red, Green, Blue and Alpha
        {
            byte b = Pixels[i];
            byte g = Pixels[i + 1];
            byte r = Pixels[i + 2];
            byte a = Pixels[i + 3]; // a
            clr = Color.FromArgb(a, r, g, b);
        }
        if (Depth == 24) // For 24 bpp get Red, Green and Blue
        {
            byte b = Pixels[i];
            byte g = Pixels[i + 1];
            byte r = Pixels[i + 2];
            clr = Color.FromArgb(r, g, b);
        }
        if (Depth == 8)
        // For 8 bpp get color value (Red, Green and Blue values are the same)
        {
            byte c = Pixels[i];
            clr = Color.FromArgb(c, c, c);
  1. Because bitmaps are stored as arrays in memory.

    (y * Width) is the dimension, + x) is the pixel in the dimension and * cCount is the step per pixel. (every pixel needs cCount bytes in memory.)

Think of it as if you put all pixels in a line, and it starts with bottom left, goes to top left, and ends up 2nd row from left bottom to 2nd row top bottom and goes on until it reaches top right.

Mafii
  • 7,227
  • 1
  • 35
  • 55
  • According to the site, it is always Depth / 8. I actually want to know what is this `color component`. From my understanding, `depth` here is `bpp`. So why is cCount always `bpp / 8`? – Liren Yeo Mar 22 '16 at 09:31
  • 2
    @Liren Depth is in bits, you're addressing bytes. A byte has 8 bits. Of course, this breaks down when depth is 7 or 2, or 14, for example :) – Luaan Mar 22 '16 at 09:33
  • @Liren yep thats it, dw, it happens to all of us – Mafii Mar 22 '16 at 09:35
  • did my explanation of why its `((y * Width) + x) * cCount` help? – Mafii Mar 22 '16 at 09:36
  • I now understand the `* cCount` part. But why `(y * Width) + x`? Why multiply with Width? Does it work the same for `y + (x * Height)`? – Liren Yeo Mar 22 '16 at 10:03
  • width is the height of the bitmap. Bitmaps are saved in storage line for line in an array. so the 3rd pixel from bottom left (just an example, it could also be top left, im not sure) is (x (the row = 9, zero based array ofc) * height result of this multiplication is ofc 0) + the index of it in the other dimension = 3. Not if you do the same on the 2nd row from the right, its 1 (2nd row in 0 based array) is 1 * height + 3. The 3rd pixel from bottom in the 7th pixel from left would be (6 * height) + 3 ... Clear enough? – Mafii Mar 22 '16 at 10:35