3

In C#, .NET 2.0, Windows Forms, Visual Studio Express 2010, I'm saving an image made of the same color:

  Bitmap bitmap = new Bitmap(width, height, PixelFormat.Format32bppArgb);
  using (Graphics graphics = Graphics.FromImage(bitmap))
  {
      Brush brush = new SolidBrush(color);
      graphics.FillRectangle(brush, 0, 0, width, height);
      brush.Dispose();
  }

  bitmap.Save("test.png");
  bitmap.Save("test.bmp");

If I'm using, for example

Color [A=153, R=193, G=204, B=17] or #C1CC11

after I'm saving the image and open it in an external viewer such as Paint.NET, IrfanView, XNView, etc. I am told that the color of the image is actually:

Color [A=153, R=193, G=203, B=16] or #C1CB10

so it's a similar color, but not the same!

I tried with both PNG and BMP saving.

When transparency (alpha) is involved, .NET saves a different color! When the alpha is 255 (no transparency), it saves the corrent color.

TechAurelian
  • 5,561
  • 5
  • 50
  • 65
  • what if you force a transparent background first? –  Dec 14 '11 at 16:14
  • Have you loaded your saved information back into your test code and looked at the values inside of there to double check it's not something on the code-side of things that is presenting the discrepancy? – Samuel Slade Dec 14 '11 at 16:15
  • When reloading the saved bitmap using Bitmap.FromFile and checking the color with Bitmap.GetPixel, I get the same results as Paint.NET, etc., so a different color is saved to the bitmap in the first place. – TechAurelian Dec 14 '11 at 16:24
  • The `Graphics` object is responsible for changing the color, it happens on `FillRectangle`. – Joe Dec 14 '11 at 16:41
  • Why does the Graphics object change the color? (I've also tried with graphics.Clear(color) and it still changes the color.) – TechAurelian Dec 14 '11 at 16:45
  • Not sure I was looking into, I am able to get the color to change more drastically if I change the SmoothingMode to HighQuality, but I can not get it to be exactly the same. – Joe Dec 14 '11 at 16:48
  • This is common with GDI+, it favors speed over accuracy. As long as the side-effect isn't visible to the human eye. I suspect this one has something to do with a code chunk that handles both regular and pre-multiplied alpha pixel formats. Nothing you can do about it. – Hans Passant Dec 14 '11 at 16:50
  • Is there another way to save a simple png image filled with the same color from C#, that will not bring this GDI+ issue? – TechAurelian Dec 14 '11 at 16:59

2 Answers2

2

Just wanted to add to this a little. From my experience, the last code snippet probably will throw a runtime error. This is due to the fact that the array of argb values in the bitmap is stored like this: [a1, b1, g1, r1, a2, b2, g2, r2, a3,...] etc., where a, r, g, b are int. You should run some test cases on your images to see where exactly the a,r,g,b values are in the array. Bitmaps don't necessarily guarantee this ordinality (yes, even in .Net).

To re-write it a little:

   byte[] rgbValues1 = new byte[4];

                            System.Drawing.Imaging.BitmapData bpData =
                            bm.LockBits(new Rectangle(0,0,bm.Width,bm.Height),System.Drawing.Imaging.ImageLockMode.ReadWrite,
                            bm.PixelFormat);

                        int thisPixel = ptStart.X * 4 + ptStart.Y * bpData.Stride;

                        IntPtr px = bpData.Scan0 + thisPixel;

                        System.Runtime.InteropServices.Marshal.Copy(px, rgbValues1, 0, rgbValues1.Length);


                        rgbValues1[0] = (byte)(255);//blue channel

                        rgbValues1[1] = (byte)(0);//green channel

                        rgbValues1[2] = (byte)(0);//red channel

                        rgbValues1[3] = (byte)(127)//should be alpha channel

                        System.Runtime.InteropServices.Marshal.Copy(rgbValues1, 0, px, 4);

                        bm.UnlockBits(bpData);

This would set a single pixel to blue with a transparency of 50% (or it should)... I'm still working on it myself...

2

Thank you, Joe and Hans Passant for your comments.

Yes, as Joe said, the problem is on the line:

graphics.FillRectangle(brush, 0, 0, width, height);

Here GDI+ modifies the color with a similar color, but not the exact one.

It seems that the solution is to write the color values directly in the pixels, using Bitmap.LockBits and Marshal.Copy:

        Bitmap bitmap = new Bitmap(this.currentSampleWidth, this.currentSampleHeight, PixelFormat.Format32bppArgb);

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

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

        // Declare an array to hold the bytes of the bitmap (32 bits per pixel)
        int pixelsCount = bitmap.Width * bitmap.Height;
        int[] argbValues = new int[pixelsCount];

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

        // Set the color value for each pixel.
        for (int counter = 0; counter < argbValues.Length; counter++)
            argbValues[counter] = color.ToArgb();

        // Copy the RGB values back to the bitmap
        System.Runtime.InteropServices.Marshal.Copy(argbValues, 0, ptr, pixelsCount);

        // Unlock the bits.
        bitmap.UnlockBits(bmpData);

        return bitmap;
TechAurelian
  • 5,561
  • 5
  • 50
  • 65