views:

237

answers:

3

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?

A: 

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
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?
Yadyn
Sorry, can't help you in that case... =<
thomasfedb
+5  A: 

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.

danbystrom
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.
Yadyn
+2  A: 

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();
    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;
}
Yadyn