/* * gifenc.c - minimal streaming GIF89a encoder (see gifenc.h). * * LZW stream: min code size 8, CLEAR = 256, EOI = 257, fixed 9-bit codes. * After a CLEAR the decoder's dictionary holds 258 entries and grows by one * per pixel code; emitting CLEAR every 253 pixels keeps it below 512, so * the code width never has to grow past 9 bits. This is the 256-colour * generalisation of the 16-colour/5-bit encoder in counter.c. * * kimi-k3, October 2026 */ #include "gifenc.h" #define CLEAR 256 #define EOI 257 #define SPAN 253 /* pixel codes between CLEARs: 258+253 < 512 */ static void emit9(GifEnc *g, int code) { g->bitacc |= (unsigned long)code << g->bitcnt; g->bitcnt += 9; while (g->bitcnt >= 8) { if (g->blklen < (int)sizeof(g->blk)) g->blk[g->blklen++] = (unsigned char)(g->bitacc & 0xff); g->bitacc >>= 8; g->bitcnt -= 8; } } static void flushblk(GifEnc *g) { int i = 0, chunk; while (i < g->blklen) { chunk = g->blklen - i > 255 ? 255 : g->blklen - i; fputc(chunk, g->out); fwrite(g->blk + i, 1, (size_t)chunk, g->out); i += chunk; } g->blklen = 0; } static void flushbits(GifEnc *g) { if (g->bitcnt > 0) { if (g->blklen < (int)sizeof(g->blk)) g->blk[g->blklen++] = (unsigned char)(g->bitacc & 0xff); g->bitacc = 0; g->bitcnt = 0; } } static void putshort(FILE *f, int v) { fputc(v & 0xff, f); fputc((v >> 8) & 0xff, f); } void gif_begin(GifEnc *g, FILE *out, int w, int h, unsigned char pal[256][3]) { int i; g->out = out; g->bitacc = 0; g->bitcnt = 0; g->blklen = 0; g->px = 0; fwrite("GIF89a", 1, 6, out); putshort(out, w); putshort(out, h); fputc(0xf7, out); /* GCT present, 8-bit colour res, 256 entries */ fputc(0, out); /* background index */ fputc(0, out); /* aspect */ for (i = 0; i < 256; i++) { fputc(pal[i][0], out); fputc(pal[i][1], out); fputc(pal[i][2], out); } fputc(',', out); /* image descriptor */ putshort(out, 0); putshort(out, 0); putshort(out, w); putshort(out, h); fputc(0, out); /* no local table */ fputc(8, out); /* LZW min code size */ emit9(g, CLEAR); } void gif_pixel(GifEnc *g, int idx) { if (g->px == SPAN) { emit9(g, CLEAR); g->px = 0; } emit9(g, idx); g->px++; if (g->blklen > 200) flushblk(g); } void gif_end(GifEnc *g) { emit9(g, EOI); flushbits(g); flushblk(g); fputc(0, g->out); /* block terminator */ fputc(0x3b, g->out); /* trailer */ fflush(g->out); }