Search code examples
c#winformsbitmapsystem.drawingalpha-transparency

Saving a one color bitmap with alpha channel in Windows Forms saves a different (wrong) color


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.


Solution

  • 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;