Chapter 3: Uploading Images

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.NET's GIF Output Support

AspJpeg.NET's GIF functionality is encapsulated in the JpegGif object which provides a wide range of operations on GIF images. An instance of this object is obtained via the JpegManager.Gif property. The JpegGif object is completely autonomous, i.e. it is not affected by AspJpeg.NET's other properties and methods.

A simple GIF image is created by calling the JpegGif.AddFrame method followed by some drawing routines, and saved via the Save method. Other saving routines available in the JpegImage object (SaveUnique, SendBinary and Binary) are supported by the JpegGif 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 JpegGif 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 JpegGif.CurrentFrame property to the 1-based index of the desired frame. The total number of frames is returned via JpegGif.FrameCount. A frame can be removed via JpegGif.RemoveFrame.

The JpegGif object supports most of the drawing routines the JpegCanvas object supports, such as PrintText, DrawLine, etc. Setting various drawing properties is somewhat streamlined compared to the Canvas object. For example, an equivalent of JpegImage.Canvas.Pen.Color is simply JpegGif.PenColor. Colors in the JpegGif 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:

<%@ Page Language="C#" debug="true" %>

<%@ Import Namespace="Persits.Jpeg"%>

<html>
<head>
<title>AspJpeg.NET User Manual Chapter 9 - GIF Animation</title>
<script runat="server" languge="C#">

void Page_Load( Object Source, EventArgs E)
{
JpegManager objJpeg = new JpegManager();

// Obtain GIF management object
JpegGif objGif = objJpeg.Gif;

// Font path
string strFont = objJpeg.WindowsDirectory + @"\Fonts\Cour.ttf";

// 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.FillRect(0, 0, 300, 200);

  objGif.PenColor = 201;
  objGif.FontSize = 20;
  objGif.PrintText("XYZ, Inc. Market Share", 18, 20, strFont);

  objGif.PrintText((2010 + i).ToString(), 120, 50, strFont);

  // Draw pie chart
  objGif.PenColor = 0;
  objGif.BrushColor = 30;
  objGif.FillSegment(150, 130, 50, 50, 0, nMarketShare*360/100, 0);
  objGif.BrushColor = 20;
  objGif.FillSegment(150, 130, 50, 50, nMarketShare*360/100, 360, 0);

  objGif.PenColor = 210;
  objGif.PrintText(nMarketShare.ToString() + "%", 200, 100, strFont);

  // 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
string strFilename = objGif.SaveUnique(Server.MapPath("chart.gif"));

OutputImage.ImageUrl = strFilename;
}
</script>
</head>

<form runat="server">
<asp:image runat="server" id="OutputImage"/>
</form>
</html>
<%@ Page Language="vb" debug="true" %>

<%@ Import Namespace="Persits.Jpeg"%>

<html>
<head>
<title>AspJpeg.NET User Manual Chapter 9 - GIF Animation</title>
<script runat="server" languge="vb">

Sub Page_Load(Source As Object, E As EventArgs)
Dim objJpeg As JpegManager = New JpegManager()

' Obtain GIF management object
Dim objGif As JpegGif = objJpeg.Gif
' Font path
Dim strFont As String = objJpeg.WindowsDirectory + "\Fonts\Cour.ttf"

' initial market share of hypothetical XYZ company
Dim nMarketShare As Integer = 6

' create a 5-frame animated gif
For i As Integer = 1 To 5
  objGif.AddFrame(300, 200, 0, 0)

  objGif.PenColor = 10
  objGif.BrushColor = 10
  objGif.FillRect(0, 0, 300, 200)

  objGif.PenColor = 201
  objGif.FontSize = 20
  objGif.PrintText("XYZ, Inc. Market Share", 18, 20, strFont)

  objGif.PrintText((2010 + i).ToString(), 120, 50, strFont)

  ' Draw pie chart
  objGif.PenColor = 0
  objGif.BrushColor = 30
  objGif.FillSegment(150, 130, 50, 50, 0, nMarketShare*360/100, 0)
  objGif.BrushColor = 20
  objGif.FillSegment(150, 130, 50, 50, nMarketShare*360/100, 360, 0)

  objGif.PenColor = 210
  objGif.PrintText(nMarketShare.ToString() + "%", 200, 100, strFont)

  ' market share almost doubles every year!
  nMarketShare = nMarketShare * 2 - 3

  ' increase delay on the last frame
  If i = 5 Then objGif.Delay = 300 ' 3 sec
Next

' Save
Dim strFilename As String =
objGif.SaveUnique(Server.MapPath("chart.gif"))

OutputImage.ImageUrl = strFilename
End Sub
</script>
</head>

<form runat="server">
<asp:image runat="server" id="OutputImage"/>
</form>
</html>

Click the links below to run this code sample:

9.3 GIF Image Resizing

The JpegGif object is capable of resizing animated GIF images while preserving their animation and transparency. Resizing is performed via the JpegGif.Resize image which accepts three arguments, 2 required and 1 optional: new width, new height and a resizing algorithm. The resizing algorithm is 0 (nearest-neighbor) by default. More information about the resizing algorithms supported by AspJpeg.NET is available here.

Original Image
Resized Image

The resized image above on the right was generated using the following code:

// Open and resize by 50%
objGif.Open(strPath);
objGif.Resize(objGif.Width / 2, objGif.Height / 2);

objGif.SaveUnique(Server.MapPath("WalkingCat_small.gif"));
' Open and resize by 50%
objGif.Open(strPath)
objGif.Resize(objGif.Width / 2, objGif.Height / 2)

