Use 100 ms as the default GIF animation delay if it's not specified, or very small.
This is compatible with IE, Mozilla, and Qt.
2 SDL_image: An example image loading library for use with SDL
3 Copyright (C) 1997-2019 Sam Lantinga <slouken@libsdl.org>
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
22 /* This is a GIF image file loading framework */
24 #include "SDL_image.h"
28 /* Code from here to end of file has been adapted from XPaint: */
29 /* +-------------------------------------------------------------------+ */
30 /* | Copyright 1990, 1991, 1993 David Koblas. | */
31 /* | Copyright 1996 Torsten Martinsen. | */
32 /* | Permission to use, copy, modify, and distribute this software | */
33 /* | and its documentation for any purpose and without fee is hereby | */
34 /* | granted, provided that the above copyright notice appear in all | */
35 /* | copies and that both that copyright notice and this permission | */
36 /* | notice appear in supporting documentation. This software is | */
37 /* | provided "as is" without express or implied warranty. | */
38 /* +-------------------------------------------------------------------+ */
40 /* Adapted for use in SDL by Sam Lantinga -- 7/20/98 */
41 /* Changes to work with SDL:
43 Include SDL header file
44 Use SDL_Surface rather than xpaint Image structure
45 Define SDL versions of RWSetMsg(), ImageNewCmap() and ImageSetCmap()
49 #define Image SDL_Surface
50 #define RWSetMsg IMG_SetError
51 #define ImageNewCmap(w, h, s) SDL_CreateRGBSurface(SDL_SWSURFACE,w,h,8,0,0,0,0)
52 #define ImageSetCmap(s, i, R, G, B) do { \
53 s->format->palette->colors[i].r = R; \
54 s->format->palette->colors[i].g = G; \
55 s->format->palette->colors[i].b = B; \
59 #define GIF_DISPOSE_NA 0 /* No disposal specified */
60 #define GIF_DISPOSE_NONE 1 /* Do not dispose */
61 #define GIF_DISPOSE_RESTORE_BACKGROUND 2 /* Restore to background */
62 #define GIF_DISPOSE_RESTORE_PREVIOUS 3 /* Restore to previous */
64 #define MAXCOLORMAPSIZE 256
73 #define MAX_LWZ_BITS 12
75 #define INTERLACE 0x40
76 #define LOCALCOLORMAP 0x80
77 #define BitSet(byte, bit) (((byte) & (bit)) == (bit))
79 #define ReadOK(file,buffer,len) SDL_RWread(file, buffer, len, 1)
81 #define LM_to_uint(a,b) (((b)<<8)|(a))
87 unsigned char ColorMap[3][MAXCOLORMAPSIZE];
88 unsigned int BitPixel;
89 unsigned int ColorResolution;
90 unsigned int Background;
91 unsigned int AspectRatio;
102 unsigned char buf[280];
103 int curbit, lastbit, done, last_byte;
106 int code_size, set_code_size;
107 int max_code, max_code_size;
108 int firstcode, oldcode;
109 int clear_code, end_code;
110 int table[2][(1 << MAX_LWZ_BITS)];
111 int stack[(1 << (MAX_LWZ_BITS)) * 2], *sp;
130 static int ReadColorMap(SDL_RWops * src, int number,
131 unsigned char buffer[3][MAXCOLORMAPSIZE], int *flag);
132 static int DoExtension(SDL_RWops * src, int label, State_t * state);
133 static int GetDataBlock(SDL_RWops * src, unsigned char *buf, State_t * state);
134 static int GetCode(SDL_RWops * src, int code_size, int flag, State_t * state);
135 static int LWZReadByte(SDL_RWops * src, int flag, int input_code_size, State_t * state);
136 static Image *ReadImage(SDL_RWops * src, int len, int height, int,
137 unsigned char cmap[3][MAXCOLORMAPSIZE],
138 int gray, int interlace, int ignore, State_t * state);
140 static SDL_bool NormalizeFrames(Frame_t *frames, int count)
144 int lastDispose = GIF_DISPOSE_RESTORE_BACKGROUND;
150 if (SDL_HasColorKey(frames[0].image)) {
151 image = SDL_ConvertSurfaceFormat(frames[0].image, SDL_PIXELFORMAT_ARGB8888, 0);
153 image = SDL_ConvertSurfaceFormat(frames[0].image, SDL_PIXELFORMAT_RGB888, 0);
159 fill = SDL_MapRGBA(image->format, 0, 0, 0, SDL_ALPHA_TRANSPARENT);
166 for (i = 0; i < count; ++i) {
167 switch (lastDispose) {
168 case GIF_DISPOSE_RESTORE_BACKGROUND:
169 SDL_FillRect(image, &rect, fill);
171 case GIF_DISPOSE_RESTORE_PREVIOUS:
172 SDL_BlitSurface(frames[iRestore].image, &rect, image, &rect);
178 if (frames[i].disposal != GIF_DISPOSE_RESTORE_PREVIOUS) {
182 rect.x = (Sint16)frames[i].x;
183 rect.y = (Sint16)frames[i].y;
184 rect.w = frames[i].image->w;
185 rect.h = frames[i].image->h;
186 SDL_BlitSurface(frames[i].image, NULL, image, &rect);
188 SDL_FreeSurface(frames[i].image);
189 frames[i].image = SDL_DuplicateSurface(image);
190 if (!frames[i].image) {
194 lastDispose = frames[i].disposal;
197 SDL_FreeSurface( image );
203 IMG_LoadGIF_RW_Internal(SDL_RWops *src, SDL_bool load_anim)
206 unsigned char buf[16];
208 unsigned char localColorMap[3][MAXCOLORMAPSIZE];
210 int useGlobalColormap;
215 Frame_t *frames, *frame;
218 state.ZeroDataBlock = FALSE;
225 start = SDL_RWtell(src);
227 anim = (Anim_t *)SDL_calloc(1, sizeof(*anim));
233 if (!ReadOK(src, buf, 6)) {
234 RWSetMsg("error reading magic number");
237 if (SDL_strncmp((char *) buf, "GIF", 3) != 0) {
238 RWSetMsg("not a GIF file");
241 SDL_memcpy(version, (char *) buf + 3, 3);
244 if ((SDL_strcmp(version, "87a") != 0) && (SDL_strcmp(version, "89a") != 0)) {
245 RWSetMsg("bad version number, not '87a' or '89a'");
248 state.Gif89.transparent = -1;
249 state.Gif89.delayTime = -1;
250 state.Gif89.inputFlag = -1;
251 state.Gif89.disposal = GIF_DISPOSE_NA;
253 if (!ReadOK(src, buf, 7)) {
254 RWSetMsg("failed to read screen descriptor");
257 state.GifScreen.Width = LM_to_uint(buf[0], buf[1]);
258 state.GifScreen.Height = LM_to_uint(buf[2], buf[3]);
259 state.GifScreen.BitPixel = 2 << (buf[4] & 0x07);
260 state.GifScreen.ColorResolution = (((buf[4] & 0x70) >> 3) + 1);
261 state.GifScreen.Background = buf[5];
262 state.GifScreen.AspectRatio = buf[6];
264 if (BitSet(buf[4], LOCALCOLORMAP)) { /* Global Colormap */
265 if (ReadColorMap(src, state.GifScreen.BitPixel,
266 state.GifScreen.ColorMap, &state.GifScreen.GrayScale)) {
267 RWSetMsg("error reading global colormap");
272 if (!ReadOK(src, &c, 1)) {
273 RWSetMsg("EOF / read error on image data");
276 if (c == ';') { /* GIF terminator */
279 if (c == '!') { /* Extension */
280 if (!ReadOK(src, &c, 1)) {
281 RWSetMsg("EOF / read error on extention function code");
284 DoExtension(src, c, &state);
287 if (c != ',') { /* Not a valid start character */
291 if (!ReadOK(src, buf, 9)) {
292 RWSetMsg("couldn't read left/top/width/height");
295 useGlobalColormap = !BitSet(buf[8], LOCALCOLORMAP);
297 bitPixel = 1 << ((buf[8] & 0x07) + 1);
299 if (!useGlobalColormap) {
300 if (ReadColorMap(src, bitPixel, localColorMap, &grayScale)) {
301 RWSetMsg("error reading local colormap");
304 image = ReadImage(src, LM_to_uint(buf[4], buf[5]),
305 LM_to_uint(buf[6], buf[7]),
306 bitPixel, localColorMap, grayScale,
307 BitSet(buf[8], INTERLACE),
310 image = ReadImage(src, LM_to_uint(buf[4], buf[5]),
311 LM_to_uint(buf[6], buf[7]),
312 state.GifScreen.BitPixel, state.GifScreen.ColorMap,
313 state.GifScreen.GrayScale, BitSet(buf[8], INTERLACE),
318 if (state.Gif89.transparent >= 0) {
319 SDL_SetColorKey(image, SDL_TRUE, state.Gif89.transparent);
322 frames = (Frame_t *)SDL_realloc(anim->frames, (anim->count + 1) * sizeof(*anim->frames));
328 anim->frames = frames;
329 frame = &anim->frames[anim->count - 1];
331 frame->image = image;
332 frame->x = LM_to_uint(buf[0], buf[1]);
333 frame->y = LM_to_uint(buf[2], buf[3]);
334 frame->disposal = state.Gif89.disposal;
335 frame->delay = (state.Gif89.delayTime < 2 ? 10 : state.Gif89.delayTime) * 10;
338 /* We only need one frame, we're done */
345 if (anim->count > 1) {
346 /* Normalize the frames */
347 if (!NormalizeFrames(anim->frames, anim->count)) {
349 for (i = 0; i < anim->count; ++i) {
350 SDL_FreeSurface(anim->frames[i].image);
355 if (anim->count == 0) {
356 SDL_free(anim->frames);
364 ReadColorMap(SDL_RWops *src, int number,
365 unsigned char buffer[3][MAXCOLORMAPSIZE], int *gray)
368 unsigned char rgb[3];
373 for (i = 0; i < number; ++i) {
374 if (!ReadOK(src, rgb, sizeof(rgb))) {
375 RWSetMsg("bad colormap");
378 buffer[CM_RED][i] = rgb[0];
379 buffer[CM_GREEN][i] = rgb[1];
380 buffer[CM_BLUE][i] = rgb[2];
381 flag &= (rgb[0] == rgb[1] && rgb[1] == rgb[2]);
386 *gray = (number == 2) ? PBM_TYPE : PGM_TYPE;
397 DoExtension(SDL_RWops *src, int label, State_t * state)
399 unsigned char buf[256];
403 case 0x01: /* Plain Text Extension */
404 str = "Plain Text Extension";
406 case 0xff: /* Application Extension */
407 str = "Application Extension";
409 case 0xfe: /* Comment Extension */
410 str = "Comment Extension";
411 while (GetDataBlock(src, (unsigned char *) buf, state) > 0)
414 case 0xf9: /* Graphic Control Extension */
415 str = "Graphic Control Extension";
416 (void) GetDataBlock(src, (unsigned char *) buf, state);
417 state->Gif89.disposal = (buf[0] >> 2) & 0x7;
418 state->Gif89.inputFlag = (buf[0] >> 1) & 0x1;
419 state->Gif89.delayTime = LM_to_uint(buf[1], buf[2]);
420 if ((buf[0] & 0x1) != 0)
421 state->Gif89.transparent = buf[3];
423 while (GetDataBlock(src, (unsigned char *) buf, state) > 0)
428 SDL_snprintf(str, 256, "UNKNOWN (0x%02x)", label);
432 while (GetDataBlock(src, (unsigned char *) buf, state) > 0)
439 GetDataBlock(SDL_RWops *src, unsigned char *buf, State_t * state)
443 if (!ReadOK(src, &count, 1)) {
444 /* pm_message("error in getting DataBlock size" ); */
447 state->ZeroDataBlock = count == 0;
449 if ((count != 0) && (!ReadOK(src, buf, count))) {
450 /* pm_message("error in reading DataBlock" ); */
457 GetCode(SDL_RWops *src, int code_size, int flag, State_t * state)
468 if ((state->curbit + code_size) >= state->lastbit) {
470 if (state->curbit >= state->lastbit)
471 RWSetMsg("ran off the end of my bits");
474 state->buf[0] = state->buf[state->last_byte - 2];
475 state->buf[1] = state->buf[state->last_byte - 1];
477 if ((ret = GetDataBlock(src, &state->buf[2], state)) > 0)
478 count = (unsigned char) ret;
484 state->last_byte = 2 + count;
485 state->curbit = (state->curbit - state->lastbit) + 16;
486 state->lastbit = (2 + count) * 8;
489 for (i = state->curbit, j = 0; j < code_size; ++i, ++j)
490 ret |= ((state->buf[i / 8] & (1 << (i % 8))) != 0) << j;
492 state->curbit += code_size;
498 LWZReadByte(SDL_RWops *src, int flag, int input_code_size, State_t * state)
503 /* Fixed buffer overflow found by Michael Skladnikiewicz */
504 if (input_code_size > MAX_LWZ_BITS)
508 state->set_code_size = input_code_size;
509 state->code_size = state->set_code_size + 1;
510 state->clear_code = 1 << state->set_code_size;
511 state->end_code = state->clear_code + 1;
512 state->max_code_size = 2 * state->clear_code;
513 state->max_code = state->clear_code + 2;
515 GetCode(src, 0, TRUE, state);
519 for (i = 0; i < state->clear_code; ++i) {
520 state->table[0][i] = 0;
521 state->table[1][i] = i;
523 state->table[1][0] = 0;
524 for (; i < (1 << MAX_LWZ_BITS); ++i)
525 state->table[0][i] = 0;
527 state->sp = state->stack;
530 } else if (state->fresh) {
531 state->fresh = FALSE;
533 state->firstcode = state->oldcode = GetCode(src, state->code_size, FALSE, state);
534 } while (state->firstcode == state->clear_code);
535 return state->firstcode;
537 if (state->sp > state->stack)
540 while ((code = GetCode(src, state->code_size, FALSE, state)) >= 0) {
541 if (code == state->clear_code) {
542 for (i = 0; i < state->clear_code; ++i) {
543 state->table[0][i] = 0;
544 state->table[1][i] = i;
546 for (; i < (1 << MAX_LWZ_BITS); ++i)
547 state->table[0][i] = state->table[1][i] = 0;
548 state->code_size = state->set_code_size + 1;
549 state->max_code_size = 2 * state->clear_code;
550 state->max_code = state->clear_code + 2;
551 state->sp = state->stack;
552 state->firstcode = state->oldcode = GetCode(src, state->code_size, FALSE, state);
553 return state->firstcode;
554 } else if (code == state->end_code) {
556 unsigned char buf[260];
558 if (state->ZeroDataBlock)
561 while ((count = GetDataBlock(src, buf, state)) > 0)
566 * pm_message("missing EOD in data stream (common occurence)");
573 if (code >= state->max_code) {
574 *state->sp++ = state->firstcode;
575 code = state->oldcode;
577 while (code >= state->clear_code) {
578 /* Guard against buffer overruns */
579 if (code < 0 || code >= (1 << MAX_LWZ_BITS)) {
580 RWSetMsg("invalid LWZ data");
583 *state->sp++ = state->table[1][code];
584 if (code == state->table[0][code]) {
585 RWSetMsg("circular table entry BIG ERROR");
588 code = state->table[0][code];
591 /* Guard against buffer overruns */
592 if (code < 0 || code >= (1 << MAX_LWZ_BITS)) {
593 RWSetMsg("invalid LWZ data");
596 *state->sp++ = state->firstcode = state->table[1][code];
598 if ((code = state->max_code) < (1 << MAX_LWZ_BITS)) {
599 state->table[0][code] = state->oldcode;
600 state->table[1][code] = state->firstcode;
602 if ((state->max_code >= state->max_code_size) &&
603 (state->max_code_size < (1 << MAX_LWZ_BITS))) {
604 state->max_code_size *= 2;
608 state->oldcode = incode;
610 if (state->sp > state->stack)
617 ReadImage(SDL_RWops * src, int len, int height, int cmapSize,
618 unsigned char cmap[3][MAXCOLORMAPSIZE],
619 int gray, int interlace, int ignore, State_t * state)
624 int xpos = 0, ypos = 0, pass = 0;
626 (void) gray; /* unused */
629 ** Initialize the compression routines
631 if (!ReadOK(src, &c, 1)) {
632 RWSetMsg("EOF / read error on image data");
635 if (LWZReadByte(src, TRUE, c, state) < 0) {
636 RWSetMsg("error reading image");
640 ** If this is an "uninteresting picture" ignore it.
643 while (LWZReadByte(src, FALSE, c, state) >= 0)
647 image = ImageNewCmap(len, height, cmapSize);
649 for (i = 0; i < cmapSize; i++)
650 ImageSetCmap(image, i, cmap[CM_RED][i],
651 cmap[CM_GREEN][i], cmap[CM_BLUE][i]);
653 while ((v = LWZReadByte(src, FALSE, c, state)) >= 0) {
654 ((Uint8 *)image->pixels)[xpos + ypos * image->pitch] = (Uint8)v;
672 if (ypos >= height) {
701 /* Load a GIF type animation from an SDL datasource */
702 IMG_Animation *IMG_LoadGIFAnimation_RW(SDL_RWops *src)
704 Anim_t *internal = IMG_LoadGIF_RW_Internal(src, SDL_TRUE);
706 IMG_Animation *anim = (IMG_Animation *)SDL_malloc(sizeof(*anim));
708 anim->w = internal->frames[0].image->w;
709 anim->h = internal->frames[0].image->h;
710 anim->count = internal->count;
712 anim->frames = (SDL_Surface **)SDL_calloc(anim->count, sizeof(*anim->frames));
713 anim->delays = (int *)SDL_calloc(anim->count, sizeof(*anim->delays));
715 if (anim->frames && anim->delays) {
718 for (i = 0; i < anim->count; ++i) {
719 anim->frames[i] = internal->frames[i].image;
720 anim->delays[i] = internal->frames[i].delay;
723 IMG_FreeAnimation(anim);
730 SDL_free(internal->frames);
739 /* Load a GIF type animation from an SDL datasource */
740 IMG_Animation *IMG_LoadGIFAnimation_RW(SDL_RWops *src)
745 #endif /* LOAD_GIF */
747 #if !defined(__APPLE__) || defined(SDL_IMAGE_USE_COMMON_BACKEND)
751 /* See if an image is contained in a data source */
752 int IMG_isGIF(SDL_RWops *src)
760 start = SDL_RWtell(src);
762 if ( SDL_RWread(src, magic, sizeof(magic), 1) ) {
763 if ( (SDL_strncmp(magic, "GIF", 3) == 0) &&
764 ((SDL_memcmp(magic + 3, "87a", 3) == 0) ||
765 (SDL_memcmp(magic + 3, "89a", 3) == 0)) ) {
769 SDL_RWseek(src, start, RW_SEEK_SET);
773 /* Load a GIF type image from an SDL datasource */
774 SDL_Surface *IMG_LoadGIF_RW(SDL_RWops *src)
776 SDL_Surface *image = NULL;
777 Anim_t *internal = IMG_LoadGIF_RW_Internal(src, SDL_FALSE);
779 image = internal->frames[0].image;
780 SDL_free(internal->frames);
788 #pragma warning(disable : 4100) /* warning C4100: 'op' : unreferenced formal parameter */
791 /* See if an image is contained in a data source */
792 int IMG_isGIF(SDL_RWops *src)
797 /* Load a GIF type image from an SDL datasource */
798 SDL_Surface *IMG_LoadGIF_RW(SDL_RWops *src)
803 #endif /* LOAD_GIF */
805 #endif /* !defined(__APPLE__) || defined(SDL_IMAGE_USE_COMMON_BACKEND) */