How to save a .gif file with a new color table by using Visual C#

Article translations Article translations
Article ID: 319061 - View products that this article applies to.
This article was previously published under Q319061
Expand all | Collapse all

On This Page

SUMMARY

The CompuServe Graphics Interchange Format (GIF) is designed with a maximum of 256 colors that are arranged in a color table. To make common modifications to a .gif image file, you must change a custom color table. However, when System.Drawing 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 by using the GIF encoder, you must work with a 256-color copy of the Image that System.Drawing has not modified.

Understanding .gif Files That Are Written By System.Drawing and GDI+

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.

The System.Drawing namespace is primarily a wrapper around GDI+, therefore this article refers to the namespace as GDI+ unless behavior that is specific to the System.Drawing namespace is discussed, in which case, the term System.Drawing is used.

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 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 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.

Writing 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:
  1. Create a duplicate Bitmap object that is the same size as the source Image.
  2. Set the custom color table of the Bitmap object to the desired color table.
  3. Use the LockBits method to gain write access to the image bits of the copy.
  4. 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.
  5. Use UnLockBits to release the image bits.
  6. Use the Bitmap copy with the custom color table to save the Image to a file by using Save and the GIF encoder.
  7. 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 four parameters:
  • Any GDI+ Image object.
  • The file name for the target file.
  • The number of colors for the .gif file.
  • A flag that indicates whether a transparent color is needed.
The function first creates a Bitmap object that has the pixel format of PixelFormat.Format8BPPIndexed because that is the object that is saved to create the .gif file with nColors. Next, 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 native unmanaged code by using 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 ImageFormat.Gif object 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 use 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 Palette.Count property.

For more information about the GIF codec, see the "References" section of this article.

Unfortunately, the ColorPalette class of the System.Drawing namespace in the .NET Framework cannot be instantiated independent of a Bitmap object. This is a restriction that only the System.Drawing.Bitmap class imposes in the .NET Framework; however, to use the approach in this article, the Bitmap object must have a new ColorPalette object that contains fewer colors than the default 256 ColorPalette.

To achieve this, the sample code defines a function named GetColorPalette. This function creates a temporary Bitmap object that has a color depth close to the requested number of colors. The function then references the Palette property and returns it to the caller. This creates a new ColorPalette with one of several possible color counts: 256 colors, 16 colors, or two colors (monochrome). Although you can create color tables in .gif files that are smaller than 256 colors, color tables are limited to sizes that are a power of two.

When you limit color table sizes to a power of two, you minimize wasted space. The resulting color table in this example is 8 colors (2x2x2). With the sample code, the .gif file would be created with a color table of 16 colors because that is the smallest PixelFormat for a Bitmap that accomodates six colors.

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.

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

protected ColorPalette GetColorPalette( uint nColors )
{
    // Assume monochrome image.
    PixelFormat     bitscolordepth = PixelFormat.Format1bppIndexed;
    ColorPalette    palette;    // The Palette we are stealing
    Bitmap          bitmap;     // The source of the stolen palette

    // Determine number of colors.
    if (nColors > 2)
        bitscolordepth = PixelFormat.Format4bppIndexed;
    if (nColors > 16)
        bitscolordepth = PixelFormat.Format8bppIndexed;

    // Make a new Bitmap object to get its Palette.
    bitmap = new Bitmap( 1, 1, bitscolordepth );

    palette = bitmap.Palette;   // Grab the palette
    
    bitmap.Dispose();           // cleanup the source Bitmap

    return palette;             // Send the palette back
}

				
protected void SaveGIFWithNewColorTable(
    Image       image,
    string      filename,
    uint        nColors,
    bool        fTransparent
    )
{

    // GIF codec supports 256 colors maximum, monochrome minimum.
    if (nColors > 256)
        nColors = 256;
    if (nColors < 2)
        nColors = 2;

    // Make a new 8-BPP indexed bitmap that is the same size as the source image.
    int   Width = image.Width;
    int   Height = image.Height;

    // Always use PixelFormat8bppIndexed because that is the color
    // table-based interface to the GIF codec.
    Bitmap  bitmap = new Bitmap(Width, 
                            Height, 
                            PixelFormat.Format8bppIndexed); 

    // Create a color palette big enough to hold the colors you want.
    ColorPalette pal = GetColorPalette(nColors);

    // Initialize a new color table with entries that are determined
    // by some optimal palette-finding algorithm; for demonstration 
    // purposes, use a grayscale.
    for (uint i = 0; i < nColors; i++)
    {
        uint Alpha = 0xFF;                      // Colors are opaque.
        uint Intensity = i*0xFF/(nColors-1);    // Even distribution. 

        // The GIF encoder makes the first entry in the palette
        // that has a ZERO alpha the transparent color in the GIF.
        // 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, 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.
        pal.Entries[i] = Color.FromArgb( (int)Alpha, 
                                        (int)Intensity, 
                                        (int)Intensity, 
                                        (int)Intensity );
    }

    // Set the palette into the new Bitmap object.
    bitmap.Palette = pal;


    // 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 as the
    // image that you want to export. Or, try to
    // interpret the native pixel format of the image by using a LockBits
    // call. Use PixelFormat32BppARGB so you can wrap a Graphics  
    // around it.
    Bitmap BmpCopy = new Bitmap(Width, 
                            Height, 
                            PixelFormat.Format32bppArgb); 
    {
        Graphics g = Graphics.FromImage(BmpCopy);

        g.PageUnit = GraphicsUnit.Pixel;

        // Transfer the Image to the Bitmap
        g.DrawImage(image, 0, 0, Width, Height);

        // g goes out of scope and is marked for garbage collection.
        // Force it, just to keep things clean.
        g.Dispose();
    }

    // Lock a rectangular portion of the bitmap for writing.
    BitmapData  bitmapData;
    Rectangle   rect = new Rectangle(0, 0, Width, Height);

    bitmapData = bitmap.LockBits(
        rect,
        ImageLockMode.WriteOnly,
        PixelFormat.Format8bppIndexed);

    // Write to the temporary buffer that is 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.
    IntPtr pixels = bitmapData.Scan0;

    unsafe 
    { 
        // Get the pointer to the image bits.
        // This is the unsafe operation.
        byte *  pBits;
        if (bitmapData.Stride > 0)
            pBits = (byte *)pixels.ToPointer();
        else
            // If the Stide is negative, Scan0 points to the last 
            // scanline in the buffer. To normalize the loop, obtain
            // a pointer to the front of the buffer that is located 
            // (Height-1) scanlines previous.
            pBits = (byte *)pixels.ToPointer() + bitmapData.Stride*(Height-1);
        uint stride = (uint)Math.Abs(bitmapData.Stride);

        for ( uint row = 0; row < Height; ++row )
        {
            for ( uint col = 0; col < Width; ++col )
            {
                // Map palette indexes for a gray scale.
                // If you use some other technique to color convert,
                // put your favorite color reduction algorithm here.
                Color     pixel;    // The source pixel.

                // The destination pixel.
                // The pointer to the color index byte of the
                // destination; this real pointer causes this
                // code to be considered unsafe.
                byte *    p8bppPixel = pBits + row*stride + col;

                pixel = BmpCopy.GetPixel((int)col, (int)row);

                // Use luminance/chrominance conversion to get grayscale.
                // Basically, turn the image into black and white TV.
                // Do not calculate Cr or Cb because you 
                // discard 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.R *0.299) +
                    (pixel.G *0.587) +
                    (pixel.B *0.114);

                // Gray scale is an intensity map from black to white.
                // Compute the index to the grayscale entry that
                // approximates the luminance, and then round the index.
                // Also, constrain the index choices by the number of
                // colors to do, and then set that pixel's index to the 
                // byte value.
                *p8bppPixel = (byte)(luminance * (nColors-1)/255 +0.5);

            } /* end loop for col */ 
        } /* end loop for row */ 
    } /* end unsafe */ 

    // To commit the changes, unlock the portion of the bitmap.  
    bitmap.UnlockBits(bitmapData);

    bitmap.Save(filename, ImageFormat.Gif);

    // Bitmap goes out of scope here and is also marked for
    // garbage collection.
    // Pal is referenced by bitmap and goes away.
    // BmpCopy goes out of scope here and is marked for garbage
    // collection. Force it, because it is probably quite large.
    // The same applies to bitmap.
    BmpCopy.Dispose();
    bitmap.Dispose();

}

				

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 Microsoft Advisory Services. For more information, visit these Microsoft Web sites:

