Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Manipulating GIF Color Tables

0.00/5 (No votes)
10 Nov 2018 1  
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:

HEX_Counter

I opened it in a HEX editor and gave it a bit of color - I'll explain below:

HEX_Dump

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:

  • 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 of GifParts. 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;

// 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 UserControls 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 GifParts in the list
  • Including new GifParts
  • 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

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here