/* * gifread.c * * Copyright (C) 2003-2009 Cosmin Truta. * This software was derived from "giftopnm.c" by David Koblas, * and is distributed under the same copyright and warranty terms. * * The original copyright notice is provided below. */ /* +-------------------------------------------------------------------+ */ /* | Copyright 1990, 1991, 1993, David Koblas. (koblas@netcom.com) | */ /* | Permission to use, copy, modify, and distribute this software | */ /* | and its documentation for any purpose and without fee is hereby | */ /* | granted, provided that the above copyright notice appear in all | */ /* | copies and that both that copyright notice and this permission | */ /* | notice appear in supporting documentation. This software is | */ /* | provided "as is" without express or implied warranty. | */ /* +-------------------------------------------------------------------+ */ #include #include #include #include #include "gifread.h" #define FALSE 0 #define TRUE 1 #define MAX_LZW_BITS 12 /* These macros are masquerading as inline functions. */ #define GIF_FREAD(buf, len, file) \ { if (fread(buf, len, 1, file) <= 0) GIFError(ErrRead); } #define GIF_FGETC(ch, file) \ { if ((ch = getc(file)) == EOF) GIFError(ErrRead); } #define GIF_GETW(buf) \ ((buf)[0] + ((buf)[1] << 8)) #ifdef GIF_DEBUG #define GIF_TRACE(args) printf args #else #define GIF_TRACE(args) ((void)0) #endif static const char *ErrRead = "Error reading file or unexpected end of file"; static void GIFReadNextImage(struct GIFImage *image, FILE *stream); static void GIFReadNextExtension(struct GIFExtension *ext, FILE *stream); static void ReadImageData(struct GIFImage *image, FILE *stream); static void SkipDataBlocks(FILE *stream); static int ReadDataBlock(unsigned char *buf, FILE *stream); static int LZWGetCode(int code_size, int flag, FILE *stream); static int LZWReadByte(int flag, int input_code_size, FILE *stream); /** * Reads the GIF screen and the global color table. **/ void GIFReadScreen(struct GIFScreen *screen, FILE *stream) { unsigned char buf[7]; GIF_TRACE(("Reading Header\n")); GIF_FREAD(buf, 6, stream); if (strncmp((char *)buf, "GIF", 3) != 0) GIFError("Not a GIF file"); if ((strncmp((char *)buf + 3, "87a", 3) != 0) && (strncmp((char *)buf + 3, "89a", 3) != 0)) GIFWarning("Invalid GIF version number, not \"87a\" or \"89a\""); GIF_TRACE(("Reading Logical Screen Descriptor\n")); GIF_FREAD(buf, 7, stream); screen->Width = GIF_GETW(buf + 0); screen->Height = GIF_GETW(buf + 2); screen->GlobalColorFlag = (buf[4] & 0x80) ? 1 : 0; screen->ColorResolution = ((buf[4] & 0x70) >> 3) + 1; screen->SortFlag = (buf[4] & 0x08) ? 1 : 0; screen->GlobalNumColors = 2 << (buf[4] & 0x07); screen->Background = buf[5]; screen->PixelAspectRatio = buf[6]; if (screen->GlobalColorFlag) { GIF_TRACE(("Reading Global Color Table\n")); GIF_FREAD(screen->GlobalColorTable, 3 * screen->GlobalNumColors, stream); } GIF_TRACE(("Validating Logical Screen Descriptor\n")); if (screen->Width == 0 || screen->Height == 0) GIFError("Invalid image dimensions"); if (screen->Background > 0) { if ((screen->GlobalColorFlag && (screen->Background >= screen->GlobalNumColors)) || !screen->GlobalColorFlag) { #if 0 /* too noisy */ GIFWarning("Invalid background color index"); #endif screen->Background = 0; } } } /** * Initializes the GIF image structure. **/ void GIFInitImage(struct GIFImage *image, struct GIFScreen *screen, unsigned char **rows) { image->Screen = screen; image->Rows = rows; } /** * Initializes the GIF extension structure. */ void GIFInitExtension(struct GIFExtension *ext, struct GIFScreen *screen, unsigned char *buf, unsigned int size) { ext->Screen = screen; ext->BufferSize = size; ext->Buffer = buf; } /** * Reads the next GIF block (image or extension) structure. **/ int GIFReadNextBlock(struct GIFImage *image, struct GIFExtension *ext, FILE *stream) { int ch; int foundBogus; foundBogus = 0; for ( ; ; ) { GIF_FGETC(ch, stream); switch (ch) { case GIF_IMAGE: /* ',' */ GIFReadNextImage(image, stream); return ch; case GIF_EXTENSION: /* '!' */ GIFReadNextExtension(ext, stream); return ch; case GIF_TERMINATOR: /* ';' */ return ch; default: if (!foundBogus) GIFWarning("Bogus data in GIF"); foundBogus = 1; } } } /** * Reads the next GIF image and local color table. **/ static void GIFReadNextImage(struct GIFImage *image, FILE *stream) { struct GIFScreen *screen; unsigned char buf[9]; GIF_TRACE(("Reading Local Image Descriptor\n")); GIF_FREAD(buf, 9, stream); if (image == NULL) { SkipDataBlocks(stream); return; } image->LeftPos = GIF_GETW(buf + 0); image->TopPos = GIF_GETW(buf + 2); image->Width = GIF_GETW(buf + 4); image->Height = GIF_GETW(buf + 6); image->LocalColorFlag = (buf[8] & 0x80) ? 1 : 0; image->InterlaceFlag = (buf[8] & 0x40) ? 1 : 0; image->SortFlag = (buf[8] & 0x20) ? 1 : 0; image->LocalNumColors = image->LocalColorFlag ? (2 << (buf[8] & 0x07)) : 0; if (image->LocalColorFlag) { GIF_TRACE(("Reading Local Color Table\n")); GIF_FREAD(image->LocalColorTable, 3 * image->LocalNumColors, stream); } GIF_TRACE(("Validating Logical Screen Descriptor\n")); screen = image->Screen; if (image->Width == 0 || image->Height == 0 || image->LeftPos + image->Width > screen->Width || image->TopPos + image->Height > screen->Height) GIFError("Invalid image dimensions"); ReadImageData(image, stream); } /** * Reads the next GIF extension. **/ static void GIFReadNextExtension(struct GIFExtension *ext, FILE *stream) { unsigned int offset, len; int count, label; GIF_FGETC(label, stream); GIF_TRACE(("Reading Extension (0x%X)\n", label)); if (ext == NULL) { SkipDataBlocks(stream); return; } ext->Label = (unsigned char)label; offset = 0; len = ext->BufferSize; for ( ; ; ) { if (len < UCHAR_MAX) { ext->BufferSize += 1024; ext->Buffer = realloc(ext->Buffer, ext->BufferSize); if (ext->Buffer == NULL) GIFError("Out of memory"); len += 1024; } count = ReadDataBlock(ext->Buffer + offset, stream); if (count == 0) break; offset += count; len -= count; } } static int ZeroDataBlock = FALSE; static int ReadDataBlock(unsigned char *buf, FILE *stream) { int count; GIF_FGETC(count, stream); if (count > 0) { ZeroDataBlock = FALSE; GIF_FREAD(buf, (unsigned int)count, stream); } else ZeroDataBlock = TRUE; return count; } static void SkipDataBlocks(FILE *stream) { int count; unsigned char buf[UCHAR_MAX]; for ( ; ; ) { GIF_FGETC(count, stream) if (count > 0) { GIF_FREAD(buf, (unsigned int)count, stream); } else return; } } static int LZWGetCode(int code_size, int flag, FILE *stream) { static unsigned char buf[280]; static int curbit, lastbit, done, last_byte; int count, i, j, ret; if (flag) { curbit = 0; lastbit = 0; done = FALSE; return 0; } if ((curbit + code_size) >= lastbit) { if (done) { if (curbit >= lastbit) GIFError("GIF/LZW error: ran off the end of my bits"); return -1; } buf[0] = buf[last_byte-2]; buf[1] = buf[last_byte-1]; if ((count = ReadDataBlock(&buf[2], stream)) == 0) done = TRUE; last_byte = 2 + count; curbit = (curbit - lastbit) + 16; lastbit = (2 + count) * 8; } ret = 0; for (i = curbit, j = 0; j < code_size; ++i, ++j) ret |= ((buf[ i / 8 ] & (1 << (i % 8))) != 0) << j; curbit += code_size; return ret; } static int LZWReadByte(int flag, int input_code_size, FILE *stream) { static int fresh = FALSE; int code, incode; static int code_size, set_code_size; static int max_code, max_code_size; static int firstcode, oldcode; static int clear_code, end_code; static int table[2][(1 << MAX_LZW_BITS)]; static int stack[(1 << MAX_LZW_BITS) * 2], *sp; int i; if (flag) { set_code_size = input_code_size; code_size = set_code_size+1; clear_code = 1 << set_code_size; end_code = clear_code + 1; max_code_size = 2 * clear_code; max_code = clear_code + 2; LZWGetCode(0, TRUE, stream); fresh = TRUE; for (i = 0; i < clear_code; ++i) { table[0][i] = 0; table[1][i] = i; } for ( ; i < (1 << MAX_LZW_BITS); ++i) { table[0][i] = table[1][0] = 0; } sp = stack; return 0; } else if (fresh) { fresh = FALSE; do { firstcode = oldcode = LZWGetCode(code_size, FALSE, stream); } while (firstcode == clear_code); return firstcode; } if (sp > stack) return *--sp; while ((code = LZWGetCode(code_size, FALSE, stream)) >= 0) { if (code == clear_code) { for (i = 0; i < clear_code; ++i) { table[0][i] = 0; table[1][i] = i; } for ( ; i < (1 << MAX_LZW_BITS); ++i) { table[0][i] = table[1][i] = 0; } code_size = set_code_size+1; max_code_size = 2*clear_code; max_code = clear_code+2; sp = stack; firstcode = oldcode = LZWGetCode(code_size, FALSE, stream); return firstcode; } else if (code == end_code) { int count; unsigned char buf[260]; if (ZeroDataBlock) return -2; while ((count = ReadDataBlock(buf, stream)) > 0) ; #if 0 /* too noisy */ if (count != 0) GIFWarning("missing EOD in data stream (common occurence)"); #endif return -2; } incode = code; if (code >= max_code) { *sp++ = firstcode; code = oldcode; } while (code >= clear_code) { *sp++ = table[1][code]; if (code == table[0][code]) GIFError("GIF/LZW error: circular table entry"); code = table[0][code]; } *sp++ = firstcode = table[1][code]; if ((code = max_code) < (1 << MAX_LZW_BITS)) { table[0][code] = oldcode; table[1][code] = firstcode; ++max_code; if ((max_code >= max_code_size) && (max_code_size < (1 << MAX_LZW_BITS))) { max_code_size *= 2; ++code_size; } } oldcode = incode; if (sp > stack) return *--sp; } return code; } static void ReadImageData(struct GIFImage *image, FILE *stream) { int minCodeSize, interlaced, val, pass; unsigned int width, height, numColors, xpos, ypos; unsigned char **rows; GIF_TRACE(("Reading Image Data\n")); /* Initialize the compression routines. */ GIF_FGETC(minCodeSize, stream); if (minCodeSize >= MAX_LZW_BITS) /* this should be in fact <= 8 */ GIFError("GIF/LZW error: invalid LZW code size"); if (LZWReadByte(TRUE, minCodeSize, stream) < 0) GIFError("Error reading GIF image"); /* Ignore the picture if it is "uninteresting". */ rows = image->Rows; if (rows == NULL) { #if 1 /* This is faster, but possible LZW errors may go undetected. */ SkipDataBlocks(stream); #else /* This is safer, but slower. */ while (LZWReadByte(FALSE, minCodeSize, stream) >= 0) ; #endif return; } width = image->Width; height = image->Height; interlaced = image->InterlaceFlag; GIFGetColorTable(image, &numColors); xpos = ypos = 0; pass = 0; while ((val = LZWReadByte(FALSE, minCodeSize, stream)) >= 0) { if ((unsigned int)val >= numColors) { GIFWarning("Pixel value out of range"); val = numColors - 1; } rows[ypos][xpos] = (unsigned char)val; if (++xpos == width) { xpos = 0; if (interlaced) { switch (pass) { case 0: case 1: ypos += 8; break; case 2: ypos += 4; break; case 3: ypos += 2; break; } if (ypos >= height) { switch (++pass) { case 1: ypos = 4; break; case 2: ypos = 2; break; case 3: ypos = 1; break; default: goto fini; } } } else ++ypos; } if (ypos >= height) break; } fini: /* Ignore the trailing garbage. */ while (LZWReadByte(FALSE, minCodeSize, stream) >= 0) ; } /** * Constructs a GIF graphic control extension structure * from a raw extension structure. **/ void GIFGetGraphicCtl(struct GIFExtension *ext, struct GIFGraphicCtlExt *graphicExt) { unsigned char *buf; GIF_TRACE(("Loading Graphic Control Extension\n")); if (ext->Label != GIF_GRAPHICCTL) { GIFWarning("Not a graphic control extension"); return; } if (ext->BufferSize < 4) { GIFWarning("Broken graphic control extension"); return; } buf = ext->Buffer; graphicExt->DisposalMethod = (buf[0] >> 2) & 0x07; graphicExt->InputFlag = (buf[0] >> 1) & 0x01; graphicExt->TransparentFlag = buf[0] & 0x01; graphicExt->DelayTime = GIF_GETW(buf + 1); graphicExt->Transparent = buf[3]; } /* The GIF spec says that if neither global nor local * color maps are present, the decoder should use a system * default map, which should have black and white as the * first two colors. So we use black, white, red, green, blue, * yellow, purple and cyan. * Missing color tables are not a common case, and are not * handled by most GIF readers. */ static /*const*/ unsigned char DefaultColorTable[] = { 0, 0, 0, /* black */ 255, 255, 255, /* white */ 255, 0, 0, /* red */ 0, 255, 255, /* cyan */ 0, 255, 0, /* green */ 255, 0, 255, /* purple */ 0, 0, 255, /* blue */ 255, 255, 0, /* yellow */ }; /** * Returns the local or the global color table (whichever is applicable), * or a predefined color table if both of these tables are missing. **/ unsigned char *GIFGetColorTable(struct GIFImage *image, unsigned int *numColors) { struct GIFScreen *screen; if (image->LocalColorFlag) { GIF_TRACE(("Loading Local Color Table\n")); *numColors = image->LocalNumColors; return image->LocalColorTable; } screen = image->Screen; if (screen->GlobalColorFlag) { GIF_TRACE(("Loading Global Color Table\n")); *numColors = screen->GlobalNumColors; return screen->GlobalColorTable; } GIF_TRACE(("Loading Default Color Table\n")); *numColors = sizeof(DefaultColorTable) / 3; return DefaultColorTable; } /** * Error handling. **/ static void GIFDefaultError(const char *msg) { fprintf(stderr, "%s\n", msg); exit(EXIT_FAILURE); } static void GIFDefaultWarning(const char *msg) { fprintf(stderr, "%s\n", msg); } void (*GIFError)(const char *msg) = GIFDefaultError; void (*GIFWarning)(const char *msg) = GIFDefaultWarning;