Files
mini/gifenc.h

229 lines
7.3 KiB
C++

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
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, uint8_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);
}
}