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 frequently 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 this 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 bits of the object 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.
back to the top
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. This 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 color table that you want.
- 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.
back to the top
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 sample function takes the following four parameters:
- Any GDI+ Image object.
- The file name for the destination 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.
Because the Visual Basic .NET Language cannot access memory buffers directly by
means of a pointer as in other languages, an intermediate step is necessary.
This code creates a working
Byte buffer in which are placed the color indexes by the color
conversion loops. This buffer is a managed data structure whose elements can be
manipulated by Visual Basic .NET code. After the image color index defitions
are placed in this buffer, interop services are used to call the
RtlMoveMemory Operating System function to transfer the contents of this
Byte buffer to the memory buffer that is referenced by the
Scan0 IntPtr of the
BitmapData object.
The
Win32API class that is defined in the sample code demonstrates how to
define and use an unmanaged operating system function by using .NET Framework
interop services.
RtlMoveMemory is declared by this class to be the
CopyArrayTo method. This method is defined to marshal the appropriate Visual
Basic .NET data types into the appropriate parameters for
RtlMoveMemory.
CopyArrayTo therefore takes the source
Byte array in the second parameter and copies the contents of the
Byte array to the memory that is pointed at by the value in the first
parameter that is defined as an
Int32. The first parameter to
CopyArrayTo is an
System.Int32 type because that is the value type that can be obtained from the
IntPtr class.
After you create the
Bitmap copy, use the
Save method with the
ImageFormat.Gif object to write the bitmap to the destination
file.
back to the top
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.
back to the top
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 completely 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.
back to the top
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 must
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
back to the top
Sample Code
Imports System.Drawing
Imports System.Drawing.Imaging
Imports System.Runtime.InteropServices
Public Class Form1
Inherits System.Windows.Forms.Form
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
Dim pic As Image = Image.FromFile("test.jpg")
SaveGIFWithNewColorTable(pic, "test.gif", 16, True)
End Sub
Class Win32API
<DllImport("KERNEL32.DLL", EntryPoint:="RtlMoveMemory", _
SetLastError:=True, CharSet:=CharSet.Auto, _
ExactSpelling:=True, _
CallingConvention:=CallingConvention.StdCall)> _
Public Shared Sub CopyArrayTo(<[In](), MarshalAs(UnmanagedType.I4)> ByVal hpvDest As Int32, <[In](), Out()> ByVal hpvSource() As Byte, ByVal cbCopy As Integer)
' Leave function empty - DLLImport attribute forwards calls to CopyArrayTo to
' RtlMoveMemory in KERNEL32.DLL.
End Sub
End Class
Private Function GetColorPalette(ByVal nColors As Integer) As ColorPalette
' Assume monochrome image.
Dim bitscolordepth As PixelFormat = PixelFormat.Format1BppIndexed
Dim palette As ColorPalette 'The Palette we are stealing
Dim bitmap As Bitmap 'The source of the stolen palette
' Determine number of colors.
If nColors > 2 Then
bitscolordepth = PixelFormat.Format4BppIndexed
End If
If (nColors > 16) Then
bitscolordepth = PixelFormat.Format8BppIndexed
End If
' 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
End Function
Private Sub SaveGIFWithNewColorTable(ByVal image As Image, ByVal filename As String, ByVal nColors As Integer, ByVal fTransparent As Boolean)
' GIF codec supports 256 colors maximum, monochrome minimum.
If (nColors > 256) Then
nColors = 256
End If
If (nColors < 2) Then
nColors = 2
End If
' Make a new 8-BPP indexed bitmap that is the same size as the source image.
Dim Width As Integer = image.Width
Dim Height As Integer = image.Height
' Always use PixelFormat8BppIndexed because that is the color
' table based interface to the GIF codec.
Dim bitmap As Bitmap = New Bitmap(Width, Height, PixelFormat.Format8BppIndexed)
' Create a color palette big enough to hold the colors you want.
Dim pal As ColorPalette = GetColorPalette(nColors)
' Initialize a new color table with entries that are determined
' by some optimal palette-finding algorithm; for demonstration
' purposes, use a grayscale.
Dim i As Integer
For i = 0 To nColors - 1
Dim Alpha As Integer = 255 ' Colors are opaque
Dim Intensity As Double = CDbl(i) * 255 / (nColors - 1) ' even distribution
' The GIF encoder makes the first entry in the palette
' with a ZERO alpha the transparent color in the GIF.
' Pick the first one arbitrarily, for demonstration purposes.
If (i = 0 And fTransparent) Then ' Make this color index...
Alpha = 0 ' Transparent
End If
' 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(Alpha, Intensity, Intensity, Intensity)
Next i
' 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. Next, 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.
Dim BmpCopy As Bitmap = New Bitmap(Width, Height, PixelFormat.Format32BppArgb)
Dim g As Graphics
g = Graphics.FromImage(BmpCopy)
g.PageUnit = GraphicsUnit.Pixel
' Transfer the Image to the Bitmap.
g.DrawImage(image, 0, 0, Width, Height)
' Force g to release its resources, namely BmpCopy.
g.Dispose()
' Lock a rectangular portion of the bitmap for writing.
Dim bitmapData As BitmapData
Dim rect As Rectangle = New Rectangle(0, 0, Width, Height)
bitmapData = bitmap.LockBits(rect, ImageLockMode.WriteOnly, PixelFormat.Format8BppIndexed)
' Write to a temporary buffer, and then copy to the buffer that
' LockBits provides. Copy the pixels from the source image in this
' loop. Because you want an index, convert RGB to the appropriate
' palette index here.
Dim pixels As IntPtr = bitmapData.Scan0
Dim bits As Byte() ' the working buffer
' Get the pointer to the image bits.
Dim pBits As Int32
If (bitmapData.Stride > 0) Then
pBits = pixels.ToInt32()
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 = pixels.ToInt32() + bitmapData.Stride * (Height - 1)
End If
Dim stride As Integer = Math.Abs(bitmapData.Stride)
ReDim bits(Height * stride) ' Allocate the working buffer.
Dim row As Integer
Dim col As Integer
For row = 0 To Height - 1
For col = 0 To Width - 1
' Map palette indices for a gray scale.
' Put your favorite color reduction algorithm here.
' If you use some other technique to color convert.
Dim pixel As Color ' The source pixel.
' The destination pixel.
Dim i8BppPixel As Integer = row * stride + col
pixel = BmpCopy.GetPixel(col, 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 should be integer math for performance;
' however, because GetPixel above is the slowest part of
' this loop, the expression is left as floating point
' for clarity.
Dim luminance As Double = (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.
Dim colorIndex As Double = Math.Round((luminance * (nColors - 1) / 255))
bits(i8BppPixel) = CByte(colorIndex)
' /* end loop for col */
Next col
' /* end loop for row */
Next row
' Put the image bits definition into the bitmap.
Win32API.CopyArrayTo(pBits, bits, Height * stride)
' 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 for bitmap.
BmpCopy.Dispose()
bitmap.Dispose()
End Sub
End Class
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:
back to the top
Troubleshooting
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.
back to the top