Pergunta

I need to locate the points/cordinates (x,y) of the first pixel it finds with the specified color. I have used the GetPixel() method, but it's a bit too slow and were looking into LockBits. How ever I can't figure out if this actually could solve my problem. Can I return the points for the found pixel using LockBits?

Here is my current code:

public Point FindPixel(Image Screen, Color ColorToFind)
{
    Bitmap bit = new Bitmap(Screen);
    BitmapData bmpData = bit.LockBits(new Rectangle(0, 0, bit.Width, bit.Height),
                                    ImageLockMode.ReadWrite,
                                    PixelFormat.Format32bppPArgb);
    unsafe
    {
        byte* ptrSrc = (byte*)bmpData.Scan0;
        for (int y = 0; y < bmpData.Height; y++)
        {
            for (int x = 0; x < bmpData.Width; x++)
            {
                Color c = bit.GetPixel(x, y);
                if (c == ColorToFind)
                    return new Point(x, y);
            }
        }
    }
    bit.UnlockBits(bmpData);
    return new Point(0, 0);
}
Foi útil?

Solução

There are few things wrong with your code:

  1. You are using PixelFormat.Format32bppPArgb - you should use pixel format of the image, if they won't match, all pixels will be copied under the hood anyways.
  2. You are still using GetPixel, so all this hassle will not give you any advantage.

To use LockBits efficiently, you basically want to lock your image and then use unsafe pointers to get values of pixels. Code to do this will vary a bit for different pixel formats, assuming you really will have 32bpp format with blue being on LSB, your code could look like this:

for (int y = 0; y < bmpData.Height; ++y)
{ 
    byte* ptrSrc = (byte*)(bmpData.Scan0 + y * bmpData.Stride);
    int* pixelPtr = (int*)ptrSrc;

    for (int x = 0; x < bmpData.Width; ++x)
    {
        Color col = Color.FromArgb(*pixelPtr);

        if (col == ColorToFind) return new Point(x, y);

        ++pixelPtr; //Increate ptr by 4 bytes, because it is int
    }
}

Few remarks:

  • For each line new ptrSrc is computed using Scan0 + stride value. This is because just increasing the pointer might fail, if Stride != bpp * width, which may be the case.
  • I assumed that blue pixel is represented as LSB, and alpha as MSB, which I think was not the case, because those GDI pixel formats were.. strange ;), just make sure you check it, if it's the other way, inverse bytes before using FromArgb() method.
  • If your pixel format is 24bpp, it's a bit more tricky, because you can't use int pointer and increase it by 1 (4 bytes), for obvious reasons.

Outras dicas

You didn't stop using GetPixel() so you are not ahead. Write it like this instead:

    int IntToFind = ColorToFind.ToArgb();
    int height = bmpData.Height;    // These properties are slow so read them only once
    int width = bmpData.Width;
    unsafe
    {
        for (int y = 0; y < height; y++)
        {
            int* pline = (int*)bmpData.Scan0 + y * bmpData.Stride/4;
            for (int x = 0; x < width; x++)
            {
                if (pline[x] == IntToFind)
                    return new Point(x, bit.Height - y - 1);
            }
        }
    }

The odd looking Point constructor is necessary because lines are stored upside-down in a bitmap. And don't return new Point(0, 0) on failure, that's a valid pixel.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top