A slim, fast and header-only GIF loader written in C

Overview

gif_load

This is an ANSI C compatible animated GIF loader in a single header file of less than 300 lines of code (less than 200 without empty lines and comments). It defines 1 new struct and 1 new enum, and requires 1 function call to load a GIF. 'ANSI C compatible' means that it builds fine with -pedantic -ansi compiler flags but includes stdint.h which is unavailable prior to C99.

gif_load is free and unencumbered software released into the public domain, blah blah. See the header file for details.

There are no strict dependencies on the standard C library. The only external function used by default is realloc() (both for freeing and allocation), but it`s possible to override it by defining a macro called GIF_MGET(m,s,a,c) prior to including the header; m stands for a uint8_t*-typed pointer to the memory block being allocated or freed, s is the target block size, typed unsigned long, a is the value of the fifth parameter passed to GIF_Load() (mainly used to hold a pointer to some user-defined structure if need be; see below), typed void*, and c equals 0 on freeing and 1 on allocation. For example, GIF_MGET might be defined as follows if malloc() / free() pair is to be used instead of realloc():

#include <stdlib.h>
#define GIF_MGET(m,s,a,c) if (c) m = (uint8_t*)malloc(s); else free(m);
#include "gif_load.h"

Loading GIFs immediately from disk is not supported: target files must be read or otherwise mapped into RAM by the caller.

The main function that does the actual loading is called GIF_Load(). It requires a callback function to create the animation structure of any user-defined format. This function, further referred to as the 'frame writer callback', will be executed once every frame.

Aditionally, the main function accepts a second callback used to process GIF application-specific extensions, i.e. metadata; in the vast majority of GIFs no such extensions are present, so this callback is optional.

Both frame writer callback and metadata callback need 2 parameters:

  1. callback-specific data
  2. pointer to a struct GIF_WHDR that encapsulates GIF frame information (callbacks may alter any fields at will, as the structure passed to them is a proxy that is discarded after every call):
  • GIF_WHDR::xdim - global GIF width, always constant across frames (further referred to as 'ACAF'); [0; 65535]
  • GIF_WHDR::ydim - global GIF height, ACAF; [0; 65535]
  • GIF_WHDR::clrs - number of colors in the current palette (local palettes are not that rare so it may vary across frames, further referred to as 'MVAF'); {2; 4; 8; 16; 32; 64; 128; 256}
  • GIF_WHDR::bkgd - 0-based background color index for the current palette, ACAF (sic ACAF, as this index is set globally)
  • GIF_WHDR::tran - 0-based transparent color index for the current palette (or −1 when transparency is disabled), MVAF
  • GIF_WHDR::intr - boolean flag indicating whether the current frame is interlaced; deinterlacing it is up to the caller (see the examples below), MVAF
  • GIF_WHDR::mode - next frame (sic next, not current) blending mode, MVAF: [GIF_NONE:] no blending, mainly used in single-frame GIFs, functionally equivalent to GIF_CURR; [GIF_CURR:] leave the current image state as is; [GIF_BKGD:] restore the background color (or transparency, in case GIF_WHDR::tran ≠ −1) in the boundaries of the current frame; [GIF_PREV:] restore the last image whose mode differed from this one, functionally equivalent to GIF_BKGD when assigned to the first frame in a GIF; N.B.: if right before a GIF_PREV frame came a GIF_BKGD one, the state to be restored is before a certain part of the resulting image was filled with the background color, not after!
  • GIF_WHDR::frxd - current frame width, MVAF; [0; 65535]
  • GIF_WHDR::fryd - current frame height, MVAF; [0; 65535]
  • GIF_WHDR::frxo - current frame horizontal offset, MVAF; [0; 65535]
  • GIF_WHDR::fryo - current frame vertical offset, MVAF; [0; 65535]
  • GIF_WHDR::time - next frame delay in GIF time units (1 unit = 10 msec), MVAF; negative values are possible here, they mean that the frame requires user input to advance, and the actual delay equals −(time + 1) GIF time units: zero delay + user input = wait for input indefinitely, nonzero delay + user input = wait for either input or timeout (whichever comes first); N.B.: user input requests can be safely ignored, disregarding the GIF standard
  • GIF_WHDR::ifrm - 0-based index of the current frame, always varies across frames (further referred to as 'AVAF')
  • GIF_WHDR::nfrm - total frame count, negative if the GIF data supplied is incomplete, ACAF during a single GIF_Load() call but may vary across GIF_Load() calls
  • GIF_WHDR::bptr - [frame writer:] pixel indices for the current frame, ACAF (it is only the pointer address that is constant; the pixel indices stored inside = MVAF); [metadata callback:] app metadata header (8+3 bytes) followed by a GIF chunk (1 byte designating length L, then L bytes of metadata, and so forth; L = 0 means end of chunk), AVAF
  • GIF_WHDR::cpal - the current palette containing 3 uint8_t values for each of the colors: R for the red channel, G for green and B for blue; this pointer is guaranteed to be the same across frames if and only if the global palette is used for those frames (local palettes are strictly frame-specific, even when they contain the same number of identical colors in identical order), MVAF

Neither of the two callbacks needs to return a value, thus having void for a return type.

GIF_Load(), in its turn, needs 6 parameters:

  1. a pointer to GIF data in RAM
  2. GIF data size; may be larger than the actual data if the GIF has a proper ending mark
  3. a pointer to the frame writer callback
  4. a pointer to the metadata callback; may be left empty
  5. callback-specific data
  6. number of frames to skip before executing the callback; useful to resume loading the partial file

Partial GIFs are also supported, but only at a granularity of one frame. For example, if the file ends in the middle of the fifth frame, no attempt would be made to recover the upper half, and the resulting animation will only contain 4 frames. When more data is available the loader might be called again, this time with the skip parameter equalling 4 to skip those 4 frames. Note that the metadata callback is not affected by skip and gets called again every time the frames between which the metadata was written are skipped.

The return value of the function above, if positive, equals the total number of frames in the animation and indicates that the GIF data stream ended with a proper termination mark. Negative return value is the number of frames loaded per current call multiplied by −1, suggesting that the GIF data stream being decoded is still incomplete. Zero, in its turn, means that the call could not decode any more frames.

gif_load is endian-aware. If the target machine can be big-endian the user has to determine that manually and add #define GIF_BIGE 1 to the source prior to the header being included if that`s the case, or otherwise define the endianness to be used (0 = little, 1 = big), e.g. by declaring a helper function and setting GIF_BIGE to expand into its call, or by passing it as a compiler parameter (e.g. -DGIF_BIGE=1 for GCC / Clang). Although GIF data is little-endian, all multibyte integers passed to the user through long-typed fields of GIF_WHDR have correct byte order regardless of the endianness of the target machine, provided that GIF_BIGE is set correctly. Most other data, e.g. pixel indices of a frame, consists of single bytes and thus does not require endianness correction. One notable exception is GIF application metadata which is passed as the raw chunk of bytes (for details see the description of GIF_WHDR::bptr provided above), and then it`s the callback`s job to parse it and decide whether to decode and how to do that.

