MORE INFORMATION
The .gif image file can express a maximum of 256 colors. Because color is a scarce resource in the .gif file, optimizing those colors is a commonly requested task. To affect an optimized color table, you must be able to set any arbitrary custom color table in a .gif file.
After GDI+ modifies an
Image and then writes an image to a file by using the GIF encoder, GDI+ writes the file by using a halftone palette to which the
Image object's bits have been color reduced. GDI+ 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 made with the GDI+ 32-BPP graphics engine.
Although GDI+ 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 GDI+. However, an
Image or
Bitmap that is
not modified by GDI+ retains its original pixel format and can be written to a file by 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.
You can write an unmodified
Bitmap with the GIF encoder and keep the
Bitmap color table intact; therefore, you can use this method to save a .gif file with a new color table.
The method 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, which is the pixel format that is used to save a .gif file. The
Bitmap color table is set by using the
SetPalette method, and then the image definition is copied to the temporary
Bitmap. After you create the temporary
Bitmap with a duplicate definition, you can use the
Save method to save it with the GIF encoder, which preserves 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 that is 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 copy.
- Create an image definition in the copy by writing color indexes to the memory that is obtained from LockBits that duplicate the pixels in the original Image.
- Use UnLockBits to release the image bits.
- Use the Bitmap copy with the custom color table to save the Image to a file by using Save and the GIF encoder.
- Release the Bitmap copy of the Image.
Using the Sample Code
The sample code in this article demonstrates how to use
Bitmap.Save to write a .gif file with a custom color table of arbitrary size. The code is not optimized for performance because its purpose is for demonstration only. The best opportunities for optimization are in the pixel processing loops.
GetPixel is a convenient abstraction of the pixel format, but it is remarkably slow. The sample code would be much faster if you used
LockBits to access the pixel format directly. To increase the speed, do not use the
GetPixel method and the
Color class abstraction. To improve performance, rewrite the grayscale conversion by using integer math, rather than floating point.
The sample function takes the following five parameters:
- Any GDI+ Image object.
- The file name for the target file.
- The class identifier (CLSID) of the GIF encoder.
- The number of colors for the .gif file.
- A flag that indicates whether a transparent color is needed.
For more information about the GDI+
GetEncoderClsid function and to download the sample code, see the MSDN Help documentation at the following link:
The function first creates a
Bitmap object that has the pixel format of
PixelFormat8BPPIndexed because that is the object that is saved to create the .gif file with
nColors, and then a color palette with the custom colors is created. The .gif file obtains the size and specific entries for its color table from the
Bitmap object's
ColorPalette. The sample code creates a gray scale for demonstration purposes because that algorithm is easy to extend over various color table sizes.
To create the .gif file, you must initialize the 8-BPP
Bitmap object with the image definition that is to be written to the file. In the sample code, a central set of loops is used to color convert the incoming image to essentially the black and white TV color space.
For demonstration purposes, the source image pixels are accessed by means of the
GetPixel method of a
Bitmap object that is a copy of the source image. A
Bitmap copy is made because the
Image class does not implement the
GetPixel method.
You can use other techniques to access the pixels, such as direct access to the pixels by using the
LockBits method or interop with
Windows GDI DIB Sections. When you use the
BitBlt function to copy a bitmap from a Gdi+ HDC to a GDI DIB Section memory domain controller, the
GBitBlt functions uses the color matching abilities of GDI.
After you create the
Bitmap copy, use the
Save method with the provided GIF CLSID to write the bitmap to the target file.
GIF Files with Fewer Than 256 Colors
The GIF codec in GDI+ version 1.0 encodes only GDI+
Images that are 8 BPP. All other
Image formats are converted before encoding. This code uses the 8-BPP
Bitmap format to write .gif files that have fewer than 256 colors because the GIF codec recognizes 8-BPP
Bitmap objects that contain fewer than 256 colors by the
ColorPalette.Count property.
For more information about GIF codec pixel formats, see the "
References" section of this article.
The code in the processing loop that copies the image's pixel definitions to the 8-BPP
Bitmap takes into account the size of the palette when the code computes a pixel's index value. The GIF codec limits the size of the palette and restricts the image definition to index values that are compatible with the palette size (that is, the potential GIF color table), and can therefore create .gif files with fewer than 256 colors.
GIF Transparency
In the sample code, the
ColorPalette creation routine sets the first entry to be the GIF transparent color to demonstrate the use of the transparency feature. The code does this by setting the Alpha component of the
Color entry to ZERO. The sample code in this article is for demonstration purposes only, therefore, the transparency color is an arbitrary choice and may have unexpected results that depend entirely on the source
Image.
The GIF encoder identifies the first color in the
ColorPalette that has an Alpha value of ZERO as the transparent color. This means that the transparent color does not have to be the first entry in the
ColorPalette. It can be any one of the possible 256 colors in the palette, on the condition that all preceeding entries contain Alpha components with non-zero values. Any later entries with Alpha component values of ZERO are ignored. All entries that have non-zero Alpha components are considered opaque.
NOTE: when you use this code to overwrite an existing file, you might see a problem with the size of the resulting file. This occurs because of a bug in GDI+ version 1.0 that does not truncate the file.
For more information about Image file sizes, see the "
References" section of this article. Although the article referenced there discusses the use of the
Bitmap class in the
System.Drawing namespace of the .NET Framework, the topic applies to GDI+ because
System.Drawing is implemented by GDI+.
The GIF/LZW Licensing Issue
Microsoft has obtained a license from Unisys to use the .gif file format and other LZW technologies that are covered by the Unisys-owned U.S. and foreign patents in a number of Microsoft products. However, this license does not extend to third-party developers who use Microsoft development products or toolkits to develop applications. As a third-party developer, you need to determine whether you must obtain a license from Unisys to use the GIF format or the LZW technologies.
For additional information about LZW licenses and GIF, click the article number below
to view the article in the Microsoft Knowledge Base:
193543 INFO: Unisys GIF and LZW Technology License Information
Sample Code
Status SaveGIFWithNewColorTable(
Image *pImage,
const WCHAR* filename,
const CLSID* clsidEncoder,
DWORD nColors,
BOOL fTransparent
)
{
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 that is the same size as 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 continuing.
// Allocate the new color table.
DWORD dwSizeColorPalette;
dwSizeColorPalette = sizeof(ColorPalette); // Size of core structure.
dwSizeColorPalette += sizeof(ARGB)*(nColors-1); // The other entries.
// Allocate some raw space to back the ColorPalette structure pointer.
ColorPalette *ppal = (ColorPalette *)new BYTE[dwSizeColorPalette];
if (ppal == NULL) return OutOfMemory;
ZeroMemory(ppal, dwSizeColorPalette);
// Initialize a new color table with entries that are determined by
// some optimal palette finding algorithm; for demonstration
// purposes, just do a grayscale.
if (fTransparent)
ppal->Flags = PaletteFlagsHasAlpha;
else
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 makes the first entry in the palette with a
// zero alpha the "transparent" color in the GIF.
// For demonstration purposes, the first one is picked arbitrarily.
if ( i == 0 && fTransparent) // Make this color index...
Alpha = 0; // Transparent
// Create a gray scale for demonstration purposes.
// Otherwise, use your favorite color reduction algorithm
// 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,
Intensity,
Intensity,
Intensity );
}
// Set the palette into the new Bitmap object.
bitmap.SetPalette(ppal);
// Use GetPixel below to pull out the color data of Image.
// Because GetPixel isn't defined on an Image, make a copy in a Bitmap
// instead. Make a new Bitmap that is the same size of the image that
// you want to export. Otherwise, you might try to interpret the native
// pixel format of the image by using a LockBits call.
// Use PixelFormat32BppARGB so that you can wrap a Graphics around it.
Bitmap BmpCopy(dwWidth, dwHeight, PixelFormat32BppARGB);
stat = BmpCopy.GetLastStatus();
if (stat == Ok)
{
Graphics g(&BmpCopy);
// 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;
return stat;
}
// Lock the whole of the bitmap for writing.
BitmapData bitmapData;
Rect rect(0, 0, dwWidth, dwHeight);
stat = bitmap.LockBits(
&rect,
ImageLockModeWrite,
PixelFormat8BppIndexed,
&bitmapData);
if (stat == Ok)
{
// Write to the temporary buffer provided by LockBits.
// Copy the pixels from the source image in this loop.
// Because you want an index, convert RGB to the appropriate
// palette index here.
BYTE *pixels;
if (bitmapData.Stride > 0)
pixels = (BYTE*) bitmapData.Scan0;
else
// If the Stride is negative, Scan0 points to the last // scanline in the buffer.
// To normalize the loop, obtain the start of the buffer,
// which is located (Height-1) scanlines previous.
pixels = (BYTE*) bitmapData.Scan0 + bitmapData.Stride*(dwHeight-1);
UINT stride = abs(bitmapData.Stride);
// Top-down and bottom-up is not relevant to this algorithm.
for(UINT row = 0; row < dwHeight; ++row)
{
for(UINT col = 0; col < dwWidth; ++col)
{
// Map palette indexes for a grayscale.
// If you use some other technique to color convert,
// put your favorite color reduction algorithm here.
Color pixel;
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.
// You do not have to to calculate Cr or Cb because you
// throw away the color anyway.
// Y = Red * 0.299 + Green * 0.587 + Blue * 0.114
// This expression is best as integer math for performance,
// however, because GetPixel listed earlier is 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
// approximates the luminance, and then round the index.
// Also, constrain the index choices by the number of colors to do
pixels[i8BppPixel] = (BYTE)(luminance * (nColors-1)/255 +0.5);
}
}
// To commit the changes, unlock the portion of the bitmap.
stat = bitmap.UnlockBits(&bitmapData);
}
// If destination work was successful, see whether the source was successful.
if (stat == Ok) stat = BmpCopy.GetLastStatus();
// See whether the code has been successful to this point.
if (stat == Ok)
{
// Write it out to the disk.
stat = bitmap.Save(filename, clsidEncoder, NULL);
}
// Clean up after yourself.
delete [] (LPBYTE) ppal;
ppal = NULL;
// BmpCopy goes away on its own when it falls out of scope.
return stat;
}
About Sample Code
Microsoft provides programming examples for illustration only, without warranty either
expressed or implied, including, but not limited to, the implied warranties of
merchantability and/or fitness for a particular purpose. This article assumes
that you are familiar with the programming language being demonstrated and the
tools used to create and debug procedures. Microsoft support professionals can
help explain the functionality of a particular procedure, but they will not
modify these examples to provide added functionality or construct procedures to
meet your specific needs. If you have limited programming experience, you may
want to contact a Microsoft Certified Partner or the Microsoft fee-based
consulting line at (800) 936-5200. For more information about Microsoft Certified
Partners, please visit the following Microsoft Web site:
For more information about the support options that are available and about how to contact Microsoft, visit the following Microsoft Web site: