3

I've got a PNG image that I'm operating on via the System.Drawing API in .NET. It has large transparent regions, and I would like to replace the transparent regions with white fill--so that there are no transparent regions in the image. Easy enough in an image editing program... but so far I've had no success doing this in C#.

Can someone give me some pointers?

Piotr Salaciak
  • 1,653
  • 1
  • 15
  • 28
Josh
  • 7,232
  • 8
  • 48
  • 75
  • 1
    It all depends how you define "transparent" and "replace with white fill". PNG supports alpha-transparency, so in graphics editing program, your white can end up as gray because color of pixel was black with 50% transparency. – Euphoric Mar 24 '11 at 21:00
  • 1
    I'm sure all of these are helpful solutions, but I had trouble getting them to work, and found a much simpler solution was available on the client side. In my case I was sending an image from a Flex app to .NET to save to the server, and I instead went the route of using the Flex/Flash APIs to recolor the image, before shuttling it off to the server. – Josh Oct 15 '12 at 17:20

4 Answers4

4

I'm not sure how to detect transparent pixel. I know if the Alpha is 0 it's completly transparent and if it's 255 it's opaque. I'm not sure if you should check for Alpha == 0 or Alpha != 255 ; if you can try it and give me a feedback that would be helpful.

From MSDN

The alpha component specifies the transparency of the color: 0 is fully transparent, and 255 is fully opaque. Likewise, an A value of 255 represents an opaque color. An A value from 1 through 254 represents a semitransparent color. The color becomes more opaque as A approaches 255.

    void  Foo(Bitmap image)
    {
        for (int y = 0; y < image.Height; ++y)
        {
            for (int x = 0; x < image.Width; ++x)
            {
                // not very sure about the condition.                   
                if (image.GetPixel(x, y).A != 255)
                {
                    image.SetPixel(x,y,Color.White);
                }
            }
        }

    }
Bala R
  • 107,317
  • 23
  • 199
  • 210
  • I tested it, it works great, but it is a *lot* slower than Euphoric's answer. GetPixel seems to lock each call, but you can do the same with one lock. Or even add some special functions: http://www.codeproject.com/Tips/240428/Work-with-bitmap-faster-with-Csharp – Meep Oct 21 '13 at 19:23
4

My example:

    public void FillPngWhite(Bitmap bmp)
    {
        if (bmp.PixelFormat != PixelFormat.Format32bppArgb)
            throw new ApplicationException("Not supported PNG image!");

        // Lock the bitmap's bits.  
        Rectangle rect = new Rectangle(0, 0, bmp.Width, bmp.Height);
        BitmapData bmpData = bmp.LockBits(rect, ImageLockMode.ReadWrite, bmp.PixelFormat);

        // Get the address of the first line.
        IntPtr ptr = bmpData.Scan0;

        // Declare an array to hold the bytes of the bitmap.
        int bytes  = Math.Abs(bmpData.Stride) * bmp.Height;
        byte[] rgbaValues = new byte[bytes];

        // Copy the RGB values into the array.
        System.Runtime.InteropServices.Marshal.Copy(ptr, rgbaValues, 0, bytes);

        // array consists of values RGBARGBARGBA

        for (int counter = 0; counter < rgbaValues.Length; counter += 4)
        {
            double t = rgbaValues[counter + 3]/255.0; // transparency of pixel between 0 .. 1 , easier to do math with this
            double rt = 1 - t; // inverted value of transparency

            // C = C * t + W * (1-t) // alpha transparency for your case C-color, W-white (255)
            // same for each color
            rgbaValues[counter] = (byte) (rgbaValues[counter]*t + 255*rt); // R color
            rgbaValues[counter + 1] = (byte)(rgbaValues[counter + 1] * t + 255 * rt); // G color
            rgbaValues[counter + 2] = (byte)(rgbaValues[counter + 2] * t + 255 * rt); // B color

            rgbaValues[counter + 3] = 255; // A = 255 => no transparency 
        }
        // Copy the RGB values back to the bitmap
        System.Runtime.InteropServices.Marshal.Copy(rgbaValues, 0, ptr, bytes);

        // Unlock the bits.
        bmp.UnlockBits(bmpData);
    }

This is different bacause:

I use LockBits instead GetPixel and SetPixel. It is much more faster, but little harder to understand. It's a little modified example from : MSDN

I'm taking real aplha value into consideration, as I said in the comment to your question. This will make black with 50% transparency (128) look like gray instead of black. Reason for this is by "replace alpha with white in graphics editor" I imagine creating new layer underneath you image filled with white and then flattening both layers together. This example will have same effect.

Euphoric
  • 12,645
  • 1
  • 30
  • 44
0

Once you have a handle to the bitmap object, just do something like:

Bitmap yourImage = HOWEVER YOU LOAD YOUR IMAGE;
int width = YOUR IMAGE WIDTH;
int height = YOUR IMAGE HEIGHT;

Color c;
Color white = new Color(255,255,255,255)
for(int w = 0; w < width; w++)
for(int h = 0; h < height; h++)
{
    c = yourImage.GetPixel(w,h);
    yourImage.SetPixel(w,h, ((((short)(c.A)) & 0x00FF) <= 0)? white:c); //replace 0 here with some higher tolerance if needed
}
  • Color.A ranges from 0 to 255. why `abs(c.A)` ? – Bala R Mar 24 '11 at 20:57
  • depending on the system this value between 0 and 255 is only stored in 1 byte which can be signed meaning that 255 will show up as -1, 254 will show up as -2 and so forth –  Mar 24 '11 at 21:00
  • actually a better way to do it would be (((short)(c.A)) & 0x00FF) –  Mar 24 '11 at 21:02
  • c.A is of type byte and in c# byte ranges from 0 to 255, no? http://msdn.microsoft.com/en-us/library/5bdb6693(VS.80).aspx – Bala R Mar 24 '11 at 21:03
  • possibly but ive seen it to be problematic in the past so in my opinion its better to stay on the safe side –  Mar 24 '11 at 21:05
0

This may be oversimplifying your problem, but if it's on a form or other readily available control, you could simply paint the background White before placing the Image on top.