There is a possibility to build gif_load as a shared library. GIF_EXTR is a convenience macro to be defined so that the GIF_Load() function gets an entry in the export table of the library. See the Python example for further information.

C / C++ usage example

Here is an example of how to use GIF_Load() to transform an animated GIF file into a 32-bit uncompressed TGA. For the sake of simplicity all frames are concatenated one below the other and no attempt is made to keep all of them if the resulting height exceeds the TGA height limit which is 0xFFFF.

#ifdef _MSC_VER /** MSVC is definitely not my favourite compiler... >_< **/ #pragma warning(disable:4996) #include #else #include #endif #ifndef O_BINARY #define O_BINARY 0 #endif #pragma pack(push, 1) typedef struct { void *data, *pict, *prev; unsigned long size, last; int uuid; } STAT; /** #pragma avoids -Wpadded on 64-bit machines **/ #pragma pack(pop) void Frame(void*, struct GIF_WHDR*); /** keeps -Wmissing-prototypes happy **/ void Frame(void *data, struct GIF_WHDR *whdr) { uint32_t *pict, *prev, x, y, yoff, iter, ifin, dsrc, ddst; uint8_t head[18] = {0}; STAT *stat = (STAT*)data; #define BGRA(i) ((whdr->bptr[i] == whdr->tran)? 0 : \ ((uint32_t)(whdr->cpal[whdr->bptr[i]].R << ((GIF_BIGE)? 8 : 16)) \ | (uint32_t)(whdr->cpal[whdr->bptr[i]].G << ((GIF_BIGE)? 16 : 8)) \ | (uint32_t)(whdr->cpal[whdr->bptr[i]].B << ((GIF_BIGE)? 24 : 0)) \ | ((GIF_BIGE)? 0xFF : 0xFF000000))) if (!whdr->ifrm) { /** TGA doesn`t support heights over 0xFFFF, so we have to trim: **/ whdr->nfrm = ((whdr->nfrm < 0)? -whdr->nfrm : whdr->nfrm) * whdr->ydim; whdr->nfrm = (whdr->nfrm < 0xFFFF)? whdr->nfrm : 0xFFFF; /** this is the very first frame, so we must write the header **/ head[ 2] = 2; head[12] = (uint8_t)(whdr->xdim ); head[13] = (uint8_t)(whdr->xdim >> 8); head[14] = (uint8_t)(whdr->nfrm ); head[15] = (uint8_t)(whdr->nfrm >> 8); head[16] = 32; /** 32 bits depth **/ head[17] = 0x20; /** top-down flag **/ write(stat->uuid, head, 18UL); ddst = (uint32_t)(whdr->xdim * whdr->ydim); stat->pict = calloc(sizeof(uint32_t), ddst); stat->prev = calloc(sizeof(uint32_t), ddst); } /** [TODO:] the frame is assumed to be inside global bounds, however it might exceed them in some GIFs; fix me. **/ pict = (uint32_t*)stat->pict; ddst = (uint32_t)(whdr->xdim * whdr->fryo + whdr->frxo); ifin = (!(iter = (whdr->intr)? 0 : 4))? 4 : 5; /** interlacing support **/ for (dsrc = (uint32_t)-1; iter < ifin; iter++) for (yoff = 16U >> ((iter > 1)? iter : 1), y = (8 >> iter) & 7; y < (uint32_t)whdr->fryd; y += yoff) for (x = 0; x < (uint32_t)whdr->frxd; x++) if (whdr->tran != (long)whdr->bptr[++dsrc]) pict[(uint32_t)whdr->xdim * y + x + ddst] = BGRA(dsrc); write(stat->uuid, pict, sizeof(uint32_t) * (uint32_t)whdr->xdim * (uint32_t)whdr->ydim); if ((whdr->mode == GIF_PREV) && !stat->last) { whdr->frxd = whdr->xdim; whdr->fryd = whdr->ydim; whdr->mode = GIF_BKGD; ddst = 0; } else { stat->last = (whdr->mode == GIF_PREV)? stat->last : (unsigned long)(whdr->ifrm + 1); pict = (uint32_t*)((whdr->mode == GIF_PREV)? stat->pict : stat->prev); prev = (uint32_t*)((whdr->mode == GIF_PREV)? stat->prev : stat->pict); for (x = (uint32_t)(whdr->xdim * whdr->ydim); --x; pict[x - 1] = prev[x - 1]); } if (whdr->mode == GIF_BKGD) /** cutting a hole for the next frame **/ for (whdr->bptr[0] = (uint8_t)((whdr->tran >= 0)? whdr->tran : whdr->bkgd), y = 0, pict = (uint32_t*)stat->pict; y < (uint32_t)whdr->fryd; y++) for (x = 0; x < (uint32_t)whdr->frxd; x++) pict[(uint32_t)whdr->xdim * y + x + ddst] = BGRA(0); #undef BGRA } int main(int argc, char *argv[]) { STAT stat = {0}; if (argc < 3) write(1, "arguments: .gif .tga (1 or more times)\n", 48UL); for (stat.uuid = 2, argc -= (~argc & 1); argc >= 3; argc -= 2) { if ((stat.uuid = open(argv[argc - 2], O_RDONLY | O_BINARY)) <= 0) return 1; stat.size = (unsigned long)lseek(stat.uuid, 0UL, 2 /** SEEK_END **/); lseek(stat.uuid, 0UL, 0 /** SEEK_SET **/); read(stat.uuid, stat.data = realloc(0, stat.size), stat.size); close(stat.uuid); unlink(argv[argc - 1]); stat.uuid = open(argv[argc - 1], O_CREAT | O_WRONLY | O_BINARY, 0644); if (stat.uuid > 0) { GIF_Load(stat.data, (long)stat.size, Frame, 0, (void*)&stat, 0L); stat.pict = realloc(stat.pict, 0L); stat.prev = realloc(stat.prev, 0L); close(stat.uuid); stat.uuid = 0; } stat.data = realloc(stat.data, 0L); } return stat.uuid; }">
#include "gif_load.h"
#include <fcntl.h>
#ifdef _MSC_VER
    /** MSVC is definitely not my favourite compiler...   >_<   **/
    #pragma warning(disable:4996)
    #include <io.h>
