Introduction
I was looking for some nice free animated GIFs for another project of mine. I found many but they didn't match my color theme so as they are free to modify I tried to change the color - that's easy enough for simple images using GIMP but when it comes to animated GIFs, it quicky becomes a time-consuming nightmare: one simple animation, 100 frames and with GIMP, you can only adjust the colors of one frame per time.
So I started searching for a good tool to do that for me: NOTHING! All tools I found didn't do as expected or totally messed up the result. And when transparency was involved, nothing worked. So I ended up taking a look into the GIF format and decided to reach my goal, it wasn't that complicated at all.
Background
So here - as on many useful websites I came across - I'll give a little introduction to the general GIF format. The core of GIF, the method to store images is a bitmap compressed with a GIF version of the LZW (Lempel-Ziv-Welch) algorithim. I didn't bother going into that because the colors are stored in separate color tables which are not compressed and easy to access. So, here is one image overview of the GIF container format:
Now, let's have a deeper look into the format using an animated GIF image as example:
I opened it in a HEX editor and gave it a bit of color - I'll explain below:
Region 1: Header Information
The first 13 bytes are always the header of the GIF file.
Offset | Length | style="width: 90px">Name | Description |
0x000000 | 6 | Identity &
Version | The first 3 bytes are always "GIF" as an identifier. Then follows the file version: 89a or 87a |
0x000006 | 2 | Width | The width of the image - the first byte is the least significant: "01 00" would be 1 as "00 01" would be 256 |
0x000008 | 2 | Height | The height of the image - the first byte is the least significant again |
0x00000A | 1 | Flags | This byte contains some options in its bits (most to least significant from left to right starting with index 0 - so 0 means 128 and 7 means 1):
Bits | Name | Description | 0 | UseGlobalColorTable | If set to 1, a global color table is used | 1-3 | ColorResolution | The number of bits used to describe a pixel | 4 | TableSorted | If set to 1, the global color table is sorted by ascending occurrence in the image data | 5-7 | GlobalColorTableSize | The size of the global color table. The number of colors in the table is calculated with 2^(N+1) - so 001 would stand for 4 colors |
|
0x00000B | 1 | BG Color | The index of the background color - irrelevant if no global color table is used |
0x00000C | 1 | Aspect Ratio | This is ignored nowadays |
Region 2: Global Color Table (Optional)
This is a simple list of colors, each 3 bytes in format RGB. "optional
" means that if the UseGlobalColorTable
flag is set to 0
, this does not exist and the image parts begin here. The byte count can be calculated as shown above with 3 * 2 ^ (GlobalColorTableSize + 1)
.
Region 3: Image Parts
After the header and - if used - after the global color table follows a list of image parts directly after each other.
Data Region
To go on, you have to understand data regions in a GIF file. The data region begins with a one byte length definition followed by that amount of data bytes. That pattern is repeated as long as the length definition is 0
, in which case the data region is ended.
Image Part Types
There are two major image part types which can be determined by the first byte:
Identifier | Name | Description |
0x2C | LWZ Image | This contains image data with its own structure |
0x21 | Meta Data | This contains different kinds of meta data - but all have the same general structure |
LWZ Image
The LWZ image structure is similar to the GIF file itself. There is header information and there can be a local color table in each LWZ image part. This is the structure with a relative offset:
Offset | Length | Name | Description |
0x000000 | 1 | Identifier | The LWZ image part identification 0x2C |
0x000001 | 2 | Left | The horizontal position of this image in the global image surface - least significant byte first |
0x000003 | 2 | Top | The vertical position of this image in the global image surface- least significant byte first |
0x000005 | 2 | Width | The width of this image part - least significant byte first |
0x000007 | 2 | Height | The height of this image part - least significant byte first |
0x000009 | 1 | Flags | This byte contains some options in its bits (most to least significant from left to right starting with index 0):
Bits | Name | Description | 0 | UseLocalColorTable | If set to 1, a local color table is used | 1 | Interlaced | Only used by the renderer | 2 | TableSorted | If set to 1, the local color table is sorted by ascending occurrence in the image data | 3-4 | Reserved | These are not used yet and always 0 | 5-7 | LocalColorTableSize | The size of the local color table. The number of colors in the table is calculated with 2^(N+1) again |
|
0x00000A | * | LocalColorTable | A simple list of 3 byte color definitions (see Global Color Table), if UseLocalColorTable is 0 , this section does not exist |
* | 1 | Minimum code size | The initial number of bits used for LZW codes in the image data |
* | * | Data | A Data Region containing the compressed image bitmap |
Meta Data
Meta data is used to control animation and image part properties which are not included in the LWZ image data. These are included in GIF version 89a only - but then that's almost every GIF image out there. All meta data types have the same structure with a relative offset:
Offset | Length | Name | Description |
0x000000 | 1 | Identifier | The Meta Data identification 0x21 |
0x000001 | 1 | SubType | The type of meta data |
0x000002 | * | Data | A Data Region containing the meta data |
There are 4 kinds of known meta data types - but theoretically, there could be any type:
Region 4: Termination
The last byte of a GIF image is always 0x3B.
Using the Code
The code is written in C# and included in a Visual Studio 2015 solution.
GIF Container Implementation
The main classes implementing the GIF container format are all in the file GifWrapper.cs:
GifWrapper
- Implements a GIF container as a List of GifPart
s. A loaded file is split into its parts and re-merged when GetData()
is called so that each part can be extended or reduced individually.
GifPartHeader
- The Header of the file - plus global color table if included GifPartLzwImage
- A LWZ compressed image part with header and (if included) local color table GifPartMetaData
- A general class implementing a general meta data part - the following implementations allow access to the individual options:
GifPartMetaData.TextDraw
- A specified text meta part GifPartMetaData.GraphicsControl
- A specified graphics control meta part GifPartMetaData.Comment
- A specified comment meta part GifPartMetaData.ApplicationData
- A specified application data part
GifPartTerminator
- A terminator part (containing the 0x3B byte) GifPartGarbageData
- File data from after the file terminator
Die code usage is based on the GifWrapper
class:
GifWrapper wrapper;
wrapper = new GifWrapper(@"C:\some\file.gif");
Stream stream = new FileStream(@"C:\some\file.gif", FileMode.Open);
wrapper = new GifWrapper(stream);
byte[] data = File.ReadAllBytes(@"C:\some\file.gif");
wrapper = new GifWrapper(data);
foreach (GifPart part in wrapper) {
if (part is GifPartHeader) {
Console.WriteLine("Size: {0}x{1}",
(part as GifPartHeader).Width, (part as GifPartHeader).Height);
}
}
The GifPartHeader
and GifPartLwzImage
each contain a property to access the color table. Each can be set to null
to delete the color table and with a List of Colors to set a new color table. The Use***ColorTable
and ColorTableSize
properties are automatically set with setting the color table.
Each property is implemented to directly change the underlying data.
User Interface
The main window is GifWrapperFrm
and implements:
- The part list with type icons
- The preview
- The part implementation pane
- File load / save and drag'n'drop
- Batch delay time changing
In the subfolder GifWrapperCtrls, there are individual UserControl
s implementing UIs for many types of GifPart
:
GifPartGeneralHexViewCtrl
- All parts containing just data bytes as content, only shows the data as HEX view without edit function: GifPartMetaData.Comment
, GifPartMetaData
and GifPartGarbageData
GifPartHeaderCtrl
- Editable control for the GifPartHeader
, includes a GifColorTableCtrl
to edit ColorTables GifPartLwzImageCtrl
- Editable control for the GifPartLwzImage
, includes a GifColorTableCtrl
to edit ColorTables GifPartMetaApplicationCtrl
- Editable control for the GifPartMetaData.ApplicationControl
if the AppIdentifier
is NOT "NETSCAPE2.0
" GifPartMetaGraphicsControlCtrl
- Editable control for the GifPartMetaData.GraphicsControl
GifPartMetaLoopCtrl
- Editable control for the GifPartMetaData.ApplicationControl
if the AppIdentifier is "NETSCAPE2.0
" GifPartMetaTextCtrl
- Editable control for the GifPartMetaData.TextDraw
- There is no control for
GifPartTerminator
Any change in the editable controls results in an instant reload of the preview image containing the changes except there was an exception in the change.
Invalid option values that result in a parser error of the GIF file result is a preview image containing the error message as string (see image to the right).
| |
Points of Interest
The GifPartLwzImage
contains a rudimentary constructor by Image
. This works by using the C# API, saving the image as GIF and extracting the needed GIF image parts into the new GifPartLwzImage
instance. So the effectiveness is low.
History
Missing improvements:
- Moving of
GifPart
s in the list - Including new
GifPart
s - Color table wide manipulation (brightness, contrast, hue, ...)
- Better validation of options
Version 1.1:
- Bug-fix: "Save as..." menu function could only overwrite existing files
- Added: Color transformation with brightness, contrast and color (hue) on color tables