/* * Minimal dependency-free PNG encoder (RGBA) using zlib. * Avoids linking SDL2_image (which drags in an incompatible SDL3 at runtime). */ #include #include #include #include "kknd.h" #include static uint32_t crc_table[256]; static int crc_done = 0; static void crc_init(void) { for (uint32_t n = 0; n < 256; n++) { uint32_t c = n; for (int k = 0; k < 8; k++) c = (c & 1) ? (0xedb88320U ^ (c >> 1)) : (c >> 1); crc_table[n] = c; } crc_done = 1; } static uint32_t crc_update(uint32_t c, const uint8_t *buf, size_t len) { c = ~c; for (size_t i = 0; i < len; i++) c = crc_table[(c ^ buf[i]) & 0xff] ^ (c >> 8); return ~c; } static void chunk(FILE *f, const char *type, const uint8_t *data, uint32_t len) { uint8_t hdr[4]; hdr[0] = (len >> 24) & 0xff; hdr[1] = (len >> 16) & 0xff; hdr[2] = (len >> 8) & 0xff; hdr[3] = len & 0xff; fwrite(hdr, 1, 4, f); fwrite(type, 1, 4, f); if (len) fwrite(data, 1, len, f); uint32_t crc = crc_update(0, (const uint8_t *)type, 4); crc = crc_update(crc, data, len); uint8_t cr[4]; cr[0] = (crc >> 24) & 0xff; cr[1] = (crc >> 16) & 0xff; cr[2] = (crc >> 8) & 0xff; cr[3] = crc & 0xff; fwrite(cr, 1, 4, f); } int png_save(const char *path, int w, int h, const uint8_t *rgba) { if (!crc_done) crc_init(); FILE *f = fopen(path, "wb"); if (!f) return -1; static const uint8_t sig[8] = {137,80,78,71,13,10,26,10}; fwrite(sig, 1, 8, f); uint8_t ihdr[13]; ihdr[0] = (w >> 24) & 0xff; ihdr[1] = (w >> 16) & 0xff; ihdr[2] = (w >> 8) & 0xff; ihdr[3] = w & 0xff; ihdr[4] = (h >> 24) & 0xff; ihdr[5] = (h >> 16) & 0xff; ihdr[6] = (h >> 8) & 0xff; ihdr[7] = h & 0xff; ihdr[8] = 8; /* bit depth */ ihdr[9] = 6; /* colour type RGBA */ ihdr[10] = 0; ihdr[11] = 0; ihdr[12] = 0; chunk(f, "IHDR", ihdr, 13); /* raw scanlines with filter byte 0 */ uint8_t *raw = (uint8_t *)malloc((size_t)(w * 4 + 1) * h); if (!raw) { fclose(f); return -1; } for (int y = 0; y < h; y++) { raw[(size_t)y * (w * 4 + 1)] = 0; memcpy(raw + (size_t)y * (w * 4 + 1) + 1, rgba + (size_t)y * w * 4, (size_t)w * 4); } uLongf cmp_cap = compressBound((uLong)(w * 4 + 1) * h); uint8_t *cmp = (uint8_t *)malloc(cmp_cap); if (!cmp) { free(raw); fclose(f); return -1; } uLongf cmp_len = cmp_cap; compress2((Bytef *)cmp, &cmp_len, (const Bytef *)raw, (uLong)(w * 4 + 1) * h, Z_BEST_COMPRESSION); chunk(f, "IDAT", cmp, (uint32_t)cmp_len); chunk(f, "IEND", NULL, 0); free(cmp); free(raw); fclose(f); return 0; }