#else
    #include <unistd.h>
#endif
#ifndef O_BINARY
    #define O_BINARY 0
#endif

#pragma pack(push, 1)
typedef struct {
    void *data, *pict, *prev;
    unsigned long size, last;
    int uuid;
} STAT; /** #pragma avoids -Wpadded on 64-bit machines **/
#pragma pack(pop)

void Frame(void*, struct GIF_WHDR*); /** keeps -Wmissing-prototypes happy **/
void Frame(void *data, struct GIF_WHDR *whdr) {
    uint32_t *pict, *prev, x, y, yoff, iter, ifin, dsrc, ddst;
    uint8_t head[18] = {0};
    STAT *stat = (STAT*)data;

    #define BGRA(i) ((whdr->bptr[i] == whdr->tran)? 0 : \
          ((uint32_t)(whdr->cpal[whdr->bptr[i]].R << ((GIF_BIGE)? 8 : 16)) \
         | (uint32_t)(whdr->cpal[whdr->bptr[i]].G << ((GIF_BIGE)? 16 : 8)) \
         | (uint32_t)(whdr->cpal[whdr->bptr[i]].B << ((GIF_BIGE)? 24 : 0)) \
         | ((GIF_BIGE)? 0xFF : 0xFF000000)))
    if (!whdr->ifrm) {
        /** TGA doesn`t support heights over 0xFFFF, so we have to trim: **/
        whdr->nfrm = ((whdr->nfrm < 0)? -whdr->nfrm : whdr->nfrm) * whdr->ydim;
        whdr->nfrm = (whdr->nfrm < 0xFFFF)? whdr->nfrm : 0xFFFF;
        /** this is the very first frame, so we must write the header **/
        head[ 2] = 2;
        head[12] = (uint8_t)(whdr->xdim     );
        head[13] = (uint8_t)(whdr->xdim >> 8);
        head[14] = (uint8_t)(whdr->nfrm     );
        head[15] = (uint8_t)(whdr->nfrm >> 8);
        head[16] = 32;   /** 32 bits depth **/
        head[17] = 0x20; /** top-down flag **/
        write(stat->uuid, head, 18UL);
        ddst = (uint32_t)(whdr->xdim * whdr->ydim);
        stat->pict = calloc(sizeof(uint32_t), ddst);
        stat->prev = calloc(sizeof(uint32_t), ddst);
    }
    /** [TODO:] the frame is assumed to be inside global bounds,
                however it might exceed them in some GIFs; fix me. **/
    pict = (uint32_t*)stat->pict;
    ddst = (uint32_t)(whdr->xdim * whdr->fryo + whdr->frxo);
    ifin = (!(iter = (whdr->intr)? 0 : 4))? 4 : 5; /** interlacing support **/
    for (dsrc = (uint32_t)-1; iter < ifin; iter++)
        for (yoff = 16U >> ((iter > 1)? iter : 1), y = (8 >> iter) & 7;
             y < (uint32_t)whdr->fryd; y += yoff)
            for (x = 0; x < (uint32_t)whdr->frxd; x++)
                if (whdr->tran != (long)whdr->bptr[++dsrc])
                    pict[(uint32_t)whdr->xdim * y + x + ddst] = BGRA(dsrc);
    write(stat->uuid, pict, sizeof(uint32_t) * (uint32_t)whdr->xdim
                                             * (uint32_t)whdr->ydim);
    if ((whdr->mode == GIF_PREV) && !stat->last) {
        whdr->frxd = whdr->xdim;
        whdr->fryd = whdr->ydim;
        whdr->mode = GIF_BKGD;
        ddst = 0;
    }
    else {
        stat->last = (whdr->mode == GIF_PREV)?
                      stat->last : (unsigned long)(whdr->ifrm + 1);
        pict = (uint32_t*)((whdr->mode == GIF_PREV)? stat->pict : stat->prev);
        prev = (uint32_t*)((whdr->mode == GIF_PREV)? stat->prev : stat->pict);
        for (x = (uint32_t)(whdr->xdim * whdr->ydim); --x;
             pict[x - 1] = prev[x - 1]);
    }
    if (whdr->mode == GIF_BKGD) /** cutting a hole for the next frame **/
        for (whdr->bptr[0] = (uint8_t)((whdr->tran >= 0)?
                                        whdr->tran : whdr->bkgd), y = 0,
             pict = (uint32_t*)stat->pict; y < (uint32_t)whdr->fryd; y++)
            for (x = 0; x < (uint32_t)whdr->frxd; x++)
                pict[(uint32_t)whdr->xdim * y + x + ddst] = BGRA(0);
    #undef BGRA
}

