59

Is there a way to convert an image to grayscale 16 bits per pixel format, rather than setting each of the r,g and b components to luminance. I currently have a bmp from file.

Bitmap c = new Bitmap("filename");

I want a Bitmap d, that is grayscale version of c. I do see a constructor that includes System.Drawing.Imaging.PixelFormat, but I don't understand how to use that. I'm new to Image Processing and the relevant C# libraries, but have a moderate experience with C# itself.

Any help, reference to an online source, hint or suggestion will be appreciated.

EDIT: d is the grayscale version of c.

Dale K
  • 16,372
  • 12
  • 37
  • 62
0fnt
  • 6,986
  • 9
  • 42
  • 60

6 Answers6

79

"I want a Bitmap d, that is grayscale. I do see a consructor that includes System.Drawing.Imaging.PixelFormat, but I don't understand how to use that."

Here is how to do this

Bitmap grayScaleBP = new 
         System.Drawing.Bitmap(2, 2, System.Drawing.Imaging.PixelFormat.Format16bppGrayScale);

EDIT: To convert to grayscale

             Bitmap c = new Bitmap("fromFile");
             Bitmap d;
             int x, y;

             // Loop through the images pixels to reset color.
             for (x = 0; x < c.Width; x++)
             {
                 for (y = 0; y < c.Height; y++)
                 {
                     Color pixelColor = c.GetPixel(x, y);
                     Color newColor = Color.FromArgb(pixelColor.R, 0, 0);
                     c.SetPixel(x, y, newColor); // Now greyscale
                 }
             }
            d = c;   // d is grayscale version of c  

Faster Version from switchonthecode follow link for full analysis:

public static Bitmap MakeGrayscale3(Bitmap original)
{
   //create a blank bitmap the same size as original
   Bitmap newBitmap = new Bitmap(original.Width, original.Height);

   //get a graphics object from the new image
   using(Graphics g = Graphics.FromImage(newBitmap)){

       //create the grayscale ColorMatrix
       ColorMatrix colorMatrix = new ColorMatrix(
          new float[][] 
          {
             new float[] {.3f, .3f, .3f, 0, 0},
             new float[] {.59f, .59f, .59f, 0, 0},
             new float[] {.11f, .11f, .11f, 0, 0},
             new float[] {0, 0, 0, 1, 0},
             new float[] {0, 0, 0, 0, 1}
          });

       //create some image attributes
       using(ImageAttributes attributes = new ImageAttributes()){

           //set the color matrix attribute
           attributes.SetColorMatrix(colorMatrix);

           //draw the original image on the new image
           //using the grayscale color matrix
           g.DrawImage(original, new Rectangle(0, 0, original.Width, original.Height),
                       0, 0, original.Width, original.Height, GraphicsUnit.Pixel, attributes);
       }
   }
   return newBitmap;
}
ghostbust555
  • 1,930
  • 14
  • 27
