Chapter 9: GIF Output
Contents
9.1 GIF Format Overview
Introduced by CompuServe in 1987, Graphics Interchange Format (GIF, pronounced Jiff) is widely used on the Web for sharp-edged images with a limited number of colors, such as navigation buttons, logos, graphs, charts and icons. GIF is based on LZW, a lossless compression algorithm ideally suited for simple graphics (but not photographs.)
A GIF image can contain up to 256 distinct colors from the 24-bit color set. Pixel colors in a GIF image are numbers between 0 and 255 which are indices to a color palette included with the image.
Any pixel of a GIF image can be assigned a "transparent", or see-through, color. This enables GIF images to take arbitrary shapes when appearing on a web page.
GIF is the only widely used graphics format that supports animation. A GIF image can contain multiple frames that are displayed one after another as a movie. To reduce overall image size, some frames are often made smaller than the image itself, and are displayed at an offset to only affect those portions of the image that need redrawing.
9.2 AspJpeg's GIF Output Support
Starting with version 2.0, AspJpeg includes a new object, appropriated called Gif, which provides a wide range of operations on GIF images. An instance of this object is obtained via the Jpeg.Gif property. The Gif object is completely autonomous, i.e. it is not affected by AspJpeg's other properties and methods.
A simple GIF image is created by calling the Gif.AddFrame method followed by some drawing routines, and saved via the Save method. Other saving routines available in the ASPJpeg object (SaveUnique, SendBinary and Binary) are supported by the Gif object as well. AddFrame requires 4 arguments: frame width and height, and horizontal and vertical offsets. For a single-frame image, the offset arguments are usually 0.
Most properties and methods of the Gif object operate on the current frame. Once a new frame is added, it becomes the current one. Another frame can be set current by setting the Gif.CurrentFrame property to the 1-based index of the desired frame. The total number of frames is returned via Gif.FrameCount. A frame can be removed via Gif.RemoveFrame.
The Gif object supports most of the drawing routines the Canvas object supports, such as PrintText, DrawLine, etc. Setting various drawing properties is somewhat streamlined compared to the Canvas object. For example, an equivalent of Jpeg.Canvas.Pen.Color is simply Gif.PenColor. Colors in the Gif object are always numbers in the range 0 to 255 which are indices within the current palette. Palette management is described in Section 9.5 below.
The following code sample creates a simple animated 5-frame drawing with some text and a pie chart:
Set Gif = Jpeg.Gif ' Obtain GIF management object
MarketShare = 6 ' initial market share
' create a 5-frame animated gif
For i = 1 to 5
Gif.AddFrame 300, 200, 0, 0 Set Jpeg = Server.CreateObject("Persits.Jpeg")
Set Gif = Jpeg.Gif ' Obtain GIF management object
MarketShare = 6 ' initial market share
' create a 5-frame animated gif
For i = 1 to 5
Gif.AddFrame 300, 200, 0, 0
Gif.PenColor = 10
Gif.BrushColor = 10
Gif.DrawBar 0, 0, 300, 200
Gif.PenColor = 201
Gif.FontFamily = "Courier"
Gif.PrintText 18, 15, "XYZ, Inc. Market Share"
Gif.PenColor = 210
Gif.PrintText 120, 50, 2002 + i
' Draw pie chart
Gif.PenColor = 0
Gif.BrushColor = 30
Gif.DrawPie 150, 130, 50, 0, MarketShare * 360 / 100
Gif.BrushColor = 20
Gif.DrawPie 150, 130, 50, MarketShare * 360 / 100, 360
Gif.PrintText 200, 100, MarketShare & "%"
' market share almost doubles every year!
MarketShare = MarketShare * 2 - 3
' increase delay on the last frame
if i = 5 then Gif.Delay = 300 ' 3 sec
Next
' Save
Gif.Save Server.MapPath("chart.gif")
Gif.PenColor = 10
Gif.BrushColor = 10
Gif.DrawBar 0, 0, 300, 200
Gif.PenColor = 201
Gif.FontFamily = "Courier"
Gif.PrintText 18, 15, "XYZ, Inc. Market Share"
Gif.PenColor = 210
Gif.PrintText 120, 50, 2002 + i
' Draw pie chart
Gif.PenColor = 0
Gif.BrushColor = 30
Gif.DrawPie 150, 130, 50, 0, MarketShare * 360 / 100
Gif.BrushColor = 20
Gif.DrawPie 150, 130, 50, MarketShare * 360 / 100, 360
Gif.PrintText 200, 100, MarketShare & "%"
' market share almost doubles every year!
MarketShare = MarketShare * 2 - 3
' increase delay on the last frame
if i = 5 then Gif.Delay = 300 ' 3 sec
Next
' Save
Gif.Save Server.MapPath("chart.gif")
IGif objGif;
objJpeg = new ASPJpeg();
objGif = objJpeg.Gif;
// initial market share of hypothetical XYZ company
int nMarketShare = 6;
// create a 5-frame animated gif
for( int i = 1; i <= 5; i++ )
{
objGif.AddFrame( 300, 200, 0, 0 );
objGif.PenColor = 10;
objGif.BrushColor = 10;
objGif.DrawBar( 0, 0, 300, 200 );
objGif.PenColor = 201;
objGif.FontFamily = "Courier";
objGif.PrintText( 18, 15, "XYZ, Inc. Market Share", Missing.Value );
objGif.PrintText( 120, 50, (2002 + i).ToString(), Missing.Value );
// Draw pie chart
objGif.PenColor = 0;
objGif.BrushColor = 30;
objGif.DrawPie( 150, 130, 50, 0, nMarketShare * 360 / 100 );
objGif.BrushColor = 20;
objGif.DrawPie( 150, 130, 50, nMarketShare * 360 / 100, 360 );
objGif.PenColor = 210;
objGif.PrintText( 200, 100, nMarketShare.ToString() + "%", Missing.Value );
// market share almost doubles every year!
nMarketShare = nMarketShare * 2 - 3;
// increase delay on the last frame
if( i == 5 )
objGif.Delay = 300; // 3 sec
}
// Save
objGif.Save( Server.MapPath("chart.gif") );
Click the links below to run this code sample:
9.3 GIF Image Resizing
The Gif object is capable of resizing animated GIF images while preserving their animation and transparency. Resizing is performed via the Gif.Resize image which accepts three optional arguments: new width, new height and a resizing algorithm. You must specify either a new width, new height or both. If only one dimension is specified, the other will be calculated automatically to preserve the original aspect ratio. The resizing algorithm is 0 (nearest-neighbor) by default. More information about the resizing algorithms supported by AspJpeg is available here.
The resized image above on the right was generated using the following code:
Gif.Open Server.MapPath("..\images\WalkingCat.gif")
' Resize to half the width, omit height
Gif.Resize Gif.Width / 2
' Save
Gif.Save Server.MapPath("WalkingCat_small.gif")
objGif.Open( Server.MapPath("..\\images\\WalkingCat.gif") );
// Resize to half the width, omit height
objGif.Resize( objGif.Width / 2, Missing.Value, Missing.Value );
// Save
objGif.Save( Server.MapPath("WalkingCat_small.gif") );
Click the links below to run this code sample:
Note that the file size of a resized animated image may be larger that the original image even though the pixel size is smaller (as in this example.) Also note that the 3rd argument to the Resize method (resizing algorithm) can be set to 1 to create higher-quality thumbnails, but some animated images may produce undesired "artifacts" in that case.
9.4 Using External Images as Frames
The Gif object is capable of converting RGB images such as JPEGs into 256-colors GIFs with only a minor loss in image quality. An arbitrary existing image can be added to a GIF as a new frame via the Gif.AddImage method. This method expects a populated instance of the ASPJpeg object as the first argument, and (X, Y)-offsets of this new frame within the GIF image being created. You must perform all desired operations over the ASPJpeg object (resizing, cropping, drawing, etc.) before passing it to the AddImage method. The image being added must be in the RGB color space.
The following snippet resizes a JPEG image and converts it to a single-frame GIF:
Set Gif = Jpeg.Gif
' Another instance of ASPJpeg object
Set Image = Server.CreateObject("Persits.Jpeg")
Image.Open "c:\images\picture.jpg"
Image.PreserveAspectRatio = True
Image.Width = 200
Gif.AddImage Image, 0, 0
' Save
Gif.Save "c:\images\picture.gif"
Because of the loss of quality and larger file size, converting a JPEG photograph to GIF is not beneficial, unless other GIF features are used, such as animation.
The code sample of Section 6.1 generates an image containing the thumbnails of several photographs shown side by side. Let's rewrite this application to generate an animated GIF showing the thumbnails in rotation instead:
Set Gif = Jpeg.Gif ' Obtain GIF management object
' Read images from Images directory of the installation
Dim FileNames(3)
FileNames(0) = "apple.jpg"
FileNames(1) = "clock.jpg"
FileNames(2) = "photo.jpg"
Path = Server.MapPath("../images")
' Stipulate output image size
Gif.Width = 100
Gif.Height = 100
For i = 0 To 2
Jpeg.Open Path & "\" & FileNames(i)
' Resize to inscribe in 100x100 square
Jpeg.PreserveAspectRatio = True
If Jpeg.OriginalWidth > 100 or Jpeg.OriginalHeight > 100 Then
If Jpeg.OriginalWidth > Jpeg.OriginalHeight Then
Jpeg.Width = 100
Else
Jpeg.Height = 100
End If
End If
Gif.AddImage Jpeg, (100 - Jpeg.Width) / 2, (100 - Jpeg.Height) / 2
Gif.DisposalMethod =2
Next
' Save
Gif.Save Server.MapPath("rotation.gif")
IGif objGif;
objJpeg = new ASPJpeg();
objGif = objJpeg.Gif;
// Read images from Images directory of the installation
String [] arrFileNames = new String[3];
arrFileNames[0] = "apple.jpg";
arrFileNames[1] = "clock.jpg";
arrFileNames[2] = "photo.jpg";
String strPath = Server.MapPath("../images");
// Stipulate output image size
objGif.Width = 100;
objGif.Height = 100;
for( int i = 0; i < 3; i++ )
{
objJpeg.Open( strPath + "\\" + arrFileNames[i] );
// Resize to inscribe in 100x100 square
objJpeg.PreserveAspectRatio = 1;
if(objJpeg.OriginalWidth>100 || objJpeg.OriginalHeight>100)
{
if( objJpeg.OriginalWidth > objJpeg.OriginalHeight )
objJpeg.Width = 100;
else
objJpeg.Height = 100;
}
objGif.AddImage( (ASPJpeg)objJpeg,
(100 - objJpeg.Width) / 2, (100 - objJpeg.Height) / 2 );
objGif.DisposalMethod = 2;
}
// Save
objGif.Save( Server.MapPath("rotation.gif") );
Click the links below to run this code sample:
Note that the overall GIF image size is dictated by the size of the first frame added, unless we explicitly specify it via Gif.Width and Gif.Height properties. In this application, we want the image to always be 100 x 100 regardless of the size and orientation of the thumbnails being added. Therefore, we have to explicitly specify the image size.
Note also that we set the DisposalMethod for each frame to 2, which means the canvas should be restored to the background color before the next frame is drawn. This property is 1 by default which means the previous frame needs to be left in place and the next frame is to be drawn on top of it. In our application, the default behavior is undesirable since all thumbnails are different sizes.
Converting true-color to 256-color images is a fairly complex process usually referred to as quantization. The speed and quality of the conversion is controlled by the property Gif.Quantization. Valid values for this property are 1 to 30, 1 being highest quality and slowest speed. The default value of 20 provides a reasonably good trade-off between quality and speed.
9.5 Palette Management
9.5.1 GIF Palette Overview
GIF is an indexed color format. Each pixel color is specified via an index pointing to an RGB entry in a palette. Each GIF image contains at least one palette. Usually, there is a single global palette which applies to each frame of the image. In some cases, a frame has its own local palette which takes precedence over the global palette within that frame but does not apply to any other frames. If each frame has its own local palette, a global palette is usually not present at all.
GIF format requires that a palette contain 2, 4, 8, 16, 32, 64, 128, or 256 colors. Each color entry in a palette contains exactly three bytes: the R, G, and B values.
9.5.2 Accessing and Modifying Palettes
The Gif object offers a number of properties and methods to access and modify the global and local palettes of an image.
To specify an entire palette (global or local) in a single step, the SetPalette method should be used. This method expects a Boolean flag indicating whether the palette is global (True) or local (False), and a Variant-packed array of numbers specifying the RGB values of the entire palette. The array must contain a valid number of colors (2, 4, 8, etc.) multiplied by 3. If the first argument is False (indicating a local palette) the image must already contain at least one frame, and the palette pertaining to the current frame will be affected.
The following code snippet sets the global palette to contain 4 colors: black (0, 0, 0), white (255, 255, 255), green (0, 255, 0) and yellow (255, 255, 0):
Gif.SetPalette True, Colors
{0,0,0, 255,255,255, 0,255,0, 255,255,0};
objGif.SetPalette( true, Colors );
To set or obtain the size of a palette, use the property PaletteSize which expects the same global/local flag as the SetPalette method. The size of a palette is the number of colors, not the total number of color components. Valid values are 2, 4, 8, ..., 256. Setting this property to 0 effectively removes the palette entirely. Examples of using this parameterized property are as follows:
N = Gif.PaletteSize( False )
int N = objGif.get_PaletteSize( false );
To set or get an individual color component of a palette, use the property PaletteItem which expects two parameters: the global/local flag and the address (0-based index) of the desired color component within the palette. The three RGB components of the 1st color have the addresses 0, 1 and 2, the 2nd color -- 3, 4, and 5, etc. The address parameter must be in the range [0, PaletteSize * 3 - 1].
The Gif object enables you to specify one of several built-in palettes via the SetStockPalette method. This method expects two arguments: the global/local flag, and the palette number. Currently, 3 stock palettes are available:
Palette #1: Web-safe colors (default palette, 216 actual colors, 40 reserved slots);
Palette #2: Standard HTML colors (16 colors);
Palette #3: Grayscale colors (256 colors).
Palette #1 is the default global palette in all new images created with the Gif object. This palette contains the standard Web-safe colors as described in the HTML specifications. There are 216 standard Web-safe colors, and the indices 216 to 255 are not used and set to black. These unused slots can be set to any arbitrary colors, if necessary.
The standard Web-safe color palette looks as follows:
0 | 1 | 2 | 3 | 4 | 5 | |
0 | 000000 | 000033 | 000066 | 000099 | 0000CC | 0000FF |
6 | 003300 | 003333 | 003366 | 003399 | 0033CC | 0033FF |
12 | 006600 | 006633 | 006666 | 006699 | 0066CC | 0066FF |
18 | 009900 | 009933 | 009966 | 009999 | 0099CC | 0099FF |
24 | 00CC00 | 00CC33 | 00CC66 | 00CC99 | 00CCCC | 00CCFF |
30 | 00FF00 | 00FF33 | 00FF66 | 00FF99 | 00FFCC | 00FFFF |
36 | 330000 | 330033 | 330066 | 330099 | 3300CC | 3300FF |
42 | 333300 | 333333 | 333366 | 333399 | 3333CC | 3333FF |
48 | 336600 | 336633 | 336666 | 336699 | 3366CC | 3366FF |
54 | 339900 | 339933 | 339966 | 339999 | 3399CC | 3399FF |
60 | 33CC00 | 33CC33 | 33CC66 | 33CC99 | 33CCCC | 33CCFF |
66 | 33FF00 | 33FF33 | 33FF66 | 33FF99 | 33FFCC | 33FFFF |
72 | 660000 | 660033 | 660066 | 660099 | 6600CC | 6600FF |
78 | 663300 | 663333 | 663366 | 663399 | 6633CC | 6633FF |
84 | 666600 | 666633 | 666666 | 666699 | 6666CC | 6666FF |
90 | 669900 | 669933 | 669966 | 669999 | 6699CC | 6699FF |
96 | 66CC00 | 66CC33 | 66CC66 | 66CC99 | 66CCCC | 66CCFF |
102 | 66FF00 | 66FF33 | 66FF66 | 66FF99 | 66FFCC | 66FFFF |
108 | 990000 | 990033 | 990066 | 990099 | 9900CC | 9900FF |
114 | 993300 | 993333 | 993366 | 993399 | 9933CC | 9933FF |
120 | 996600 | 996633 | 996666 | 996699 | 9966CC | 9966FF |
126 | 999900 | 999933 | 999966 | 999999 | 9999CC | 9999FF |
132 | 99CC00 | 99CC33 | 99CC66 | 99CC99 | 99CCCC | 99CCFF |
138 | 99FF00 | 99FF33 | 99FF66 | 99FF99 | 99FFCC | 99FFFF |
144 | CC0000 | CC0033 | CC0066 | CC0099 | CC00CC | CC00FF |
150 | CC3300 | CC3333 | CC3366 | CC3399 | CC33CC | CC33FF |
156 | CC6600 | CC6633 | CC6666 | CC6699 | CC66CC | CC66FF |
162 | CC9900 | CC9933 | CC9966 | CC9999 | CC99CC | CC99FF |
168 | CCCC00 | CCCC33 | CCCC66 | CCCC99 | CCCCCC | CCCCFF |
174 | CCFF00 | CCFF33 | CCFF66 | CCFF99 | CCFFCC | CCFFFF |
180 | FF0000 | FF0033 | FF0066 | FF0099 | FF00CC | FF00FF |
186 | FF3300 | FF3333 | FF3366 | FF3399 | FF33CC | FF33FF |
192 | FF6600 | FF6633 | FF6666 | FF6699 | FF66CC | FF66FF |
198 | FF9900 | FF9933 | FF9966 | FF9999 | FF99CC | FF99FF |
204 | FFCC00 | FFCC33 | FFCC66 | FFCC99 | FFCCCC | FFCCFF |
210 | FFFF00 | FFFF33 | FFFF66 | FFFF99 | FFFFCC | FFFFFF |
To obtain the index for a given color, add the numbers in the leftmost column and top row corresponding to this color.
Palette #2 contains the standard 16 HTML colors (alphabetically ordered by name), as follows:
Index | Color | Name | Index | Color | Name |
---|---|---|---|---|---|
0 | 00FFFF | aqua | 8 | 000080 | navy |
1 | 000000 | black | 9 | 808000 | olive |
2 | 0000FF | blue | 10 | 800080 | purple |
3 | FF00FF | fuchsia | 11 | FF0000 | red |
4 | 008000 | green | 12 | C0C0C0 | silver |
5 | 808080 | grey | 13 | 008080 | teal |
6 | 00FF00 | lime | 14 | FFFFFF | white |
7 | 800000 | maroon | 15 | FFFF00 | yellow |
Palette #3 contains 256 grayscale colors from RGB(0, 0, 0) to RGB(255, 255, 255) and is not shown here.
9.6 Transparency
Any single color index in a frame can be assigned to be transparent, or see-through. This is done via the property Gif.TranspColor. The following code snippet creates a new GIF image and assigns color index 216 (the first unused index in the default palette) to be transparent, and then fills the image with a transparent background:
Gif.TranspColor = 216
Gif.PenColor = 216
Gif.BrushColor = 216
Gif.DrawBar 0, 0, 100, 100
...
objGif.TranspColor = 216;
objGif.PenColor = 216;
objGif.BrushColor = 216;
objGif.DrawBar( 0, 0, 100, 100 );
...
To remove transparency from the current frame, the property Gif.TranspColorSet needs to be set to False.
9.7 Miscellaneous Features
9.7.1 Other Frame Management Methods
In addition to the Gif.AddFrame and Gif.RemoveFrame methods covered above, there are also Gif.Clear and Gif.MoveFrame methods.
The Clear method, which takes no arguments, simply removes all frames from the image. An image with no frames cannot be saved or drawn on.
MoveFrame takes two arguments: the original index and desired index. It moves the frame specified by the first argument to a new location specified by the 2nd argument. If the current frame is affected by the move, the CurrentFrame property will change to reflect the new position of the current frame. The current frame remains the same, albeit at a new location.
9.7.2 Other Animation Management Properties
The Gif.Loops property controls how many times the animation sequence runs before stopping. By default, this property is 0 which designates an infinite number of loops.
The Gif.Delay property affects the time delay of the current frame measured in 1/100 sec. By default, this property is set to 100 (1 sec) for every frame being added.
9.7.3 Saving Individual Frames
The property Gif.FrameToSave, if set to a non-zero value, instructs the Gif object to save only the frame specified by this property, and no other frames, when a Save method is called. This enables you to view every individual frame of an animated GIF. This feature was added mostly for debugging purposes.
The method Gif.FindClosestColor enables you to search a palette for the index of a color closest to a given RGB trio. The method expects the global/local flag and three RGB values. It returns an index in the specified palette which corresponds to the color closest to the specified RGB values.
The property Gif.DisposalMethod controls how frames in an animated GIF replace each other. The valid values are:
- Leave the current image in place and draw the next image on top of it. This is the default method.
- The canvas should be restored to the background color before the next image is rendered.
- The canvas should be restored to its previous state before the current image is drawn.