//
// aegis - project change supervisor
// Copyright (C) 1998, 1999, 2002-2008 Peter Miller
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see
// .
//
#include
#include
#include
#include
#include
#include
#include
#include
#include
//
// This symbol is defined if you want the code to tolerate
// some really stupid deviations from the stated GIF standard.
//
#define TOLERATE_FOOLS
struct table_ty
{
int emit;
int chain;
};
enum state_ty
{
state_normal,
state_before_clear,
state_after_clear
};
static void
format_error(const char *fn)
{
sub_context_ty *scp;
scp = sub_context_new();
sub_var_set_charstar(scp, "File_Name", fn);
fatal_intl(scp, i18n("read $filename: format error"));
// NOTREACHED
}
static int
gif_getc(FILE *fp, const char *fn)
{
int c;
c = getc(fp);
if (c == EOF)
{
if (ferror(fp))
{
sub_context_ty *scp;
int errno_old;
errno_old = errno;
scp = sub_context_new();
sub_errno_setx(scp, errno_old);
sub_var_set_charstar(scp, "File_Name", fn);
fatal_intl(scp, i18n("read $filename: $errno"));
// NOTREACHED
}
format_error(fn);
}
return c;
}
static void
read_image(FILE *fp, const char *fn, int row_bytes, unsigned char *data,
int width, int height, int interlaced)
{
int c;
int unpack_buffer;
int unpack_buffer_len;
int unpack_runlen;
int clear_cmd;
int end_cmd;
int code_prev = 0; // last chain
int code_cur; // current chain
int code_size;
int code_size_minimum;
int code_mask;
int pixel_prev = 0; // last pixel of last chain
int pixel_cur; // last pixel of current chain (eventually)
int phase;
int x;
int y;
unsigned char *dp;
table_ty table[1 << MAX_BITS];
int table_length = 0;
int table_pos_minimum;
int table_pos = 0;
state_ty state;
unsigned char lifo[1 << MAX_BITS];
unsigned char *lifo_ptr;
c = gif_getc(fp, fn);
trace(("code_size = %d;", c));
if (c < 2 || c > 8)
{
//
// bad code size
//
format_error(fn);
}
code_size = c;
clear_cmd = (1 << code_size);
end_cmd = clear_cmd + 1;
table_pos_minimum = clear_cmd + 2;
code_size++;
code_size_minimum = code_size;
code_mask = (1 << code_size) - 1;
x = 0;
y = 0;
phase = 0;
dp = data;
unpack_buffer = 0;
unpack_buffer_len = 0;
unpack_runlen = 0;
state = state_before_clear;
lifo_ptr = lifo;
for (;;)
{
//
// read in the next code
//
while (unpack_buffer_len < code_size)
{
if (!unpack_runlen)
{
unpack_runlen = gif_getc(fp, fn);
if (!unpack_runlen)
{
//
// zero length packet too early
//
format_error(fn);
}
}
c = gif_getc(fp, fn);
--unpack_runlen;
unpack_buffer |= c << unpack_buffer_len;
unpack_buffer_len += 8;
}
code_cur = unpack_buffer & code_mask;
trace(("code_cur = 0x%.*X", (code_size+3)/4, code_cur));
unpack_buffer >>= code_size;
unpack_buffer_len -= code_size;
//
// see if we are finished
//
if (code_cur == end_cmd)
break;
//
// see if it is a clear code
// reset everything
//
if (code_cur == clear_cmd)
{
code_size = code_size_minimum;
table_length = (1 << code_size);
code_mask = table_length - 1;
trace(("code_size = %d;", code_size));
trace(("table_length = %d;", table_length));
trace(("code_mask = 0x%04X;", code_mask));
table_pos = table_pos_minimum;
trace(("table_pos = 0x%02X;", table_pos));
state = state_after_clear;
continue;
}
switch ((int)state)
{
case state_before_clear:
//
// clear code missing
//
trace(("clear code missing\n"));
format_error(fn);
break;
case state_after_clear:
//
// immediately after clear
// we must also initialize the "last" info
// Also, the data after a clear MUST be raw.
//
if (code_cur > code_mask)
{
//
// code after clear must be raw pixel
//
trace(("code after clear must be raw pixel\n"));
format_error(fn);
}
pixel_prev = code_cur;
trace(("pixel_prev = 0x%03X;", pixel_prev));
break;
}
//
// initially, pixel_cur is the table index
//
pixel_cur = code_cur;
//
// if it is the next element in the table,
// then repeat the last pixel and the last chain.
//
if (pixel_cur > table_pos)
{
//
// code off end of table
//
trace(("code off end of table\n"));
format_error(fn);
}
if (pixel_cur >= table_pos)
{
pixel_cur = code_prev;
*lifo_ptr++ = pixel_prev;
}
//
// follow the chain through the table
//
while (pixel_cur >= clear_cmd)
{
*lifo_ptr++ = table[pixel_cur].emit;
pixel_cur = table[pixel_cur].chain;
}
pixel_prev = pixel_cur;
trace(("pixel_prev = 0x%03X;", pixel_prev));
*lifo_ptr++ = pixel_cur;
//
// emit the sequence, in reverse
//
while (lifo_ptr > lifo)
{
if (y >= height)
{
//
// too many pixels!
//
trace(("too many pixels\n"));
format_error(fn);
}
*dp++ = *--lifo_ptr;
trace(("pixel[%d][%d] = 0x%02X;", y, x, dp[-1]));
if (++x >= width)
{
x = 0;
if (interlaced)
{
switch (phase)
{
case 0:
y += 8;
if (y >= height)
{
phase++;
y = 4;
}
break;
case 1:
y += 8;
if (y >= height)
{
phase++;
y = 2;
}
break;
case 2:
y += 4;
if (y >= height)
{
phase++;
y = 1;
}
break;
case 3:
y += 2;
if (y >= height)
y = height;
break;
}
}
else
++y;
dp = data + y * row_bytes;
}
}
//
// add the next element to the table
//
if (state == state_normal)
{
if (table_pos >= (1 << MAX_BITS))
{
//
// tries to write code off end of table
//
trace(("tries to write code off end of table"));
format_error(fn);
}
trace(("table_pos = 0x%02X;", table_pos));
table[table_pos].chain = code_prev;
trace(("table[0x%03X].chain = 0x%03X;", table_pos, code_prev));
table[table_pos].emit = pixel_cur;
trace(("table[0x%03X].emit = 0x%03X;", table_pos, pixel_cur));
table_pos++;
}
code_prev = code_cur;
trace(("code_prev = 0x%03X;", code_prev));
state = state_normal;
//
// move the table position on
//
if (table_pos >= table_length)
{
if (code_size < MAX_BITS)
{
code_size++;
table_length <<= 1;
code_mask = table_length - 1;
trace(("code_size = %d;", code_size));
trace(("table_length = %d;", table_length));
trace(("code_mask = 0x%04X;", code_mask));
}
else
state = state_before_clear;
}
}
//
// check that we were given all of the lines
//
// Many implementations don't write the last line
// (probably anencephalic programmers),
// and some assume that absent lines mean pixel number 0.
//
#ifndef TOLERATE_FOOLS
if (y != height)
{
trace(("too few pixels (y = %d, height, x = %d, width = %d)\n",
y, height, x, width));
format_error(fn);
}
#endif
//
// check that all of last packet was used
//
// Many imlementations always write 255 byte packets,
// even when the last packet is only partially full.
//
#ifndef TOLERATE_FOOLS
if (unpack_runlen)
{
trace(("last packet not all used"));
format_error(fn);
}
#endif
//
// Check for zero-length packet at end of image.
//
// Many implementations leave it out.
//
c = gif_getc(fp, fn);
if (c)
{
#ifdef TOLERATE_FOOLS
ungetc(c, fp);
#else
trace(("image 0 terminator missing"));
format_error(fn);
#endif
}
}
static int
get_le_short(FILE *fp, const char *fn)
{
int c1;
int c2;
//
// must use temp vars
// to guarantee order of evaluation
// (see ANSI C std, discussion of sequence points)
//
c1 = gif_getc(fp, fn);
c2 = gif_getc(fp, fn);
return (c1 + (c2 << 8));
}
gif_ty *
gif_open(const char *fn, int mode)
{
gif_ty *result;
int j;
FILE *fp;
unsigned char *cp;
int c;
int bits_per_pixel;
int read_colormap;
trace(("gif_open(fn = \"%s\", mode = %d)", fn, mode));
//
// initialize things
//
result = (gif_ty *)mem_alloc(sizeof(gif_ty));
for (j = 0; j < 256; ++j)
{
result->colormap[j][0] = j;
result->colormap[j][1] = j;
result->colormap[j][2] = j;
}
result->image_flat = 0;
result->image = 0;
result->mode = (gif_mode_ty)mode;
result->mime = 0;
//
// open the file
//
if (fn)
{
result->fn = mem_copy_string(fn);
fp = fopen(fn, "rb");
if (!fp)
{
sub_context_ty *scp;
int errno_old;
errno_old = errno;
scp = sub_context_new();
sub_errno_setx(scp, errno_old);
sub_var_set_charstar(scp, "File_Name", fn);
fatal_intl(scp, i18n("open $filename: $errno"));
// NOTREACHED
}
}
else
{
result->fn = 0;
fn = "standard input";
fp = stdin;
}
//
// make sure the magic number is there
//
for (cp = gif_magic_number; *cp; ++cp)
{
c = gif_getc(fp, fn);
if (c != *cp)
format_error(fn);
}
//
// get the size
//
result->width = get_le_short(fp, fn);
if (!result->width || result->width > (int)SIZE_MAX)
format_error(fn);
result->height = get_le_short(fp, fn);
if (!result->height || result->height > (int)SIZE_MAX)
format_error(fn);
//
// allocate the image
//
result->image_flat = (unsigned char *)mem_alloc(
(size_t)result->width * (size_t)result->height);
result->image = (unsigned char **)mem_alloc(
(size_t)result->height * sizeof(unsigned char *));
for (j = 0; j < result->height; ++j)
result->image[j] = result->image_flat + j * result->width;
//
// get encoding info
//
c = gif_getc(fp, fn);
bits_per_pixel = 1 + (c & 7);
if (c & 0x80)
read_colormap = 1 << bits_per_pixel;
else
read_colormap = 0;
//
// get background color
//
c = gif_getc(fp, fn);
memset
(
result->image_flat,
c,
(size_t)result->width * (size_t)result->height
);
//
// this byte should be zero
//
c = gif_getc(fp, fn);
if (c != 0)
format_error(fn);
//
// read the colormap
//
if (read_colormap)
{
for (j = 0; j < read_colormap; ++j)
for (c = 0; c < 3; ++c)
result->colormap[j][c] = gif_getc(fp, fn);
}
//
// read the image
//
for (;;)
{
c = gif_getc(fp, fn);
switch (c)
{
default:
format_error(fn);
break;
case ';':
if (fp != stdin)
fclose(fp);
return result;
case '!':
//
// extension block - ignore
//
c = gif_getc(fp, fn);
while (c > 0)
{
gif_getc(fp, fn);
--c;
}
break;
case ',':
{
int image_left;
int image_top;
int image_width;
int image_height;
int flags;
int interlaced;
//
// image specifier
//
image_left = get_le_short(fp, fn);
if (image_left >= result->width)
format_error(fn);
image_top = get_le_short(fp, fn);
if (image_top >= result->height)
format_error(fn);
image_width = get_le_short(fp, fn);
if (image_left + image_width > result->width)
format_error(fn);
image_height = get_le_short(fp, fn);
if (image_top + image_height > result->height)
format_error(fn);
flags = gif_getc(fp, fn);
if (flags & 0x80)
{
//
// uses local color map,
// which we can't cope with
//
format_error(fn);
}
interlaced = (flags & 0x40) != 0;
read_image
(
fp,
fn,
result->width,
result->image_flat + image_left + image_top * result->width,
image_width,
image_height,
interlaced
);
}
break;
}
}
}