• Recent Tutorials

  • Gamma Correction

    This article will talk about how you can apply gamma correction to your games or programs. The article covers Windows and X11 (using XFree86 extensions to X11). X11 covers Linux/Unix systems and works for the entire screen (as it's not possible to only apply it to your program). Gamma correction can help make some colors to appear brighter or darker. This is useful for CRT monitors whose colors get increasingly darker with time.

    1. Introduction

    For a better understanding of gamma correction I suggest reading this wikipedia entry:
    http://en.wikipedia.org/wiki/Gamma_correction

    This article does not cover the basics of Windows GDI nor X11. If you're unsure about the basics for your target platform, you should familiarize yourself with that platform before proceeding.

    2. Basics

    2.1 General

    For the systems discussed here it all boils down to similar operations. We usually have three arrays (red, green, blue) of 256 color values, called a gamma ramp, which we get from the graphics device or set to the graphics device.

    While we can adjust values for individual colors, we'll usually use a single factor, which in this article ranges from -1.0(dark) to 1.0(bright), where 0.0 is normal(default) value. However, while the system may have routines which also use a factor (instead of arrays), the range may be different. This is the case for X11, where the range is from 0.100 to 10.0 (1.0 being normal). Windows does not provide routines which use a factor, only ramps.

    It's important to store the original values, before we change anything, so that when our program finishes we can restore these values. This helps not to disturb whatever settings the user has for his desktop or graphics device. In this case you'll want to store the entire gamma ramp as the way we calculate gamma may differ from the system (or device).

    2.2 Structure

    The type of structure we'll be using for both platforms is the same. We have 3 arrays, each of 256 word elements. This can be represented as a single multidimensional array. Where array 0 is red, 1 green, and 2 blue.

    Code: [View]
    TYPE
       TGammaRamp = array[0..2, 0..255] of word;


    3. Windows


    3.1 Preparation

    According to MSDN the functionality is available since Windows 2000. I am unsure if this functionality is available in earlier Windows operating systems.

    The Windows functions we use mostly accept a pointer to a gamma ramp structure. Depending on your Pascal compiler, this may be passed on as a var parameter (hence, no pointer needed). Such is the case for Free Pascal.

    To set or get the gamma ramp we need to specify a device context handle to the Windows functions we use. The easiest way to obtain the device context handle is to use the GetDC() windows function, and specify 0 or NULL as the window handle (which returns the device context handle for the desktop).

    Code: [View]
    var
       dc: HDC;
    
    begin
      dc := GetHDC(0);
    end;


    3.2 Structure


    The structure where we store our values is consisted of three arrays, each having 256 word values. The arrays are for red, green and blue colors respectively. The structure is described in

    3.3 Setting the gamma ramp

    To set the gamma ramp, we use the SetDeviceGammaRamp() routine which takes a device context handle, and a pointer to the gamma ramp array (structure described in 3.1).

    Code: [View]
    SetDeviceGammaRamp(dc, ramp);
    3.4 Getting the gamma ramp

    To get the gamma ramp, we use the GetDeviceGammaRamp() routine which takes a device context handle, and a pointer to the gamma ramp array (structure described in 3.1).

    Code: [View]
    GetDeviceGammaRamp(dc, ramp);


    4. X11


    4.1 Preparation

    Under X11, for each function we call, we'll need to have a display opened. Here we'll refer to it as dpy.

    A simple way to open the default display is to call the XOpenDisplay()function with a nil pointer as the parameter. This will return a pointer to a display.

    Code: [View]
    var
       dpy: PDisplay = nil;
    
    begin
       dpy := XOpenDisplay(nil);
    end;
    Additionaly, you'll also need to specify the screen parameter for some functions. To get the default screen you can use the DefaultScreen() function to get the default screen for the specified display.

    Code: [View]
    screen := DefaultScreen(dpy);
    To use gamma functionality, we need the XF86VidMode extension for X11. This usually means including the xf86vmode unit into our program. But, doing just that does not guarantee that the extension is available.

    We need to check if the extension is available calling the XF86VidModeQueryExtension()function. This function will return a non-zero value if the extension is present. If it returns 0 then the extension is not available, and we cannot perform any gamma functionality.

    Code: [View]
    var
       event_base, error_base: longint;
    
    begin
       if(XF86VidModeQueryExtension(lnxDPY, @event_base, @error_base) <> 0) then
          writeln('We have xf86vmode');
    end;
    The event_base and error_base are longints which are written to provide a detail error condition, but can be ignored since we'll know if there was an error by the return value.

    Before the gamma set and get routines have any effect, we'll need to process(get) events from X11. You can use the XNextEvent() routine to achieve this. Since this is a standard part of any X11 application I will not go into detail on X11 event processing.

    All routines return a non-zero value (usually 1) on success, and 0 if they fail. In C any non-zero value means true and 0 means false, and X11 is C based.

    4.2 Structure

    There is no specific structure, as the functions expect a pointer to arrays of dword. However, three are always three arrays. One for red, green and blue. Though the functions accept also a size(longint) parameter you can safely assume that the size of the arrays is 256. Perhaps this might change when we go to 48-bit colors. Throughout the article we'll use the structure as described in section 2.2.

    You can always get the size of the gamma ramp by using XF86VidModeGetGammaRampSize(). To store arrays of different sizes will require you to use dynamic arrays.

    Code: [View]
    XF86VidModeGetGammaRampSize(dpy, 0, @size);
    Code: [View]
    for idx := 0 to 255 do begin
       v := round(idx*((gamma*0.5+0.5)*255+128));
       if(v> 65535) then v := 65535;
       ramp[0][idx] := word(v);
       ramp[1][idx] := word(v);
    4.3 Getting the gamma ramp

    To get the gamma ramp we'll use the XF86VidModeGetGammaRamp() function, which takes the display, screen, ramp size and pointers to red, green and blue arrays to which the values will be stored.

    Code: [View]
    XF86VidModeGetGammaRamp(dpy, screen, size, @ramp[0], @ramp[1], @ramp[2]);


    4.4 Setting the gamma ramp


    To get the gamma ramp we'll use the XF86VidModeSetGammaRamp() function. which takes the display, screen, ramp size and pointers to red, green and blue arrays from which the values will be read.

    Code: [View]
    XF86VidModeSetGammaRamp(dpy, screen, size, @ramp[0], @ramp[1], @ramp[2]);


    4.5 Alternative way


    XF86VidMode also provides an alternative, simpler, way to set or get the gamma values. This is what I use in my own code.

    First, the structure used contains three single precision floating point values for red, green and blue color.

    Code: [View]
    TYPE
       TGamma = record
          red, green, blue: single;
       end;
    The values for these range from 0.100 to 10.0.

    To get the gamma values use the XF86VidModeGetGamma() routine, and to set use XF86VidModeSetGamma(). Both functions take a display, a screen and a pointer to the gamma structure as parameters.

    Code: [View]
    XF86VidModeGetGamma(dpy, screen, @gamma);
    XF86VidModeSetGamma(dpy, screen, @gamma);


    5. Calculating gamma correction values


    Okay, we have a gamma correction factor (-1.0 to 1.0), but how to calculate the individual values for the gamma ramp structure? The maximum value that any color can have is 65535(bright), and lowest can be 0(dark). The normal value is in the middle (32767). If we have a factor of 1.0, then we want to have the brightest values, in case we have -1.0 then we want to have darkest values.

    Here is a sample code how to calculate the values. idx is the color index 0..255, v the word value we calculate, gamma is the gamma correction factor and ramp is the gamma ramp structure described in section 2.2.

    Code: [View]
    for idx := 0 to 255 do begin
       v := round(idx*((gamma*0.5+0.5)*255+128));
       if(v> 65535) then v := 65535;
       ramp[0][idx] := word(v);
       ramp[1][idx] := word(v);
       ramp[2][idx] := word(v);
    end;
    Since the value we calculate can easily go over 65535 we need to make sure we clamp it to 65535, which is why here v is not a word value (it's a dword). If we used v as a word, it could get a value which is less than 65535 due to overrun, even when we wanted to have the maximum value.

    6. Examples

    Provided in the attachments are demo programs for both X11 and Windows. They are small and demonstrate the gamma functionality in the simplest possible way.



    Last Update: 21.10.2010.

    by Dejan Boras (de_jean_7777)
    Comments 4 Comments
    1. chronozphere's Avatar
      chronozphere -
      Interesting. Never actually thought about incorporating that into my games. Good to have an article 'bout it.
    1. de_jean_7777's Avatar
      de_jean_7777 -
      Well, I probably would not have implemented it myself If I did not have a CRT monitor which benefits from this kind of feature. It's 5 years old and the dark colors are getting even darker, which is annoying. I always find it annoying when commercial games don't have a gamma correction feature, since sometimes a dark game can be too dark. This may not be a problem on LCDs, but it can be useful. It does not require a lot of code, so you can implement it pretty quickly. While this option is also available in the system, sometimes you want a different setting for your game, and the game to automatically adjust it.
    1. code_glitch's Avatar
      code_glitch -
      Im the kind of person to have always seen that as an annoying feature games have for 5 year olds to annoy you by setting the gamma of you games to extremes because they think it looks funny. Might just add that to prometheus if there is a need, I have mainly WXGA's though, but I can't speak for everyone *look up into attic at older machines gathering dust* Although I doubt I can do much graphics on a 486... hahaha.
    1. de_jean_7777's Avatar
      de_jean_7777 -
      I've improved the article a bit, cleared up some things for X11 ( I made an error, the range under X11 is 0.100 to 10.0 instead of 0.0 to 2.0). I've also added demo programs for both X11 and Windows to the attachments.
Comodo SSL