GIFLIB
| Language | C (usable from C++) |
|---|---|
| License | MIT |
| Repository | sourceforge.net/projects/giflib |
| Documentation | GIFLIB API documentation |
| Depends on | nothing |
| Read / write | yes / yes; GIF87a and GIF89a, all frames, all extension blocks |
GIFLIB reads and writes the GIF format at the block level. The high-level “slurp” API loads a whole file into a GifFileType structure (DGifSlurp) and writes one back (EGifSpew); a low-level API reads and writes record by record for streaming. Helper functions convert Graphic Control Extensions to and from a GraphicsControlBlock struct with the delay, disposal mode and transparent color.
GIFLIB works with palette indices only. It doesn’t composite animation frames, doesn’t convert pixels to RGB, and has no color quantizer for encoding RGB images. Version 6 changed EGifSpew to take an error code pointer; the rest of the API is the same as 5.x.
Installation#
# Debian / Ubuntu
sudo apt install libgif-dev
# Fedora
sudo dnf install giflib-devel
# macOS
brew install giflib
# Windows (vcpkg)
vcpkg install giflib
Include gif_lib.h and link with -lgif.
Basic usage: read all frames of a GIF#
#include <gif_lib.h>
#include <stdio.h>
int main(void) {
int err = 0;
GifFileType *gif = DGifOpenFileName("in.gif", &err);
if (!gif) {
fprintf(stderr, "open: %s\n", GifErrorString(err));
return 1;
}
if (DGifSlurp(gif) == GIF_ERROR) { // reads all frames, de-interlaces
fprintf(stderr, "decode: %s\n", GifErrorString(gif->Error));
DGifCloseFile(gif, &err);
return 1;
}
printf("canvas %d x %d, %d frames\n", gif->SWidth, gif->SHeight, gif->ImageCount);
for (int i = 0; i < gif->ImageCount; i++) {
SavedImage *frame = &gif->SavedImages[i];
GifImageDesc *d = &frame->ImageDesc;
GraphicsControlBlock gcb;
DGifSavedExtensionToGCB(gif, i, &gcb); // fills defaults when there's no GCB
ColorMapObject *cmap = d->ColorMap ? d->ColorMap : gif->SColorMap;
printf("frame %d: %dx%d at (%d,%d), delay %d0 ms, disposal %d, transparent %d\n",
i, d->Width, d->Height, d->Left, d->Top,
gcb.DelayTime, gcb.DisposalMode, gcb.TransparentColor);
// RGB of the top-left pixel of this frame
if (cmap) {
GifColorType c = cmap->Colors[frame->RasterBits[0]];
printf(" first pixel: %d %d %d\n", c.Red, c.Green, c.Blue);
}
}
DGifCloseFile(gif, &err);
return 0;
}
RasterBits holds Width * Height palette indices, top row first.
How to composite animation frames to RGBA#
Frames can be smaller than the canvas and depend on the previous frame. Keep an RGBA canvas and apply each frame’s disposal method:
#include <stdlib.h>
#include <string.h>
// canvas: SWidth*SHeight*4 bytes, cleared to transparent before frame 0
void draw_frame(GifFileType *gif, int i, unsigned char *canvas) {
SavedImage *f = &gif->SavedImages[i];
GifImageDesc *d = &f->ImageDesc;
ColorMapObject *cmap = d->ColorMap ? d->ColorMap : gif->SColorMap;
GraphicsControlBlock gcb;
DGifSavedExtensionToGCB(gif, i, &gcb);
if (!cmap) return;
for (int y = 0; y < d->Height; y++) {
for (int x = 0; x < d->Width; x++) {
int cx = d->Left + x, cy = d->Top + y;
if (cx >= gif->SWidth || cy >= gif->SHeight) continue;
int idx = f->RasterBits[y * d->Width + x];
if (idx == gcb.TransparentColor || idx >= cmap->ColorCount) continue;
unsigned char *p = &canvas[(cy * gif->SWidth + cx) * 4];
p[0] = cmap->Colors[idx].Red;
p[1] = cmap->Colors[idx].Green;
p[2] = cmap->Colors[idx].Blue;
p[3] = 255;
}
}
// after displaying: DISPOSE_BACKGROUND clears the frame rectangle to transparent,
// DISPOSE_PREVIOUS restores the canvas saved before this frame was drawn
}
How to write an animated GIF that loops forever#
Fill a GifFileType in memory and write it with EGifSpew. The Netscape loop block goes in the extension blocks of the first frame:
#include <gif_lib.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// frames[i] holds w*h palette indices into palette (256 colors)
int write_animation(const char *path, int w, int h, int nframes,
unsigned char **frames, GifColorType *palette, int delay_cs) {
int err = 0;
GifFileType *gif = EGifOpenFileName(path, false, &err);
if (!gif) {
fprintf(stderr, "open: %s\n", GifErrorString(err));
return 1;
}
gif->SWidth = w;
gif->SHeight = h;
gif->SColorResolution = 8;
gif->SBackGroundColor = 0;
gif->SColorMap = GifMakeMapObject(256, palette);
for (int i = 0; i < nframes; i++) {
SavedImage *img = GifMakeSavedImage(gif, NULL); // zeroed
img->ImageDesc.Width = w;
img->ImageDesc.Height = h;
img->RasterBits = malloc((size_t)w * h); // freed by EGifSpew
memcpy(img->RasterBits, frames[i], (size_t)w * h);
if (i == 0) {
unsigned char loop[3] = {1, 0, 0}; // sub-block 1, loop count 0 = forever
GifAddExtensionBlock(&img->ExtensionBlockCount, &img->ExtensionBlocks,
APPLICATION_EXT_FUNC_CODE, 11, (unsigned char *)"NETSCAPE2.0");
GifAddExtensionBlock(&img->ExtensionBlockCount, &img->ExtensionBlocks,
CONTINUE_EXT_FUNC_CODE, sizeof(loop), loop);
}
GraphicsControlBlock gcb = {DISPOSE_DO_NOT, false, delay_cs, NO_TRANSPARENT_COLOR};
EGifGCBToSavedExtension(&gcb, gif, i);
}
// writes the file, then frees everything and closes it (GIFLIB 6 signature)
if (EGifSpew(gif, &err) == GIF_ERROR) {
fprintf(stderr, "write: %s\n", GifErrorString(err));
return 1;
}
return 0;
}
GIFLIB 5.x uses EGifSpew(gif) without the error argument.
How to read GIF comments#
Comment extensions before a frame are in its ExtensionBlocks; those after the last frame are in gif->ExtensionBlocks:
for (int i = 0; i < gif->ImageCount; i++) {
SavedImage *f = &gif->SavedImages[i];
for (int k = 0; k < f->ExtensionBlockCount; k++) {
ExtensionBlock *eb = &f->ExtensionBlocks[k];
if (eb->Function == COMMENT_EXT_FUNC_CODE)
printf("comment: %.*s\n", eb->ByteCount, (char *)eb->Bytes);
}
}
Tips#
DGifOpen(userPtr, readFunc, &err)andEGifOpen(userPtr, writeFunc, &err)read from and write to memory or any stream.- Check
gif->SWidth * gif->SHeightand frame rectangles before allocating canvases; malformed files can claim huge sizes or frames outside the canvas. - The GIFLIB distribution includes
gifbuild(GIF to and from text),giftextandgifclrmpfor inspecting and editing files.