{"id":13732042,"url":"https://github.com/hidefromkgb/gif_load","last_synced_at":"2025-05-08T06:31:07.315Z","repository":{"id":67641177,"uuid":"65492602","full_name":"hidefromkgb/gif_load","owner":"hidefromkgb","description":"A slim, fast and header-only GIF loader written in C","archived":false,"fork":false,"pushed_at":"2019-01-06T07:13:00.000Z","size":84,"stargazers_count":79,"open_issues_count":1,"forks_count":6,"subscribers_count":5,"default_branch":"master","last_synced_at":"2024-08-04T02:10:43.528Z","etag":null,"topics":["animated-gif","ansi-c","big-endian","gif","gif-animation","gif-library","gifs","public-domain","single-header-lib"],"latest_commit_sha":null,"homepage":"","language":"C","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/hidefromkgb.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null}},"created_at":"2016-08-11T18:32:40.000Z","updated_at":"2024-05-26T10:31:09.000Z","dependencies_parsed_at":"2023-03-15T01:04:58.627Z","dependency_job_id":null,"html_url":"https://github.com/hidefromkgb/gif_load","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hidefromkgb%2Fgif_load","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hidefromkgb%2Fgif_load/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hidefromkgb%2Fgif_load/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hidefromkgb%2Fgif_load/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/hidefromkgb","download_url":"https://codeload.github.com/hidefromkgb/gif_load/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":224707606,"owners_count":17356362,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["animated-gif","ansi-c","big-endian","gif","gif-animation","gif-library","gifs","public-domain","single-header-lib"],"created_at":"2024-08-03T02:01:44.584Z","updated_at":"2024-11-14T23:30:49.941Z","avatar_url":"https://github.com/hidefromkgb.png","language":"C","funding_links":[],"categories":["Graphics"],"sub_categories":[],"readme":"# gif_load\nThis is an ANSI C compatible animated GIF loader in a single header file of\nless than 300 lines of code (less than 200 without empty lines and comments).\nIt defines 1 new struct and 1 new enum, and requires 1 function call to load\na GIF. 'ANSI C compatible' means that it builds fine with `-pedantic -ansi`\ncompiler flags but includes `stdint.h` which is unavailable prior to C99.\n\n`gif_load` is free and unencumbered software released into the public domain,\nblah blah. See the header file for details.\n\nThere are no strict dependencies on the standard C library. The only external\nfunction used by default is `realloc()` (both for freeing and allocation), but\nit\\`s possible to override it by defining a macro called `GIF_MGET(m,s,a,c)`\nprior to including the header; `m` stands for a `uint8_t*`-typed pointer to\nthe memory block being allocated or freed, `s` is the target block size, typed\n`unsigned long`, `a` is the value of the fifth parameter passed to `GIF_Load()`\n(mainly used to hold a pointer to some user-defined structure if need be; see\nbelow), typed `void*`, and `c` equals 0 on freeing and 1 on allocation. For\nexample, `GIF_MGET` might be defined as follows if `malloc()` / `free()` pair\nis to be used instead of `realloc()`:\n\n```c\n#include \u003cstdlib.h\u003e\n#define GIF_MGET(m,s,a,c) if (c) m = (uint8_t*)malloc(s); else free(m);\n#include \"gif_load.h\"\n```\n\nLoading GIFs immediately from disk is not supported: target files must\nbe read or otherwise mapped into RAM by the caller.\n\nThe main function that does the actual loading is called `GIF_Load()`.\nIt requires a callback function to create the animation structure of\nany user-defined format. This function, further referred to as the\n'frame writer callback', will be executed once every frame.\n\nAditionally, the main function accepts a second callback used to process GIF\n[application-specific extensions](https://stackoverflow.com/a/28486261/7019311),\ni.e. metadata; in the vast majority of GIFs no such extensions are present, so\nthis callback is optional.\n\nBoth frame writer callback and metadata callback need 2 parameters:\n\n1. callback-specific data\n2. pointer to a `struct GIF_WHDR` that encapsulates GIF frame information\n(callbacks may alter any fields at will, as the structure passed to them is a\nproxy that is discarded after every call):\n  * `GIF_WHDR::xdim` - global GIF width, always constant across frames\n                       (further referred to as 'ACAF'); [0; 65535]\n  * `GIF_WHDR::ydim` - global GIF height, ACAF; [0; 65535]\n  * `GIF_WHDR::clrs` - number of colors in the current palette (local palettes\n                       are not that rare so it may vary across frames, further\n                       referred to as 'MVAF'); {2; 4; 8; 16; 32; 64; 128; 256}\n  * `GIF_WHDR::bkgd` - 0-based background color index for the current palette,\n                       ACAF (sic ACAF, as this index is set globally)\n  * `GIF_WHDR::tran` - 0-based transparent color index for the current palette\n                       (or −1 when transparency is disabled), MVAF\n  * `GIF_WHDR::intr` - boolean flag indicating whether the current frame is\n                  [interlaced](https://en.wikipedia.org/wiki/GIF#Interlacing);\n                       deinterlacing it is up to the caller (see the examples\n                       below), MVAF\n  * `GIF_WHDR::mode` - next frame (sic next, not current) blending mode, MVAF:\n                       [`GIF_NONE`:] no blending, mainly used in single-frame\n                       GIFs, functionally equivalent to `GIF_CURR`;\n                       [`GIF_CURR`:] leave the current image state as is;\n                       [`GIF_BKGD`:] restore the background color (or\n                       transparency, in case `GIF_WHDR::tran` ≠ −1) in the\n                       boundaries of the current frame; [`GIF_PREV`:] restore\n                       the last image whose mode differed from this one,\n                       functionally equivalent to `GIF_BKGD` when assigned to\n                       the first frame in a GIF; *N.B.:* if right before a\n                       `GIF_PREV` frame came a `GIF_BKGD` one, the state to\n                       be restored is before a certain part of the resulting\n                       image was filled with the background color, not after!\n  * `GIF_WHDR::frxd` - current frame width, MVAF; [0; 65535]\n  * `GIF_WHDR::fryd` - current frame height, MVAF; [0; 65535]\n  * `GIF_WHDR::frxo` - current frame horizontal offset, MVAF; [0; 65535]\n  * `GIF_WHDR::fryo` - current frame vertical offset, MVAF; [0; 65535]\n  * `GIF_WHDR::time` - next frame delay in GIF time units (1 unit = 10 msec),\n                       MVAF; negative values are possible here, they mean\n                       that the frame requires user input to advance, and the\n                       actual delay equals −(`time` + 1) GIF time units: zero\n                       delay + user input = wait for input indefinitely,\n                       nonzero delay + user input = wait for either input or\n                       timeout (whichever comes first); *N.B.:* user input\n                       requests can be safely ignored, disregarding the GIF\n                       standard\n  * `GIF_WHDR::ifrm` - 0-based index of the current frame, always varies\n                       across frames (further referred to as 'AVAF')\n  * `GIF_WHDR::nfrm` - total frame count, negative if the GIF data supplied\n                       is incomplete, ACAF during a single `GIF_Load()` call\n                       but may vary across `GIF_Load()` calls\n  * `GIF_WHDR::bptr` - [frame writer:] pixel indices for the current frame,\n                       ACAF (it is only the pointer address that is constant;\n                       the pixel indices stored inside = MVAF); [metadata\n                       callback:] app metadata header (8+3 bytes) followed by\n                       a GIF chunk (1 byte designating length L, then L bytes\n                       of metadata, and so forth; L = 0 means end of chunk),\n                       AVAF\n  * `GIF_WHDR::cpal` - the current palette containing 3 `uint8_t` values for\n                       each of the colors: `R` for the red channel, `G` for\n                       green and `B` for blue; this pointer is guaranteed\n                       to be the same across frames if and only if the global\n                       palette is used for those frames (local palettes are\n                       strictly frame-specific, even when they contain the\n                       same number of identical colors in identical order),\n                       MVAF\n\nNeither of the two callbacks needs to return a value, thus having `void` for\na return type.\n\n`GIF_Load()`, in its turn, needs 6 parameters:\n\n1. a pointer to GIF data in RAM\n2. GIF data size; may be larger than the actual data if the GIF has a proper\n   ending mark\n3. a pointer to the frame writer callback\n4. a pointer to the metadata callback; may be left empty\n5. callback-specific data\n6. number of frames to skip before executing the callback; useful to resume\n   loading the partial file\n\nPartial GIFs are also supported, but only at a granularity of one frame. For\nexample, if the file ends in the middle of the fifth frame, no attempt would\nbe made to recover the upper half, and the resulting animation will only\ncontain 4 frames. When more data is available the loader might be called\nagain, this time with the `skip` parameter equalling 4 to skip those 4 frames.\nNote that the metadata callback is not affected by `skip` and gets called\nagain every time the frames between which the metadata was written are\nskipped.\n\nThe return value of the function above, if positive, equals the total number\nof frames in the animation and indicates that the GIF data stream ended with\na proper termination mark. Negative return value is the number of frames\nloaded per current call multiplied by −1, suggesting that the GIF data stream\nbeing decoded is still incomplete. Zero, in its turn, means that the call\ncould not decode any more frames.\n\n`gif_load` is endian-aware. If the target machine can be big-endian the user\nhas to determine that manually and add `#define GIF_BIGE 1` to the source\nprior to the header being included if that\\`s the case, or otherwise define\nthe endianness to be used (0 = little, 1 = big), e.g. by declaring a helper\nfunction and setting `GIF_BIGE` to expand into its call, or by passing it as a\ncompiler parameter (e.g. `-DGIF_BIGE=1` for GCC / Clang). Although GIF data is\nlittle-endian, all multibyte integers passed to the user through `long`-typed\nfields of `GIF_WHDR` have correct byte order regardless of the endianness of\nthe target machine, provided that `GIF_BIGE` is set correctly. Most other\ndata, e.g. pixel indices of a frame, consists of single bytes and thus does\nnot require endianness correction. One notable exception is GIF application\nmetadata which is passed as the raw chunk of bytes (for details see the\ndescription of `GIF_WHDR::bptr` provided above), and then it\\`s the\ncallback\\`s job to parse it and decide whether to decode and how to do that.\n\nThere is a possibility to build `gif_load` as a shared library. `GIF_EXTR` is\na convenience macro to be defined so that the `GIF_Load()` function gets an\nentry in the export table of the library. See the Python example for further\ninformation.\n\n\n\n# C / C++ usage example\nHere is an example of how to use `GIF_Load()` to transform an animated GIF\nfile into a 32-bit uncompressed TGA. For the sake of simplicity all frames\nare concatenated one below the other and no attempt is made to keep all of\nthem if the resulting height exceeds the TGA height limit which is 0xFFFF.\n\n```c\n#include \"gif_load.h\"\n#include \u003cfcntl.h\u003e\n#ifdef _MSC_VER\n    /** MSVC is definitely not my favourite compiler...   \u003e_\u003c   **/\n    #pragma warning(disable:4996)\n    #include \u003cio.h\u003e\n#else\n    #include \u003cunistd.h\u003e\n#endif\n#ifndef O_BINARY\n    #define O_BINARY 0\n#endif\n\n#pragma pack(push, 1)\ntypedef struct {\n    void *data, *pict, *prev;\n    unsigned long size, last;\n    int uuid;\n} STAT; /** #pragma avoids -Wpadded on 64-bit machines **/\n#pragma pack(pop)\n\nvoid Frame(void*, struct GIF_WHDR*); /** keeps -Wmissing-prototypes happy **/\nvoid Frame(void *data, struct GIF_WHDR *whdr) {\n    uint32_t *pict, *prev, x, y, yoff, iter, ifin, dsrc, ddst;\n    uint8_t head[18] = {0};\n    STAT *stat = (STAT*)data;\n\n    #define BGRA(i) ((whdr-\u003ebptr[i] == whdr-\u003etran)? 0 : \\\n          ((uint32_t)(whdr-\u003ecpal[whdr-\u003ebptr[i]].R \u003c\u003c ((GIF_BIGE)? 8 : 16)) \\\n         | (uint32_t)(whdr-\u003ecpal[whdr-\u003ebptr[i]].G \u003c\u003c ((GIF_BIGE)? 16 : 8)) \\\n         | (uint32_t)(whdr-\u003ecpal[whdr-\u003ebptr[i]].B \u003c\u003c ((GIF_BIGE)? 24 : 0)) \\\n         | ((GIF_BIGE)? 0xFF : 0xFF000000)))\n    if (!whdr-\u003eifrm) {\n        /** TGA doesn`t support heights over 0xFFFF, so we have to trim: **/\n        whdr-\u003enfrm = ((whdr-\u003enfrm \u003c 0)? -whdr-\u003enfrm : whdr-\u003enfrm) * whdr-\u003eydim;\n        whdr-\u003enfrm = (whdr-\u003enfrm \u003c 0xFFFF)? whdr-\u003enfrm : 0xFFFF;\n        /** this is the very first frame, so we must write the header **/\n        head[ 2] = 2;\n        head[12] = (uint8_t)(whdr-\u003exdim     );\n        head[13] = (uint8_t)(whdr-\u003exdim \u003e\u003e 8);\n        head[14] = (uint8_t)(whdr-\u003enfrm     );\n        head[15] = (uint8_t)(whdr-\u003enfrm \u003e\u003e 8);\n        head[16] = 32;   /** 32 bits depth **/\n        head[17] = 0x20; /** top-down flag **/\n        write(stat-\u003euuid, head, 18UL);\n        ddst = (uint32_t)(whdr-\u003exdim * whdr-\u003eydim);\n        stat-\u003epict = calloc(sizeof(uint32_t), ddst);\n        stat-\u003eprev = calloc(sizeof(uint32_t), ddst);\n    }\n    /** [TODO:] the frame is assumed to be inside global bounds,\n                however it might exceed them in some GIFs; fix me. **/\n    pict = (uint32_t*)stat-\u003epict;\n    ddst = (uint32_t)(whdr-\u003exdim * whdr-\u003efryo + whdr-\u003efrxo);\n    ifin = (!(iter = (whdr-\u003eintr)? 0 : 4))? 4 : 5; /** interlacing support **/\n    for (dsrc = (uint32_t)-1; iter \u003c ifin; iter++)\n        for (yoff = 16U \u003e\u003e ((iter \u003e 1)? iter : 1), y = (8 \u003e\u003e iter) \u0026 7;\n             y \u003c (uint32_t)whdr-\u003efryd; y += yoff)\n            for (x = 0; x \u003c (uint32_t)whdr-\u003efrxd; x++)\n                if (whdr-\u003etran != (long)whdr-\u003ebptr[++dsrc])\n                    pict[(uint32_t)whdr-\u003exdim * y + x + ddst] = BGRA(dsrc);\n    write(stat-\u003euuid, pict, sizeof(uint32_t) * (uint32_t)whdr-\u003exdim\n                                             * (uint32_t)whdr-\u003eydim);\n    if ((whdr-\u003emode == GIF_PREV) \u0026\u0026 !stat-\u003elast) {\n        whdr-\u003efrxd = whdr-\u003exdim;\n        whdr-\u003efryd = whdr-\u003eydim;\n        whdr-\u003emode = GIF_BKGD;\n        ddst = 0;\n    }\n    else {\n        stat-\u003elast = (whdr-\u003emode == GIF_PREV)?\n                      stat-\u003elast : (unsigned long)(whdr-\u003eifrm + 1);\n        pict = (uint32_t*)((whdr-\u003emode == GIF_PREV)? stat-\u003epict : stat-\u003eprev);\n        prev = (uint32_t*)((whdr-\u003emode == GIF_PREV)? stat-\u003eprev : stat-\u003epict);\n        for (x = (uint32_t)(whdr-\u003exdim * whdr-\u003eydim); --x;\n             pict[x - 1] = prev[x - 1]);\n    }\n    if (whdr-\u003emode == GIF_BKGD) /** cutting a hole for the next frame **/\n        for (whdr-\u003ebptr[0] = (uint8_t)((whdr-\u003etran \u003e= 0)?\n                                        whdr-\u003etran : whdr-\u003ebkgd), y = 0,\n             pict = (uint32_t*)stat-\u003epict; y \u003c (uint32_t)whdr-\u003efryd; y++)\n            for (x = 0; x \u003c (uint32_t)whdr-\u003efrxd; x++)\n                pict[(uint32_t)whdr-\u003exdim * y + x + ddst] = BGRA(0);\n    #undef BGRA\n}\n\nint main(int argc, char *argv[]) {\n    STAT stat = {0};\n\n    if (argc \u003c 3)\n        write(1, \"arguments: \u003cin\u003e.gif \u003cout\u003e.tga (1 or more times)\\n\", 48UL);\n    for (stat.uuid = 2, argc -= (~argc \u0026 1); argc \u003e= 3; argc -= 2) {\n        if ((stat.uuid = open(argv[argc - 2], O_RDONLY | O_BINARY)) \u003c= 0)\n            return 1;\n        stat.size = (unsigned long)lseek(stat.uuid, 0UL, 2 /** SEEK_END **/);\n        lseek(stat.uuid, 0UL, 0 /** SEEK_SET **/);\n        read(stat.uuid, stat.data = realloc(0, stat.size), stat.size);\n        close(stat.uuid);\n        unlink(argv[argc - 1]);\n        stat.uuid = open(argv[argc - 1], O_CREAT | O_WRONLY | O_BINARY, 0644);\n        if (stat.uuid \u003e 0) {\n            GIF_Load(stat.data, (long)stat.size, Frame, 0, (void*)\u0026stat, 0L);\n            stat.pict = realloc(stat.pict, 0L);\n            stat.prev = realloc(stat.prev, 0L);\n            close(stat.uuid);\n            stat.uuid = 0;\n        }\n        stat.data = realloc(stat.data, 0L);\n    }\n    return stat.uuid;\n}\n```\n\n\n\n# Python usage example\nHere is an example of how to use `GIF_Load()` from Python 2.x or 3.x.\n\n*N.B.:* The implementation shown here complies to the GIF standard much\nbetter than the one PIL has (at least as of 2018-02-07): for example\nit preserves transparency and supports local frame palettes.\n\nFirst of all, `gif_load.h` has to be built as a shared library:\n\nLinux / macOS:\n```bash\n# Only works when executed from the directory where gif_load.h resides\nrm gif_load.so 2\u003e/dev/null\nuname -s | grep -q ^Darwin \u0026\u0026 CC=clang || CC=gcc\n$CC -pedantic -ansi -s -DGIF_EXTR=extern -xc gif_load.h -o gif_load.so \\\n    -shared -fPIC -Wl,--version-script=\u003c(echo \"{global:GIF_Load;local:*;};\")\n```\n\nWindows:\n```bash\nrem Only works when executed from the directory where gif_load.h resides\ndel gif_load.exp gif_load.lib gif_load.dll\ncl /LD /Zl /DGIF_EXTR=__declspec(dllexport) /Tc gif_load.h msvcrt.lib /Fegif_load.dll\n```\n\nThen the loading function can be called using CTypes\n([Python 2 reference](https://docs.python.org/2/library/ctypes.html),\n [Python 3 reference](https://docs.python.org/3/library/ctypes.html)):\n\n```python\nfrom PIL import Image\n\ndef GIF_Load(file):\n    from platform import system\n    from ctypes import string_at, Structure, c_long as cl, c_ubyte, \\\n                       py_object, pointer, POINTER as PT, CFUNCTYPE, CDLL\n    class GIF_WHDR(Structure): _fields_ = \\\n       [(\"xdim\", cl), (\"ydim\", cl), (\"clrs\", cl), (\"bkgd\", cl),\n        (\"tran\", cl), (\"intr\", cl), (\"mode\", cl), (\"frxd\", cl), (\"fryd\", cl),\n        (\"frxo\", cl), (\"fryo\", cl), (\"time\", cl), (\"ifrm\", cl), (\"nfrm\", cl),\n        (\"bptr\", PT(c_ubyte)), (\"cpal\", PT(c_ubyte))]\n    def intr(y, x, w, base, tran): base.paste(tran.crop((0, y, x, y + 1)), w)\n    def skew(i, r): return r \u003e\u003e ((7 - (i \u0026 2)) \u003e\u003e (1 + (i \u0026 1)))\n    def fill(w, d, p):\n        retn = Image.new(\"L\", d, w.bkgd) if (w.tran \u003c 0) else \\\n               Image.new(\"RGBA\", d)\n        if (w.tran \u003c 0):\n            retn.putpalette(p)\n        return retn\n    def WriteFunc(d, w):\n        cpal = string_at(w[0].cpal, w[0].clrs * 3)\n        list = d.contents.value[0]\n        if (len(list) == 0):\n            list.append(Image.new(\"RGBA\", (w[0].xdim, w[0].ydim)))\n        tail = len(list) - 1\n        base = Image.frombytes(\"L\", (w[0].frxd, w[0].fryd),\n                               string_at(w[0].bptr, w[0].frxd * w[0].fryd))\n        if (w[0].intr != 0):\n            tran = base.copy()\n            [intr(skew(y, y) + (skew(y, w[0].fryd - 1) + 1, 0)[(y \u0026 7) == 0],\n                  w[0].frxd, (0, y), base, tran) for y in range(w[0].fryd)]\n        tran = Image.eval(base, lambda indx: (255, 0)[indx == w[0].tran])\n        base.putpalette(cpal)\n        list[tail].paste(base, (w[0].frxo, w[0].fryo), tran)\n        list[tail].info = {\"delay\" : w[0].time}\n        if (w[0].ifrm != (w[0].nfrm - 1)):\n            trgt = (tail, d.contents.value[1])[w[0].mode == 3]\n            list.append(list[trgt].copy() if (trgt \u003e= 0) else\n                        fill(w[0], (w[0].xdim, w[0].ydim), cpal))\n            if (w[0].mode != 3):\n                d.contents.value[1] = w[0].ifrm\n            if (w[0].mode == 2):\n                list[tail + 1].paste(fill(w[0], (w[0].frxd, w[0].fryd), cpal),\n                                                (w[0].frxo, w[0].fryo))\n    try: file = open(file, \"rb\")\n    except IOError: return []\n    file.seek(0, 2)\n    size = file.tell()\n    file.seek(0, 0)\n    list = [[], -1]\n    CDLL((\"%s.so\", \"%s.dll\")[system() == \"Windows\"] % \"./gif_load\"). \\\n    GIF_Load(file.read(), size,\n             CFUNCTYPE(None, PT(py_object), PT(GIF_WHDR))(WriteFunc),\n             None, pointer(py_object(list)), 0)\n    file.close()\n    return list[0]\n\ndef GIF_Save(file, fext):\n    list = GIF_Load(\"%s.gif\" % file)\n    [pict.save(\"%s_f%d.%s\" % (file, indx, fext))\n     for (indx, pict) in enumerate(list)]\n\nGIF_Save(\"insert_gif_name_here_without_extension\", \"png\")\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhidefromkgb%2Fgif_load","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fhidefromkgb%2Fgif_load","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhidefromkgb%2Fgif_load/lists"}