0

I'd like to, in code and on demand, convert a 32-bit RGBA Image object (originally a 32-bit PNG) to its 32-bit grayscale counterpart.

I've already read several other questions here, as well as many articles online. I've tried using ColorMatrix to do it, but it doesn't seem to handle the alpha very well. Pixels that are entirely opaque grayscale perfectly. Any pixel that is partially transparent seems not to translate well as there are still tinges of color in those pixels. It is enough to be noticeable.

The ColorMatrix I use is as follows:

new System.Drawing.Imaging.ColorMatrix(new float[][]{
                    new float[] {0.299f, 0.299f, 0.299f, 0, 0},
                    new float[] {0.587f, 0.587f, 0.587f, 0, 0},
                    new float[] {0.114f, 0.114f, 0.114f, 0, 0},
                    new float[] {     0,      0,      0, 1, 0},
                    new float[] {     0,      0,      0, 0, 1}
                    });

This is, as I've read, a pretty standard NTSC weighted matrix. I then use it, along with Graphics.DrawImage, but as I said the partially transparent pixels are still colored. I should point out this is displaying the Image object via a WinForms PictureBox on a white background. Could it be perhaps just the way PictureBox's draw their images and handle the transparent parts? The background colors are not affecting it (the tinge of color is from the original image for sure), but perhaps PictureBox isn't redrawing the transparent pixels correctly?

I've seen some methods that use a FormatConvertedBitmap along with an OpacityMask. I haven't tried it, mainly because I'd really prefer not to have to import PresentationCore.dll (not to mention that means it won't work in .NET 2.0 limited apps). Surely the basic System.Drawing.* stuff can do this simple procedure? Or not?

Community
  • 1
  • 1
Sean Hanley
  • 5,379
  • 7
  • 39
  • 51
  • Are you sure the tinge of colour isn't due to what's behind the image? As long as the RGB values are exactly equal there shouldn't be any tinge. Post you ColorMatrix definition. – ChrisF Jun 16 '10 at 14:03
  • I'm sure it's not. The image is presented via a PictureBox. The background of the PictureBox is, of course, transparent, which really just means it uses its parent's color, which is, in this case, white. The transparent edges of the new grayscale image, however, are tinged green still (the original image was mostly green in color). – Sean Hanley Jun 16 '10 at 14:17

3 Answers3

5

Are you by any chance painting the image onto itself using a ColorMatrix? That won't work of course (because if you paint something semi-transparent-gray over a green pixel, some green will shine through). You need to paint it onto a new, empty bitmap containing only transparent pixels.

Dan Byström
  • 8,682
  • 5
  • 34
  • 66
  • Ah crap, you're probably right. Man, I was really hoping this time to have found a bug or something! But, as usual, it's just me being stupid. – Sean Hanley Jun 16 '10 at 14:30
4

Thanks to danbystrom's idle curiosity, I was indeed redrawing on top of the original. For anyone interested, here's the corrected method I used:

using System.Drawing;
using System.Drawing.Imaging;

public Image ConvertToGrayscale(Image image)
{
    Image grayscaleImage = new Bitmap(image.Width, image.Height, image.PixelFormat);

    // Create the ImageAttributes object and apply the ColorMatrix
    ImageAttributes attributes = new System.Drawing.Imaging.ImageAttributes();
    ColorMatrix grayscaleMatrix = new ColorMatrix(new float[][]{
        new float[] {0.299f, 0.299f, 0.299f, 0, 0},
        new float[] {0.587f, 0.587f, 0.587f, 0, 0},
        new float[] {0.114f, 0.114f, 0.114f, 0, 0},
        new float[] {     0,      0,      0, 1, 0},
        new float[] {     0,      0,      0, 0, 1}
        });
    attributes.SetColorMatrix(grayscaleMatrix);

    // Use a new Graphics object from the new image.
    using (Graphics g = Graphics.FromImage(grayscaleImage))
    {
        // Draw the original image using the ImageAttributes created above.
        g.DrawImage(image,
                    new Rectangle(0, 0, grayscaleImage.Width, grayscaleImage.Height),
                    0, 0, grayscaleImage.Width, grayscaleImage.Height,
                    GraphicsUnit.Pixel,
                    attributes);
    }

    return grayscaleImage;
}
Sean Hanley
  • 5,379
  • 7
  • 39
  • 51
  • Just wanted to say thanks for this. I had to declare the grayscaleMatrix datatype to get this method working standalone - but works great. Thank you. https://gist.github.com/4f55bd017b69fe54785e58cf8e35b7b3 – Christopher D. Emerson Dec 26 '18 at 17:41
  • 1
    @ChrisEmerson Thanks, never noticed that bug all these years. Edited and fixed. – Sean Hanley Jan 04 '19 at 22:44
0

If you convert the image to TGA, an uncompressed imaeg format you can use "RubyPixels" to edit the pixel data directly, doing whatever you please. You can then convert it back to PNG.

I recomend doing to conversion with ImageMagick, also from ruby.

thomasfedb
  • 5,907
  • 1
  • 33
  • 64
  • Maybe I wasn't clear, but I'm not concerned really with the original format of the image (GIF, PNG, JPG, etc.) or converting it to save it or anything. I was merely specifing that an Image object in .NET was created originally from a PNG. I'm using this Image object in a WinForms environment directly and manipulating its color for, in this case, a disabled look. I have no intention to save it back. It's just for on-the-fly "graying out". Even so, could you provide some links to your suggestions in case anyone else is interested? – Sean Hanley Jun 16 '10 at 14:19
  • Sorry, can't help you in that case... = – thomasfedb Jun 16 '10 at 16:05