Microsoft Certified Partners - https://partner.microsoft.com/global/30000104

Microsoft Advisory Services - http://support.microsoft.com/gp/advisoryservice

For more information about the support options that are available and about how to contact Microsoft, visit the following Microsoft Web site:http://support.microsoft.com/default.aspx?scid=fh;EN-US;CNTACTMS

Troubleshooting

When you use this code to overwrite an existing file, you may see what seems to be a problem with the size of the resulting file. This occurs because of a bug in GDIPlus version 1.0 that does not truncate the file. For more information about Image file sizes, see the "References" section.

The sample code also uses the keyword unsafe. It does this because the code that moves the pixel definitions from the source Bitmap to the destination Bitmap uses a pointer to byte values. Real pointers can only be used in code that is marked unsafe and that is compiled with the /unsafe compiler option. Any code written this way has security impliciations. In particular, such managed code may have to request permissions and may fail to run if it is not trusted.

REFERENCES

For additional information about GIF codec pixel formats, click the article number below to view the article in the Microsoft Knowledge Base:
318343 INFO: GDI+ GIF Files are Saved Using the 8-BPP Format
For additional information about Image file sizes, click the article number below to view the article in the Microsoft Knowledge Base:
312119 PRB: Save Method of Bitmap Class Does Not Truncate File Size

For additional information about this technique using Microsoft Visual C++, click the article number below to view the article in the Microsoft Knowledge Base:
315780 HOWTO: Save a GIF with a New Color Table By Using GDI+

Glossary

BPP
bits per pixel - the number of bits used to represent the color value of each pixel in a digitized image; describes the physical layout of each pixel's color definition in an image. Common and generically referenced pixel formats include 32 BPP, 24 BPP, 16 BPP, 8 BPP, 4 BPP, 1 BPP.
8 BPP
The image pixel format that is expressed as eight bits contained in one byte. The byte value is used as an index into a color table that contains the actual red-green-blue (RGB) color definitions. Because the index is one byte in size, the color table is limited to 256 colors.
GIF
Graphics Interchange Format - a streamable image file format that was created by CompuServe.
RGB
Red, green and blue - each commonly expressed as a byte, and resulting in a color 3-byte triplet.

Properties

Article ID: 319061 - Last Review: December 11, 2006 - Revision: 4.7
APPLIES TO
  • Microsoft .NET Framework Software Development Kit 1.0
  • Microsoft Visual C# .NET 2002 Standard Edition
  • Microsoft Visual C# 2005 Express Edition
  • Microsoft Windows XP Professional
  • the operating system: Microsoft Windows XP 64-Bit Edition
Keywords: 
kbdswgdi2003swept kbhowtomaster KB319061

Give Feedback

 

Contact us for more help

Contact us for more help
Connect with Answer Desk for expert help.
Get more support from smallbusiness.support.microsoft.com