int main(int argc, char *argv[]) {
    STAT stat = {0};

    if (argc < 3)
        write(1, "arguments: 
       
        .gif 
        
         .tga (1 or more times)
         \n
         "
        
       , 48UL);
    for (stat.uuid = 2, argc -= (~argc & 1); argc >= 3; argc -= 2) {
        if ((stat.uuid = open(argv[argc - 2], O_RDONLY | O_BINARY)) <= 0)
            return 1;
        stat.size = (unsigned long)lseek(stat.uuid, 0UL, 2 /** SEEK_END **/);
        lseek(stat.uuid, 0UL, 0 /** SEEK_SET **/);
        read(stat.uuid, stat.data = realloc(0, stat.size), stat.size);
        close(stat.uuid);
        unlink(argv[argc - 1]);
        stat.uuid = open(argv[argc - 1], O_CREAT | O_WRONLY | O_BINARY, 0644);
        if (stat.uuid > 0) {
            GIF_Load(stat.data, (long)stat.size, Frame, 0, (void*)&stat, 0L);
            stat.pict = realloc(stat.pict, 0L);
            stat.prev = realloc(stat.prev, 0L);
            close(stat.uuid);
            stat.uuid = 0;
        }
        stat.data = realloc(stat.data, 0L);
    }
    return stat.uuid;
}

Python usage example

Here is an example of how to use GIF_Load() from Python 2.x or 3.x.

N.B.: The implementation shown here complies to the GIF standard much better than the one PIL has (at least as of 2018-02-07): for example it preserves transparency and supports local frame palettes.

First of all, gif_load.h has to be built as a shared library:

Linux / macOS:

# Only works when executed from the directory where gif_load.h resides
rm gif_load.so 2>/dev/null
uname -s | grep -q ^Darwin && CC=clang || CC=gcc
$CC -pedantic -ansi -s -DGIF_EXTR=extern -xc gif_load.h -o gif_load.so \
    -shared -fPIC -Wl,--version-script=<(echo "{global:GIF_Load;local:*;};")

Windows:

rem Only works when executed from the directory where gif_load.h resides
del gif_load.exp gif_load.lib gif_load.dll
cl /LD /Zl /DGIF_EXTR=__declspec(dllexport) /Tc gif_load.h msvcrt.lib /Fegif_load.dll

Then the loading function can be called using CTypes (Python 2 reference, Python 3 reference):

> ((7 - (i & 2)) >> (1 + (i & 1))) def fill(w, d, p): retn = Image.new("L", d, w.bkgd) if (w.tran < 0) else \ Image.new("RGBA", d) if (w.tran < 0): retn.putpalette(p) return retn def WriteFunc(d, w): cpal = string_at(w[0].cpal, w[0].clrs * 3) list = d.contents.value[0] if (len(list) == 0): list.append(Image.new("RGBA", (w[0].xdim, w[0].ydim))) tail = len(list) - 1 base = Image.frombytes("L", (w[0].frxd, w[0].fryd), string_at(w[0].bptr, w[0].frxd * w[0].fryd)) if (w[0].intr != 0): tran = base.copy() [intr(skew(y, y) + (skew(y, w[0].fryd - 1) + 1, 0)[(y & 7) == 0], w[0].frxd, (0, y), base, tran) for y in range(w[0].fryd)] tran = Image.eval(base, lambda indx: (255, 0)[indx == w[0].tran]) base.putpalette(cpal) list[tail].paste(base, (w[0].frxo, w[0].fryo), tran) list[tail].info = {"delay" : w[0].time} if (w[0].ifrm != (w[0].nfrm - 1)): trgt = (tail, d.contents.value[1])[w[0].mode == 3] list.append(list[trgt].copy() if (trgt >= 0) else fill(w[0], (w[0].xdim, w[0].ydim), cpal)) if (w[0].mode != 3): d.contents.value[1] = w[0].ifrm if (w[0].mode == 2): list[tail + 1].paste(fill(w[0], (w[0].frxd, w[0].fryd), cpal), (w[0].frxo, w[0].fryo)) try: file = open(file, "rb") except IOError: return [] file.seek(0, 2) size = file.tell() file.seek(0, 0) list = [[], -1] CDLL(("%s.so", "%s.dll")[system() == "Windows"] % "./gif_load"). \ GIF_Load(file.read(), size, CFUNCTYPE(None, PT(py_object), PT(GIF_WHDR))(WriteFunc), None, pointer(py_object(list)), 0) file.close() return list[0] def GIF_Save(file, fext): list = GIF_Load("%s.gif" % file) [pict.save("%s_f%d.%s" % (file, indx, fext)) for (indx, pict) in enumerate(list)] GIF_Save("insert_gif_name_here_without_extension", "png")">
from PIL import Image

