// // aegis - project change supervisor // Copyright (C) 1998, 1999, 2002-2006, 2008, 2012 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 static unsigned char block[256]; static int block_len; static unsigned long pack_buffer; static int pack_buffer_len; struct table_ty { int emit; int chain; }; enum state_ty { state_normal, state_before_clear, state_after_clear }; static void put_le_short(const output::pointer &fp, int n) { fp->fputc(n & 0xFF); fp->fputc((n >> 8) & 0xFF); } static void oof_flush(const output::pointer &fp) { if (!block_len) return; fp->fputc(block_len); for (int j = 0; j < block_len; ++j) fp->fputc(block[j]); block_len = 0; } static void oof(const output::pointer &fp, int c) { if (block_len >= 255) oof_flush(fp); block[block_len++] = c; } static void code_write(const output::pointer &fp, int code, int code_size) { trace(("code_write(fp = %p, code = 0x%.*X, code_size = %d)", fp.get(), (code_size + 3) / 4, code, code_size)); pack_buffer |= code << pack_buffer_len; pack_buffer_len += code_size; trace(("pack_buffer = 0x%.*lX", (pack_buffer_len + 3) / 4, pack_buffer)); trace(("pack_buffer_len = %d;", pack_buffer_len)); while (pack_buffer_len >= 8) { oof(fp, pack_buffer & 0xFF); pack_buffer >>= 8; pack_buffer_len -= 8; trace(("pack_buffer = 0x%.*lX", (pack_buffer_len + 3) / 4, pack_buffer)); trace(("pack_buffer_len = %d;", pack_buffer_len)); } } #define IMPOSSIBLE 0xFFFF static void write_image(const output::pointer &fp, unsigned char *data, int width, int height, int init_bits) { int clear_cmd; int code_cur = 0; // position in chain being built int code_max; // maximum code, given code_size int code_next; // candidate for chain extension int code_prev = 0; // last code emitted int code_size; // number of bits/code int code_size_minimum; int end_cmd; unsigned short invert[1 << (MAX_BITS + 1)]; int invert_shift; int invert_pos; int j; int pixel_cur = 0; // last pixel of current chain int pixel_prev = 0; // last pixel of previous chain int pixel_next; // candidate for chain extension int pixel_first = 0; // first pixel of current chain state_ty state; table_ty table[1 << MAX_BITS]; int table_pos = 0; // first unused entry int table_pos_minimum; table_ty *tp; int x; int y; int new_chain; trace(("write_image(fp = %p, data = %p, width = %d, height = %d, " "init_bits = %d)", fp.get(), data, width, height, init_bits)); // // Write out the initial code size // if (init_bits < 2) init_bits = 2; fp->fputc(init_bits); // // Set up the necessary values // pack_buffer = 0; pack_buffer_len = 0; code_size = init_bits + 1; code_max = 1 << code_size; code_size_minimum = code_size; clear_cmd = 1 << init_bits; end_cmd = clear_cmd + 1; table_pos_minimum = clear_cmd + 2; invert_shift = (MAX_BITS + 1) - init_bits; for (j = 0; j < clear_cmd; ++j) { tp = &table[j]; tp->chain = 0; tp->emit = j; } x = 0; y = 0; state = state_before_clear; for (;;) { if (state == state_before_clear) { code_write(fp, clear_cmd, code_size); for (j = 0; j < (1 << (MAX_BITS + 1)); ++j) invert[j] = 0; table_pos = table_pos_minimum; trace(("table_pos = 0x%03X;", table_pos)); code_size = code_size_minimum; trace(("code_size = %d;", code_size)); code_max = 1 << code_size; trace(("code_max = 0x%03X;", code_max)); state = state_after_clear; code_cur = IMPOSSIBLE; // no current chain being built code_prev = IMPOSSIBLE; // no previously emitted code pixel_cur = IMPOSSIBLE; // no current chain being built pixel_prev = IMPOSSIBLE; // no previously emitted code trace(("code_cur = 0x%03X;", code_cur)); trace(("code_prev = 0x%03X;", code_prev)); trace(("pixel_cur = 0x%03X;", pixel_cur)); trace(("pixel_prev = 0x%03X;", pixel_prev)); } if (y >= height) { if (code_cur != IMPOSSIBLE) { code_write(fp, code_cur, code_size); if (table_pos > code_max && code_size < MAX_BITS) code_size++; } break; } trace(("x == %d, y == %d", x, y)); pixel_next = *data++; trace(("pixel_next = 0x%02X;", pixel_next)); if (++x >= width) { x = 0; ++y; } // // look through the inverted list for this combination // if (code_cur != IMPOSSIBLE) { invert_pos = (pixel_next << invert_shift) ^ code_cur; for (;;) { code_next = invert[invert_pos]; if (code_next == 0) break; tp = &table[code_next]; if (tp->chain == code_cur && tp->emit == pixel_next) break; invert_pos++; if (invert_pos >= (1 << (MAX_BITS + 1))) invert_pos = 0; } trace(("invert_pos = %d; code_next = 0x%03X;", invert_pos, code_next)); // // if it already exists, then chain along it // if (code_next != 0) { code_cur = code_next; trace(("code_cur = 0x%03X;", code_cur)); pixel_cur = pixel_next; trace(("pixel_cur = 0x%03X;", pixel_cur)); continue; } } // // if it does not exist, but it is an extension of the last one, // make one up on the end of the table // if ( state == state_normal && code_cur == code_prev && pixel_first == pixel_next ) { code_cur = table_pos; trace(("code_cur == 0x%03X;", code_cur)); pixel_cur = pixel_prev; trace(("pixel_cur == 0x%03X;", pixel_cur)); new_chain = 0; } else new_chain = 1; // // emit the current code // if (code_cur != IMPOSSIBLE) { code_write(fp, code_cur, code_size); // // add it to the table // if (state == state_normal && table_pos < (1 << MAX_BITS)) { trace(("table_pos == 0x%03X;", table_pos)); tp = &table[table_pos]; tp->emit = pixel_first; tp->chain = code_prev; invert_pos = (pixel_cur << invert_shift) ^ code_prev; invert[invert_pos] = table_pos; trace(("table[0x%03X].emit = 0x%03X;", table_pos, tp->emit)); trace(("table[0x%03X].chain = 0x%03X;", table_pos, tp->chain)); trace(("invert[invert_pos = %d] = 0x%03X;", invert_pos, table_pos)); ++table_pos; } state = state_normal; // // fix the code size // if (table_pos >= code_max) { if (code_size < MAX_BITS) { code_size++; trace(("code_size = %d;", code_size)); code_max = 1 << code_size; trace(("code_max = 0x%03X;", code_max)); } else state = state_before_clear; } // // only have a code_prev after emitting something // code_prev = code_cur; trace(("code_prev = 0x%03X;", code_prev)); pixel_prev = pixel_cur; trace(("pixel_prev = 0x%03X;", pixel_prev)); } // // new chain starts from the pixel we didn't use // if (new_chain) { if (state == state_before_clear) { --data; if (x) --x; else { --y; x = width - 1; } code_cur = IMPOSSIBLE; pixel_cur = IMPOSSIBLE; } else { code_cur = pixel_next; pixel_cur = pixel_next; pixel_first = pixel_next; } } else { code_cur = IMPOSSIBLE; pixel_cur = IMPOSSIBLE; } trace(("code_cur = 0x%03X;", code_cur)); trace(("pixel_cur = 0x%03X;", pixel_cur)); } // // finish up // code_write(fp, end_cmd, code_size); if (pack_buffer_len) oof(fp, pack_buffer & 0xFF); oof_flush(fp); // // empty last packet // fp->fputc(0); } static void flush(gif_ty *gp) { // // initialize things // trace(("gif_flush(gp = %p)", gp)); output::pointer fp; if (gp->fn) { fp = output_file::binary_open(gp->fn); } else { fp = output_stdout::create(); } int color_resolution = 8; int bits_per_pixel = 8; // // write out the header // if (gp->mime) fp->fprintf("Content-Type: image/gif\n\n"); for (const unsigned char *cp = gif_magic_number; *cp; ++cp) fp->fputc(*cp); put_le_short(fp, gp->width); put_le_short(fp, gp->height); fp->fputc ( (0x80 | ((unsigned)(color_resolution - 1) << 4) | (bits_per_pixel - 1)) ); fp->fputc(0); // background fp->fputc(0); // future expansion // // global color map // for (int j = 0; j < 256; ++j) for (int c = 0; c < 3; ++c) fp->fputc(gp->colormap[j][c]); // // image separator // fp->fputc(','); // // image header // left, top, width, height // put_le_short(fp, 0); put_le_short(fp, 0); put_le_short(fp, gp->width); put_le_short(fp, gp->height); // // the image is not interlaced // and has no local color map // fp->fputc(0); // // write the image // write_image(fp, gp->image_flat, gp->width, gp->height, bits_per_pixel); // // file terminator // fp->fputc(';'); // // finish up // fp.reset(); } void gif_close(gif_ty *gp) { if (gp->mode == gif_mode_rdwr) flush(gp); if (gp->fn) mem_free(gp->fn); mem_free(gp->image_flat); mem_free(gp->image); mem_free(gp); } // vim: set ts=8 sw=4 et :