83 lines
2.7 KiB
C
83 lines
2.7 KiB
C
/*
|
|
* Minimal dependency-free PNG encoder (RGBA) using zlib.
|
|
* Avoids linking SDL2_image (which drags in an incompatible SDL3 at runtime).
|
|
*/
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include "kknd.h"
|
|
#include <zlib.h>
|
|
|
|
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;
|
|
}
|