Files
psxrecomp/runtime/src/png_write.h
T
Matthew Stanley bceadc6124 Add DuckStation-compatible texture pack mod and gameplay dumping
Load modern texpage/texupload PNG replacements independently of DuckStation source. Integrate pack folders and options with mods, preserve native VRAM, and validate GL rendering, format matching, dump safety, and upload invalidation.
2026-10-06 23:24:47 -07:00

124 lines
5.0 KiB
C

/* png_write.h — minimal, dependency-free RGB→PNG writer.
*
* A real PNG (8-bit truecolor) whose IDAT is a zlib stream of "stored"
* (uncompressed) DEFLATE blocks: bigger on disk than a compressed PNG, but
* accepted by every viewer (and the harness Read tool), and nothing new to link
* against — which keeps the self-contained static binaries self-contained.
*
* Header-only with `static` linkage so both the runtime debug server and the
* Beetle debug server (separate binaries / translation units) can emit PNG from
* one source, with no shared object to add to either build. */
#ifndef PSX_PNG_WRITE_H
#define PSX_PNG_WRITE_H
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
static uint32_t s_png_crc_tbl[256];
static int s_png_crc_init = 0;
static void png_crc_build(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);
s_png_crc_tbl[n] = c;
}
s_png_crc_init = 1;
}
static uint32_t png_crc_update(uint32_t crc, const uint8_t *p, size_t n) {
if (!s_png_crc_init) png_crc_build();
for (size_t i = 0; i < n; i++) crc = s_png_crc_tbl[(crc ^ p[i]) & 0xFF] ^ (crc >> 8);
return crc;
}
static uint32_t png_adler32(const uint8_t *p, size_t n) {
uint32_t a = 1, b = 0;
/* process in blocks so the 16-bit sums never overflow before the mod */
while (n) {
size_t k = n < 5552 ? n : 5552;
for (size_t i = 0; i < k; i++) { a += p[i]; b += a; }
a %= 65521; b %= 65521; p += k; n -= k;
}
return (b << 16) | a;
}
static void png_put_be32(FILE *f, uint32_t v) {
uint8_t b[4] = { (uint8_t)(v >> 24), (uint8_t)(v >> 16),
(uint8_t)(v >> 8), (uint8_t)v };
fwrite(b, 1, 4, f);
}
static void png_chunk(FILE *f, const char *type, const uint8_t *data, size_t len) {
png_put_be32(f, (uint32_t)len);
uint32_t crc = 0xFFFFFFFFu;
crc = png_crc_update(crc, (const uint8_t *)type, 4);
crc = png_crc_update(crc, data, len);
fwrite(type, 1, 4, f);
if (len) fwrite(data, 1, len, f);
png_put_be32(f, crc ^ 0xFFFFFFFFu);
}
/* Write top-down RGB/RGBA data. This internal helper has static linkage so
* the public wrappers stay usable by the existing C debug servers. */
static int png_write_channels(FILE *f, const uint8_t *pixels, uint32_t w,
uint32_t h, unsigned channels) {
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]=(uint8_t)(w>>24); ihdr[1]=(uint8_t)(w>>16); ihdr[2]=(uint8_t)(w>>8); ihdr[3]=(uint8_t)w;
ihdr[4]=(uint8_t)(h>>24); ihdr[5]=(uint8_t)(h>>16); ihdr[6]=(uint8_t)(h>>8); ihdr[7]=(uint8_t)h;
ihdr[8]=8; /* bit depth */
ihdr[9]=(uint8_t)(channels == 4 ? 6 : 2); /* RGBA or RGB */
ihdr[10]=0; /* compression */
ihdr[11]=0; /* filter */
ihdr[12]=0; /* interlace */
png_chunk(f, "IHDR", ihdr, sizeof ihdr);
/* Filtered raw scanlines: each row prefixed with filter byte 0 (None). */
size_t raw_len = (size_t)h * (1 + (size_t)w * channels);
uint8_t *raw = (uint8_t *)malloc(raw_len);
if (!raw) return 0;
for (uint32_t y = 0; y < h; y++) {
uint8_t *row = raw + (size_t)y * (1 + (size_t)w * channels);
row[0] = 0;
memcpy(row + 1, pixels + (size_t)y * w * channels, (size_t)w * channels);
}
/* zlib stream: 2-byte header + stored DEFLATE blocks + 4-byte Adler32. */
size_t nblocks = (raw_len + 65534) / 65535; if (nblocks == 0) nblocks = 1;
size_t z_len = 2 + nblocks * 5 + raw_len + 4;
uint8_t *z = (uint8_t *)malloc(z_len);
if (!z) { free(raw); return 0; }
size_t zi = 0;
z[zi++] = 0x78; /* CMF: 32K window, deflate */
z[zi++] = 0x01; /* FLG: check bits make 0x7801 a multiple of 31 */
size_t off = 0;
while (off < raw_len) {
size_t n = raw_len - off; if (n > 65535) n = 65535;
int final = (off + n >= raw_len);
z[zi++] = (uint8_t)(final ? 1 : 0); /* BFINAL | BTYPE=00 */
z[zi++] = (uint8_t)(n & 0xFF); z[zi++] = (uint8_t)(n >> 8);
uint16_t nl = (uint16_t)~n;
z[zi++] = (uint8_t)(nl & 0xFF); z[zi++] = (uint8_t)(nl >> 8);
memcpy(z + zi, raw + off, n); zi += n; off += n;
}
uint32_t ad = png_adler32(raw, raw_len);
z[zi++] = (uint8_t)(ad >> 24); z[zi++] = (uint8_t)(ad >> 16);
z[zi++] = (uint8_t)(ad >> 8); z[zi++] = (uint8_t)ad;
free(raw);
png_chunk(f, "IDAT", z, zi);
free(z);
png_chunk(f, "IEND", NULL, 0);
return !ferror(f);
}
/* Write an RGB (3 bytes/pixel, top-down) buffer as a PNG. Returns 1 on success. */
static int png_write_rgb(FILE *f, const uint8_t *rgb, uint32_t w, uint32_t h) {
return png_write_channels(f, rgb, w, h, 3);
}
/* Preserve raw 8-bit RGBA, including DuckStation's ST alpha convention. */
static int png_write_rgba(FILE *f, const uint8_t *rgba, uint32_t w, uint32_t h) {
return png_write_channels(f, rgba, w, h, 4);
}
#endif /* PSX_PNG_WRITE_H */