blob: b24aa6e9f6d60c25b723649d84ca7d403451f4a7 [file] [log] [blame]
/**
* @file gifread.c
* A simple GIF reader.
*
* @author Cosmin Truta
*
* @section Copyright
* Copyright (C) 2003-2011 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.
* <pre>
* +-------------------------------------------------------------------+
* | 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. |
* +-------------------------------------------------------------------+
* </pre>
*
* @bug GIF/LZW decompression is not thread-safe.
**/
#include "gifread.h"
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#if UCHAR_MAX == 255
#define GIF_UCHAR_MAX 255
#else
#error This module requires 8-bit bytes.
#endif
#define LZW_BITS_MAX 12
#define LZW_CODE_MAX ((1 << LZW_BITS_MAX) - 1)
#define LZW_FALSE 0
#define LZW_TRUE 1
static void GIFReadNextImage(struct GIFImage *image, FILE *stream);
static void GIFReadImageData(struct GIFImage *image, FILE *stream);
static int GIFReadDataBlock(unsigned char *buffer, FILE *stream);
static void GIFSkipDataBlocks(FILE *stream);
static int LZWGetCode(int code_size, int init_flag, FILE *stream);
static int LZWReadByte(int init_flag, int input_code_size, FILE *stream);
static void GIFReadNextExtension(struct GIFExtension *ext, FILE *stream);
static void DefaultError(const char *message);
static void DefaultWarning(const char *message);
static void MemoryError(void);
static void ReadError(void);
#define GIF_GETW(buffer) \
((buffer)[0] + ((buffer)[1] << 8))
#define GIF_FGETC(ch, file) \
{ if ((ch = getc(file)) == EOF) ReadError(); }
#define GIF_FREAD(buffer, len, file) \
{ if (fread(buffer, len, 1, file) <= 0) ReadError(); }
#ifdef GIF_DEBUG
#define GIF_TRACE(args) (printf args)
#else
#define GIF_TRACE(args) ((void)0)
#endif
/*
* Reads the GIF screen and the global color table.
*/
void GIFReadScreen(struct GIFScreen *screen, FILE *stream)
{
unsigned char buffer[7];
GIF_TRACE(("Reading Header\n"));
GIF_FREAD(buffer, 6, stream);
if (memcmp(buffer, "GIF", 3) != 0)
GIFError("Not a GIF file");
if ((memcmp(buffer + 3, "87a", 3) != 0) &&
(memcmp(buffer + 3, "89a", 3) != 0))
GIFWarning("Invalid GIF version number, not \"87a\" or \"89a\"");
GIF_TRACE(("Reading Logical Screen Descriptor\n"));
GIF_FREAD(buffer, 7, stream);
screen->Width = GIF_GETW(buffer + 0);
screen->Height = GIF_GETW(buffer + 2);
screen->GlobalColorFlag = (buffer[4] & 0x80) ? 1 : 0;
screen->ColorResolution = ((buffer[4] & 0x70) >> 3) + 1;
screen->SortFlag = (buffer[4] & 0x08) ? 1 : 0;
screen->GlobalNumColors = 2 << (buffer[4] & 0x07);
screen->Background = buffer[5];
screen->PixelAspectRatio = buffer[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 a GIF image object.
*/
void GIFInitImage(struct GIFImage *image,
struct GIFScreen *screen, unsigned char **rows)
{
image->Screen = screen;
image->Rows = rows;
}
/*
* Destroys a GIF image object.
*/
void GIFDestroyImage(struct GIFImage *image)
{
(void)image; /* nothing to do */
}
/*
* Reads the next GIF block into an image or extension object.
*/
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 buffer[9];
GIF_TRACE(("Reading Local Image Descriptor\n"));
GIF_FREAD(buffer, 9, stream);
if (image == NULL)
{
GIFSkipDataBlocks(stream);
return;
}
image->LeftPos = GIF_GETW(buffer + 0);
image->TopPos = GIF_GETW(buffer + 2);
image->Width = GIF_GETW(buffer + 4);
image->Height = GIF_GETW(buffer + 6);
image->LocalColorFlag = (buffer[8] & 0x80) ? 1 : 0;
image->InterlaceFlag = (buffer[8] & 0x40) ? 1 : 0;
image->SortFlag = (buffer[8] & 0x20) ? 1 : 0;
image->LocalNumColors = image->LocalColorFlag ? (2 << (buffer[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");
GIFReadImageData(image, stream);
}
static void GIFReadImageData(struct GIFImage *image, FILE *stream)
{
int minCodeSize;
unsigned char **rows;
unsigned int width, height, interlaced;
unsigned char *colors;
unsigned int numColors;
unsigned int xpos, ypos;
int pass, val;
GIF_TRACE(("Reading Image Data\n"));
/* Initialize the compression routines. */
GIF_FGETC(minCodeSize, stream);
if (minCodeSize >= LZW_BITS_MAX) /* this should be in fact <= 8 */
GIFError("GIF/LZW error: invalid LZW code size");
if (LZWReadByte(LZW_TRUE, minCodeSize, stream) < 0)
GIFError("Error reading GIF image");
/* Ignore the picture if it is "uninteresting". */
rows = image->Rows;
if (rows == NULL)
{
#if 0
while (LZWReadByte(LZW_FALSE, minCodeSize, stream) >= 0)
{
}
#else
/* This is faster, but possible LZW errors may go undetected. */
GIFSkipDataBlocks(stream);
#endif
return;
}
width = image->Width;
height = image->Height;
interlaced = image->InterlaceFlag;
GIFGetColorTable(&colors, &numColors, image);
xpos = ypos = 0;
pass = 0;
while ((val = LZWReadByte(LZW_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(LZW_FALSE, minCodeSize, stream) >= 0)
{
}
}
static int DataBlockSize = 0;
static int GIFReadDataBlock(unsigned char *buffer, FILE *stream)
{
int count;
GIF_FGETC(count, stream);
DataBlockSize = count;
if (count > 0)
{
GIF_FREAD(buffer, (unsigned int)count, stream);
}
return count;
}
static void GIFSkipDataBlocks(FILE *stream)
{
int count;
unsigned char buffer[GIF_UCHAR_MAX + 1];
for ( ; ; )
{
GIF_FGETC(count, stream)
if (count > 0)
{
GIF_FREAD(buffer, (unsigned int)count, stream);
}
else
return;
}
}
static int LZWGetCode(int code_size, int init_flag, FILE *stream)
{
static unsigned char buffer[280];
static int curbit, lastbit, done, last_byte;
int count, i, j, ret;
if (init_flag)
{
curbit = 0;
lastbit = 0;
done = LZW_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;
}
buffer[0] = buffer[last_byte-2];
buffer[1] = buffer[last_byte-1];
if ((count = GIFReadDataBlock(&buffer[2], stream)) == 0)
done = LZW_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 |= ((buffer[ i / 8 ] & (1 << (i % 8))) != 0) << j;
curbit += code_size;
return ret;
}
static int LZWReadByte(int init_flag, int input_code_size, FILE *stream)
{
static int fresh = LZW_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][LZW_CODE_MAX + 1];
static int stack[(LZW_CODE_MAX + 1) * 2], *sp;
int i;
if (init_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, LZW_TRUE, stream);
fresh = LZW_TRUE;
for (i = 0; i < clear_code; ++i)
{
table[0][i] = 0;
table[1][i] = i;
}
for ( ; i <= LZW_CODE_MAX; ++i)
{
table[0][i] = 0;
table[1][i] = 0;
}
sp = stack;
return 0;
}
else if (fresh)
{
fresh = LZW_FALSE;
do
{
firstcode = oldcode =
LZWGetCode(code_size, LZW_FALSE, stream);
} while (firstcode == clear_code);
return firstcode;
}
if (sp > stack)
return *--sp;
while ((code = LZWGetCode(code_size, LZW_FALSE, stream)) >= 0)
{
if (code == clear_code)
{
for (i = 0; i < clear_code; ++i)
{
table[0][i] = 0;
table[1][i] = i;
}
for ( ; i <= LZW_CODE_MAX; ++i)
{
table[0][i] = 0;
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, LZW_FALSE, stream);
return firstcode;
}
else if (code == end_code)
{
int count;
unsigned char buffer[260];
if (DataBlockSize == 0)
return -2;
while ((count = GIFReadDataBlock(buffer, stream)) > 0)
{
}
#if 0 /* too noisy */
if (count != 0)
GIFWarning("missing EOD in data stream (common occurence)");
#else
(void)count;
#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) <= LZW_CODE_MAX)
{
table[0][code] = oldcode;
table[1][code] = firstcode;
++max_code;
if ((max_code >= max_code_size) && (max_code_size <= LZW_CODE_MAX))
{
max_code_size *= 2;
++code_size;
}
}
oldcode = incode;
if (sp > stack)
return *--sp;
}
return code;
}
/*
* 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 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.
*/
void GIFGetColorTable(unsigned char **colors, unsigned int *numColors,
struct GIFImage *image)
{
struct GIFScreen *screen;
if (image->LocalColorFlag)
{
GIF_TRACE(("Loading Local Color Table\n"));
*colors = image->LocalColorTable;
*numColors = image->LocalNumColors;
return;
}
screen = image->Screen;
if (screen->GlobalColorFlag)
{
GIF_TRACE(("Loading Global Color Table\n"));
*colors = screen->GlobalColorTable;
*numColors = screen->GlobalNumColors;
return;
}
GIF_TRACE(("Loading Default Color Table\n"));
*colors = DefaultColorTable;
*numColors = sizeof(DefaultColorTable) / 3;
}
/*
* Initializes a GIF extension object.
*/
void GIFInitExtension(struct GIFExtension *ext,
struct GIFScreen *screen, unsigned int initBufferSize)
{
unsigned char *newBuffer;
ext->Screen = screen;
if (initBufferSize > 0)
{
newBuffer = (unsigned char *)malloc(initBufferSize);
if (newBuffer == NULL)
MemoryError();
ext->Buffer = newBuffer;
ext->BufferSize = initBufferSize;
}
else
{
ext->Buffer = NULL;
ext->BufferSize = 0;
}
}
/*
* Destroys a GIF extension object.
*/
void GIFDestroyExtension(struct GIFExtension *ext)
{
free(ext->Buffer);
}
/*
* Reads the next GIF extension.
*/
static void GIFReadNextExtension(struct GIFExtension *ext, FILE *stream)
{
unsigned char *newBuffer;
unsigned int newBufferSize;
unsigned int offset, len;
int count, label;
GIF_FGETC(label, stream);
GIF_TRACE(("Reading Extension (0x%X)\n", label));
if (ext == NULL)
{
GIFSkipDataBlocks(stream);
return;
}
ext->Label = (unsigned char)label;
offset = 0;
len = ext->BufferSize;
for ( ; ; )
{
if (len < GIF_UCHAR_MAX)
{
newBufferSize = ext->BufferSize + 1024;
newBuffer = (unsigned char *)realloc(ext->Buffer, newBufferSize);
if (newBuffer == NULL)
MemoryError();
ext->BufferSize = newBufferSize;
ext->Buffer = newBuffer;
len += 1024;
}
count = GIFReadDataBlock(ext->Buffer + offset, stream);
if (count == 0)
break;
offset += count;
len -= count;
}
}
/*
* Constructs a GIF graphic control extension object
* from a raw extension object.
*/
void GIFGetGraphicCtl(struct GIFGraphicCtlExt *graphicExt,
struct GIFExtension *ext)
{
unsigned char *buffer;
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;
}
buffer = ext->Buffer;
graphicExt->DisposalMethod = (buffer[0] >> 2) & 0x07;
graphicExt->InputFlag = (buffer[0] >> 1) & 0x01;
graphicExt->TransparentFlag = buffer[0] & 0x01;
graphicExt->DelayTime = GIF_GETW(buffer + 1);
graphicExt->Transparent = buffer[3];
}
/*
* The default error handler.
*/
static void DefaultError(const char *message)
{
fprintf(stderr, "%s\n", message);
exit(EXIT_FAILURE);
}
/*
* The default warning handler.
*/
static void DefaultWarning(const char *message)
{
fprintf(stderr, "%s\n", message);
}
/*
* The common memory error handler.
*/
static void MemoryError(void)
{
GIFError("Out of memory");
}
/*
* The common read error handler.
*/
static void ReadError(void)
{
GIFError("Error reading file or unexpected end of file");
}
/*
* The error handling callback.
*/
void (*GIFError)(const char *message) = DefaultError;
/*
* The warning handling callback.
*/
void (*GIFWarning)(const char *message) = DefaultWarning;