/* * aegis - project change supervisor * Copyright (C) 1998, 1999 Peter Miller; * All rights reserved. * * 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 2 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, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA. * * MANIFEST: functions to open GIF files */ #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 typedef struct table_ty table_ty; struct table_ty { int emit; int chain; }; enum state_ty { state_normal, state_before_clear, state_after_clear }; typedef enum state_ty state_ty; static void format_error _((char *)); static void format_error(fn) 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 *, char *)); static int gif_getc(fp, fn) FILE *fp; char *fn; { int c; c = getc(fp); if (c == EOF) { if (ferror(fp)) { sub_context_ty *scp; scp = sub_context_new(); sub_errno_set(scp); 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, char *fn, int row_bytes, unsigned char *data, int width, int height, int interlaced)); static void read_image(fp, fn, row_bytes, data, width, height, interlaced) FILE *fp; 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, 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)", 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 *, char *)); static int get_le_short(fp, fn) FILE *fp; 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(fn, mode) 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 = 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 = 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; scp = sub_context_new(); sub_errno_set(scp); 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 > SIZE_MAX) format_error(fn); result->height = get_le_short(fp, fn); if (!result->height || result->height > SIZE_MAX) format_error(fn); /* * allocate the image */ result->image_flat = mem_alloc((size_t)result->width * (size_t)result->height); result->image = 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; } } }