2021-08-17 18:14:38 (UTC-03:00)
Marcel Rodrigues <marcelgmr@gmail.com>
Implement optional transparent background.
diff --git a/README b/README index 35b159e..6f300f4 100644 --- a/README +++ b/README @@ -9,6 +9,7 @@ Features * user-defined palette of any depth from 1 up to 8 * each frame has its own (user-specified) delay time * flexible looping options: no loop, N repetitions, infinite loop + * optional transparent background * GIF size optimization: only stores frame differences * memory efficient: saves frames to file as soon as possible * small and portable: less than 300 lines of C99 @@ -35,6 +36,7 @@ handler: const char *fname, /* GIF file name */ uint16_t width, uint16_t height, /* frame size */ uint8_t *palette, int depth, /* color table */ + int bgindex, /* transparent color */ int loop /* looping information */ ); @@ -64,6 +66,9 @@ grey colors equally spaced between black and white, excluding both. If `depth` < 0 and `palette` is not NULL, then the default table with 2 ^ -depth colors is used and it is stored in the array at the `palette` address. +The `bgindex` parameter specifies the color number to be used as transparent +background. If `bgindex` < 0, then transparency is disabled. + If the `loop` parameter is zero, the resulting GIF will loop forever. If it is a positive number, the animation will be played that number of times. If `loop` is negative, no looping information is stored in the GIF file (for most GIF @@ -85,11 +90,14 @@ Pixel data is read from `gif->frame`, which points to a memory block like this: uint8_t _frame_[gif->width * gif->height]; -Note that the address of `gif->frame` changes between calls to ge_add_frame() -(*). For this reason, each frame must be written in its entirety to the current -address, even if one only wants to change a few pixels from the last frame. The -encoder will automatically detect the difference between two consecutive frames -in order to minimize the size of the output. +Note that, iif transparency is disabled, the address of `gif->frame` changes +between calls to ge_add_frame() (*). For this reason, each frame must be written +in its entirety to the current address, even if one only wants to change a few +pixels from the last frame. The encoder will automatically detect the difference +between two consecutive frames in order to minimize the size of the output. This +optimization is not applied when `gif->bgindex` >= 0, in which case it's safe to +reuse `gif->frame`'s address and content. Transparent GIFs are still slightly +optimized by encoding only the rectangular region containing all opaque pixels. Each byte in the frame buffer represents a pixel. The value of each pixel is an index to a palette entry. For instance, given the example palette above, we can diff --git a/gifenc.c b/gifenc.c index 5be9ffe..3a6ad4d 100644 --- a/gifenc.c +++ b/gifenc.c @@ -84,15 +84,17 @@ static void put_loop(ge_GIF *gif, uint16_t loop); ge_GIF * ge_new_gif( const char *fname, uint16_t width, uint16_t height, - uint8_t *palette, int depth, int loop + uint8_t *palette, int depth, int bgindex, int loop ) { int i, r, g, b, v; int store_gct, custom_gct; - ge_GIF *gif = calloc(1, sizeof(*gif) + 2*width*height); + int nbuffers = bgindex < 0 ? 2 : 1; + ge_GIF *gif = calloc(1, sizeof(*gif) + nbuffers*width*height); if (!gif) goto no_gif; gif->w = width; gif->h = height; + gif->bgindex = bgindex; gif->frame = (uint8_t *) &gif[1]; gif->back = &gif->frame[width*height]; #ifdef _WIN32 @@ -118,7 +120,7 @@ ge_new_gif( if (depth < 0) depth = -depth; gif->depth = depth > 1 ? depth : 2; - write(gif->fd, (uint8_t []) {0xF0 | (depth-1), 0x00, 0x00}, 3); + write(gif->fd, (uint8_t []) {0xF0 | (depth-1), (uint8_t) bgindex, 0x00}, 3); if (custom_gct) { write(gif->fd, palette, 3 << depth); } else if (depth <= 4) { @@ -252,12 +254,14 @@ get_bbox(ge_GIF *gif, uint16_t *w, uint16_t *h, uint16_t *x, uint16_t *y) { int i, j, k; int left, right, top, bottom; + uint8_t back; left = gif->w; right = 0; top = gif->h; bottom = 0; k = 0; for (i = 0; i < gif->h; i++) { for (j = 0; j < gif->w; j++, k++) { - if (gif->frame[k] != gif->back[k]) { + back = gif->bgindex >= 0 ? gif->bgindex : gif->back[k]; + if (gif->frame[k] != back) { if (j < left) left = j; if (j > right) right = j; if (i < top) top = i; @@ -278,9 +282,10 @@ get_bbox(ge_GIF *gif, uint16_t *w, uint16_t *h, uint16_t *x, uint16_t *y) static void set_delay(ge_GIF *gif, uint16_t d) { - write(gif->fd, (uint8_t []) {'!', 0xF9, 0x04, 0x04}, 4); + uint8_t flags = ((gif->bgindex >= 0 ? 2 : 1) << 2) + 1; + write(gif->fd, (uint8_t []) {'!', 0xF9, 0x04, flags}, 4); write_num(gif->fd, d); - write(gif->fd, "\0\0", 2); + write(gif->fd, (uint8_t []) {(uint8_t) gif->bgindex, 0x00}, 2); } void @@ -302,9 +307,11 @@ ge_add_frame(ge_GIF *gif, uint16_t delay) } put_image(gif, w, h, x, y); gif->nframes++; - tmp = gif->back; - gif->back = gif->frame; - gif->frame = tmp; + if (gif->bgindex < 0) { + tmp = gif->back; + gif->back = gif->frame; + gif->frame = tmp; + } } void diff --git a/gifenc.h b/gifenc.h index 9b63c7e..95547c4 100644 --- a/gifenc.h +++ b/gifenc.h @@ -10,6 +10,7 @@ extern "C" { typedef struct ge_GIF { uint16_t w, h; int depth; + int bgindex; int fd; int offset; int nframes; @@ -20,7 +21,7 @@ typedef struct ge_GIF { ge_GIF *ge_new_gif( const char *fname, uint16_t width, uint16_t height, - uint8_t *palette, int depth, int loop + uint8_t *palette, int depth, int bgindex, int loop ); void ge_add_frame(ge_GIF *gif, uint16_t delay); void ge_close_gif(ge_GIF* gif);