Search code examples
c#image-processingcolormatrix

Convert image to monochrome using ColorMatrix


I'm trying to build a function to convert a color image to monochrome. There's a lot of confusion around terminology, but I'm talking about true black-and-white, where we only have 2 colors: black and white - no grays.

Here is an example of what I'm trying to achieve:

Original Image --> Monochrome Image

I also wanted a way to control the "threshold" of the conversion (i.e. how "bright" does a pixel have to be to produce a white pixel).

Here is what I have right now.

private Bitmap bitmap2Monochrome(Bitmap srcImg, int brightness)
{
    Bitmap monoImg = new Bitmap(srcImg.Width, srcImg.Height);

    // Loop through all the pixels in the image
    for (int y = 0; y < srcImg.Height; y++)
    {
        for (int x = 0; x < srcImg.Width; x++)
        {
            // Get pixel color
            Color existingColor = srcImg.GetPixel(x, y);
            
            // Average R, G, and B to determine "brightness" of this pixel
            int gray = (int)((existingColor.R + existingColor.G + existingColor.G) / 3);

            // If the "brightness" is greater than the threshold, this is a white pixel
            int color = 0;
            if (gray > brightness)
            {
                color = 255;
            }

            // Update the output image
            Color newColor = Color.FromArgb(color, color, color);
            monoImg.SetPixel(x, y, newColor);
        }
    }

    return monoImg;
}

This method works exactly the way I want, but it is (understandably) very slow. This is due to the fact that the GetPixel() and SetPixel() functions are very slow.

I would love to use a ColorMatrix because they are so fast, but I'm not very good at matrices, and I can't seem to come up with a matrix that would achieve this result.

Here's an example of how the logic works:

  1. Source color = (200, 150, 250)
  2. So, grayscale equivalent = (200, 200, 200)
  3. So, gray value = 200
  4. So, 200 > threshold
  5. So, monochrome value = 255
  6. So, monochrome color = (255, 255, 255)

Is there any way I could achieve this sort of functionality using a ColorMatrix?

I'm also open to other ideas!


Solution

  • Consider using an identity ColorMatrix with the fifth row of

    { -threshold, -threshold, -threshold, 0, 1 }
    

    This will allow you to shift all colors below your threshold to 0

    Then you could use a second ColorMatrix to multiply all RGB so they are larger than 1.0d. That would create the effect you want(assuming the implementation of ColorMatrix prevents negative values and larger values than 1.0d)