objGif.SaveUnique(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 JpegImage 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 JpegImage 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:

JpegManager objJpeg = new JpegManager();
JpegGif objGif = objJpeg.Gif;

JpegImage objImage = objJpeg.OpenImage(@"c:\path\image.jpg");
objImage.PreserveAspectRatio = true;
objImage.Width = 200;

objGif.AddImage(objImage, 0, 0);

// Save
objGif.SaveUnique(@"c:\path\out.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:

<%@ Page Language="C#" debug="true" %>

<%@ Import Namespace="Persits.Jpeg"%>

<html>
<head>
<title>AspJpeg.NET User Manual Chapter 9 - Images as Frames</title>
<script runat="server" languge="C#">

void Page_Load( Object Source, EventArgs E)
{
JpegManager objJpeg = new JpegManager();

// Obtain GIF management object
JpegGif 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++ )
{
  JpegImage objImage =
  objJpeg.OpenImage( strPath + "\\" + arrFileNames[i] );

  // Resize to inscribe in 100x100 square
  objImage.PreserveAspectRatio = true;
  if(objImage.OriginalWidth > 100 || objImage.OriginalHeight > 100)
  {
    if( objImage.OriginalWidth > objImage.OriginalHeight )
      objImage.Width = 100;
    else
      objImage.Height = 100;
  }

  objGif.AddImage( objImage,
    (100 - objImage.Width) / 2, (100 - objImage.Height) / 2 );

  objGif.DisposalMethod = 2;
}

// Save
string strFilename = objGif.SaveUnique(Server.MapPath("picture.gif"));

OutputImage.ImageUrl = strFilename;
}
</script>
</head>

<form runat="server">
<asp:image runat="server" id="OutputImage"/>
</form>
</html>
<%@ Page Language="vb" debug="true" %>

<%@ Import Namespace="Persits.Jpeg"%>

<html>
<head>
<title>AspJpeg.NET User Manual Chapter 9 - Images as Frames</title>
<script runat="server" languge="vb">

Sub Page_Load(Source As Object, E As EventArgs)
Dim objJpeg As JpegManager = New JpegManager()

' Obtain GIF management object
Dim objGif As JpegGif = objJpeg.Gif

' Read images from Images directory of the installation
Dim arrFileNames(3) As String
arrFileNames(0) = "apple.jpg"
arrFileNames(1) = "clock.jpg"
arrFileNames(2) = "photo.jpg"

Dim strPath As String = Server.MapPath("../images")

' Stipulate output image size
objGif.Width = 100
objGif.Height = 100

For i As Integer = 0 To 2
  Dim objImage As JpegImage =
  objJpeg.OpenImage( strPath + "\\" + arrFileNames(i) )

  ' Resize to inscribe in 100x100 square
  objImage.PreserveAspectRatio = true
  if objImage.OriginalWidth>100 Or objImage.OriginalHeight>100 Then
    If objImage.OriginalWidth > objImage.OriginalHeight Then
      objImage.Width = 100
    Else
      objImage.Height = 100
    End If
  End If

  objGif.AddImage( objImage,
    (100 - objImage.Width) / 2, (100 - objImage.Height) / 2 )

  objGif.DisposalMethod = 2
Next

' Save
Dim strFilename =
objGif.SaveUnique(Server.MapPath("picture.gif"))

OutputImage.ImageUrl = strFilename
End Sub
</script>
</head>

<form runat="server">
<asp:image runat="server" id="OutputImage"/>
</form>
</html>

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 JpegGif.Width and JpegGif.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 JpegGif.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 JpegGif 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 byte 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):

...
byte[] Colors = new byte[] { 0, 0, 0,
  255, 255, 255,
  0, 255, 0,
  255, 255, 255 };

objGif.SetPalette(true, Colors);
...
...
Dim Colors() as Byte = { 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 methods SetPaletteSize and GetPaletteSize, respectively. These methods expect the same global/local flag as the SetPalette method as the first argument. SetPaletteSize also expects the palette size as the 2nd argument. GetPaletteSize returns the palette size. 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. For example:

...
objGif.SetPaletteSize(false, 256);

int N = objGif.GetPaletteSize( false );
...
...
objGif.SetPaletteSize(False, 256)

Dim N As Integer = objGif.GetPaletteSize( False )
...

To set or get an individual color component of a palette, use the methods SetPaletteItem and GetPaletteItem. Both expect the the global/local flag as the first argument. The 2nd argument is the address (0-based) 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, GetPaletteSize() * 3 - 1]. The 3rd argument of SetPaletteItem is the value for the specified color component to be set to. GetPaletteItem returns the value for the specified color component.

9.5.3 Stock Palettes

The JpegGif 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 JpegGif 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 JpegGif.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:

objGif.AddFrame( 100, 100, 0, 0 );
objGif.TranspColor = 216;

objGif.PenColor = 216;
objGif.BrushColor = 216;
objGif.FillRect( 0, 0, 100, 100 );
...
objGif.AddFrame( 100, 100, 0, 0 )
objGif.TranspColor = 216

objGif.PenColor = 216
objGif.BrushColor = 216
objGif.FillRect( 0, 0, 100, 100 )
...

To remove transparency from the current frame, the property JpegGif.TranspColorSet needs to be set to false.

9.7 Miscellaneous Features

9.7.1 Other Frame Management Methods

In addition to the JpegGif.AddFrame and JpegGif.RemoveFrame methods covered above, there are also JpegGif.Clear and JpegGif.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 JpegGif.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 JpegGif.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 JpegGif.FrameToSave, if set to a non-zero value, instructs the JpegGif 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 JpegGif.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 JpegGif.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.