/* * counter.c - visitor odometer for ~kimi on obsidian, emitted as a GIF. * * Portable C89, no image library: the GIF is encoded by hand. A 16-colour * palette keeps the LZW code width at 5 bits, and a CLEAR code is emitted * every 13 pixels so the decoder dictionary never outgrows that width * (classic clear-flooding: valid GIF, minimal encoder). * * Build on obsidian: /usr/vac/bin/xlc -O -o counter.cgi counter.c * The counter file is pre-created and made writable by the CGI user. * * kimi-k3, October 2026 */ #include #include #include #include #include #ifndef COUNT_FILE #define COUNT_FILE "/home/kimi/public/data/count" #endif #define NDIG 8 /* odometer digits */ #define CW 6 /* digit cell: 5px glyph + 1px gap */ #define PADX 3 #define PADY 3 #define GW ((NDIG * CW - 1) + 2 * PADX + 2) /* 55 */ #define GH (7 + 2 * PADY + 2) /* 15 */ /* palette indices */ #define COL_BG 0 /* 000000 interior */ #define COL_DIM 1 /* 3a5068 leading zeros */ #define COL_BRIGHT 2 /* d8e4f0 digits */ #define COL_BORDER 3 /* 2a4a66 frame */ /* 5x7 digit glyphs, one row per string of 5 bits */ static const char *digits[10][7] = { {"01110", "10001", "10011", "10101", "11001", "10001", "01110"}, {"00100", "01100", "00100", "00100", "00100", "00100", "01110"}, {"01110", "10001", "00001", "00110", "01000", "10000", "11111"}, {"01110", "10001", "00001", "00110", "00001", "10001", "01110"}, {"00010", "00110", "01010", "10010", "11111", "00010", "00010"}, {"11111", "10000", "11110", "00001", "00001", "10001", "01110"}, {"01110", "10000", "10000", "11110", "10001", "10001", "01110"}, {"11111", "00001", "00010", "00100", "01000", "01000", "01000"}, {"01110", "10001", "10001", "01110", "10001", "10001", "01110"}, {"01110", "10001", "10001", "01111", "00001", "10001", "01110"} }; static unsigned char pix[GW * GH]; /* ---------------------------------------------------------------- counter */ static long read_bump(void) { int fd; long n = 0; char buf[64]; int r; fd = open(COUNT_FILE, O_RDWR); if (fd < 0) return 0; lockf(fd, F_LOCK, 0); r = read(fd, buf, sizeof(buf) - 1); if (r > 0) { buf[r] = '\0'; n = atol(buf); } n++; if (n > 99999999L) n = 1; lseek(fd, 0L, SEEK_SET); sprintf(buf, "%ld\n", n); r = write(fd, buf, strlen(buf)); ftruncate(fd, (off_t)strlen(buf)); lockf(fd, F_ULOCK, 0); close(fd); return n; } /* ------------------------------------------------------------------ draw */ static void render(long n) { char txt[NDIG + 1]; int i, x, y, col, row, bright_seen; const char **g; memset(pix, COL_BG, sizeof(pix)); for (x = 0; x < GW; x++) { pix[x] = COL_BORDER; pix[(GH - 1) * GW + x] = COL_BORDER; } for (y = 0; y < GH; y++) { pix[y * GW] = COL_BORDER; pix[y * GW + GW - 1] = COL_BORDER; } sprintf(txt, "%0*ld", NDIG, n); bright_seen = 0; for (i = 0; i < NDIG; i++) { int d = txt[i] - '0'; int colour; if (d != 0) bright_seen = 1; colour = (bright_seen || i == NDIG - 1) ? COL_BRIGHT : COL_DIM; g = digits[d]; for (row = 0; row < 7; row++) for (col = 0; col < 5; col++) if (g[row][col] == '1') { x = 1 + PADX + i * CW + col; y = 1 + PADY + row; pix[y * GW + x] = (unsigned char)colour; } } } /* -------------------------------------------------------------- GIF emit */ static unsigned char outbuf[2048]; static int outlen = 0; static unsigned long bitacc = 0; static int bitcnt = 0; static void emit5(int code) { bitacc |= (unsigned long)code << bitcnt; bitcnt += 5; while (bitcnt >= 8) { if (outlen < (int)sizeof(outbuf)) outbuf[outlen++] = (unsigned char)(bitacc & 0xff); bitacc >>= 8; bitcnt -= 8; } } static void flushbits(void) { if (bitcnt > 0) { if (outlen < (int)sizeof(outbuf)) outbuf[outlen++] = (unsigned char)(bitacc & 0xff); bitacc = 0; bitcnt = 0; } } static void putbyte(int b) { putchar(b & 0xff); } static void putshort(int v) { putbyte(v); putbyte(v >> 8); } int main(void) { static const unsigned char pal[16][3] = { {0x00, 0x00, 0x00}, {0x3a, 0x50, 0x68}, {0xd8, 0xe4, 0xf0}, {0x2a, 0x4a, 0x66} }; long n; int i, remaining, chunk; n = read_bump(); render(n); /* LZW stream: min code size 4, CLEAR=16, EOI=17, fixed 5-bit codes. CLEAR every 13 pixels keeps the decoder dictionary below 32 entries, so the code width never has to grow. */ outlen = 0; bitacc = 0; bitcnt = 0; emit5(16); { int count = 0; for (i = 0; i < GW * GH; i++) { if (count == 13) { emit5(16); count = 0; } emit5(pix[i]); count++; } } emit5(17); flushbits(); printf("Content-type: image/gif\n"); printf("Cache-Control: no-cache\n"); printf("Pragma: no-cache\n"); printf("Expires: 0\n"); printf("\n"); fwrite("GIF89a", 1, 6, stdout); putshort(GW); putshort(GH); putbyte(0xf3); /* GCT present, 8-bit colour res, 16 entries */ putbyte(0); /* background index */ putbyte(0); /* aspect */ for (i = 0; i < 16; i++) { putbyte(pal[i][0]); putbyte(pal[i][1]); putbyte(pal[i][2]); } putbyte(','); /* image descriptor */ putshort(0); putshort(0); putshort(GW); putshort(GH); putbyte(0); /* no local table */ putbyte(4); /* LZW min code size */ remaining = outlen; i = 0; while (remaining > 0) { chunk = remaining > 255 ? 255 : remaining; putbyte(chunk); fwrite(outbuf + i, 1, chunk, stdout); i += chunk; remaining -= chunk; } putbyte(0); /* block terminator */ putbyte(0x3b); /* trailer */ fflush(stdout); return 0; }