Image Editing in C# – Contrast

When attempting to get machines to process images you sometimes run into noise problems. Filters are regularly applied first to clean them up.

Editing the contrast of an image changes its dynamic range. In its most basic form, it will move pixel values away from the center (normally 128 if using 8 bits per color).

There are many different ways to to perform contrast adjustment.

The basic process is:

  • For each pixel in the image…
  • Divide each of the red, green and blue values by 255 to get a decimal value between 0 and 1
  • Subtract 0.5 from each of these values to get a delta from average
  • Multiply each of these value by a contrast ratio
  • Add 0.5 back to each value
  • Multiply each value by 255
  • Change any values less than 0 to 0
  • Change any values greater than 255 to 255

The contrast ratio is sometimes calculated from a RMS of the entire image contrast.
In this simple (but effective) case:

  • Take a contrast adjustment value between -100 and 100
  • Add 100 to the value and divide by 100 (To get a value between 0 and 2)
  • Square this result (Now between 0 and 4)

An input contrast adjustment value of 10 will result in a contrast ratio of (110/100)^2 = 1.21 and the following results:
0,0,0 – > 0,0,0
64,64,64 -> 51,51,51
128,128,128 – > 128,128,128
192,192,192 -> 208,208,208
255,255,255 -> 255,255,255

As you can see, the darkest, brightest and middle shades are retained, everything else is exponentially pushed towards one of the two extremes.

Here is some C# code to perform the steps above. It takes a Bitmap type and a byte value between -100 and 100.

public static Bitmap Contrast(Bitmap oBitmap, sbyte iContrast)
{
    if (iContrast < -100) return oBitmap;
    if (iContrast > 100) return oBitmap;
 
    double pixel = 0;
    //convert contrast to a decimal value
    double contrast = (100.0 + iContrast) / 100.0;
 
    contrast *= contrast;
 
    BitmapData bmData = oBitmap.LockBits(new Rectangle(0, 0, oBitmap.Width, oBitmap.Height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
 
    int stride = bmData.Stride;
    System.IntPtr Scan0 = bmData.Scan0;
 
    unsafe
    {
        byte* p = (byte*)(void*)Scan0;
 
        int iRowEnd = stride - oBitmap.Width * 3;
 
        for (int y = 0; y < oBitmap.Height; ++y)
        {
            for (int x = 0; x < oBitmap.Width; ++x)
            {
                for (int color = 0; color < 3; color++)
                //0 = Blue, 1 = Green, 2 = Red
                {
 
                    pixel = p[color] / 255.0;
                    pixel -= 0.5;
                    pixel *= contrast;
                    pixel += 0.5;
                    pixel *= 255;
                    if (pixel < 0) pixel = 0;
                    if (pixel > 255) pixel = 255;
                    p[color] = (byte)pixel;
                }
                p += 3;
            }
            p += iRowEnd;
        }
    }
 
    oBitmap.UnlockBits(bmData);
 
    return oBitmap;
}

You May Also Like

About the Author: John

Leave a Reply

Your email address will not be published. Required fields are marked *