| 1 | #ifndef GIF_LOAD_H  |
| 2 | #define GIF_LOAD_H  |
| 3 |   |
| 4 | /** gif_load: A slim, fast and header-only GIF loader written in C.  |
| 5 | Original author: hidefromkgb (hidefromkgb@gmail.com)  |
| 6 | _________________________________________________________________________  |
| 7 |   |
| 8 | This is free and unencumbered software released into the public domain.  |
| 9 |   |
| 10 | Anyone is free to copy, modify, publish, use, compile, sell, or  |
| 11 | distribute this software, either in source code form or as a compiled  |
| 12 | binary, for any purpose, commercial or non-commercial, and by any means.  |
| 13 |   |
| 14 | In jurisdictions that recognize copyright laws, the author or authors  |
| 15 | of this software dedicate any and all copyright interest in the  |
| 16 | software to the public domain. We make this dedication for the benefit  |
| 17 | of the public at large and to the detriment of our heirs and  |
| 18 | successors. We intend this dedication to be an overt act of  |
| 19 | relinquishment in perpetuity of all present and future rights to this  |
| 20 | software under copyright law.  |
| 21 |   |
| 22 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,  |
| 23 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF  |
| 24 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  |
| 25 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR  |
| 26 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,  |
| 27 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR  |
| 28 | OTHER DEALINGS IN THE SOFTWARE.  |
| 29 | _________________________________________________________________________  |
| 30 | **/  |
| 31 |   |
| 32 | #ifdef __cplusplus  |
| 33 | extern "C" {  |
| 34 | #endif  |
| 35 | #include <stdint.h> /** imports uint8_t, uint16_t and uint32_t **/  |
| 36 | #ifndef GIF_MGET  |
| 37 | #include <stdlib.h>  |
| 38 | #define GIF_MGET(m,s,a,c) m = (uint8_t*)realloc((c)? 0 : m, (c)? s : 0UL);  |
| 39 | #endif  |
| 40 | #ifndef GIF_BIGE  |
| 41 | #define GIF_BIGE 0  |
| 42 | #endif  |
| 43 | #ifndef GIF_EXTR  |
| 44 | #define GIF_EXTR static  |
| 45 | #endif  |
| 46 | #define _GIF_SWAP(h) ((GIF_BIGE)? ((uint16_t)(h << 8) | (h >> 8)) : h)  |
| 47 |   |
| 48 | #pragma pack(push, 1)  |
| 49 | struct GIF_WHDR { /** ======== frame writer info: ======== **/  |
| 50 | long xdim, ydim, clrs, /** global dimensions, palette size **/  |
| 51 | bkgd, tran, /** background index, transparent index **/  |
| 52 | intr, mode, /** interlace flag, frame blending mode **/  |
| 53 | frxd, fryd, frxo, fryo, /** current frame dimensions and offset **/  |
| 54 | time, ifrm, nfrm; /** delay, frame number, frame count **/  |
| 55 | uint8_t *bptr; /** frame pixel indices or metadata **/  |
| 56 | struct { /** [==== GIF RGB palette element: ====] **/  |
| 57 | uint8_t R, G, B; /** [color values - red, green, blue ] **/  |
| 58 | } *cpal; /** current palette **/  |
| 59 | };  |
| 60 | #pragma pack(pop)  |
| 61 |   |
| 62 | enum {GIF_NONE = 0, GIF_CURR = 1, GIF_BKGD = 2, GIF_PREV = 3};  |
| 63 |   |
| 64 | /** [ internal function, do not use ] **/  |
| 65 | static long _GIF_SkipChunk(uint8_t **buff, long size) {  |
| 66 | long skip;  |
| 67 |   |
| 68 | for (skip = 2, ++size, ++(*buff); ((size -= skip) > 0) && (skip > 1);  |
| 69 | *buff += (skip = 1 + **buff));  |
| 70 | return size;  |
| 71 | }  |
| 72 |   |
| 73 | /** [ internal function, do not use ] **/  |
| 74 | static long (unsigned gflg, uint8_t **buff, void **rpal,  |
| 75 | unsigned fflg, long *size, long flen) {  |
| 76 | if (flen && (!(*buff += flen) || ((*size -= flen) <= 0)))  |
| 77 | return -2; /** v--[ 0x80: "palette is present" flag ]--, **/  |
| 78 | if (flen && (fflg & 0x80)) { /** local palette has priority | **/  |
| 79 | *rpal = *buff; /** [ 3L: 3 uint8_t color channels ]--, | **/  |
| 80 | *buff += (flen = 2 << (fflg & 7)) * 3L; /** <--| | **/  |
| 81 | return ((*size -= flen * 3L) > 0)? flen : -1; /** <--' | **/  |
| 82 | } /** no local palette found, checking for the global one | **/  |
| 83 | return (gflg & 0x80)? (2 << (gflg & 7)) : 0; /** <-----' **/  |
| 84 | }  |
| 85 |   |
| 86 | /** [ internal function, do not use ] **/  |
| 87 | static long _GIF_LoadFrame(uint8_t **buff, long *size,  |
| 88 | uint8_t *bptr, uint8_t *blen) {  |
| 89 | typedef uint16_t GIF_H;  |
| 90 | const long GIF_HLEN = sizeof(GIF_H), /** to rid the scope of sizeof **/  |
| 91 | GIF_CLEN = 1 << 12; /** code table length: 4096 items **/  |
| 92 | GIF_H accu, mask; /** bit accumulator / bit mask **/  |
| 93 | long ctbl, iter, /** last code table index / index string iterator **/  |
| 94 | prev, curr, /** codes from the stream: previous / current **/  |
| 95 | ctsz, ccsz, /** code table bit sizes: min LZW / current **/  |
| 96 | bseq, bszc; /** counters: block sequence / bit size **/  |
| 97 | uint32_t *code = (uint32_t*)bptr - GIF_CLEN; /** code table pointer **/  |
| 98 |   |
| 99 | /** preparing initial values **/  |
| 100 | if ((--(*size) <= GIF_HLEN) || !*++(*buff))  |
| 101 | return -4; /** unexpected end of the stream: insufficient size **/  |
| 102 | mask = (GIF_H)((1 << (ccsz = (ctsz = *(*buff - 1)) + 1)) - 1);  |
| 103 | if ((ctsz < 2) || (ctsz > 8))  |
| 104 | return -3; /** min LZW size is out of its nominal [2; 8] bounds **/  |
| 105 | if ((ctbl = (1L << ctsz)) != (mask & _GIF_SWAP(*(GIF_H*)(*buff + 1))))  |
| 106 | return -2; /** initial code is not equal to min LZW size **/  |
| 107 | for (curr = ++ctbl; curr; code[--curr] = 0); /** actual color codes **/  |
| 108 |   |
| 109 | /** getting codes from stream (--size makes up for end-of-stream mark) **/  |
| 110 | for (--(*size), bszc = -ccsz, prev = curr = 0;  |
| 111 | ((*size -= (bseq = *(*buff)++) + 1) >= 0) && bseq; *buff += bseq)  |
| 112 | for (; bseq > 0; bseq -= GIF_HLEN, *buff += GIF_HLEN)  |
| 113 | for (accu = (GIF_H)(_GIF_SWAP(*(GIF_H*)*buff)  |
| 114 | & ((bseq < GIF_HLEN)? ((1U << (8 * bseq)) - 1U) : ~0U)),  |
| 115 | curr |= accu << (ccsz + bszc), accu = (GIF_H)(accu >> -bszc),  |
| 116 | bszc += 8 * ((bseq < GIF_HLEN)? bseq : GIF_HLEN);  |
| 117 | bszc >= 0; bszc -= ccsz, prev = curr, curr = accu,  |
| 118 | accu = (GIF_H)(accu >> ccsz))  |
| 119 | if (((curr &= mask) & ~1L) == (1L << ctsz)) {  |
| 120 | if (~(ctbl = curr + 1) & 1) /** end-of-data code (ED). **/  |
| 121 | /** -1: no end-of-stream mark after ED; 1: decoded **/  |
| 122 | return (*((*buff += bseq + 1) - 1))? -1 : 1;  |
| 123 | mask = (GIF_H)((1 << (ccsz = ctsz + 1)) - 1);  |
| 124 | } /** ^- table drop code (TD). TD = 1 << ctsz, ED = TD + 1 **/  |
| 125 | else { /** single-pixel (SP) or multi-pixel (MP) code. **/  |
| 126 | if (ctbl < GIF_CLEN) { /** is the code table full? **/  |
| 127 | if ((ctbl == mask) && (ctbl < GIF_CLEN - 1)) {  |
| 128 | mask = (GIF_H)(mask + mask + 1);  |
| 129 | ccsz++; /** yes; extending **/  |
| 130 | } /** prev = TD? => curr < ctbl = prev **/  |
| 131 | code[ctbl] = (uint32_t)prev + (code[prev] & 0xFFF000);  |
| 132 | } /** appending SP / MP decoded pixels to the frame **/  |
| 133 | prev = (long)code[iter = (ctbl > curr)? curr : prev];  |
| 134 | if ((bptr += (prev = (prev >> 12) & 0xFFF)) > blen)  |
| 135 | continue; /** skipping pixels above frame capacity **/  |
| 136 | for (prev++; (iter &= 0xFFF) >> ctsz;  |
| 137 | *bptr-- = (uint8_t)((iter = (long)code[iter]) >> 24));  |
| 138 | (bptr += prev)[-prev] = (uint8_t)iter;  |
| 139 | if (ctbl < GIF_CLEN) { /** appending the code table **/  |
| 140 | if (ctbl == curr)  |
| 141 | *bptr++ = (uint8_t)iter;  |
| 142 | else if (ctbl < curr)  |
| 143 | return -5; /** wrong code in the stream **/  |
| 144 | code[ctbl++] += ((uint32_t)iter << 24) + 0x1000;  |
| 145 | }  |
| 146 | } /** 0: no ED before end-of-stream mark; -4: see above **/  |
| 147 | return (++(*size) >= 0)? 0 : -4; /** ^- N.B.: 0 error is recoverable **/  |
| 148 | }  |
| 149 |   |
| 150 | /** _________________________________________________________________________  |
| 151 | The main loading function. Returns the total number of frames if the data  |
| 152 | includes proper GIF ending, and otherwise it returns the number of frames  |
| 153 | loaded per current call, multiplied by -1. So, the data may be incomplete  |
| 154 | and in this case the function can be called again when more data arrives,  |
| 155 | just remember to keep SKIP up to date.  |
| 156 | _________________________________________________________________________  |
| 157 | DATA: raw data chunk, may be partial  |
| 158 | SIZE: size of the data chunk that`s currently present  |
| 159 | GWFR: frame writer function, MANDATORY  |
| 160 | EAMF: metadata reader function, set to 0 if not needed  |
| 161 | ANIM: implementation-specific data (e.g. a structure or a pointer to it)  |
| 162 | SKIP: number of frames to skip before resuming  |
| 163 | **/  |
| 164 | GIF_EXTR long GIF_Load(void *data, long size,  |
| 165 | void (*gwfr)(void*, struct GIF_WHDR*),  |
| 166 | void (*eamf)(void*, struct GIF_WHDR*),  |
| 167 | void *anim, long skip) {  |
| 168 | const long GIF_BLEN = (1 << 12) * sizeof(uint32_t);  |
| 169 | const uint8_t GIF_EHDM = 0x21, /** extension header mark **/  |
| 170 | GIF_FHDM = 0x2C, /** frame header mark **/  |
| 171 | GIF_EOFM = 0x3B, /** end-of-file mark **/  |
| 172 | GIF_EGCM = 0xF9, /** extension: graphics control mark **/  |
| 173 | GIF_EAMM = 0xFF; /** extension: app metadata mark **/  |
| 174 | #pragma pack(push, 1)  |
| 175 | struct GIF_GHDR { /** ========== GLOBAL GIF HEADER: ========== **/  |
| 176 | uint8_t head[6]; /** 'GIF87a' / 'GIF89a' header signature **/  |
| 177 | uint16_t xdim, ydim; /** total image width, total image height **/  |
| 178 | uint8_t flgs; /** FLAGS:  |
| 179 | GlobalPlt bit 7 1: global palette exists  |
| 180 | 0: local in each frame  |
| 181 | ClrRes bit 6-4 bits/channel = ClrRes+1  |
| 182 | [reserved] bit 3 0  |
| 183 | PixelBits bit 2-0 |Plt| = 2 * 2^PixelBits  |
| 184 | **/  |
| 185 | uint8_t bkgd, aspr; /** background color index, aspect ratio **/  |
| 186 | } *ghdr = (struct GIF_GHDR*)data;  |
| 187 | struct GIF_FHDR { /** ======= GIF FRAME MASTER HEADER: ======= **/  |
| 188 | uint16_t frxo, fryo; /** offset of this frame in a "full" image **/  |
| 189 | uint16_t frxd, fryd; /** frame width, frame height **/  |
| 190 | uint8_t flgs; /** FLAGS:  |
| 191 | LocalPlt bit 7 1: local palette exists  |
| 192 | 0: global is used  |
| 193 | Interlaced bit 6 1: interlaced frame  |
| 194 | 0: non-interlaced frame  |
| 195 | Sorted bit 5 usually 0  |
| 196 | [reserved] bit 4-3 [undefined]  |
| 197 | PixelBits bit 2-0 |Plt| = 2 * 2^PixelBits  |
| 198 | **/  |
| 199 | } *fhdr;  |
| 200 | struct GIF_EGCH { /** ==== [EXT] GRAPHICS CONTROL HEADER: ==== **/  |
| 201 | uint8_t flgs; /** FLAGS:  |
| 202 | [reserved] bit 7-5 [undefined]  |
| 203 | BlendMode bit 4-2 000: not set; static GIF  |
| 204 | 001: leave result as is  |
| 205 | 010: restore background  |
| 206 | 011: restore previous  |
| 207 | 1--: [undefined]  |
| 208 | UserInput bit 1 1: show frame till input  |
| 209 | 0: default; ~99% of GIFs  |
| 210 | TransColor bit 0 1: got transparent color  |
| 211 | 0: frame is fully opaque  |
| 212 | **/  |
| 213 | uint16_t time; /** delay in GIF time units; 1 unit = 10 ms **/  |
| 214 | uint8_t tran; /** transparent color index **/  |
| 215 | } *egch = 0;  |
| 216 | #pragma pack(pop)  |
| 217 | struct GIF_WHDR wtmp, whdr = {0};  |
| 218 | long desc, blen;  |
| 219 | uint8_t *buff;  |
| 220 |   |
| 221 | /** checking if the stream is not empty and has a 'GIF8[79]a' signature,  |
| 222 | the data has sufficient size and frameskip value is non-negative **/  |
| 223 | if (!ghdr || (size <= (long)sizeof(*ghdr)) || (*(buff = ghdr->head) != 71)  |
| 224 | || (buff[1] != 73) || (buff[2] != 70) || (buff[3] != 56) || (skip < 0)  |
| 225 | || ((buff[4] != 55) && (buff[4] != 57)) || (buff[5] != 97) || !gwfr)  |
| 226 | return 0;  |
| 227 |   |
| 228 | buff = (uint8_t*)(ghdr + 1) /** skipping the global header and palette **/  |
| 229 | + _GIF_LoadHeader(ghdr->flgs, 0, 0, 0, 0, 0L) * 3L;  |
| 230 | if ((size -= long(buff - (uint8_t*)ghdr)) <= 0)  |
| 231 | return 0;  |
| 232 |   |
| 233 | whdr.xdim = _GIF_SWAP(ghdr->xdim);  |
| 234 | whdr.ydim = _GIF_SWAP(ghdr->ydim);  |
| 235 | for (whdr.bptr = buff, whdr.bkgd = ghdr->bkgd, blen = --size;  |
| 236 | (blen >= 0) && ((desc = *whdr.bptr++) != GIF_EOFM); /** sic: '>= 0' **/  |
| 237 | blen = _GIF_SkipChunk(&whdr.bptr, blen) - 1) /** count all frames **/  |
| 238 | if (desc == GIF_FHDM) {  |
| 239 | fhdr = (struct GIF_FHDR*)whdr.bptr;  |
| 240 | if (_GIF_LoadHeader(ghdr->flgs, &whdr.bptr, (void**)&whdr.cpal,  |
| 241 | fhdr->flgs, &blen, sizeof(*fhdr)) <= 0)  |
| 242 | break;  |
| 243 | whdr.frxd = _GIF_SWAP(fhdr->frxd);  |
| 244 | whdr.fryd = _GIF_SWAP(fhdr->fryd);  |
| 245 | whdr.frxo = (whdr.frxd > whdr.frxo)? whdr.frxd : whdr.frxo;  |
| 246 | whdr.fryo = (whdr.fryd > whdr.fryo)? whdr.fryd : whdr.fryo;  |
| 247 | whdr.ifrm++;  |
| 248 | }  |
| 249 | blen = whdr.frxo * whdr.fryo * (long)sizeof(*whdr.bptr);  |
| 250 | GIF_MGET(whdr.bptr, (unsigned long)(blen + GIF_BLEN + 2), anim, 1)  |
| 251 | whdr.nfrm = (desc != GIF_EOFM)? -whdr.ifrm : whdr.ifrm;  |
| 252 | for (whdr.bptr += GIF_BLEN, whdr.ifrm = -1; blen /** load all frames **/  |
| 253 | && (skip < ((whdr.nfrm < 0)? -whdr.nfrm : whdr.nfrm)) && (size >= 0);  |
| 254 | size = (desc != GIF_EOFM)? ((desc != GIF_FHDM) || (skip > whdr.ifrm))?  |
| 255 | _GIF_SkipChunk(&buff, size) - 1 : size - 1 : -1)  |
| 256 | if ((desc = *buff++) == GIF_FHDM) { /** found a frame **/  |
| 257 | whdr.intr = !!((fhdr = (struct GIF_FHDR*)buff)->flgs & 0x40);  |
| 258 | *(void**)&whdr.cpal = (void*)(ghdr + 1); /** interlaced? -^ **/  |
| 259 | whdr.clrs = _GIF_LoadHeader(ghdr->flgs, &buff, (void**)&whdr.cpal,  |
| 260 | fhdr->flgs, &size, sizeof(*fhdr));  |
| 261 | if ((skip <= ++whdr.ifrm) && ((whdr.clrs <= 0)  |
| 262 | || (_GIF_LoadFrame(&buff, &size,  |
| 263 | whdr.bptr, whdr.bptr + blen) < 0)))  |
| 264 | size = -(whdr.ifrm--) - 1; /** failed to load the frame **/  |
| 265 | else if (skip <= whdr.ifrm) {  |
| 266 | whdr.frxd = _GIF_SWAP(fhdr->frxd);  |
| 267 | whdr.fryd = _GIF_SWAP(fhdr->fryd);  |
| 268 | whdr.frxo = _GIF_SWAP(fhdr->frxo);  |
| 269 | whdr.fryo = _GIF_SWAP(fhdr->fryo);  |
| 270 | whdr.time = (egch)? _GIF_SWAP(egch->time) : 0;  |
| 271 | whdr.tran = (egch && (egch->flgs & 0x01))? egch->tran : -1;  |
| 272 | whdr.time = (egch && (egch->flgs & 0x02))? -whdr.time - 1  |
| 273 | : whdr.time;  |
| 274 | whdr.mode = (egch && !(egch->flgs & 0x10))?  |
| 275 | (egch->flgs & 0x0C) >> 2 : GIF_NONE;  |
| 276 | egch = 0;  |
| 277 | wtmp = whdr;  |
| 278 | gwfr(anim, &wtmp); /** passing the frame to the caller **/  |
| 279 | }  |
| 280 | }  |
| 281 | else if (desc == GIF_EHDM) { /** found an extension **/  |
| 282 | if (*buff == GIF_EGCM) /** graphics control ext. **/  |
| 283 | egch = (struct GIF_EGCH*)(buff + 1 + 1);  |
| 284 | else if ((*buff == GIF_EAMM) && eamf) { /** app metadata ext. **/  |
| 285 | wtmp = whdr;  |
| 286 | wtmp.bptr = buff + 1 + 1; /** just passing the raw chunk **/  |
| 287 | eamf(anim, &wtmp);  |
| 288 | }  |
| 289 | }  |
| 290 | whdr.bptr -= GIF_BLEN; /** for excess pixel codes ----v (here & above) **/  |
| 291 | GIF_MGET(whdr.bptr, (unsigned long)(blen + GIF_BLEN + 2), anim, 0)  |
| 292 | return (whdr.nfrm < 0)? (skip - whdr.ifrm - 1) : (whdr.ifrm + 1);  |
| 293 | }  |
| 294 |   |
| 295 | #undef _GIF_SWAP  |
| 296 | #ifdef __cplusplus  |
| 297 | }  |
| 298 | #endif  |
| 299 | #endif /** GIF_LOAD_H **/  |
| 300 | |