How can I color pixels that are not black in bitma

2020-02-07 05:15发布

问题:

Using GetPixel and SetPixel is easy but very slow so I'm trying to using LockBits.

I have this method I did long time ago to compare between two images:

public static Bitmap FastComparison(Bitmap bmp1,Bitmap bmp2)
    {
       tolerancenumeric = 15;
       int tolerance = tolerancenumeric * tolerancenumeric + 
                       tolerancenumeric * tolerancenumeric + 
                       tolerancenumeric * tolerancenumeric; //dr * dr + dg * dg + db * db;
       bmp3 = new Bitmap(512,512);  
       PixelFormat pxf = PixelFormat.Format24bppRgb;
       Rectangle rect = new Rectangle(0, 0, bmp1.Width, bmp1.Height);
       BitmapData bmpData1 = bmp1.LockBits(rect, ImageLockMode.ReadWrite, pxf);
       BitmapData bmpData2 = bmp2.LockBits(rect, ImageLockMode.ReadWrite, pxf);
       BitmapData bmpData3 = bmp3.LockBits(rect, ImageLockMode.ReadWrite, pxf);

       IntPtr ptr1 = bmpData1.Scan0;
       IntPtr ptr2 = bmpData2.Scan0;
       IntPtr ptr3 = bmpData3.Scan0;

       int numBytes = bmpData1.Stride * bmp1.Height;
       byte[] rgbValues1 = new byte[numBytes];
       Marshal.Copy(ptr1, rgbValues1, 0, numBytes);
       bmp1.UnlockBits(bmpData1);

       byte[] rgbValues2 = new byte[numBytes];
       Marshal.Copy(ptr2, rgbValues2, 0, numBytes);
       bmp2.UnlockBits(bmpData2);


       for (int counter = 0; counter < rgbValues1.Length; counter += 3)
       {
          int  dr, dg, db;
          dr = (int)rgbValues2[counter] - (int)rgbValues1[counter];
          dg = (int)rgbValues2[counter + 1] - (int)rgbValues1[counter + 1];
          db = (int)rgbValues2[counter + 2] - (int)rgbValues1[counter + 2];
          int error = dr * dr + dg * dg + db * db;

          int y, x;
          y = (counter / 3) / 512;
          x = (counter - y * 512 * 3)/3;
          if ((x == 479) && (y == 474))
          {
             Byte r1, g1, b1, r2, g2, b2;
             r1 = rgbValues1[counter];
             b1 = rgbValues1[counter+1];
             g1 = rgbValues1[counter+2];
             r2 = rgbValues2[counter];
             b2 = rgbValues2[counter+1];
             g2 = rgbValues2[counter+2];
           }

           if (error < tolerance)
           {
             rgbValues1[counter] = 0; 
             rgbValues1[counter + 1] = 0;
             rgbValues1[counter + 2] = 0;
           }
         }
         Marshal.Copy(rgbValues1, 0, ptr3, numBytes);
         bmp3.UnlockBits(bmpData3);
         return bmp3;
       }

But now I want to use also LockBits but with one image and to color all the pixels that are not black in yellow.

I started new method:

public Bitmap ChangeColors(Bitmap bmp1)
        {
            bmpColors = new Bitmap(512, 512);
            PixelFormat pxf = PixelFormat.Format24bppRgb;
            Rectangle rect = new Rectangle(0, 0, bmp1.Width, bmp1.Height);
            BitmapData bmpData1 = bmp1.LockBits(rect, ImageLockMode.ReadWrite, pxf);

            IntPtr ptr1 = bmpData1.Scan0;

            int numBytes = bmpData1.Stride * bmp1.Height;
            byte[] rgbValues1 = new byte[numBytes];
            Marshal.Copy(ptr1, rgbValues1, 0, numBytes);
            bmp1.UnlockBits(bmpData1);

            for (int counter = 0; counter < rgbValues1.Length; counter += 3)
            {
                int y, x;
                y = (counter / 3) / 512;
                x = (counter - y * 512 * 3) / 3;

                Byte r1, g1, b1;
                r1 = rgbValues1[counter];
                b1 = rgbValues1[counter + 1];
                g1 = rgbValues1[counter + 2];
            }

            return bmpColors;
        }

But not sure how to make it so the bitmap bmpColors will be the original one but with all pixels that are not black in yellow.

回答1:

How about simply testing the bytes and setting them accordingly?

Byte r1, g1, b1;
r1 = rgbValues1[counter];         // should be + 2 !!
b1 = rgbValues1[counter + 1];     // should be + 0 !!
g1 = rgbValues1[counter + 2];     // should be + 1 !!

if (r1 + b1 + g1 == 0 ) 
{
    r1 = 255;
    g1 = 255;
}

Of course this assumes a real black and a basic yellow are ok with you..

If you need more control, you need a bit more code like

if (r1 + b1 + g1 < threshold)

for shades of black, and for the yellows maybe:

    r1 = myYellow_R;
    g1 = myYellow_G;
    b1 = myYellow_B;

BTW: You need to check on those indices; the last time I looked the data in the LockBits array were reversed: not RGB, (let alone RBG as you have it) but BGR! (And for 32bpp BGRA !)

Since you are using that old method, please also make sure your pixel format is ok; if it is 32bpp you'll need a few simple modifications.



回答2:

The following should work on formats with un-indexed 8 byte colors, which is what you are using:

public static class BitmapHelper
{
    //http://referencesource.microsoft.com/#System.Drawing/commonui/System/Drawing/Advanced/PixelFormat.cs
    //http://msdn.microsoft.com/en-us/magazine/cc534995.aspx#id0070035
    private static void GetPixelFormatData(this PixelFormat pixelFormat, out byte bitsPerPixel, out byte bitsPerChannel, out bool hasAlpha, out bool premultiplyAlpha)
    {
        switch (pixelFormat)
        {
            //Specifies that the format is 24 bits per pixel; 8 bits each are used for the red, green, and blue components.
            case PixelFormat.Format24bppRgb:
                ///       Specifies that pixel format is 24 bits per pixel. The
                ///       color information specifies 16777216 shades of color of which 8 bits are red, 8
                ///       bits are green and 8 bits are blue.
                bitsPerPixel = 24;
                bitsPerChannel = 8;
                hasAlpha = false;
                premultiplyAlpha = false;
                break;

            //Specifies that the format is 32 bits per pixel; 8 bits each are used for the alpha, red, green, and blue components.
            case PixelFormat.Format32bppArgb:
                bitsPerPixel = 32;
                bitsPerChannel = 8;
                hasAlpha = true;
                premultiplyAlpha = false;
                break;

            //Specifies that the format is 32 bits per pixel; 8 bits each are used for the alpha, red, green, and blue components. The red, green, and blue components are premultiplied, according to the alpha component.
            case PixelFormat.Format32bppPArgb:
                bitsPerPixel = 32;
                bitsPerChannel = 8;
                hasAlpha = true;
                premultiplyAlpha = true;
                break;

            //Specifies that the format is 32 bits per pixel; 8 bits each are used for the red, green, and blue components. The remaining 8 bits are not used.
            case PixelFormat.Format32bppRgb:
                bitsPerPixel = 32;
                bitsPerChannel = 8;
                hasAlpha = false;
                premultiplyAlpha = false;
                break;

            //Specifies that the format is 48 bits per pixel; 16 bits each are used for the red, green, and blue components.
            case PixelFormat.Format48bppRgb:
                bitsPerPixel = 48;
                bitsPerChannel = 16;
                hasAlpha = false;
                premultiplyAlpha = false;
                break;

            //Specifies that the format is 64 bits per pixel; 16 bits each are used for the alpha, red, green, and blue components.
            case PixelFormat.Format64bppArgb:
                bitsPerPixel = 64;
                bitsPerChannel = 16;
                hasAlpha = true;
                premultiplyAlpha = false;
                break;

            //Specifies that the format is 64 bits per pixel; 16 bits each are used for the alpha, red, green, and blue components. The red, green, and blue components are premultiplied according to the alpha component.
            case PixelFormat.Format64bppPArgb:
                bitsPerPixel = 64;
                bitsPerChannel = 16;
                hasAlpha = true;
                premultiplyAlpha = true;
                break;

            default:
                throw new ArgumentException("Unsupported Pixel Format " + pixelFormat.ToString());
        }
    }

    // Derived by experimentation.
    const int BlueIndex = 0;
    const int GreenIndex = 1;
    const int RedIndex = 2;
    const int AlphaIndex = 3;

    public delegate bool TransformColorFunc(ref byte r, ref byte g, ref byte b, ref byte a);

    public static void PaintSafe(Bitmap bmp, TransformColorFunc filter)
    {
        BitmapData bData = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.ReadWrite, bmp.PixelFormat);
        try
        {
            byte bitsPerPixel;
            byte bitsPerChannel;
            bool hasAlpha;
            bool premultiplyAlpha;
            bmp.PixelFormat.GetPixelFormatData(out bitsPerPixel, out bitsPerChannel, out hasAlpha, out premultiplyAlpha);
            if (bitsPerChannel != 8)
                throw new ArgumentException();
            if ((bitsPerPixel % 8) != 0)
                throw new ArgumentException();
            if ((!hasAlpha && bitsPerPixel < 24) || (hasAlpha && bitsPerPixel < 32))
                throw new ArgumentException();

            int size = bData.Stride * bData.Height;
            byte[] data = new byte[size];

            System.Runtime.InteropServices.Marshal.Copy(bData.Scan0, data, 0, size);
            for (int iPixel = 0; iPixel < size; iPixel += bitsPerPixel / 8)
            {
                // Format is b, g, r, [a if present.]
                byte b = data[iPixel + BlueIndex];
                byte g = data[iPixel + GreenIndex];
                byte r = data[iPixel + RedIndex];
                byte a;
                if (hasAlpha)
                    a = data[iPixel + AlphaIndex];
                else
                    a = 255;
                if (filter(ref r, ref g, ref b, ref a))
                {
                    // Format is b, g, r, [a if present.]
                    data[iPixel + BlueIndex] = b;
                    data[iPixel + GreenIndex] = g;
                    data[iPixel + RedIndex] = r;
                    if (hasAlpha)
                        data[iPixel + AlphaIndex] = a;
                }
            }
            System.Runtime.InteropServices.Marshal.Copy(data, 0, bData.Scan0, data.Length);
        }
        finally
        {
            bmp.UnlockBits(bData);
        }
    }
}

Then call it like:

    static bool TransformNonBlackToYellow(ref byte r, ref byte g, ref byte b, ref byte a)
    {
        if (r != 0 || g != 0 || b != 0)
        {
            r = 255;
            g = 255;
            b = 0;
            a = 255;
            return true;
        }
        return false;
    }

    BitmapHelper.PaintSafe(bitmap, TransformNonBlackToYellow);

Note I didn't have my filter function take a Color because constructing a Color struct from Argb values can be oddly slow.