Skip to content
Larry Bank edited this page Mar 8, 2024 · 8 revisions

About GIF Decoding

GIF compression was created by CompuServe in the mid 1980's. They were basically the first internet service provider and wanted to make images less burdensome to slow 300 and 1200 baud connections. They chose LZW (Lempel-Ziv-Welsh) compression and supported palette color images of 1 to 8 bits per pixel (2 to 256 unique colors). Single frame GIF images are relatively easy to understand, a simple 2D image composed of symbols that reference a palette of 24-bit RGB colors. Soon after, Netscape Navigator added another layer on top of the humble GIF file - animation. Instead of creating a video-like codec, animated GIF uses transparent pixels to allow partial overpaint of sub-rectangles on the canvas (full size image). To properly render the current frame, a full sized canvas image must be kept in memory along with the new sub-frame to be merged on top. I originally created this library to leverage the framebuffer memory of SPI LCD displays so that microcontrollers with minimal RAM could decode animated GIFs. The way this works is that SPI displays allow the current write position (pixel cursor) to be placed anywhere, so as you decode each new line of image, transparent pixels just move the write cursor without changing the pixel color underneath. This allows a MCU to decode animated GIF images correctly without having to have enough memory to hold the full sized canvas. The down side of this technique is that every time you switch from "move cursor" to "write pixel" mode on the LCD, you add a delay. If your GIF animation has a lot of small groups of transparent pixels, this can have a dramatically bad effect on the performance. This latest release of AnimatedGIF adds code to simplify all of these different situations - if you have additional RAM, the work of merging the transparent pixels and creating fully-rendered output can be done within the library. I even added support for generating 1-bit output suitable for OLED displays like the SSD1306.

Public API

The AnimatedGIF class exposes a few simple methods detailed below. The more challenging part of using it is when you're using a new type of media/file and/or display and need to write your own callback functions. Those are documented below the API section.

This is the entire class definition for AnimatedGIF:

class AnimatedGIF {
  public:
    int open(uint8_t *pData, int iDataSize, GIF_DRAW_CALLBACK *pfnDraw);
    int open(char *szFilename, GIF_OPEN_CALLBACK *pfnOpen, GIF_CLOSE_CALLBACK *pfnClose, GIF_READ_CALLBACK *pfnRead, GIF_SEEK_CALLBACK *pfnSeek, GIF_DRAW_CALLBACK *pfnDraw);
    int openFLASH(uint8_t *pData, int iDataSize, GIF_DRAW_CALLBACK *pfnDraw);
    void close();
    void reset();
    void begin(uint8_t ucPaletteType);
    int playFrame(bool bSync, int *delayMilliseconds, void *pUser);
    int getCanvasWidth();
    int getCanvasHeight();
    int getLastError();
    int getInfo(GIFINFO *pInfo);
    int allocTurboBuf(GIF_ALLOC_CALLBACK *pfnAlloc);
    int freeTurboBuf(GIF_FREE_CALLBACK *pfnFree);
    void setTurboBuf(void *pTurboBuffer);
    int allocFrameBuf(GIF_ALLOC_CALLBACK *pfnAlloc);
    int freeFrameBuf(GIF_FREE_CALLBACK *pfnFree);
    void setFrameBuf(void *pFrameBuffer);
    uint8_t *getFrameBuf();
    uint8_t *getTurboBuf();
    int setDrawType(int iType);
    int getLoopCount();
    int getComment(char *destBuffer);

  private:
    GIFIMAGE _gif;
};

The class is basically a C++ wrapper of C code which does all of the actual work. The GIFIMAGE structure is kept private, but is passed as a pointer to the 'worker' functions. This makes it easy to lift out the C code if you want to use it in a pure C project. Here are details of each method:

open()
There are two versions of the open method - one for GIF images in memory and another for images coming from a file. Both return 1 for success, 0 for failure. The success or failure depends not just on the file successfully opening, but also on the GIF image file being parsed a little to gather the canvas size. Once this function returns successfully, you can use getCanvasWidth() and getCanvasHeight().

close()
You can call this any time to close the file handle associated with your GIF image. For GIF files coming from memory, this has no effect.

reset()
Use this function to reset a GIF animation to the first frame in the sequence. Each frame is displayed by calling playFrame(). It returns false when it has played the last frame in the sequence. If you want to start from the first frame again without closing and re-opening the file, call reset().

begin()
Call this before using the AnimatedGIF class methods. It clears the internal variables and allows you to choose whether to provide little-endian or big-endian RGB565 colors in the palette.

playFrame()
This method decodes the current frame, updates the internal file pointer to the start of the next frame and return 1 if there are additional frames to decode or 0 if the last frame has just been decoded. You can optionally have the AnimatedGIF code manage the timing of the frames by setting bSync to true. For example, if the GIF file frameDelay is set to 100ms (10 FPS) and it takes 20ms to decode+display the frame, bSync set to true will wait for the other 80ms to pass. If you want to draw frames as fast as possible or manage the timing in your code, set bSync to false. The delayMilliseconds parameter (optional - set to NULL to ignore) will return the number of milliseconds the frame is to be displayed. Each frame can have a unique delay value.
It's important to understand the each frame in a GIF animation only has to contain the changed pixels. In other words, if you want to jump forwards or backwards by N frames, you would have to re-decode the entire sequence from the start. This is why I don't offer a method to let you navigate the frames except in a forward direction, one at a time.

getCanvasWidth()
Returns the canvas width in pixels. This can be called any time after a successful open(). The canvas size determines the overall image size of an animated GIF, while each successive frame can be any size and only needs to be big enough to update the changed pixels for that frame.

getCanvasWidth()
Returns the canvas height in pixels.

allocFrameBuf(GIF_ALLOC_CALLBACK *pfnAlloc)
If your MCU/System has enough RAM to hold the entire canvas image, use this option to simplify and potentially speed up the drawing. By allocating a buffer, you allow the disposal options and transparent pixels to be drawn internally and then the draw function only needs to display the pixels instead of doing lower level operations on them. You provide a simple callback function that can allocate a block of memory.

freeFrameBuf(GIF_FREE_CALLBACK *pfnFree)
When you're finished decoding the current image, this will free the internal buffer.

getFrameBuf()
Returns the internal pointer to the canvas image. If you don't want to draw the GIF line by line, then after decoding a frame, this pointer will point to the start of the entire frame. You must successfully call allocFrameBuf() before using this.

setDrawType(int iDrawType)
Another option to simplify the GIF_DRAW_CALLBACK code. By default, the pixels passed to the draw callback are 'raw' in that they need to be translated through the current palette. Call this function with GIF_DRAW_COOKED to have the AnimatedGIF library do the palette translation for you and then the draw function will get pixels read to send to the display. This option is only available when allocFrameBuf() has been called successfully because it requires more memory.

getLastError()
Returns the last error or 0 (GIF_SUCCESS) if there was none. See AnimatedGIF.h for a list of error codes

getInfo(GIFINFO *pINFO)
Returns information about animated sequences (see structure below). This function may take a long time to return depending on the size of the file and if it's stored on a slow media such as a SDCard.

The GIFINFO structure:

typedef struct gif_info_tag
{
  int32_t iFrameCount; // total frames in file
  int32_t iDuration; // duration of animation in milliseconds
  int32_t iMaxDelay; // maximum frame delay
  int32_t iMinDelay; // minimum frame delay
} GIFINFO;

The Callback Functions

There are 7 callback functions defined by AnimatedGIF. If you're displaying a GIF from memory, then at a minimum you only need to provide a single function - GIF_DRAW_CALLBACK because the memory 'file' functions are provided for you inside the AnimatedGIF library code. The example sketches contain code which implements this sufficiently to display most GIFs properly on any LCD. Let's see what's involved in implementing it. Here's the function prototype:

typedef void (GIF_DRAW_CALLBACK)(GIFDRAW *pDraw);

The GIFDRAW structure:

typedef struct gif_draw_tag
{
  int iX, iY; // Corner offset of this frame on the canvas
  int y; // current line being drawn (0 = top line of image)
  int iWidth; // width of the current line
  uint8_t *pPixels; // 8-bit source pixels for this line
  uint16_t *pPalette; // little or big-endian RGB565 palette entries
  uint8_t ucTransparent; // transparent color
  uint8_t ucHasTransparency; // flag indicating the transparent color is in use
  uint8_t ucDisposalMethod; // frame disposal method
  uint8_t ucBackground; // background color
} GIFDRAW;

Hopefully you find the comments for each member variable pretty clear. The GIFDRAW callback is tasked with drawing the current scanline on the display. It's provided with the 8-bit pixel indexes and a palette to render those pixels as RGB565 output. It also must manage drawing (actually skipping) transparent pixels. In the example sketches you may see display library functions used which you're not accustomed to (e.g. setting the write position on the LCD). This is because working one pixel at a time on SPI LCD displays would slow the output down considerably. The only way to work efficiently on those serial displays is to write as many pixels as possible in each transaction. It also helps if your MCU can use DMA to write those pixels so that it doesn't have to wait for the each write transaction to finish. The examples which use my bb_spi_lcd library make use of DMA on a few systems. The code to manage DMA transactions hasn't been abstracted by the Arduino API, so it must be written uniquely for each MCU. Since we're trying to use as little RAM as possible on the target MCU, we need to make use of the memory built into the LCD controller. For transparent pixels, this presents a problem. We either need to have the pixels from the previous frame in our memory or "jump over" those pixels on the display. This causes quite a bit of extra delay if there are a lot of small runs of transparent pixels because switching to command mode and moving the LCD write pointer gums up the works. If you want to display frames as quickly as possible, create GIF sequences without transparent pixels.

The other 4 callback functions need to be implemented if you're working with files. They implement the standard functions of open, close, read, and seek:

typedef void * (GIF_OPEN_CALLBACK)(char *szFilename, int32_t *pFileSize);
typedef void (GIF_CLOSE_CALLBACK)(void *pHandle);
typedef int32_t (GIF_READ_CALLBACK)(GIFFILE *pFile, uint8_t *pBuf, int32_t iLen);
typedef int32_t (GIF_SEEK_CALLBACK)(GIFFILE *pFile, int32_t iPosition);
typedef void * (GIF_ALLOC_CALLBACK)(uint32_t iSize);
typedef void (GIF_FREE_CALLBACK)(void *buffer);

The challenge with the file system callbacks is that file access on Arduino is usually not associated with a simple file handle, but with a C++ class. In order to manage this in a generic way that will work for all possible systems, the AnimatedGIF class holds onto a void * pointer which you would like use to hold a class pointer. Let's look at the GIF_CLOSE_CALLBACK function I wrote for the Arduino SD library to understand how this is done:

void GIFCloseFile(void *pHandle)
{
  File *f = static_cast<File *>(pHandle);
  if (f != NULL)
    f->close();
}

The trick to making it all work is just to use static_cast to convert the void * into whatever class you need to access files.

See any of the sdcard example sketches for how to implement the other callback functions.

Clone this wiki locally