A gamma-corrected uniform palette (for Microsoft Windows)

Skip to main content (skip navigation menu)

A gamma-corrected uniform palette (for Microsoft Windows)


This paper proposes a general purpose, uniform, 256-colour palette to use in applications for Microsoft Windows. Several "common" or uniform palettes (for Windows applications) have already been proposed, notably the "Web Safe palette". The palette presented here improves on these earlier proposals on the following points:

Gray levels are important to better represent drawings and pencil sketches. Gray levels are also used to reduce the saturation of a colour by making a (dither) pattern of the colour and a gray level of the same brightness. (Dithering is not demonstrated at this page.)

There are two gamma values used in the uniform palette. Theoretically, the most correct value for gamma is 2.5. However, taking the bad adjustment of a typical computer monitor into account (most users set their monitor too bright, resulting in a serious black-level error), a gamma-correction factor of 2.0 is more appropriate. This is the value used for the gray scale ramp.

Initially, I also used a gamma of 2.0 for the 6×6×6 colour cube. When evaluating the first version of the proposed uniform palette, it turned out that it had too few saturated colours. Given the course subdivision of the colour cube, it turned out that it is better to optimize (or exaggerate) chromatic differences, at the cost of being less accurate in reproducing brightness. The new proposal uses a gamma factor of 1.5 for the colour cube.


A code snippet in C that populates a LOGPAL structure with the colours for the uniform palette appears below. The function finishes by creating a HPALETTE (palette handle) from the palette.

Creating a uniform palette
  HPALETTE MakeUniformPalette(void)
    /* the Windows' "static" colours */
    PALETTEENTRY sys_palette[20] = {
      {   0,   0,   0, 0}, {0x80,   0,   0, 0},   /* top 10 colours */
      {   0,0x80,   0, 0}, {0x80,0x80,   0, 0},
      {   0,   0,0x80, 0}, {0x80,   0,0x80, 0},
      {   0,0x80,0x80, 0}, {0xc0,0xc0,0xc0, 0},
      { 192, 220, 192, 0}, { 166, 202, 240, 0},
      { 255, 251, 240, 0}, { 160, 160, 164, 0},   /* bottom 10 colours */
      {0x80,0x80,0x80, 0}, {0xff,   0,   0, 0},
      {   0,0xff,   0, 0}, {0xff,0xff,   0, 0},
      {   0,   0,0xff, 0}, {0xff,   0,0xff, 0},
      {   0,0xff,0xff, 0}, {0xff,0xff,0xff, 0} };

    /* gamma-corrected values for Red/Green/Blue in the colour cube */
    BYTE colorlevel[6] = { 0, 87, 138, 181, 220, 255 };

    /* values for the gray scale ramp */
    BYTE graylevel[28] = { 47, 67, 82, 95, 106, 116, 125, 134, 142,
                           150, 157, 164, 171, 177, 183, 189, 195,
                           201, 206, 212, 217, 222, 227, 232, 237,
                           241, 246, 251 };

    struct {
      WORD palVersion;
      WORD palNumEntries;
      PALETTEENTRY palEntry[256];
    } logpal = { 0x300, 256 };

    int i;
    int r, g, b, entry;

    /* Basically, we construct a 6x6x6 colour cube (216 colours) and
     * add the Windows' static colours and a gray scale ramp. Eight of
     * the 20 static colours overlap with the 6x6x6 colour cube, so the
     * gray scale must fit 256 - (216+20-8) = 28 entries, without
     * overlapping any of the static colours or the colour cube.

    /* the static colours */
    memcpy(logpal.palEntry, sys_palette, 10*sizeof(PALETTEENTRY));
    memcpy(logpal.palEntry+246, sys_palette+10, 10*sizeof(PALETTEENTRY));

    /* the 6x6x6 colour cube (but skip the 8 corners of the cube) */
    for (r = 0, entry = 10; r < 6; r++)
      for (g = 0; g < 6; g++)
        for (b = 0; b < 6; b++)
          if (r != 0 && r != 5 || g != 0 && g != 5 || b != 0 && b != 5) {
            logpal.palEntry[entry].peRed   = colorlevel[r];
            logpal.palEntry[entry].peGreen = colorlevel[g];
            logpal.palEntry[entry].peBlue  = colorlevel[b];
          } /* if */
    ASSERT(entry == 10+216-8);  /* 10 static colours, 216 iterations,
                                 * 8 colours skipped */

    /* the gray scale */
    for (g = 0; g < 28; g++, entry++) {
      logpal.palEntry[entry].peRed   = graylevel[g];
      logpal.palEntry[entry].peGreen = graylevel[g];
      logpal.palEntry[entry].peBlue  = graylevel[g];
    } /* for */
    ASSERT(entry == 246);       /* should now have filled up to second
                                 * section of static colours */

    /* set the "no collapse" flag to create an identity palette */
    for (i = 10; i < 246; i++)
      logpal.palEntry[i].peFlags = (BYTE)PC_NOCOLLAPSE;

    return CreatePalette((LOGPALETTE *)&logpal);

Some notes on the code presented above:


Below is the famous Lena picture in RGB format and mapped to the uniform palette as created by the MakeUniformPalette() function presented above. To compare it to alternatives, the Lena picture is also mapped to the "Web Safe" palette and to an optimized palette. The RGB picture is in JPEG format, the 256 colour pictures are in CompuServe GIF format. None of the pictures has been dithered.

Lena source image
Original picture (RGB)
Lena in a gamma-corrected uniform palette
Gamma-corrected uniform palette
Lena in a 'Web Safe' palette
Web Safe palette
Lena in an optimized palette
Optimized palette

Note that you should be running in 24-bit RGB mode to properly view the differences in palette mappings. If you are running in 256 colour mode, the browser maps all palettes to a common palette, resulting in double loss.

The improvement of the proposed uniform palette over the "Web Safe" palette is subtle —or even disputable. In my opinion, the proposed uniform palette contains more subtle tints, especially in the lighter areas, and more pastel (low saturation) tints. While the (chromatic) contrast may be less, the source picture is reproduced more accurately (again, in my opinion). When using error diffusion dithering, less contrast between the palette entries makes the dither appear less coarse.

The easiest way to get the uniform palette proposed in this paper in your graphic editor is to save the appropriate picture to disk and to load it in your paint application of choice. Then, you can inspect the palette and, mostly, save it to disk in the native palette file format of the paint application.

Other considerations

Using uniform (256-colour) palette is a last resort: if at all possible, use an optimized palette. Note how the optimized palette results in a significantly better appearance of Lena. Even slightly optimized palettes are markedly better than no optimization at all.

Linear uniform palettes (as opposed to a gamma corrected uniform palette) are trivially dithered with a (dispersed dot) ordered dither and matrices for such ordered dithers have been well covered in literature. Gamma corrected uniform palettes require matrices that take the non-linearity of the axes' subdivision into account. Optimized palettes require quite different dithering methods, such as the (Floyd-Steinberg) error diffusion dithers and the Riemersma dither. These dithers are more complex than ordered dithers and, hence, do not lend themselves for real-time dithering.