7

I have a tool with trackbar slider controls used to adjust an image's brightness, contrast, gamma, etc.

I am trying to get real-time updates to my image while the user drags the slider. The brightness and gamma algorithms are an acceptable speed (around 170ms). But the contrast algorithm is about 380ms.

Basically my form is a tool window with sliders. Each time the image is updated, it sends an event to the parent which redraws the new image. The tool window keeps the original unmodified image locked in memory so I always have access to the bytes of it. So basically I do this each time the ValueChanged event for a slider (such as the Contrast slider) is changed.

  • LockBits of the working (destination) bitmap as Format24bppRgb (original bitmap is in Format32bppPArgb)
  • Marshal.Copy the bits to a byte[] array
  • Check which operation I'm doing (which slider was chosen)
  • Use the following code for Contrast:

Code:

double newValue = 0;
double c = (100.0 + contrast) / 100.0;

c *= c;

for (int i = 0; i < sourcePixels.Length; i++)
{
    newValue = sourcePixels[i];

    newValue /= 255.0;
    newValue -= 0.5;
    newValue *= c;
    newValue += 0.5;
    newValue *= 255;

    if (newValue < 0)
        newValue = 0;
    if (newValue > 255)
        newValue = 255;

    destPixels[i] = (byte)newValue;
}

I read once about using integer instead of floating point values to increase the speed of contrast, but I couldn't find that article again.

I tried using unsafe code (pointers) but actually noticed a speed decrease. I assume it was because the code was using nested for loops to iterate x and y instead of a single loop.

Emil
  • 7,220
  • 17
  • 76
  • 135
Trevor Elliott
  • 11,292
  • 11
  • 63
  • 102
  • possible duplicate of [Adjust the contrast of an image in C# efficiently](http://stackoverflow.com/questions/3115076/adjust-the-contrast-of-an-image-in-c-sharp-efficiently) – Magnus Jan 24 '12 at 17:11
  • The unsafe code in the question you linked to, when I test it with the same image as I was using with my code, takes over 900ms per routine. Of course I modified it so that it doesn't clone or create new bitmaps, it's just the nested loop with pointers and floating-point math. It's too slow. – Trevor Elliott Jan 24 '12 at 17:36
  • 1
    Perhaps you can modify your code to use pointers instead in unsafe code. – Magnus Jan 24 '12 at 17:39

2 Answers2

14

Depending on the machine you're running this on, your technique could be quite slow. If you're using an ARM system without an FPU, each of those operations would take quite a while. Since you're applying the same operation to every byte, a faster technique would be to create a 256-entry lookup table for the contrast level and then translate each image byte through the table. Your loop would then look like:

byte contrast_lookup[256];
double newValue = 0;
double c = (100.0 + contrast) / 100.0;

c *= c;

for (int i = 0; i < 256; i++)
{
    newValue = (double)i;
    newValue /= 255.0;
    newValue -= 0.5;
    newValue *= c;
    newValue += 0.5;
    newValue *= 255;

    if (newValue < 0)
        newValue = 0;
    if (newValue > 255)
        newValue = 255;
    contrast_lookup[i] = (byte)newValue;
}

for (int i = 0; i < sourcePixels.Length; i++)
{
    destPixels[i] = contrast_lookup[sourcePixels[i]];
}
BitBank
  • 8,500
  • 3
  • 28
  • 46
  • 1
    Much faster. That got the speed down from around 380ms to about 155ms. Thank you! – Trevor Elliott Jan 24 '12 at 17:43
  • 1
    It's worth noting that you can also apply this same approach to your brightness and gamma algorithms to make them faster still. – Seph Jan 25 '12 at 11:03
  • I thought so and I was going to try it. This method scales really well with high resolution bitmaps like the 2976x1536 files I'm dealing with. – Trevor Elliott Jan 25 '12 at 18:26
  • If you really need more speed, you might want to consider using NEON (ARM) or SSE (x86) instructions and converting it to use integer operations. With either of those instruction sets you can process up to 16 bytes at a time (in parallel). – BitBank Jan 25 '12 at 18:33
  • @BitBank, can I do something like this in Unity3D? – Kala J May 07 '15 at 02:37
  • @Kala J - sorry, I don't use Unity3D, so I can't comment on that. – BitBank May 07 '15 at 10:33
5

@BitBank answers your question as asked, I wanted to add that if you're after performance you should consider your code that is getting the pixel data and setting it afterwards.

Full working code using pointers (props to @BitBank on the for loop code):

private unsafe void ApplyContrast(double contrast, Bitmap bmp)
{
    byte[] contrast_lookup = new byte[256];
    double newValue = 0;
    double c = (100.0 + contrast) / 100.0;

    c *= c;

    for (int i = 0; i < 256; i++)
    {
        newValue = (double)i;
        newValue /= 255.0;
        newValue -= 0.5;
        newValue *= c;
        newValue += 0.5;
        newValue *= 255;

        if (newValue < 0)
            newValue = 0;
        if (newValue > 255)
            newValue = 255;
        contrast_lookup[i] = (byte)newValue;
    }

    var bitmapdata = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), 
        System.Drawing.Imaging.ImageLockMode.ReadWrite, System.Drawing.Imaging.PixelFormat.Format32bppArgb);

    int PixelSize = 4;

    for (int y = 0; y < bitmapdata.Height; y++)
    {
        byte* destPixels = (byte*)bitmapdata.Scan0 + (y * bitmapdata.Stride);
        for (int x = 0; x < bitmapdata.Width; x++)
        {
            destPixels[x * PixelSize] = contrast_lookup[destPixels[x * PixelSize]]; // B
            destPixels[x * PixelSize + 1] = contrast_lookup[destPixels[x * PixelSize + 1]]; // G
            destPixels[x * PixelSize + 2] = contrast_lookup[destPixels[x * PixelSize + 2]]; // R
            //destPixels[x * PixelSize + 3] = contrast_lookup[destPixels[x * PixelSize + 3]]; //A
        }
    }
    bmp.UnlockBits(bitmapdata);
}

If you're setting your image pixels data using Marshal.Copy you will find this performs better.

This should perform faster than your current code, and you also reduce the memory footprint which is good when dealing with very large images.

Seph
  • 8,472
  • 10
  • 63
  • 94
  • It could also be a little bit faster if you don scale it from and back to 255. I.E. newValue = (double)i; newValue -= 128; newValue *= c; newValue += 128; – DkAngelito Nov 11 '14 at 19:26
  • 1
    Another big improvement would be to calculate x* PixelSize once instead of 6 times in the inner for – DkAngelito Nov 11 '14 at 19:57