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 | |