#include #include #include #include namespace gif { struct gif_t { uint16_t w, h; int depth; int bgindex; FILE *fd; int offset; int nframes; uint8_t *frame, *back; uint32_t partial; uint8_t buffer[0xFF]; }; struct node_t { uint16_t key; node_t *children[]; }; static node_t *new_node(uint16_t key, int degree) { node_t *node = (node_t*)calloc(1, sizeof(*node) + degree * sizeof(node_t *)); if (node) node->key = key; return node; } static node_t *new_trie(int degree, int *nkeys) { node_t *root = new_node(0, degree); /* Create nodes for single pixels. */ for (*nkeys = 0; *nkeys < degree; (*nkeys)++) root->children[*nkeys] = new_node(*nkeys, degree); *nkeys += 2; /* skip clear code and stop code */ return root; } static void del_trie(node_t *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_t *gif, uint16_t loop); gif_t *create(const char *fname, uint16_t width, uint16_t height, uint8_t *palette, uint8_t depth, int16_t bgindex, int loop) { gif_t *gif = (gif_t*)calloc(1, sizeof(*gif) + (bgindex < 0 ? 2 : 1)*width*height); gif->w = width; gif->h = height; gif->bgindex = bgindex; gif->frame = (uint8_t *) &gif[1]; gif->back = &gif->frame[width*height]; gif->fd = fopen(fname, "wb"); if (!gif->fd) { free(gif); return NULL; } fwrite("GIF89a", 6, 1, gif->fd); fwrite(&width, 2, 1, gif->fd); fwrite(&height, 2, 1, gif->fd); gif->depth = depth; fputc((!palette?0x70:0xF0|(depth-1)), gif->fd); fputc(bgindex, gif->fd); fputc(0, gif->fd); if (palette) fwrite(palette, 3 << depth, 1, gif->fd); if (loop >= 0 && loop <= 0xFFFF) put_loop(gif, (uint16_t)loop); return gif; } static void put_loop(gif_t *gif, uint16_t loop) { fputc('!', gif->fd); fputc(0xFF, gif->fd); fputc(0x0B, gif->fd); fwrite("NETSCAPE2.0", 11, 1, gif->fd); fputc(0x03, gif->fd); fputc(0x01, gif->fd); fwrite(&loop, 2, 1, gif->fd); fputc(0, gif->fd); } /* 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_t *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) { fputc(0xFF, gif->fd); fwrite(gif->buffer, 0xFF, 1, gif->fd); byte_offset = 0; } gif->partial >>= 8; bits_to_write -= 8; } gif->offset = (gif->offset + key_size) % (0xFF * 8); } static void end_key(gif_t *gif) { uint8_t byte_offset; byte_offset = gif->offset >> 3; if (gif->offset & 0x07) gif->buffer[byte_offset++] = gif->partial & 0xFF; if (byte_offset) { fputc(byte_offset, gif->fd); fwrite(gif->buffer, byte_offset, 1, gif->fd); } fputc(0, gif->fd); gif->offset = gif->partial = 0; } static void put_image(gif_t *gif, uint16_t w, uint16_t h, uint16_t x, uint16_t y) { int nkeys, key_size, i, j; node_t *node, *child, *root; int degree = 1 << gif->depth; fputc(',', gif->fd); fwrite(&x, 2, 1, gif->fd); fwrite(&y, 2, 1, gif->fd); fwrite(&w, 2, 1, gif->fd); fwrite(&h, 2, 1, gif->fd); fputc(0, gif->fd); fputc(gif->depth, gif->fd); root = node = new_trie(degree, &nkeys); 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->frame[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); } else { put_key(gif, degree, key_size); /* clear code */ del_trie(root, degree); root = node = new_trie(degree, &nkeys); key_size = gif->depth + 1; } 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_t *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++) { 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; 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 add_graphics_control_extension(gif_t *gif, uint16_t d) { uint8_t flags = ((gif->bgindex >= 0 ? 2 : 1) << 2) + 1; fputc('!', gif->fd); fputc(0xF9, gif->fd); fputc(0x04, gif->fd); fputc(flags, gif->fd); fwrite(&d, 2, 1, gif->fd); fputc(gif->bgindex, gif->fd); fputc(0, gif->fd); } void addFrame(gif_t *gif, uint16_t delay) { uint16_t w, h, x, y; uint8_t *tmp; if (delay || (gif->bgindex >= 0)) add_graphics_control_extension(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++; if (gif->bgindex < 0) { tmp = gif->back; gif->back = gif->frame; gif->frame = tmp; } } void close(gif_t* gif) { fputc(';', gif->fd); fclose(gif->fd); free(gif); } }