Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Adjust the contrast of an image in C# efficiently

Tags:

c#

image

contrast

Is there an efficient way of adjusting the contrast of an image in C#?

I've seen this article which advocates doing a per-pixel operation. Not quick.

I'm using colour matrices in places already and find them to be quick. Is there a way to adjust contrast using them? (Note: This guy gets it wrong.)

I'm also using EmguCV. I notice that OpenCV (which Emgu wraps) seems to have a contrast function - is there any way of accessing this through Emgu? At the moment all I can do in Emgu is normalise the histogram, which does change the contrast, but not with any degree of control on my part.

Anyone got any ideas?

like image 779
Tom Wright Avatar asked Jun 25 '10 01:06

Tom Wright


2 Answers

@MusiGenesis,

Just wanted to note that I used this method for an image editor I've been writing. It works well, but sometimes this method triggers an AccessViolationException on this line:

byte B = row[columnOffset];

I realised it was because there was no standardisation of BitDepth, so if an image was 32 bit colour I was getting this error. So I changed this line:

BitmapData data = NewBitmap.LockBits(new Rectangle(0, 0, NewBitmap.Width, NewBitmap.Height),  ImageLockMode.ReadWrite, NewBitmap.PixelFormat);

to:

BitmapData data = NewBitmap.LockBits(new Rectangle(0, 0, NewBitmap.Width, NewBitmap.Height), ImageLockMode.ReadWrite, PixelFormat.Format32bppRgb); 

Hope this helps as it seems to have eradicated my problem.

Thanks for the post.

Jib

like image 147
Mike Avatar answered Nov 03 '22 15:11

Mike


If the code in that sample works for you, you can speed it up massively (by orders of magnitude) by using Bitmap.LockBits, which returns a BitmapData object that allows access to the Bitmap's pixel data via pointers. There are numerous samples on the web and on StackOverflow that show how to use LockBits.

Bitmap.SetPixel() and Bitmap.GetPixel() are the slowest methods known to mankind, and they both utilize the Color class, which is the slowest class known to mankind. They should have been named Bitmap.GetPixelAndByGodYoullBeSorryYouDid() and Bitmap.SetPixelWhileGettingCoffee as a warning to unwary developers.

Update: If you're going to modify the code in that sample, note that this chunk:

System.Drawing.Bitmap TempBitmap = Image;
System.Drawing.Bitmap NewBitmap = new System.Drawing.Bitmap(TempBitmap.Width,
    TempBitmap.Height);
System.Drawing.Graphics NewGraphics = 
    System.Drawing.Graphics.FromImage(NewBitmap);
NewGraphics.DrawImage(TempBitmap, new System.Drawing.Rectangle(0, 0, 
    TempBitmap.Width, TempBitmap.Height), 
    new System.Drawing.Rectangle(0, 0, TempBitmap.Width, TempBitmap.Height),
    System.Drawing.GraphicsUnit.Pixel);
NewGraphics.Dispose();

can be replaced with this:

Bitmap NewBitmap = (Bitmap)Image.Clone();

Update 2: Here is the LockBits version of the AdjustContrast method (with a few other speed improvements):

public static Bitmap AdjustContrast(Bitmap Image, float Value)
{
    Value = (100.0f + Value) / 100.0f;
    Value *= Value;
    Bitmap NewBitmap = (Bitmap)Image.Clone();
    BitmapData data = NewBitmap.LockBits(
        new Rectangle(0, 0, NewBitmap.Width, NewBitmap.Height), 
        ImageLockMode.ReadWrite,
        NewBitmap.PixelFormat);
    int Height = NewBitmap.Height;
    int Width = NewBitmap.Width;

    unsafe
    {
        for (int y = 0; y < Height; ++y)
        {
            byte* row = (byte*)data.Scan0 + (y * data.Stride);
            int columnOffset = 0;
            for (int x = 0; x < Width; ++x)
            {
                byte B = row[columnOffset];
                byte G = row[columnOffset + 1];
                byte R = row[columnOffset + 2];

                float Red = R / 255.0f;
                float Green = G / 255.0f;
                float Blue = B / 255.0f;
                Red = (((Red - 0.5f) * Value) + 0.5f) * 255.0f;
                Green = (((Green - 0.5f) * Value) + 0.5f) * 255.0f;
                Blue = (((Blue - 0.5f) * Value) + 0.5f) * 255.0f;

                int iR = (int)Red;
                iR = iR > 255 ? 255 : iR;
                iR = iR < 0 ? 0 : iR;
                int iG = (int)Green;
                iG = iG > 255 ? 255 : iG;
                iG = iG < 0 ? 0 : iG;
                int iB = (int)Blue;
                iB = iB > 255 ? 255 : iB;
                iB = iB < 0 ? 0 : iB;

                row[columnOffset] = (byte)iB;
                row[columnOffset + 1] = (byte)iG;
                row[columnOffset + 2] = (byte)iR;

                columnOffset += 4;
            }
        }
    }

    NewBitmap.UnlockBits(data);

    return NewBitmap;
}

NOTE: this code requires using System.Drawing.Imaging; in your class' using statements, and it requires that the project's allow unsafe code option be checked (on the Build Properties tab for the project).

One of the reasons GetPixel and SetPixel are so slow for pixel-by-pixel operations is that the overhead of the method call itself starts to become a huge factor. Normally, my code sample here would be considered a candidate for refactoring, since you could write your own SetPixel and GetPixel methods that use an existing BitmapData object, but the processing time for the math inside the functions would be very small relative to the method overhead of each call. This is why I removed the Clamp calls in the original method as well.

One other way to speed this up would be to simply make it a "destructive" function, and modify the passed Bitmap parameter instead of making a copy and returning the modified copy.

like image 39
MusiGenesis Avatar answered Nov 03 '22 13:11

MusiGenesis