Jump to content

Understanding image contrast: Can someone explain it to me like I'm 5?


Recommended Posts

I do not know if this is the best subforum to post this question to, but here it goes:

So, I've added the ability to modify brightness and contrast to my plug-in (which I will be releasing soon), only, I do not understand the contrast adjustment code. I've googled around, and despite reading several tutorials, I still do not grasp it. I've checked the official Paint.Net book, and while it briefly discusses contrast, it doesn't go into details or provide a formula.

 

The code I am using is from this Stackoverflow answer: https://stackoverflow.com/questions/3115076/adjust-the-contrast-of-an-image-in-c-sharp-efficiently

 

What is going on here? Why is 100f being added to the contrast Value, and why is it then being divided by 100f? Why is this Value then being multiplied by itself?

 

Value = (100.0f + Value) / 100.0f;

Value *= Value;

 

And what is the significance of the 0.5f in the code below? What variable is 0.5f filling in for?

 

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;

 

Could someone explain it to me like I'm 5? I apologize if this question seems stupid, but I am trying to understand.

 

Thank you

Link to comment
Share on other sites

Value = (100.0f + Value) / 100.0f;
Value *= Value;

It uses Value / 100 because that gives you the ratio

They wanted to square that ratio without decreasing it, so they added 100 to match the denominator so that the minimum possible value is 1

It looks like they want to use this ratio as the strength for the contrast effect, so 1 = no change

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;

The way this is written, they are expecting that the RGB channels are already floats in a range from 0 to 1. I can tell, because they multiply by 255 at the end (which is the max range of these channels) and they never divide. So by that logic, I also know that 0.5f is meant to represent the midpoint of the channel range. The way increasing contrast works, is that it pushes values away from the midpoint. So what they're doing is:

 

1. take the current value of the channel, which is between 0 and 1 inclusive

 

2. subtract 0.5, which is the midpoint of the channel's range

 

3. Multiply by the strength ratio of the contrast, aka Value (the key here is that if the channel value was under 0.5, it's now negative, and so the value decreases when you multiply it by a number that's greater than 1. Remember that Value has a minimum possible value of 1...now you're seeing the effect! If the channel value was > 0.5 to begin with, it will increase. So this is the key effect: push values that are under the midpoint further to 0, and push values above it further to 255.

 

4. Multiply by 255 so the channels are no longer in a range from 0 to 1. They can be rounded and cast to byte in a range from 0 to 255, as expected of a paint.net plugin.

 

But there are a few funky things here, to me. First, it's easy for this to go out of range. The smallest value of a channel (0) and the largest value for the Value variable (2) will produce ((0 - 0.5f) * 2 + 0.5f) * 255 = -127.5 which is WAY out of range. You could clamp that value between 0 and 1 before multiplying by 255, but it feels like the algorithm is just wrong in some way, here. And I'm not sure why they need the strength of the contrast effect to be quadratic (by squaring it) in the first place.

 

Hope this helps!

  • Like 1
  • You're a Smart Cookie! 1
Link to comment
Share on other sites

I started writing this, but was interrupted before @NinthDesertDude posted a reply.  I hope it may shed some additional light on the question.

 

(I assume that when entering into this code the contrast is in Value and is between -100 and 100, and Red, Green, and Blue have been scaled to the 0 to 1 range. I changed the name from Value to Contrast to reduce confusion)

 

The idea behind the contrast adjustment is to stretch the colors outward from the middle value of 0.5 when the contrast is positive, and compress then toward 0.5 when the contrast is negative.

 

Contrast = (100.0f + Contrast) / 100.0f;

 

This step shifts and scales the contrast from the range  -100 to 100 to the range 0 to 2.

Original -100 -> 0

Original 0 -> 1

Original 100 -> 2

 

Contrast *= Contrast;

 

This step stretches the upper Contrast range when the original Contrast is greater than 0, while not affecting original Contrasts of -100 and 0. These are special cases: -100 results in no contrast (all colors are the same), while 0 results in no color change. Presumably, it was felt desirable to increase contrast change when the Contrast is at high values without changing the special cases.

 

Original -100 -> 0

Original 0 -> 1

Original 100 -> 4

 

Red = (((Red - 0.5f) * Value) + 0.5f); (etc.)

(I omit the multiplication by 255, which is simply to scale the color components from the 0 to 1 range back to the 0 to 255 range.)

 

The subtraction of 0.5 shifts the range so that the middle value, 0.5, becomes 0, values less than the middle value become negative, and values greater than the middle value will become positive.

The scaling increases values that are now (after 0.5 is subtracted) greater than 0, decreases values that are now less than 0, and leaves values that are now 0 unchanged.

The addition of 0.5 shifts values that were originally 0.5 back to their original values, no matter what the contrast : ((0.5 - 0.5) * Contrast) + 0.5 = 0.5.

 

Now consider the several cases.

 

If the original Contrast is 0, after the first two steps it will be 1. We'll have:

Red = (((Red - 0.5f) * 1) + 0.5f);

 

It's easily seen that the color value will be unchanged.

 

If the original Contrast is -100, after the first two steps it will be 0. We'll have:

Red = (((Red - 0.5f) * 0) + 0.5f);

 

For this case, the color value will be 0.5, no matter what is originally was. So there will be no contrast in the final image -- all colors will be the same mid-gray.

 

If the original Contrast value is between -100 and 0, after the first two steps it will be between 0 and 1.  Colors less than 0.5 will be increased; colors less than 0.5 will be decreased. All the colors will be made closer to 0.5, so the contrast will be decreased.

 

If the original Contrast is greater than 0, after the first two steps it will be greater than 1. If the original Contrast was 100, after the first two steps it will be 4. Original Contrasts greater than 0 cause color values less than 0.5 to decrease and color values greater than 0.5 to increase. Consider the case where the original Contrast is 100. After the first two steps it will be 4, so:

Red = (((Red - 0.5f) * 4) + 0.5f);

 

If Red is 0, it will become -1.5.

If Red is 0.375, it will become 0.

If Red is 0.5, it will become 0.5.

If Red is 0.625, it will become 1.0.

It Red is 1, it will become 2.5.

 

All values of Red less than 0.375 will be made negative and all vales greater than 0.625 will be made greater than 1. Since the color values must be between 0 and 1, they'll need to be clamped. Looking at the original code, I see that it is clamped. The fact that color vales that aren't near 0.5 will go out of range and need to be clamped is the price that must be paid for greatly increasing the contrast of values near 0.5.

 

EDIT: I'm somewhat skeptical of this method of adjusting the contrast. I think it would cause significant color shifts, because each color component is changed independently. The color would shift towards the color of the maximum of the RGB components. Orange would shift toward red, and yellow-green would shift toward green. Maybe it's not a problem, but it seems like it might be. Seems like a better method is to, in essence, convert the color to HSV, apply the contrast adjustment to V, then convert back to RGB. I expect there's probably a more efficient way of achieving the same result, based on the minimum and maximum color components, though off hand I don't know what it would be. I tried looking at how PDN does the contrast adjustment  for Brightness/Contrast, but it's quite confusing, so without taking a bit more time, I don't know.

  • Like 1
  • You're a Smart Cookie! 1
Link to comment
Share on other sites

10 hours ago, MJW said:

Seems like a better method is to, in essence, convert the color to HSV, apply the contrast adjustment to V, then convert back to RGB. 

Do not forget about other color space such HCY, HSL, LAB, LCH, YUV, YIQ, CMYK. 

Edited by Reptillian

G'MIC Filter Developer

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

 Share

×
×
  • Create New...