def GIF_Load(file):
    from platform import system
    from ctypes import string_at, Structure, c_long as cl, c_ubyte, \
                       py_object, pointer, POINTER as PT, CFUNCTYPE, CDLL
    class GIF_WHDR(Structure): _fields_ = \
       [("xdim", cl), ("ydim", cl), ("clrs", cl), ("bkgd", cl),
        ("tran", cl), ("intr", cl), ("mode", cl), ("frxd", cl), ("fryd", cl),
        ("frxo", cl), ("fryo", cl), ("time", cl), ("ifrm", cl), ("nfrm", cl),
        ("bptr", PT(c_ubyte)), ("cpal", PT(c_ubyte))]
    def intr(y, x, w, base, tran): base.paste(tran.crop((0, y, x, y + 1)), w)
    def skew(i, r): return r >> ((7 - (i & 2)) >> (1 + (i & 1)))
    def fill(w, d, p):
        retn = Image.new("L", d, w.bkgd) if (w.tran < 0) else \
               Image.new("RGBA", d)
        if (w.tran < 0):
            retn.putpalette(p)
        return retn
    def WriteFunc(d, w):
        cpal = string_at(w[0].cpal, w[0].clrs * 3)
        list = d.contents.value[0]
        if (len(list) == 0):
            list.append(Image.new("RGBA", (w[0].xdim, w[0].ydim)))
        tail = len(list) - 1
        base = Image.frombytes("L", (w[0].frxd, w[0].fryd),
                               string_at(w[0].bptr, w[0].frxd * w[0].fryd))
        if (w[0].intr != 0):
            tran = base.copy()
            [intr(skew(y, y) + (skew(y, w[0].fryd - 1) + 1, 0)[(y & 7) == 0],
                  w[0].frxd, (0, y), base, tran) for y in range(w[0].fryd)]
        tran = Image.eval(base, lambda indx: (255, 0)[indx == w[0].tran])
        base.putpalette(cpal)
        list[tail].paste(base, (w[0].frxo, w[0].fryo), tran)
        list[tail].info = {"delay" : w[0].time}
        if (w[0].ifrm != (w[0].nfrm - 1)):
            trgt = (tail, d.contents.value[1])[w[0].mode == 3]
            list.append(list[trgt].copy() if (trgt >= 0) else
                        fill(w[0], (w[0].xdim, w[0].ydim), cpal))
            if (w[0].mode != 3):
                d.contents.value[1] = w[0].ifrm
            if (w[0].mode == 2):
                list[tail + 1].paste(fill(w[0], (w[0].frxd, w[0].fryd), cpal),
                                                (w[0].frxo, w[0].fryo))
    try: file = open(file, "rb")
    except IOError: return []
    file.seek(0, 2)
    size = file.tell()
    file.seek(0, 0)
    list = [[], -1]
    CDLL(("%s.so", "%s.dll")[system() == "Windows"] % "./gif_load"). \
    GIF_Load(file.read(), size,
             CFUNCTYPE(None, PT(py_object), PT(GIF_WHDR))(WriteFunc),
             None, pointer(py_object(list)), 0)
    file.close()
    return list[0]

def GIF_Save(file, fext):
    list = GIF_Load("%s.gif" % file)
    [pict.save("%s_f%d.%s" % (file, indx, fext))
     for (indx, pict) in enumerate(list)]

