login

<     >

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);