Asad
  • 19,788
  • 16
  • 65
  • 91
  • I'm sorry, I should have stated that in my question that I want the grayscale version of my original bitmap I've edited the post to reflect this. – 0fnt Feb 15 '10 at 13:01
  • 11
    This method (pixel by pixel is too slow) check this article here:http://www.switchonthecode.com/tutorials/csharp-tutorial-convert-a-color-image-to-grayscale – Luis Jan 12 '11 at 14:37
  • 1
    How change this method in order to take in account only the RGB values and not the transparency? Thank you – Martina Aug 07 '15 at 09:00
  • I did the first edit, it all white was turned to red, and it was still True 24-bit color...I changed the code to use green and blue, and the resulting image seemed to be the same as the original after that. – Bryan Aug 13 '15 at 19:46
  • 2
    It appears the blog with the original article is no longer available. Link or Explanation for the matrix? – amhed Feb 27 '16 at 05:00
  • Look on the [WayBack Machine](https://web.archive.org/web/20130208001434/http://tech.pro:80/tutorial/660/csharp-tutorial-convert-a-color-image-to-grayscale). – Dour High Arch Sep 10 '18 at 17:13
37
Bitmap d = new Bitmap(c.Width, c.Height);

for (int i = 0; i < c.Width; i++)
{
    for (int x = 0; x < c.Height; x++)
    {
        Color oc = c.GetPixel(i, x);
        int grayScale = (int)((oc.R * 0.3) + (oc.G * 0.59) + (oc.B * 0.11));
        Color nc = Color.FromArgb(oc.A, grayScale, grayScale, grayScale);
        d.SetPixel(i, x, nc);
    }
}

This way it also keeps the alpha channel.
Enjoy.

Vercas
  • 8,248
  • 15
  • 60
  • 102
  • 3
    I know it's an old answer, but could you elaborate on the constants that you multiply on the R, G, B values? I see it sums up to "1.0", but is there any explanation to why? – Lars Kristensen Aug 05 '13 at 11:55
  • @lukegravitt, Thanks! :) – Lars Kristensen Aug 07 '13 at 06:41
  • Now, after I have used this myself, I have to add that it's a bad idea to use it for small disabled icons or in any case where the difference has to be noticed. The grayscale result is awfully close to the color one... Hard to distinguish. – Vercas Aug 08 '13 at 12:49
  • This solution seems slow. The first "for loop" should go over the height and the second loop (the inner one) should iterate over the width (cols). Images are stored line after line in disc space. When you iterate from one line to another you "jump" from one disc space to another. When iterating over width you just go to the next value on your disc. You can take a look at the opencv topic [link](http://docs.opencv.org/2.4/doc/tutorials/core/how_to_scan_images/how_to_scan_images.html#the-efficient-way) you always see that you should iterate in rows (outer) and cols (inner loop). – Christian Gold Sep 26 '16 at 10:19
  • These loop variable names are all over the place... the "x" is actually the y coordinate, and the whole image is processed column by column because the height loop is inside the width loop. – Nyerguds Jan 24 '18 at 15:12
27

There's a static method in ToolStripRenderer class, named CreateDisabledImage. Its usage is as simple as:

Bitmap c = new Bitmap("filename");
Image d = ToolStripRenderer.CreateDisabledImage(c);

It uses a little bit different matrix than the one in the accepted answer and additionally multiplies it by a transparency of value 0.7, so the effect is slightly different than just grayscale, but if you want to just get your image grayed, it's the simplest and best solution.

Chad
  • 1,380
  • 2
  • 17
  • 42
Szybki
  • 945
  • 12
  • 25
  • 3
    Oh man, thank you for this. This matches the look of images within disabled controls in WinForms, which keeps everything looking consistent. – Jimmy Dec 27 '17 at 16:48
7

The code below is the simplest solution:

Bitmap bt = new Bitmap("imageFilePath");

for (int y = 0; y < bt.Height; y++)
{
    for (int x = 0; x < bt.Width; x++)
    {
        Color c = bt.GetPixel(x, y);

        int r = c.R;
        int g = c.G;
        int b = c.B;
        int avg = (r + g + b) / 3;
        bt.SetPixel(x, y, Color.FromArgb(avg,avg,avg));
    }   
}

bt.Save("d:\\out.bmp");
Chad
  • 1,380
  • 2
  • 17
  • 42
7

None of the examples above create 8-bit (8bpp) bitmap images. Some software, such as image processing, only supports 8bpp. Unfortunately the MS .NET libraries do not have a solution. The PixelFormat.Format8bppIndexed format looks promising but after a lot of attempts I couldn't get it working.

To create a true 8-bit bitmap file you need to create the proper headers. Ultimately I found the Grayscale library solution for creating 8-bit bitmap (BMP) files. The code is very simple:

Image image = Image.FromFile("c:/path/to/image.jpg");
GrayBMP_File.CreateGrayBitmapFile(image, "c:/path/to/8bpp/image.bmp");

The code for this project is far from pretty but it works, with one little simple-to-fix problem. The author hard-coded the image resolution to 10x10. Image processing programs do not like this. The fix is open GrayBMP_File.cs (yeah, funky file naming, I know) and replace lines 50 and 51 with the code below. The example sets the resolution to 200x200 but you should change it to the proper number.

int resX = 200;
int resY = 200;
// horizontal resolution
Copy_to_Index(DIB_header, BitConverter.GetBytes(resX * 100), 24);
// vertical resolution 
Copy_to_Index(DIB_header, BitConverter.GetBytes(resY * 100), 28);
Chad
  • 1,380
  • 2
  • 17
  • 42
Brent Matzelle
  • 3,715
  • 3
  • 24
  • 26
  • I would like to test it out and upvote, but short on time right now. Thanks for the answer though. – 0fnt Apr 23 '11 at 18:49
  • the trick with 8bpp is that you need to edit the raw internal array backing the image. You can get that by calling `image.LockBits()` and then copying in/out the data using `Marshal.Copy`. Google it to get the full process. Do note the stride of an image (a line read from the data) is often longer than the actual width, as it is rounded to the nearest multiple of 4 bytes. – Nyerguds Apr 03 '17 at 21:46
6

To summarize a few items here: There are some pixel-by-pixel options that, while being simple just aren't fast.

@Luis' comment linking to: (archived) https://web.archive.org/web/20110827032809/http://www.switchonthecode.com/tutorials/csharp-tutorial-convert-a-color-image-to-grayscale is superb.

He runs through three different options and includes timings for each.

jeffreypriebe
  • 2,167
  • 23
  • 30