GIF_Save("insert_gif_name_here_without_extension", "png")
Comments
  • Static code analyzer warning

    Static code analyzer warning

    I've probed the clang analyzer on the code and it gives one warning. As the code is .. hm .. complex .. I can't judge if this is a valid one, so I simply show it here:

    report.zip

    opened by wcout 10
  • Previous frames wrongly preserved

    Previous frames wrongly preserved

    Hello,

    In the example below previous frames are wrongly preserved in subsequent frames:

    zu4r

    =>

    spritesheet

    I tested it in the python version. Thanks for looking into it.

    opened by orestis-z 9
  • Memory corruption on test GIF

    Memory corruption on test GIF

    Is there a problem with this GIF?

    https://github.com/robert-ancell/pygif/blob/master/test-suite/extra-pixels.gif

    The following simple program crashes here with a memory corruption:

    #include <cstdio>
    
    #include "gif_load.h"
    
    void frame_cb( void *data_, GIF_WHDR *whdr_ ) {
      printf( "frame_cb: %s, %p  frame #%ld/%ld, %ld x %ld, delay: %ld\n",
        (char *)data_, (void *)whdr_, whdr_->ifrm, whdr_->nfrm,
         whdr_->frxd, whdr_->fryd, whdr_->time );
    }
    
    int main( int argc_, char *argv_[] ) {
      if ( argc_ <= 1 ) return 0;
      printf( "load %s\n", argv_[1] );
      FILE *gif = fopen( argv_[1], "r" );
      long len = 0;
      char *buf = 0;
      if ( !( gif && fseek( gif, 0, SEEK_END ) >= 0 &&
            ( len = ftell( gif ) ) >= 0             &&
            ( buf = (char *)malloc( (size_t)len ) ) &&
            fseek( gif, 0, SEEK_SET ) >= 0          &&
            fread( buf, 1, (size_t)len, gif ) == (size_t)len ) ) {
        perror( argv_[1] );
        free( buf );
        if ( gif ) fclose( gif );
        return 1;
      }
      fclose( gif );
    
      printf( "decode '%s', buf = %p, len = %lu\n", argv_[1], buf, len );
      long ret = GIF_Load( buf, len, frame_cb, 0, argv_[1], 0 );
      printf( "ret = %ld\n", ret );
    
      return 0;
    }
    

    output:

    load ../pygif/test-suite/extra-pixels.gif
    decode '../pygif/test-suite/extra-pixels.gif', buf = 0x558f4942e8b0, len = 60
    frame_cb: ../pygif/test-suite/extra-pixels.gif, 0x7fff97f3d480  frame #0/1, 1 x 1, delay: 0
    free(): invalid next size (normal)
    
    

    Strangely the demo program (from README) outputs the tgacorrectly and does not crash.

    This is with Ubuntu 18.10.

    opened by wcout 8
  • Transparency not preserved after first frame

    Transparency not preserved after first frame

    First, thanks for the awesome work.

    I noticed that the transparency is not preserved after the first frame on certain gifs. In the example below the transparent layer disappears after the first frame.

    this ones transparency is even more problematic:

    Thanks for the help in advance.

    opened by orestis-z 6
  • Usage from c++ has a minor flaw

    Usage from c++ has a minor flaw

    Thanks for this awesome work!

    I use GIFLIB currently for decoding, but am tempted to switch to this header only masterpiece.

    There is only one minor flaw using it in class contexts:

    I am including gif_load.h in the source file of my class. Declaring the private callback method static MyClass::gifLoadCallback(void *, GIF_WHDR *) in the corresponding header is unfortunately not possible, as one cannot forward declare a typedef struct.

    [Declaring the callback locally as a non member function in the source file is not an option, because called methods by MyClass would need to be declared public and with a void *argument for GIF_WHDR - both is not very nice].

    As a workaround I have changed gif_load.h by declaring the struct without typedef as _GIF_WHDR and follow it with a typedef struct _GIF_WHDR GIF_WHDR and use _GIF_WHDR for the forward declaration.

    Here is the diff:

    diff --git a/gif_load.h b/gif_load.h
    index e2cace7..28e3200 100644
    --- a/gif_load.h
    +++ b/gif_load.h
    @@ -46,7 +46,7 @@ extern "C" {
     #define _GIF_SWAP(h) ((GIF_BIGE)? ((uint16_t)(h << 8) | (h >> 8)) : h)
     
     #pragma pack(push, 1)
    -typedef struct {                 /** ======== frame writer info: ======== **/
    +struct _GIF_WHDR {               /** ======== frame writer info: ======== **/
         long xdim, ydim, clrs,       /** global dimensions, palette size      **/
              bkgd, tran,             /** background index, transparent index  **/
              intr, mode,             /** interlace flag, frame blending mode  **/
    @@ -56,8 +56,9 @@ typedef struct {                 /** ======== frame writer info: ======== **/
         struct {                     /** [==== GIF RGB palette element: ====] **/
             uint8_t R, G, B;         /** [color values - red, green, blue   ] **/
         } *cpal;                     /** current palette                      **/
    -} GIF_WHDR;
    +};
     #pragma pack(pop)
    +typedef struct _GIF_WHDR GIF_WHDR;
     
     enum {GIF_NONE = 0, GIF_CURR = 1, GIF_BKGD = 2, GIF_PREV = 3};
     
    

    Do you think this is a good solution or do you have a better idea?

    opened by wcout 4
  • artefacts in decoded anim-gif

    artefacts in decoded anim-gif

    Frame #140 contains some artefacts: testcase.gif

    Code:

    void FrameCallback(GIF_GHDR *ghdr, GIF_FHDR *curr, GIF_FHDR *prev,
                       GIF_RGBX *cpal, long clrs, uint8_t *bptr, void *data,
                       long nfrm, long tran, long time, long indx) {
        uint32_t *pict, x, y, yoff, iter, ifin, dsrc, ddst;
        uintptr_t *file = (uintptr_t*)data;
        uint8_t head[18] = {};
    
        #define BGRA(i) (cpal[bptr[i]].R << 16) | (cpal[bptr[i]].G << 8) \
                       | cpal[bptr[i]].B | ((i != tran)? 0xFF000000 : 0)
        if (!indx) {
            /** this is the very first frame, so we must write the header **/
            head[ 2] = 2;
            head[12] = (ghdr->xdim     ) & 0xFF;
            head[13] = (ghdr->xdim >> 8) & 0xFF;
            head[14] = ((labs(1) * ghdr->ydim)     ) & 0xFF;
            head[15] = ((labs(1) * ghdr->ydim) >> 8) & 0xFF;
            head[16] = 32;   /** 32 bits depth **/
            head[17] = 0x20; /** top-down flag **/
            write(file[0], head, 18);
            file[1] = (uintptr_t)calloc(ghdr->xdim * ghdr->ydim, sizeof(uint32_t));
        }
        /** interlacing support **/
        iter = (curr->flgs & GIF_FINT)? 0 : 4;
        ifin = (curr->flgs & GIF_FINT)? 4 : 5;
    
        pict = (uint32_t*)file[1];
        if ((uintptr_t)prev > (uintptr_t)sizeof(prev)) {
            /** background: previous frame with a hole **/
            ddst = ghdr->xdim * prev->yoff + prev->xoff;
            for (y = 0; y < prev->ydim; y++)
                for (x = 0; x < prev->xdim; x++)
                    pict[ghdr->xdim * y + x + ddst] = BGRA(ghdr->bkgd);
        }
        /** [TODO:] the frame is assumed to be inside global bounds,
                    however it might exceed them in some GIFs; fix me. **/
        ddst = ghdr->xdim * curr->yoff + curr->xoff;
        for (dsrc = -1; iter < ifin; iter++)
            for (yoff = 16 >> ((iter > 1)? iter : 1), y = (8 >> iter) & 7;
                 y < curr->ydim; y += yoff)
                for (x = 0; x < curr->xdim; x++)
                    if (tran != (long)bptr[++dsrc])
                        pict[ghdr->xdim * y + x + ddst] = BGRA(dsrc);
    static int counter=0;
    if( counter++ == 140 )
        write(file[0], pict, ghdr->xdim * ghdr->ydim * sizeof(uint32_t));
        #undef BGRA
    }
    int main(int argc, char *argv[])
    {
        intptr_t file[2];
        void *data;
    
        if ((file[0] = open("/tmp/t.gif", O_RDONLY)) > 0) {
            file[1] = lseek(file[0], 0, SEEK_END);
            lseek(file[0], 0, SEEK_SET);
            read(file[0], data = malloc(file[1]), file[1]);
            close(file[0]);
            if ((file[0] = open("/tmp/t.tga", O_CREAT | O_WRONLY, 0644)) > 0) {
                GIF_Load(data, file[1], 0, FrameCallback, (void*)file);
                free((void*)file[1]); /** gets rewritten in FrameCallback() **/
                close(file[0]);
            }
            free(data);
            return 0;
        }
        return 1;
    }
    
    opened by doublex 4
  • How to read GIF into memory

    How to read GIF into memory

    image

    Thanks for your great repo firstly! However, since I barely know anything about GIF, it still bothers me how to read a gif file into a data structure like above?

    opened by liqi0126 3
  • Support GIF's with no colormap

    Support GIF's with no colormap

    This is just a request for a feature you may want to support:

    The standard says, that both global and local color tables are no required blocks. In that case the application can use a default colormap.

    Currently gif_load.h does not parse such images.

    I have made some changes in my copy to support it:

    (1) Continue parsing when clrs is 0. (2) Give access to the palette definition in the GIF_WHDR structure. (3) Return the value of the GIF header field color resultion. As I read it, it can be used in such a case to determine how many colors the palette has in order to supply a suitable replacement.

    (1) and (2) would be nice to have, (3) could in principle also be done in the application, because the value is at a fixed position in the file.

    These are my changes:

    diff --git a/gif_load.h b/gif_load.h
    index 12cc402..99324bb 100644
    --- a/gif_load.h
    +++ b/gif_load.h
    @@ -48,12 +48,13 @@ extern "C" {
     #pragma pack(push, 1)
     struct GIF_WHDR {                /** ======== frame writer info: ======== **/
         long xdim, ydim, clrs,       /** global dimensions, palette size      **/
    +         cres,                   /** color resolution: 2^(cres+1) = #clrs **/
              bkgd, tran,             /** background index, transparent index  **/
              intr, mode,             /** interlace flag, frame blending mode  **/
              frxd, fryd, frxo, fryo, /** current frame dimensions and offset  **/
              time, ifrm, nfrm;       /** delay, frame number, frame count     **/
         uint8_t *bptr;               /** frame pixel indices or metadata      **/
    -    struct {                     /** [==== GIF RGB palette element: ====] **/
    +    struct CPAL {                /** [==== GIF RGB palette element: ====] **/
             uint8_t R, G, B;         /** [color values - red, green, blue   ] **/
         } *cpal;                     /** current palette                      **/
     };
    @@ -225,6 +226,7 @@ GIF_EXTR long GIF_Load(void *data, long size,
         || ((buff[4] != 55) && (buff[4] != 57)) || (buff[5] != 97) || !gwfr)
             return 0;
     
    +    whdr.cres = (ghdr->flgs >> 4) & 7;
         buff = (uint8_t*)(ghdr + 1) /** skipping the global header and palette **/
              + _GIF_LoadHeader(ghdr->flgs, 0, 0, 0, 0, 0L) * 3L;
         if ((size -= buff - (uint8_t*)ghdr) <= 0)
    @@ -238,7 +240,7 @@ GIF_EXTR long GIF_Load(void *data, long size,
             if (desc == GIF_FHDM) {
                 fhdr = (struct GIF_FHDR*)whdr.bptr;
                 if (_GIF_LoadHeader(ghdr->flgs, &whdr.bptr, (void**)&whdr.cpal,
    -                                fhdr->flgs, &blen, sizeof(*fhdr)) <= 0)
    +                                fhdr->flgs, &blen, sizeof(*fhdr)) < 0)
                     break;
                 whdr.frxd = _GIF_SWAP(fhdr->frxd);
                 whdr.fryd = _GIF_SWAP(fhdr->fryd);
    @@ -258,7 +260,7 @@ GIF_EXTR long GIF_Load(void *data, long size,
                 *(void**)&whdr.cpal = (void*)(ghdr + 1); /** interlaced? -^ **/
                 whdr.clrs = _GIF_LoadHeader(ghdr->flgs, &buff, (void**)&whdr.cpal,
                                             fhdr->flgs, &size, sizeof(*fhdr));
    -            if ((skip <= ++whdr.ifrm) && ((whdr.clrs <= 0)
    +            if ((skip <= ++whdr.ifrm) && ((whdr.clrs < 0)
                 ||  (_GIF_LoadFrame(&buff, &size,
                                      whdr.bptr, whdr.bptr + blen) < 0)))
                     size = -(whdr.ifrm--) - 1; /** failed to load the frame **/
    
    

    In my program I'm doing something like that, when there are no colors:

    
    void frame_cb(void *data_, GIF_WHDR *whdr_) {
      // ...
      if (!whdr_.clrs) {
        // no colors: use default table
        static struct GIF_WHDR::CPAL defClrs[256];
        whdr_.clrs = 1 << (whdr_.cres + 1);
        whdr_.cpal = defClrs;
        memset(defClrs, 0, sizeof(defClrs)); // Note: also sets first color to black
        defClrs[1].R = defClrs[1].G = defClrs[1].B = 0xff; // white
        for (int i = 2; i < whdr_.clrs; i++)
          defClrs[i].R = defClrs[i].G = defClrs[i].B = (uchar)(255 * i / (whdr_.clrs - 1));
      }
      // ...
    }
    
    
    enhancement 
    opened by wcout 2
Owner
Andrey Guskov
Andrey Guskov
Video, Image and GIF upscale/enlarge(Super-Resolution) and Video frame interpolation. Achieved with Waifu2x, SRMD, RealSR, Anime4K, RIFE, CAIN, DAIN and ACNet.

Video, Image and GIF upscale/enlarge(Super-Resolution) and Video frame interpolation. Achieved with Waifu2x, SRMD, RealSR, Anime4K, RIFE, CAIN, DAIN and ACNet.

Aaron Feng 8.7k Jan 7, 2023
An image and texture viewer for tga, png, apng, exr, dds, gif, hdr, jpg, tif, ico, webp, and bmp files

An image and texture viewer for tga, png, apng, exr, dds, gif, hdr, jpg, tif, ico, webp, and bmp files. Uses Dear ImGui, OpenGL, and Tacent. Useful for game devs as it displays information like the presence of an alpha channel and querying specific pixels for their colour.

Tristan Grimmer 159 Dec 31, 2022
PoC black/white image sequence to dumpy gif image sequence converter

PoC black/white image sequence to dumpy gif image sequence converter

null 69 Dec 9, 2022
Import GIF/WebP animated image as a new AnimatedTexture asset type.

Animated Texture Plugin for Unreal Engine 5 This plugin allows you to import animated pictures into your Unreal Engine 5 project as a new AnimatedText

房燕良 36 Jan 8, 2023
This program converts APNG animations into animated GIF format

apng2gif This project has been in disrepair for long time. Recently, there is need to change the number of loops in apng file. So I used it to modify

Ted Zyzsdy 2 Oct 27, 2022
A GIF art engine that will allow you to generate multi-layer GIFs from single GIFs as layers.

A GIF art engine that will allow you to generate multi-layer GIFs from single GIFs as layers. All the code in this repository has been written by me in c++, inspired by the generative art engine of HashLips that does not support GIFs as layers. The problem arose from my and my teamleader's need to generate animated images and then GIFs, in the same way as HashLips generated static images.

Andre 63 Jan 2, 2023
A header-only C++11 library for colors; color space converters for RGB, HSL, XYZ, Lab, etc. and perceptual color difference calculators such as CIEDE2000

color-util A header-only C++11 library for handling colors, including color space converters between RGB, XYZ, Lab, etc. and color difference calculat

Yuki Koyama 79 Oct 6, 2022
NanoPM, single header only PatchMatch

NanoPM, single header only PatchMatch NanoPM is a single header-only implementation of PatchMatch algorithm written in C++. Could be used for variety

null 67 Sep 27, 2022
Small header-only C library to decompress any BC compressed image

Small header-only C library to decompress any BC compressed image

null 92 Jan 1, 2023
ImGuiFileDialog is a file selection dialog built for (and using only) Dear ImGui

ImGuiFileDialog Purpose ImGuiFileDialog is a file selection dialog built for (and using only) Dear ImGui. My primary goal was to have a custom pane wi

Aiekick 790 Jan 4, 2023
A fast image processing library with low memory needs.

libvips : an image processing library Introduction libvips is a demand-driven, horizontally threaded image processing library. Compared to similar lib

libvips 26 Nov 10, 2022
The “Quite OK Image” format for fast, lossless image compression

The “Quite OK Image” format for fast, lossless image compression

Dominic Szablewski 6k Dec 30, 2022
Very fast C++ .PNG writer for 24/32bpp images.

fpng Very fast C++ .PNG writer for 24/32bpp images. fpng.cpp was written to see just how fast you can write .PNG's without sacrificing too much compre

Rich Geldreich 639 Dec 25, 2022
QOY - The "Quite OK YCbCr420A" format for fast, lossless image compression

QOY - The "Quite OK YCbCr420A" format for fast, lossless* image compression ( * colorspace conversion to/from RGBA is lossy, if used ) Single-file MIT

Chainfire 19 Oct 1, 2022
Demo of a fast PNG encoder.

Fast PNG Encoder This is a proof-of-concept fast PNG encoder that uses AVX2 and a special Huffman table to encode images faster. Speed on a single cor

Luca Versari 58 Dec 27, 2022
Fast streaming PNG<->QOI converter with some compression-improving extensions

QOIG Fast streaming PNG<->QOI converter with some compression-improving extensions. Can achieve 1%-10% better compression than QOI without sacrificing

David Rutter 3 Oct 3, 2022
NanoSVG is a simple stupid single-header-file SVG parse

This project is not actively maintained. Nano SVG Parser NanoSVG is a simple stupid single-header-file SVG parse.

Mikko Mononen 1.4k Dec 31, 2022
Spotfetch - a tool to fetch spotify info with with ascii picture written in C++

A C++ fetch tool for spotify, its a cool little tool to retrieve information on current playing song on spotify. Run the command and be treated to a pixelated version of the album art accompanied by its information.

xfcisco 14 Nov 9, 2022
Simple, generally spec-compliant, hacky PNG Decoder written in C99.

Simple, generally spec-compliant, hacky PNG Decoder written in C99.

cristei 2 Nov 2, 2021