Gamma correction details

Archived bsnes development news, feature requests and bug reports. Forum is now located at http://board.byuu.org/
Locked
blargg
Regular
Posts: 327
Joined: Thu Jun 30, 2005 1:54 pm
Location: USA
Contact:

Gamma correction details

Post by blargg »

People often think of gamma correction as merely compensation for a CRT display's non-linearity. This is only half-true. I highly recommend Poynton's The Rehabilitation of Gamma for full coverage of the issue.

The luminance of a CRT in response to a voltage is non-linear. If it were linear, twice the voltage would yield twice the luminance. The actual luminance is the voltage to the power of 2.5, so twice the voltage yields over five times the luminance. If one wants to work in a linear domain, one must apply the inverse relationship to the input, raising it to the power of 0.4. Combined with the 2.5 of the CRT, you get the original value: (n^0.4)^2.5 = n

A CRT display could include electronics to do this inverse transformation internally, making it act like it responds linearly to input voltage, but it doesn't. This is because the human visual system can distinguish smaller luminance changes in the darker end of the range than the lighter end. If luminance is represented linearly, changes in the value at the low end will be more visible than changes at the high end. If luminance is represented by gamma-corrected values, changes in value at the low end will be equally visible as changes at the high end. If this luminance value is represented with a limited number of bits, or over a wire that can pick up electrical noise, the gamma-corrected representation will make better use of these limitations. In effect, gamma correction is a form of lossy compression for images humans will be viewing.

So, gamma correction is a more useful way of representing luminance than a simple linear representation. Like other compression schemes, it's just a representation, not a way to alter the image (well, almost; see below for "rendering intent").

During image capture from an original source, like a camera or 3D rendering program, the linear RGB values should have a gamma of about 0.5 applied to them as a part of encoding into an image/video format. Then in the display, a gamma of around 2.5 undoes the original gamma, almost restoring linearity. In CRT displays, the CRT itself does the 2.5 gamma, as described above, so no extra circuitry is required. The 0.5->2.5 gamma sequence doesn't completely restore linearity, leaving a gamma of 1.25, referred to as rendering intent. This is because the images are usually captured in bright environments like a stage or outside, but displayed in dimmer ones like a living room. In a dimmer environment, a viewer's eyes have a different dynamic luminance response. The remaining gamma of about 1.25 compensates for this, allowing it to look similar to how it would have looked if the viewer were in the bright environment seeing the original scene firsthand.

PCs generally convert RGB values from the framebuffer directly to voltage sent to the display, so images have a gamma of 2.5 applied to them by the display itself. That is, RGB values in a PC framebuffer are NOT linear. If you want to display LINEAR RGB, you must apply an inverse gamma of 1/2.5=0.4 to RGB data before writing it to the framebuffer. This gamma affects all images displayed by the PC, but doesn't pose a problem because most images are ALREADY encoded in non-linear RGB.

Based on an informal test, the SNES works similar to a PC, converting the RGB values directly to a voltage that is turned into a video signal. This only makes sense, as this is the best way to make use of the limited number of bits the SNES has to represent RGB colors. So a SNES emulator on a PC should be able to just write SNES RGB values directly to the framebuffer and have them appear properly. Assuming I have no significant errors in my undersanding, that is.
Verdauga Greeneyes wrote:I still don't really understand this gamma business. Say I wanted to make a program that displays the full range of colours in a gradient. The intuitive way to do this would be to use some constant delta between subsequent colours - this would give you Linear RGB, right?
Let's simplify that to a gray ramp from 0 to 100%. If you simply put equal RGB values from 0 to 1.0 (0 to 255), the center of this ramp would NOT be half the luminance of the brightest end; it would only be approximately 18%. To make it 50%, a gamma of 0.4 must be applied to the values before writing them to the framebuffer. 50%^0.4 = ~0.76. So the framebuffer would hold 0.76 in the middle of the ramp, and when displayed, it would have 0.76^2.5 = ~50% luminance.
Now say I wanted to display this using Direct3D, would I need to use the D3DPRESENT_LINEAR_CONTENT flag in the presentation parameters to get it to display correctly? (as opposed to the case of say, displaying a picture taken with a digital camera)
Yes, it appears that Direct3D has a way to accept linear RGB values from a framebuffer and internally apply a 0.4 gamma to them before converting to video signal voltages. So with the D3DPRESENT_LINEAR_CONTENT flag, you could do a gray ramp as a simple 0 to 255 linear ramp and have it appear linear as well.

Note that the digital camera question depends entirely on how the camera encodes the image. If it's JPEG, then it already uses non-linear encoding, so it'll display correctly without this flag.
Now say I was to make a sprite in MS paint. My monitor is calibrated to use a gamma of 2.2, which would then be applied to the linear RGB values used in my image and influence my choice of colours. I save the image as a BMP and display it in my game, which uses Direct3D. Would I need to use the D3DPRESENT_LINEAR_CONTENT flag to get it to display the same way it did in paint? After all, the colour values I used were linear. Or am I misunderstanding this flag altogether?
It depends on the encoding of the file the paint program saves. Most likely, it'll just save the raw RGB values from the framebuffer, which are NOT linear. If you put these back in a PC's framebuffer, then they'll appear the same as they did to you. Images often include specification of the gamma and other color aspects, so an image loading library can convert to the PC's gamma transparently.

In general, everything you do on a PC is in one or more NON-linear RGB spaces. Only special applications use linear RGB, which is why D3D has a special flag to enable it (and why it has so many limitations about the source depth etc.).
blargg
Regular
Posts: 327
Joined: Thu Jun 30, 2005 1:54 pm
Location: USA
Contact:

Post by blargg »

Since the board is going to give me a frightening database error when trying to edit the above post, I'll just add this:

I also recommend Poynton's Gamma FAQ.
Verdauga Greeneyes
Regular
Posts: 347
Joined: Tue Mar 07, 2006 10:32 am
Location: The Netherlands

Post by Verdauga Greeneyes »

Thanks for your post, it clears up a lot of things. I'm still uncertain about a few things, but I'm not really sure what I want to ask. I'll have a look at those sources, too.
DOLLS (J) [!]
ZNES Developer
Posts: 215
Joined: Mon Aug 02, 2004 11:22 pm

Re: Gamma correction details

Post by DOLLS (J) [!] »

blargg wrote:So a SNES emulator on a PC should be able to just write SNES RGB values directly to the framebuffer and have them appear properly. Assuming I have no significant errors in my undersanding, that is.
Unless some kind of image processing that is tailored for linear gamma is applied to the output image, right?.

For instance, if a resampling filter is built to work on linear RGB space, it's recommended to compensate for non-linear gamma before feeding the result to the framebuffer.
blargg
Regular
Posts: 327
Joined: Thu Jun 30, 2005 1:54 pm
Location: USA
Contact:

Post by blargg »

If you want to do processing on linear RGB values, and all you've got are non-linear, you must convert them to linear, do the processing, then convert back to non-linear. For some processing, using non-linear RGB doesn't affect the result that much, so you can just skip the conversions.
Locked