The CompuServe Graphics Interchange Format (GIF) has design maximum of 256
colors arranged in a color table. Common modifications of a GIF image file
require the manipulation of a custom color table. However, when GdiPlus
edits an Image object and is then asked to save the image with the GIF
encoder, the resulting GIF file contains a halftone color table. To save an
Image with a custom color table using the GIF encoder, one must work with a
256 color copy of the Image that is unmodified by GdiPlus.
The GIF image file can express a maximum of only 256 colors. Since color is
a scarce resource in the GIF, optimizing those colors is a commonly
requested task. To effect an optimized color table, one needs the ability
to set any arbitrary custom color table in a GIF file.
When GdiPlus has modified an Image and then writes an image to a file using
the GIF encoder, it writes the file using a halftone palette to which the
Image object's bits have been color reduced. GdiPlus does a color
conversion from 32 bits-per-pixel (32 bpp) when it writes the image to the
file because all modifications to the image are done with GdiPlus' 32 bpp
Although GdiPlus supports the creation of Images and Bitmaps of various
pixel formats and can therefore load a GIF image, the use of the 32 bpp
graphics engine necessitates the conversion to 32 bpp when they are
modified by GdiPlus. However, an Image or Bitmap that is not modified by
GdiPlus retains its original pixel format and can be written to a file
using the Save() method with the appropriate encoder. This property forms
the basis for a technique that can save an Image to a GIF file with a
custom color table.
Since an unmodified Bitmap can be written with the GIF encoder such that
its color table is kept intact, the solution is to copy the image data from
an original Image object to a temporary Bitmap object. This temporary
Bitmap is created as an 8 bpp indexed Bitmap since that is the pixel format
that is used to save a GIF file. The Bitmap's color table is set using the
SetPalette() method and then the image definition is copied to the
temporary Bitmap. Once the temporary Bitmap has been created with a
duplicate definition, it can be saved with the GIF encoder using the Save()
method which will preserve the 8 bpp color table.
To write a GIF image to a file with a custom color table follow these steps
Create a duplicate Bitmap object of the same size as the source Image.
Set the custom color table of the Bitmap object to the desired color table.
Use the LockBits() method to gain write access to the image bits of the
Create an image definition in the copy by writing color indices to the
memory obtained from the LockBits() method that duplicate the pixels in the
Release the image bits via the UnLockBits() method.
Use the Bitmap copy with the custom color table to save the Image to a file
using the Save() method and the GIF encoder.
Release the Bitmap copy of the Image.
The following C++ sample code demonstrates this solution:
const WCHAR* filename,
const CLSID* clsidEncoder,
Status stat = GenericError;
// GIF codec supports 256 colors maximum
if (nColors > 256)
nColors = 256;
if (nColors < 2)
nColors = 2;
// make a new 8 bpp pixel indexed bitmap the size of the source image
DWORD dwWidth = pImage->GetWidth();
DWORD dwHeight = pImage->GetHeight();
// Always use PixelFormat8bppIndexed because that is the color table
// based interface to the GIF codec.
Bitmap bitmap(dwWidth, dwHeight, PixelFormat8bppIndexed);
stat = bitmap.GetLastStatus();
if (stat != Ok)
return stat; // no point in continueing
// allocate the new color table
dwSizeColorPalette = sizeof(ColorPalette); // size of core struct
dwSizeColorPalette += sizeof(ARGB)*(nColors-1); // the other entries
// Allocate some raw space to back our ColorPalette structure pointer
ColorPalette *ppal = (ColorPalette *)new BYTE[dwSizeColorPalette];
if (ppal == NULL) return OutOfMemory;
// initialize a new color table with entries that are determined by
// some optimal palette finding algorithm, for demonstration
// purposes just do a grayscale
ppal->Flags = PaletteFlagsHasAlpha;
ppal->Flags = 0;
ppal->Flags |= PaletteFlagsGrayScale;
ppal->Count = nColors;
for (UINT i = 0; i < nColors; i++)
int Alpha = 0xFF; // Colors are opaque by default
int Intensity = i*0xFF/(nColors-1); // even distribution
// The GIF encoder will make the first entry in the palette with a
// zero alpha the "transparent" color in the GIF.
// We pick the first one arbitrarily for demonstration purposes.
if ( i == 0 && fTransparent) // Make this color index...
Alpha = 0; // Transparent
// Create a gray scale for demonstration purposes
// Otherwise, one's favorite color reduction algorithm should be
// and an optimum palette for that algorithm generated here
// For example, a color histogram, or a median cut palette
ppal->Entries[i] = Color::MakeARGB( Alpha,
// set the palette into the new Bitmap object
// We want to use GetPixel below to pull out the color data of pImage
// since GetPixel isn't defined on an Image, make a copy in a Bitmap
// instead. So now make a new Bitmap the size of the image that
// you want to export. Otherwise, one could try to interpret the native
// pixel format of the image via a LockBits call.
// Use PixelFormat32bppARGB so we can wrap a Graphics around it.
Bitmap BmpCopy(dwWidth, dwHeight, PixelFormat32bppARGB);
stat = BmpCopy.GetLastStatus();
if (stat == Ok)
// Transfer the Image to the Bitmap
stat = g.DrawImage(pImage, 0, 0, dwWidth, dwHeight);
// g goes out of scope here and cleans up
if (stat != Ok)
delete  (LPBYTE) ppal;
ppal = NULL;
// Lock the whole of the bitmap for writting.
Rect rect(0, 0, dwWidth, dwHeight);
stat = bitmap.LockBits(
if (stat == Ok)
// Write to the temporary buffer provided by LockBits.
// One copies the pixels from the source image in this loop.
// Since we want an index, we convert RGB to the appropriate
// palette index here.
BYTE *pixels = (BYTE*)bitmapData.Scan0;
// top-down / bottom-up not relevant to this algorithm
UINT stride = abs(bitmapData.Stride);
for(UINT row = 0; row < dwHeight; ++row)
for(UINT col = 0; col < dwWidth; ++col)
// map palette indices for a gray scale
// one's favorite color reduction algorithm goes here
// if color converting using some other technique
UINT i8bppPixel = row * stride + col;
BmpCopy.GetPixel(col, row, &pixel);
// Use luminance/chrominance conversion to get grayscale.
// Basically - turn the image into black and white TV - YCrCb.
// We do not even bother to calculate Cr or Cb since we are
// throwing away the color anyway.
// Y = Red * 0.299 + Green * 0.587 + Blue * 0.114
// This expression should be integer math for performance.
// But, since GetPixel above is by far the slowest part of
// this loop, the expression is left as floating point
// for clarity.
double luminance = (pixel.GetRed() * 0.299) +
(pixel.GetGreen() * 0.587) +
(pixel.GetBlue() * 0.114);
// Gray scale is an intensity map from black to white
// Compute the index to the gray scale entry that
// luminance and round the index.
// Also, constrain the index choices by the number of colors
pixels[i8bppPixel] = (BYTE)(luminance * (nColors-1)/255 +0.5);
// Commit the changes by unlocking the portion of the bitmap.
stat = bitmap.UnlockBits(&bitmapData);
// if destination work was successful, see if the source was
if (stat == Ok) stat = BmpCopy.GetLastStatus();
// See if we were successful so far
if (stat == Ok)
// Write it out to the disk
stat = bitmap.Save(filename, clsidEncoder, NULL);
// Cleanup after
read more »