2015-08-03 11:42:41 (UTC-03:00)
Marcel Rodrigues <marcelgmr@gmail.com>
First commit; code adapted from congif.
diff --git a/gifenc.c b/gifenc.c new file mode 100644 index 0000000..995c494 --- /dev/null +++ b/gifenc.c @@ -0,0 +1,249 @@ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <unistd.h> +#include <stdint.h> + +#include "gifenc.h" + +/* helper to write a little-endian 16-bit number portably */ +#define write_num(fd, n) write((fd), (uint8_t []) {(n) & 0xFF, (n) >> 8}, 2) + +struct Node { + uint16_t key; + struct Node *children[]; +}; +typedef struct Node Node; + +static Node * +new_node(uint16_t key, int degree) +{ + Node *node = calloc(1, sizeof(*node) + sizeof(Node *[degree])); + if (node) + node->key = key; + return node; +} + +static void +del_trie(Node *root, int degree) +{ + if (!root) + return; + for (int i = 0; i < degree; i++) + del_trie(root->children[i], degree); + free(root); +} + +static void put_loop(GIF *gif, uint16_t loop); + +GIF * +new_gif( + const char *fname, uint16_t width, uint16_t height, + uint8_t *gct, int depth, int loop +) +{ + GIF *gif = calloc(1, sizeof(*gif) + 2*width*height); + if (!gif) + goto no_gif; + gif->w = width; gif->h = height; + gif->depth = depth > 1 ? depth : 2; + gif->cur = (uint8_t *) &gif[1]; + gif->old = &gif->cur[width*height]; + gif->fd = creat(fname, 0666); + if (gif->fd == -1) + goto no_fd; + write(gif->fd, "GIF89a", 6); + write_num(gif->fd, width); + write_num(gif->fd, height); + write(gif->fd, (uint8_t []) {0xF0 | (depth-1), 0x00, 0x00}, 3); + write(gif->fd, gct, 3 << depth); + if (loop >= 0 && loop <= 0xFFFF) + put_loop(gif, (uint16_t) loop); + return gif; +no_fd: + free(gif); +no_gif: + return NULL; +} + +static void +put_loop(GIF *gif, uint16_t loop) +{ + write(gif->fd, (uint8_t []) {'!', 0xFF, 0x0B}, 3); + write(gif->fd, "NETSCAPE2.0", 11); + write(gif->fd, (uint8_t []) {0x03, 0x01}, 2); + write_num(gif->fd, loop); + write(gif->fd, "\0", 1); +} + +/* Add packed key to buffer, updating offset and partial. + * gif->offset holds position to put next *bit* + * gif->partial holds bits to include in next byte */ +static void +put_key(GIF *gif, uint16_t key, int key_size) +{ + int byte_offset, bit_offset, bits_to_write; + byte_offset = gif->offset / 8; + bit_offset = gif->offset % 8; + gif->partial |= ((uint32_t) key) << bit_offset; + bits_to_write = bit_offset + key_size; + while (bits_to_write >= 8) { + gif->buffer[byte_offset++] = gif->partial & 0xFF; + if (byte_offset == 0xFF) { + write(gif->fd, "\xFF", 1); + write(gif->fd, gif->buffer, 0xFF); + byte_offset = 0; + } + gif->partial >>= 8; + bits_to_write -= 8; + } + gif->offset = (gif->offset + key_size) % (0xFF * 8); +} + +static void +end_key(GIF *gif) +{ + int byte_offset; + byte_offset = gif->offset / 8; + gif->buffer[byte_offset++] = gif->partial & 0xFF; + write(gif->fd, (uint8_t []) {byte_offset}, 1); + write(gif->fd, gif->buffer, byte_offset); + write(gif->fd, "\0", 1); + gif->offset = gif->partial = 0; +} + +static void +put_image(GIF *gif, uint16_t w, uint16_t h, uint16_t x, uint16_t y) +{ + int nkeys, key_size, i, j; + Node *node, *child, *root; + int degree = 1 << gif->depth; + + write(gif->fd, ",", 1); + write_num(gif->fd, x); + write_num(gif->fd, y); + write_num(gif->fd, w); + write_num(gif->fd, h); + write(gif->fd, (uint8_t []) {0x00, gif->depth}, 2); + root = new_node(0, degree); + /* Create nodes for single pixels. */ + for (nkeys = 0; nkeys < degree; nkeys++) + root->children[nkeys] = new_node(nkeys, degree); + node = root; + nkeys += 2; /* skip clear code and stop code */ + key_size = gif->depth + 1; + put_key(gif, degree, key_size); /* clear code */ + for (i = y; i < y+h; i++) { + for (j = x; j < x+w; j++) { + uint8_t pixel = gif->cur[i*gif->w+j] & (degree - 1); + child = node->children[pixel]; + if (child) { + node = child; + } else { + put_key(gif, node->key, key_size); + if (nkeys < 0x1000) { + if (nkeys == (1 << key_size)) + key_size++; + node->children[pixel] = new_node(nkeys++, degree); + } + node = root->children[pixel]; + } + } + } + put_key(gif, node->key, key_size); + put_key(gif, degree + 1, key_size); /* stop code */ + end_key(gif); + del_trie(root, degree); +} + +static int +get_bbox(GIF *gif, uint16_t *w, uint16_t *h, uint16_t *x, uint16_t *y) +{ + int i, j, k; + int left, right, top, bottom; + 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->cur[k] != gif->old[k]) { + if (j < left) left = j; + if (j > right) right = j; + if (i < top) top = i; + if (i > bottom) bottom = i; + } + } + } + if (left != gif->w && top != gif->h) { + *x = left; *y = top; + *w = right - left + 1; + *h = bottom - top + 1; + return 1; + } else { + return 0; + } +} + +static void +set_delay(GIF *gif, uint16_t d) +{ + write(gif->fd, (uint8_t []) {'!', 0xF9, 0x04, 0x04}, 4); + write_num(gif->fd, d); + write(gif->fd, "\0\0", 2); +} + +void +add_frame(GIF *gif, uint16_t delay) +{ + uint16_t w, h, x, y; + uint8_t *tmp; + + if (delay) + set_delay(gif, delay); + if (gif->nframes == 0) { + w = gif->w; + h = gif->h; + x = y = 0; + } else if (!get_bbox(gif, &w, &h, &x, &y)) { + /* image's not changed; save one pixel just to add delay */ + w = h = 1; + x = y = 0; + } + put_image(gif, w, h, x, y); + gif->nframes++; + tmp = gif->old; + gif->old = gif->cur; + gif->cur = tmp; +} + +void +close_gif(GIF* gif) +{ + write(gif->fd, ";", 1); + close(gif->fd); + free(gif); +} + +#if 1 +int +main() +{ + int i, j; + int w = 120, h = 90; + GIF *gif = new_gif("example.gif", w, h, (uint8_t []) { + 0x00, 0x00, 0x00, + 0xFF, 0x00, 0x00, + 0x00, 0xFF, 0x00, + 0x00, 0x00, 0xFF, + }, 2, 0); + for (i = 0; i < 4*6/3; i++) { + for (j = 0; j < w*h; j++) + gif->cur[j] = (i*3 + j) / 6 % 4; + add_frame(gif, 10); + } + close_gif(gif); +} +#endif diff --git a/gifenc.h b/gifenc.h new file mode 100644 index 0000000..5092da3 --- /dev/null +++ b/gifenc.h @@ -0,0 +1,17 @@ +typedef struct GIF { + uint16_t w, h; + int depth; + int fd; + int offset; + int nframes; + uint8_t *cur, *old; + uint32_t partial; + uint8_t buffer[0xFF]; +} GIF; + +GIF *new_gif( + const char *fname, uint16_t width, uint16_t height, + uint8_t *gct, int depth, int loop +); +void add_frame(GIF *gif, uint16_t delay); +void close_gif(GIF* gif);