diff options
Diffstat (limited to 'Imaging/libImaging/GifEncode.c')
-rw-r--r-- | Imaging/libImaging/GifEncode.c | 319 |
1 files changed, 319 insertions, 0 deletions
diff --git a/Imaging/libImaging/GifEncode.c b/Imaging/libImaging/GifEncode.c new file mode 100644 index 0000000..fd696c1 --- /dev/null +++ b/Imaging/libImaging/GifEncode.c @@ -0,0 +1,319 @@ +/* + * The Python Imaging Library. + * $Id: GifEncode.c 2134 2004-10-06 08:55:20Z fredrik $ + * + * encoder for uncompressed GIF data + * + * history: + * 97-01-05 fl created (writes uncompressed data) + * 97-08-27 fl fixed off-by-one error in buffer size test + * 98-07-09 fl added interlace write support + * 99-02-07 fl rewritten, now uses a run-length encoding strategy + * 99-02-08 fl improved run-length encoding for long runs + * + * Copyright (c) Secret Labs AB 1997-99. + * Copyright (c) Fredrik Lundh 1997. + * + * See the README file for information on usage and redistribution. + */ + +#include "Imaging.h" + +#include "Gif.h" + +/* codes from 0 to 255 are literals */ +#define CLEAR_CODE 256 +#define EOF_CODE 257 +#define FIRST_CODE 258 +#define LAST_CODE 511 + +enum { INIT, ENCODE, ENCODE_EOF, FLUSH, EXIT }; + +/* to make things a little less complicated, we use a simple output + queue to hold completed blocks. the following inlined function + adds a byte to the current block. it allocates a new block if + necessary. */ + +static inline int +emit(GIFENCODERSTATE *context, int byte) +{ + /* write a byte to the output buffer */ + + if (!context->block || context->block->size == 255) { + GIFENCODERBLOCK* block; + + /* no room in the current block (or no current block); + allocate a new one */ + + /* add current block to end of flush queue */ + if (context->block) { + block = context->flush; + while (block && block->next) + block = block->next; + if (block) + block->next = context->block; + else + context->flush = context->block; + } + + /* get a new block */ + if (context->free) { + block = context->free; + context->free = NULL; + } else { + block = malloc(sizeof(GIFENCODERBLOCK)); + if (!block) + return 0; + } + + block->size = 0; + block->next = NULL; + + context->block = block; + + } + + /* write new byte to block */ + context->block->data[context->block->size++] = byte; + + return 1; +} + +/* write a code word to the current block. this is a macro to make + sure it's inlined on all platforms */ + +#define EMIT(code) {\ + context->bitbuffer |= ((INT32) (code)) << context->bitcount;\ + context->bitcount += 9;\ + while (context->bitcount >= 8) {\ + if (!emit(context, (UINT8) context->bitbuffer)) {\ + state->errcode = IMAGING_CODEC_MEMORY;\ + return 0;\ + }\ + context->bitbuffer >>= 8;\ + context->bitcount -= 8;\ + }\ +} + +/* write a run. we use a combination of literals and combinations of + literals. this can give quite decent compression for images with + long stretches of identical pixels. but remember: if you want + really good compression, use another file format. */ + +#define EMIT_RUN(label) {\ +label:\ + while (context->count > 0) {\ + int run = 2;\ + EMIT(context->last);\ + context->count--;\ + if (state->count++ == LAST_CODE) {\ + EMIT(CLEAR_CODE);\ + state->count = FIRST_CODE;\ + goto label;\ + }\ + while (context->count >= run) {\ + EMIT(state->count - 1);\ + context->count -= run;\ + run++;\ + if (state->count++ == LAST_CODE) {\ + EMIT(CLEAR_CODE);\ + state->count = FIRST_CODE;\ + goto label;\ + }\ + }\ + if (context->count > 1) {\ + EMIT(state->count - 1 - (run - context->count));\ + context->count = 0;\ + if (state->count++ == LAST_CODE) {\ + EMIT(CLEAR_CODE);\ + state->count = FIRST_CODE;\ + }\ + break;\ + }\ + }\ +} + +int +ImagingGifEncode(Imaging im, ImagingCodecState state, UINT8* buf, int bytes) +{ + UINT8* ptr; + int this; + + GIFENCODERBLOCK* block; + GIFENCODERSTATE *context = (GIFENCODERSTATE*) state->context; + + if (!state->state) { + + /* place a clear code in the output buffer */ + context->bitbuffer = CLEAR_CODE; + context->bitcount = 9; + + state->count = FIRST_CODE; + + if (context->interlace) { + context->interlace = 1; + context->step = 8; + } else + context->step = 1; + + context->last = -1; + + /* sanity check */ + if (state->xsize <= 0 || state->ysize <= 0) + state->state = ENCODE_EOF; + + } + + ptr = buf; + + for (;;) + + switch (state->state) { + + case INIT: + case ENCODE: + + /* identify and store a run of pixels */ + + if (state->x == 0 || state->x >= state->xsize) { + + if (!context->interlace && state->y >= state->ysize) { + state->state = ENCODE_EOF; + break; + } + + if (context->flush) { + state->state = FLUSH; + break; + } + + /* get another line of data */ + state->shuffle( + state->buffer, + (UINT8*) im->image[state->y + state->yoff] + + state->xoff * im->pixelsize, state->xsize + ); + + state->x = 0; + + if (state->state == INIT) { + /* preload the run-length buffer and get going */ + context->last = state->buffer[0]; + context->count = state->x = 1; + state->state = ENCODE; + } + + /* step forward, according to the interlace settings */ + state->y += context->step; + while (context->interlace && state->y >= state->ysize) + switch (context->interlace) { + case 1: + state->y = 4; + context->interlace = 2; + break; + case 2: + context->step = 4; + state->y = 2; + context->interlace = 3; + break; + case 3: + context->step = 2; + state->y = 1; + context->interlace = 0; + break; + default: + /* just make sure we don't loop forever */ + context->interlace = 0; + } + + } + + this = state->buffer[state->x++]; + + if (this == context->last) + context->count++; + else { + EMIT_RUN(label1); + context->last = this; + context->count = 1; + } + break; + + + case ENCODE_EOF: + + /* write the final run */ + EMIT_RUN(label2); + + /* write an end of image marker */ + EMIT(EOF_CODE); + + /* empty the bit buffer */ + while (context->bitcount > 0) { + if (!emit(context, (UINT8) context->bitbuffer)) { + state->errcode = IMAGING_CODEC_MEMORY; + return 0; + } + context->bitbuffer >>= 8; + context->bitcount -= 8; + } + + /* flush the last block, and exit */ + if (context->block) { + GIFENCODERBLOCK* block; + block = context->flush; + while (block && block->next) + block = block->next; + if (block) + block->next = context->block; + else + context->flush = context->block; + context->block = NULL; + } + + state->state = EXIT; + + /* fall through... */ + + case EXIT: + case FLUSH: + + while (context->flush) { + + /* get a block from the flush queue */ + block = context->flush; + + if (block->size > 0) { + + /* make sure it fits into the output buffer */ + if (bytes < block->size+1) + return ptr - buf; + + ptr[0] = block->size; + memcpy(ptr+1, block->data, block->size); + + ptr += block->size+1; + bytes -= block->size+1; + + } + + context->flush = block->next; + + if (context->free) + free(context->free); + context->free = block; + + } + + if (state->state == EXIT) { + /* this was the last block! */ + if (context->free) + free(context->free); + state->errcode = IMAGING_CODEC_END; + return ptr - buf; + } + + state->state = ENCODE; + break; + } +} |