IMG_gif.c
author Sam Lantinga <slouken@libsdl.org>
Sun, 04 Jan 2004 17:41:55 +0000
changeset 97 e1161bd417c4
parent 53 96b084473b47
child 117 e613cf987897
permissions -rw-r--r--
Updated copyright information for 2004 (Happy New Year!)
     1 /*
     2     SDL_image:  An example image loading library for use with SDL
     3     Copyright (C) 1999-2004 Sam Lantinga
     4 
     5     This library is free software; you can redistribute it and/or
     6     modify it under the terms of the GNU Library General Public
     7     License as published by the Free Software Foundation; either
     8     version 2 of the License, or (at your option) any later version.
     9 
    10     This library is distributed in the hope that it will be useful,
    11     but WITHOUT ANY WARRANTY; without even the implied warranty of
    12     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    13     Library General Public License for more details.
    14 
    15     You should have received a copy of the GNU Library General Public
    16     License along with this library; if not, write to the Free
    17     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
    18 
    19     Sam Lantinga
    20     slouken@libsdl.org
    21 */
    22 
    23 /* $Id$ */
    24 
    25 /* This is a GIF image file loading framework */
    26 
    27 #include <stdio.h>
    28 #include <string.h>
    29 
    30 #include "SDL_image.h"
    31 
    32 #ifdef LOAD_GIF
    33 
    34 /* See if an image is contained in a data source */
    35 int IMG_isGIF(SDL_RWops *src)
    36 {
    37 	int is_GIF;
    38 	char magic[6];
    39 
    40 	is_GIF = 0;
    41 	if ( SDL_RWread(src, magic, 6, 1) ) {
    42 		if ( (strncmp(magic, "GIF", 3) == 0) &&
    43 		     ((memcmp(magic + 3, "87a", 3) == 0) ||
    44 		      (memcmp(magic + 3, "89a", 3) == 0)) ) {
    45 			is_GIF = 1;
    46 		}
    47 	}
    48 	return(is_GIF);
    49 }
    50 
    51 /* Code from here to end of file has been adapted from XPaint:           */
    52 /* +-------------------------------------------------------------------+ */
    53 /* | Copyright 1990, 1991, 1993 David Koblas.			       | */
    54 /* | Copyright 1996 Torsten Martinsen.				       | */
    55 /* |   Permission to use, copy, modify, and distribute this software   | */
    56 /* |   and its documentation for any purpose and without fee is hereby | */
    57 /* |   granted, provided that the above copyright notice appear in all | */
    58 /* |   copies and that both that copyright notice and this permission  | */
    59 /* |   notice appear in supporting documentation.  This software is    | */
    60 /* |   provided "as is" without express or implied warranty.	       | */
    61 /* +-------------------------------------------------------------------+ */
    62 
    63 /* Adapted for use in SDL by Sam Lantinga -- 7/20/98 */
    64 #define USED_BY_SDL
    65 
    66 #include <stdio.h>
    67 #include <string.h>
    68 
    69 #ifdef USED_BY_SDL
    70 /* Changes to work with SDL:
    71 
    72    Include SDL header file
    73    Use SDL_Surface rather than xpaint Image structure
    74    Define SDL versions of RWSetMsg(), ImageNewCmap() and ImageSetCmap()
    75 */
    76 #include "SDL.h"
    77 
    78 #define Image			SDL_Surface
    79 #define RWSetMsg		IMG_SetError
    80 #define ImageNewCmap(w, h, s)	SDL_AllocSurface(SDL_SWSURFACE,w,h,8,0,0,0,0)
    81 #define ImageSetCmap(s, i, R, G, B) do { \
    82 				s->format->palette->colors[i].r = R; \
    83 				s->format->palette->colors[i].g = G; \
    84 				s->format->palette->colors[i].b = B; \
    85 			} while (0)
    86 /* * * * * */
    87 
    88 #else
    89 
    90 /* Original XPaint sources */
    91 
    92 #include "image.h"
    93 #include "rwTable.h"
    94 
    95 #define SDL_RWops	FILE
    96 #define SDL_RWclose	fclose
    97 
    98 #endif /* USED_BY_SDL */
    99 
   100 
   101 #define	MAXCOLORMAPSIZE		256
   102 
   103 #define	TRUE	1
   104 #define	FALSE	0
   105 
   106 #define CM_RED		0
   107 #define CM_GREEN	1
   108 #define CM_BLUE		2
   109 
   110 #define	MAX_LWZ_BITS		12
   111 
   112 #define INTERLACE		0x40
   113 #define LOCALCOLORMAP	0x80
   114 #define BitSet(byte, bit)	(((byte) & (bit)) == (bit))
   115 
   116 #define	ReadOK(file,buffer,len)	SDL_RWread(file, buffer, len, 1)
   117 
   118 #define LM_to_uint(a,b)			(((b)<<8)|(a))
   119 
   120 static struct {
   121     unsigned int Width;
   122     unsigned int Height;
   123     unsigned char ColorMap[3][MAXCOLORMAPSIZE];
   124     unsigned int BitPixel;
   125     unsigned int ColorResolution;
   126     unsigned int Background;
   127     unsigned int AspectRatio;
   128     int GrayScale;
   129 } GifScreen;
   130 
   131 static struct {
   132     int transparent;
   133     int delayTime;
   134     int inputFlag;
   135     int disposal;
   136 } Gif89;
   137 
   138 static int ReadColorMap(SDL_RWops * src, int number,
   139 			unsigned char buffer[3][MAXCOLORMAPSIZE], int *flag);
   140 static int DoExtension(SDL_RWops * src, int label);
   141 static int GetDataBlock(SDL_RWops * src, unsigned char *buf);
   142 static int GetCode(SDL_RWops * src, int code_size, int flag);
   143 static int LWZReadByte(SDL_RWops * src, int flag, int input_code_size);
   144 static Image *ReadImage(SDL_RWops * src, int len, int height, int,
   145 			unsigned char cmap[3][MAXCOLORMAPSIZE],
   146 			int gray, int interlace, int ignore);
   147 
   148 Image *
   149 IMG_LoadGIF_RW(SDL_RWops *src)
   150 {
   151     unsigned char buf[16];
   152     unsigned char c;
   153     unsigned char localColorMap[3][MAXCOLORMAPSIZE];
   154     int grayScale;
   155     int useGlobalColormap;
   156     int bitPixel;
   157     int imageCount = 0;
   158     char version[4];
   159     int imageNumber = 1;
   160     Image *image = NULL;
   161 
   162     if ( src == NULL ) {
   163         goto done;
   164     }
   165     if (!ReadOK(src, buf, 6)) {
   166 	RWSetMsg("error reading magic number");
   167         goto done;
   168     }
   169     if (strncmp((char *) buf, "GIF", 3) != 0) {
   170 	RWSetMsg("not a GIF file");
   171         goto done;
   172     }
   173     strncpy(version, (char *) buf + 3, 3);
   174     version[3] = '\0';
   175 
   176     if ((strcmp(version, "87a") != 0) && (strcmp(version, "89a") != 0)) {
   177 	RWSetMsg("bad version number, not '87a' or '89a'");
   178         goto done;
   179     }
   180     Gif89.transparent = -1;
   181     Gif89.delayTime = -1;
   182     Gif89.inputFlag = -1;
   183     Gif89.disposal = 0;
   184 
   185     if (!ReadOK(src, buf, 7)) {
   186 	RWSetMsg("failed to read screen descriptor");
   187         goto done;
   188     }
   189     GifScreen.Width = LM_to_uint(buf[0], buf[1]);
   190     GifScreen.Height = LM_to_uint(buf[2], buf[3]);
   191     GifScreen.BitPixel = 2 << (buf[4] & 0x07);
   192     GifScreen.ColorResolution = (((buf[4] & 0x70) >> 3) + 1);
   193     GifScreen.Background = buf[5];
   194     GifScreen.AspectRatio = buf[6];
   195 
   196     if (BitSet(buf[4], LOCALCOLORMAP)) {	/* Global Colormap */
   197 	if (ReadColorMap(src, GifScreen.BitPixel, GifScreen.ColorMap,
   198 			 &GifScreen.GrayScale)) {
   199 	    RWSetMsg("error reading global colormap");
   200             goto done;
   201 	}
   202     }
   203     do {
   204 	if (!ReadOK(src, &c, 1)) {
   205 	    RWSetMsg("EOF / read error on image data");
   206             goto done;
   207 	}
   208 	if (c == ';') {		/* GIF terminator */
   209 	    if (imageCount < imageNumber) {
   210 		RWSetMsg("only %d image%s found in file",
   211 			 imageCount, imageCount > 1 ? "s" : "");
   212                 goto done;
   213 	    }
   214 	}
   215 	if (c == '!') {		/* Extension */
   216 	    if (!ReadOK(src, &c, 1)) {
   217 		RWSetMsg("EOF / read error on extention function code");
   218                 goto done;
   219 	    }
   220 	    DoExtension(src, c);
   221 	    continue;
   222 	}
   223 	if (c != ',') {		/* Not a valid start character */
   224 	    continue;
   225 	}
   226 	++imageCount;
   227 
   228 	if (!ReadOK(src, buf, 9)) {
   229 	    RWSetMsg("couldn't read left/top/width/height");
   230             goto done;
   231 	}
   232 	useGlobalColormap = !BitSet(buf[8], LOCALCOLORMAP);
   233 
   234 	bitPixel = 1 << ((buf[8] & 0x07) + 1);
   235 
   236 	if (!useGlobalColormap) {
   237 	    if (ReadColorMap(src, bitPixel, localColorMap, &grayScale)) {
   238 		RWSetMsg("error reading local colormap");
   239                 goto done;
   240 	    }
   241 	    image = ReadImage(src, LM_to_uint(buf[4], buf[5]),
   242 			      LM_to_uint(buf[6], buf[7]),
   243 			      bitPixel, localColorMap, grayScale,
   244 			      BitSet(buf[8], INTERLACE),
   245 			      imageCount != imageNumber);
   246 	} else {
   247 	    image = ReadImage(src, LM_to_uint(buf[4], buf[5]),
   248 			      LM_to_uint(buf[6], buf[7]),
   249 			      GifScreen.BitPixel, GifScreen.ColorMap,
   250 			      GifScreen.GrayScale, BitSet(buf[8], INTERLACE),
   251 			      imageCount != imageNumber);
   252 	}
   253     } while (image == NULL);
   254 
   255 #ifdef USED_BY_SDL
   256     if ( Gif89.transparent >= 0 ) {
   257         SDL_SetColorKey(image, SDL_SRCCOLORKEY, Gif89.transparent);
   258     }
   259 #endif
   260 
   261 done:
   262     return image;
   263 }
   264 
   265 static int
   266 ReadColorMap(SDL_RWops *src, int number,
   267              unsigned char buffer[3][MAXCOLORMAPSIZE], int *gray)
   268 {
   269     int i;
   270     unsigned char rgb[3];
   271     int flag;
   272 
   273     flag = TRUE;
   274 
   275     for (i = 0; i < number; ++i) {
   276 	if (!ReadOK(src, rgb, sizeof(rgb))) {
   277 	    RWSetMsg("bad colormap");
   278 	    return 1;
   279 	}
   280 	buffer[CM_RED][i] = rgb[0];
   281 	buffer[CM_GREEN][i] = rgb[1];
   282 	buffer[CM_BLUE][i] = rgb[2];
   283 	flag &= (rgb[0] == rgb[1] && rgb[1] == rgb[2]);
   284     }
   285 
   286 #if 0
   287     if (flag)
   288 	*gray = (number == 2) ? PBM_TYPE : PGM_TYPE;
   289     else
   290 	*gray = PPM_TYPE;
   291 #else
   292     *gray = 0;
   293 #endif
   294 
   295     return FALSE;
   296 }
   297 
   298 static int
   299 DoExtension(SDL_RWops *src, int label)
   300 {
   301     static unsigned char buf[256];
   302     char *str;
   303 
   304     switch (label) {
   305     case 0x01:			/* Plain Text Extension */
   306 	str = "Plain Text Extension";
   307 	break;
   308     case 0xff:			/* Application Extension */
   309 	str = "Application Extension";
   310 	break;
   311     case 0xfe:			/* Comment Extension */
   312 	str = "Comment Extension";
   313 	while (GetDataBlock(src, (unsigned char *) buf) != 0);
   314 	return FALSE;
   315     case 0xf9:			/* Graphic Control Extension */
   316 	str = "Graphic Control Extension";
   317 	(void) GetDataBlock(src, (unsigned char *) buf);
   318 	Gif89.disposal = (buf[0] >> 2) & 0x7;
   319 	Gif89.inputFlag = (buf[0] >> 1) & 0x1;
   320 	Gif89.delayTime = LM_to_uint(buf[1], buf[2]);
   321 	if ((buf[0] & 0x1) != 0)
   322 	    Gif89.transparent = buf[3];
   323 
   324 	while (GetDataBlock(src, (unsigned char *) buf) != 0);
   325 	return FALSE;
   326     default:
   327 	str = (char *)buf;
   328 	sprintf(str, "UNKNOWN (0x%02x)", label);
   329 	break;
   330     }
   331 
   332     while (GetDataBlock(src, (unsigned char *) buf) != 0);
   333 
   334     return FALSE;
   335 }
   336 
   337 static int ZeroDataBlock = FALSE;
   338 
   339 static int
   340 GetDataBlock(SDL_RWops *src, unsigned char *buf)
   341 {
   342     unsigned char count;
   343 
   344     if (!ReadOK(src, &count, 1)) {
   345 	/* pm_message("error in getting DataBlock size" ); */
   346 	return -1;
   347     }
   348     ZeroDataBlock = count == 0;
   349 
   350     if ((count != 0) && (!ReadOK(src, buf, count))) {
   351 	/* pm_message("error in reading DataBlock" ); */
   352 	return -1;
   353     }
   354     return count;
   355 }
   356 
   357 static int
   358 GetCode(SDL_RWops *src, int code_size, int flag)
   359 {
   360     static unsigned char buf[280];
   361     static int curbit, lastbit, done, last_byte;
   362     int i, j, ret;
   363     unsigned char count;
   364 
   365     if (flag) {
   366 	curbit = 0;
   367 	lastbit = 0;
   368 	done = FALSE;
   369 	return 0;
   370     }
   371     if ((curbit + code_size) >= lastbit) {
   372 	if (done) {
   373 	    if (curbit >= lastbit)
   374 		RWSetMsg("ran off the end of my bits");
   375 	    return -1;
   376 	}
   377 	buf[0] = buf[last_byte - 2];
   378 	buf[1] = buf[last_byte - 1];
   379 
   380 	if ((count = GetDataBlock(src, &buf[2])) == 0)
   381 	    done = TRUE;
   382 
   383 	last_byte = 2 + count;
   384 	curbit = (curbit - lastbit) + 16;
   385 	lastbit = (2 + count) * 8;
   386     }
   387     ret = 0;
   388     for (i = curbit, j = 0; j < code_size; ++i, ++j)
   389 	ret |= ((buf[i / 8] & (1 << (i % 8))) != 0) << j;
   390 
   391     curbit += code_size;
   392 
   393     return ret;
   394 }
   395 
   396 static int
   397 LWZReadByte(SDL_RWops *src, int flag, int input_code_size)
   398 {
   399     static int fresh = FALSE;
   400     int code, incode;
   401     static int code_size, set_code_size;
   402     static int max_code, max_code_size;
   403     static int firstcode, oldcode;
   404     static int clear_code, end_code;
   405     static int table[2][(1 << MAX_LWZ_BITS)];
   406     static int stack[(1 << (MAX_LWZ_BITS)) * 2], *sp;
   407     register int i;
   408 
   409     if (flag) {
   410 	set_code_size = input_code_size;
   411 	code_size = set_code_size + 1;
   412 	clear_code = 1 << set_code_size;
   413 	end_code = clear_code + 1;
   414 	max_code_size = 2 * clear_code;
   415 	max_code = clear_code + 2;
   416 
   417 	GetCode(src, 0, TRUE);
   418 
   419 	fresh = TRUE;
   420 
   421 	for (i = 0; i < clear_code; ++i) {
   422 	    table[0][i] = 0;
   423 	    table[1][i] = i;
   424 	}
   425 	for (; i < (1 << MAX_LWZ_BITS); ++i)
   426 	    table[0][i] = table[1][0] = 0;
   427 
   428 	sp = stack;
   429 
   430 	return 0;
   431     } else if (fresh) {
   432 	fresh = FALSE;
   433 	do {
   434 	    firstcode = oldcode = GetCode(src, code_size, FALSE);
   435 	} while (firstcode == clear_code);
   436 	return firstcode;
   437     }
   438     if (sp > stack)
   439 	return *--sp;
   440 
   441     while ((code = GetCode(src, code_size, FALSE)) >= 0) {
   442 	if (code == clear_code) {
   443 	    for (i = 0; i < clear_code; ++i) {
   444 		table[0][i] = 0;
   445 		table[1][i] = i;
   446 	    }
   447 	    for (; i < (1 << MAX_LWZ_BITS); ++i)
   448 		table[0][i] = table[1][i] = 0;
   449 	    code_size = set_code_size + 1;
   450 	    max_code_size = 2 * clear_code;
   451 	    max_code = clear_code + 2;
   452 	    sp = stack;
   453 	    firstcode = oldcode = GetCode(src, code_size, FALSE);
   454 	    return firstcode;
   455 	} else if (code == end_code) {
   456 	    int count;
   457 	    unsigned char buf[260];
   458 
   459 	    if (ZeroDataBlock)
   460 		return -2;
   461 
   462 	    while ((count = GetDataBlock(src, buf)) > 0);
   463 
   464 	    if (count != 0) {
   465 		/*
   466 		 * pm_message("missing EOD in data stream (common occurence)");
   467 		 */
   468 	    }
   469 	    return -2;
   470 	}
   471 	incode = code;
   472 
   473 	if (code >= max_code) {
   474 	    *sp++ = firstcode;
   475 	    code = oldcode;
   476 	}
   477 	while (code >= clear_code) {
   478 	    *sp++ = table[1][code];
   479 	    if (code == table[0][code])
   480 		RWSetMsg("circular table entry BIG ERROR");
   481 	    code = table[0][code];
   482 	}
   483 
   484 	*sp++ = firstcode = table[1][code];
   485 
   486 	if ((code = max_code) < (1 << MAX_LWZ_BITS)) {
   487 	    table[0][code] = oldcode;
   488 	    table[1][code] = firstcode;
   489 	    ++max_code;
   490 	    if ((max_code >= max_code_size) &&
   491 		(max_code_size < (1 << MAX_LWZ_BITS))) {
   492 		max_code_size *= 2;
   493 		++code_size;
   494 	    }
   495 	}
   496 	oldcode = incode;
   497 
   498 	if (sp > stack)
   499 	    return *--sp;
   500     }
   501     return code;
   502 }
   503 
   504 static Image *
   505 ReadImage(SDL_RWops * src, int len, int height, int cmapSize,
   506 	  unsigned char cmap[3][MAXCOLORMAPSIZE],
   507 	  int gray, int interlace, int ignore)
   508 {
   509     Image *image;
   510     unsigned char c;
   511     int i, v;
   512     int xpos = 0, ypos = 0, pass = 0;
   513 
   514     /*
   515     **	Initialize the compression routines
   516      */
   517     if (!ReadOK(src, &c, 1)) {
   518 	RWSetMsg("EOF / read error on image data");
   519 	return NULL;
   520     }
   521     if (LWZReadByte(src, TRUE, c) < 0) {
   522 	RWSetMsg("error reading image");
   523 	return NULL;
   524     }
   525     /*
   526     **	If this is an "uninteresting picture" ignore it.
   527      */
   528     if (ignore) {
   529 	while (LWZReadByte(src, FALSE, c) >= 0);
   530 	return NULL;
   531     }
   532     image = ImageNewCmap(len, height, cmapSize);
   533 
   534     for (i = 0; i < cmapSize; i++)
   535 	ImageSetCmap(image, i, cmap[CM_RED][i],
   536 		     cmap[CM_GREEN][i], cmap[CM_BLUE][i]);
   537 
   538     while ((v = LWZReadByte(src, FALSE, c)) >= 0) {
   539 #ifdef USED_BY_SDL
   540 	((Uint8 *)image->pixels)[xpos + ypos * image->pitch] = v;
   541 #else
   542 	image->data[xpos + ypos * len] = v;
   543 #endif
   544 	++xpos;
   545 	if (xpos == len) {
   546 	    xpos = 0;
   547 	    if (interlace) {
   548 		switch (pass) {
   549 		case 0:
   550 		case 1:
   551 		    ypos += 8;
   552 		    break;
   553 		case 2:
   554 		    ypos += 4;
   555 		    break;
   556 		case 3:
   557 		    ypos += 2;
   558 		    break;
   559 		}
   560 
   561 		if (ypos >= height) {
   562 		    ++pass;
   563 		    switch (pass) {
   564 		    case 1:
   565 			ypos = 4;
   566 			break;
   567 		    case 2:
   568 			ypos = 2;
   569 			break;
   570 		    case 3:
   571 			ypos = 1;
   572 			break;
   573 		    default:
   574 			goto fini;
   575 		    }
   576 		}
   577 	    } else {
   578 		++ypos;
   579 	    }
   580 	}
   581 	if (ypos >= height)
   582 	    break;
   583     }
   584 
   585   fini:
   586 
   587     return image;
   588 }
   589 
   590 #else
   591 
   592 /* See if an image is contained in a data source */
   593 int IMG_isGIF(SDL_RWops *src)
   594 {
   595 	return(0);
   596 }
   597 
   598 /* Load a GIF type image from an SDL datasource */
   599 SDL_Surface *IMG_LoadGIF_RW(SDL_RWops *src)
   600 {
   601 	return(NULL);
   602 }
   603 
   604 #endif /* LOAD_GIF */