Manipulating GIF Color Tables





5.00/5 (21 votes)
Editing GIF colors without touching the image data within
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):
| |||||||||||||||
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):
| ||||||||||||||||||
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:
- 0x01 - Plain Text: This can be used to draw plain text onto an image - I haven't tried this though.
DataRegion Name Description 1 Options
This is always 12 bytes long and contains this information structure: Offset Length Name Description 0x000000
2
Left
The horizontal position of the text 0x000002
2
Top
The vertical position of the text 0x000004
2
Width
The width of the text field 0x000006
2
Height
The height of the text field 0x000008
1
CharWidth
The character width 0x000009
1
CharHeight
The character height 0x00000A
1
TextColor
The index of the text color 0x00000B
1
BGColor
The index of the background color 2-* Text
This is the text to draw - if longer than 255 bytes it is split into separate Data Regions - 0xF9 - Graphics Control: This can be used to add more options for the following image data:
DataRegion Name Description 1 Options This is always 4 bytes long and contains this information structure: Offset Length Name Description 0x000000 1 Options
Bit 0-2: Reserved
Bit 3-5: Disposal Method
Bit 6: Use User Input
Bit 7: Use Transparency
Disposal Methods:- 000: Not specified
- 001: Do not dispose
- 010: Restore to BG color
- 011: Restore to previous
- 1xx: Reserved
0x000001 2 DelayTime
The duration this frame should be displayed in 1/100 seconds 0x000003 1 TransColor
The index of the transparent color - irrelevant if Use Transparency is 0 - 0xFE - Comment: This can be used to insert a comment in the file - I haven't tried that though. I guess the whole Data Region data together makes the comment then
- 0xFF - Application: This can be used to store information for specific applications:
DataRegion Name Description 1 AppIdent
This should always be 11 bytes long. The first 8 bytes are the name of the application, the other 3 bytes should build a kind of authentification. 2-* Data
This is the application specific data. If the AppIdent is
NETSCAPE2.0
(like in the example) the GIF image becomes an animation and the application specific data consists of 3 bytes where the first is always 01 and the 2nd and 3rd bytes specify how many times the animation is to be repeated - 0 means forever.
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 ofGifPart
s. A loaded file is split into its parts and re-merged whenGetData()
is called so that each part can be extended or reduced individually.GifPartHeader
- The Header of the file - plus global color table if includedGifPartLzwImage
- A LWZ compressed image part with header and (if included) local color tableGifPartMetaData
- A general class implementing a general meta data part - the following implementations allow access to the individual options:GifPartMetaData.TextDraw
- A specified text meta partGifPartMetaData.GraphicsControl
- A specified graphics control meta partGifPartMetaData.Comment
- A specified comment meta partGifPartMetaData.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;
// You can open files
wrapper = new GifWrapper(@"C:\some\file.gif");
// streams
Stream stream = new FileStream(@"C:\some\file.gif", FileMode.Open);
wrapper = new GifWrapper(stream);
// or a byte array
byte[] data = File.ReadAllBytes(@"C:\some\file.gif");
wrapper = new GifWrapper(data);
// You can then loop through the parts
foreach (GifPart part in wrapper) {
// Then see what type of part it is
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
andGifPartGarbageData
GifPartHeaderCtrl
- Editable control for theGifPartHeader
, includes aGifColorTableCtrl
to edit ColorTablesGifPartLwzImageCtrl
- Editable control for theGifPartLwzImage
, includes aGifColorTableCtrl
to edit ColorTablesGifPartMetaApplicationCtrl
- Editable control for theGifPartMetaData.ApplicationControl
if theAppIdentifier
is NOT "NETSCAPE2.0
"GifPartMetaGraphicsControlCtrl
- Editable control for theGifPartMetaData.GraphicsControl
GifPartMetaLoopCtrl
- Editable control for theGifPartMetaData.ApplicationControl
if the AppIdentifier is "NETSCAPE2.0
"GifPartMetaTextCtrl
- Editable control for theGifPartMetaData.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 | ![]() |
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