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 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 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")
IASPJpeg objJpeg;
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.

Original Image
Resized Image

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 Jpeg = Server.CreateObject("Persits.Jpeg")
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 Jpeg = Server.CreateObject("Persits.Jpeg")
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")
IASPJpeg objJpeg;
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):

Colors = Array(0,0,0, 255,255,255, 0,255,0, 255,255,0)
Gif.SetPalette True, Colors
Object [] Colors = new Object[]
  {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:

Gif.PaletteSize( False ) = 256

N = Gif.PaletteSize( False )
objGif.set_PaletteSize( false, 256 );

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

9.5.3 Stock Palettes

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:

IndexColorNameIndexColorName
000FFFFaqua8000080navy
1000000black9808000olive
20000FFblue10800080purple
3FF00FFfuchsia11FF0000red
4008000green12C0C0C0silver
5808080grey13008080teal
600FF00lime14FFFFFFwhite
7800000maroon15FFFF00yellow

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.AddFrame 100, 100, 0, 0
Gif.TranspColor = 216

Gif.PenColor = 216
Gif.BrushColor = 216
Gif.DrawBar 0, 0, 100, 100
...
objGif.AddFrame( 100, 100, 0, 0 );
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.

9.7.4 Finding a Closest Color

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.

9.7.5 Disposal Methods

The property Gif.DisposalMethod controls how frames in an animated GIF replace each other. The valid values are:

  1. Leave the current image in place and draw the next image on top of it. This is the default method.
  2. The canvas should be restored to the background color before the next image is rendered.
  3. The canvas should be restored to its previous state before the current image is drawn.