png decoder

This commit is contained in:
Zack Buhman 2026-07-22 19:32:29 -05:00
parent f781e5f134
commit 48b3929cd4
6 changed files with 521 additions and 1 deletions

4
.gitignore vendored
View File

@ -1,3 +1,7 @@
*.wasm *.wasm
*.pyc *.pyc
__pycache__ __pycache__
*.o
*.data
.gdb_history
*.elf

44
src/Makefile Normal file
View File

@ -0,0 +1,44 @@
MINIZ = $(HOME)/miniz
CFLAGS = \
--target=wasm32 \
-g \
-O0 \
-nostdlib \
-I$(MINIZ) \
-I. \
-Werror \
-Wfatal-errors
# -flto \
MFLAGS = \
-DMINIZ_NO_STDIO \
-DMINIZ_NO_ARCHIVE_APIS \
-DMINIZ_NO_TIME \
-DMINIZ_NO_ZLIB_APIS \
-DMINIZ_NO_MALLOC
# -Wl,--lto-O1 \
LDFLAGS = \
-Wl,--no-entry \
-Wl,--export=build_crc_table \
-Wl,--export=decode \
-Wl,--print-map \
-Wl,--import-memory \
-Wl,-z,stack-size=1048576
%.o: %.c
clang $(CFLAGS) $(MFLAGS) -o $@ -c $<
%.o: %.cpp
clang++ $(CFLAGS) $(MFLAGS) -o $@ -c $<
PNG_OBJ = \
png.o \
stdlib.o \
$(MINIZ)/miniz_tinfl.o
png.wasm: $(PNG_OBJ)
clang++ $(CFLAGS) $(LDFLAGS) -o $@ $^

34
src/Makefile_linux Normal file
View File

@ -0,0 +1,34 @@
MINIZ = $(HOME)/miniz
CFLAGS = \
-g \
-O0 \
-I$(MINIZ) \
-Werror \
-Wfatal-errors
# -flto \
MFLAGS = \
-DMINIZ_NO_STDIO \
-DMINIZ_NO_ARCHIVE_APIS \
-DMINIZ_NO_TIME \
-DMINIZ_NO_ZLIB_APIS \
-DMINIZ_NO_MALLOC \
-DPNG_MAIN
# -Wl,--lto-O1 \
LDFLAGS =
%.o: %.c
clang $(CFLAGS) $(MFLAGS) -o $@ -c $<
%.o: %.cpp
clang++ $(CFLAGS) $(MFLAGS) -o $@ -c $<
PNG_OBJ = \
png.o \
$(MINIZ)/miniz_tinfl.o
png.elf: $(PNG_OBJ)
clang++ $(CFLAGS) $(LDFLAGS) -o $@ $^

405
src/png.cpp Normal file
View File

@ -0,0 +1,405 @@
#include <stdint.h>
#include <stddef.h>
#ifdef PNG_MAIN
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>
#include <assert.h>
#endif
#include "miniz_tinfl.h"
struct CT {
static constexpr uint32_t IHDR = 0x49484452;
static constexpr uint32_t gAMA = 0x67414d41;
static constexpr uint32_t cHRM = 0x6348524d;
static constexpr uint32_t pHYs = 0x70485973;
static constexpr uint32_t tIME = 0x74494d45;
static constexpr uint32_t bKGD = 0x624b4744;
static constexpr uint32_t IDAT = 0x49444154;
static constexpr uint32_t tEXt = 0x74455874;
static constexpr uint32_t IEND = 0x49454e44;
};
struct Header {
int width;
int height;
int bit_depth;
int color_type;
int compression_method;
int filter_method;
int interlace_method;
};
#define IBUF_SIZE ((1024 + 1) * 1024 * 4)
struct png_state {
tinfl_decompressor decomp;
char const * error;
uint8_t ibuf[IBUF_SIZE];
int iindex;
tinfl_status status;
};
static png_state _png_state;
extern "C" void build_crc_table(uint32_t * crc_table);
void build_crc_table(uint32_t * crc_table)
{
for (uint32_t i = 0; i < 256; i++) {
uint32_t c = i;
for (int j = 0; j < 8; j++) {
if (c & 1)
c = 0xedb88320 ^ (c >> 1);
else
c = c >> 1;
}
crc_table[i] = c;
}
}
static uint32_t calculate_crc(uint32_t const * crc_table, uint8_t const * mem, int offset, int length)
{
uint32_t crc = 0xffffffffu;
for (int i = 0; i < length; i++) {
crc = crc_table[(crc ^ mem[offset + i]) & 0xff] ^ (crc >> 8);
}
return crc ^ 0xffffffffu;
}
static uint32_t decode_int32be(uint8_t const * mem, int offset)
{
return
(((uint32_t)mem[offset + 0]) << 24)
| (((uint32_t)mem[offset + 1]) << 16)
| (((uint32_t)mem[offset + 2]) << 8)
| (((uint32_t)mem[offset + 3]) << 0)
;
}
static int paeth(int a, int b, int c)
{
int p = a + b - c;
int pa = __builtin_abs(p - a);
int pb = __builtin_abs(p - b);
int pc = __builtin_abs(p - c);
if (pa <= pb and pa <= pc)
return a;
else if (pb <= pc)
return b;
else
return c;
}
static int buffer_get(uint8_t const * buf, int stride, int x, int y)
{
if (x < 0) return 0;
if (y < 0) return 0;
return buf[y * stride + x];
}
static int reconstruct(int function,
uint8_t const * ibuf, int istride,
uint8_t const * obuf, int ostride,
int x, int y, int cpp)
{
int f = ibuf[y * istride + x + 1];
switch (function) {
case 0: // none
{
return f;
}
case 1: // sub
{
int a = buffer_get(obuf, ostride, x - cpp, y);
return (f + a) & 0xff;
}
case 2: // up
{
int b = buffer_get(obuf, ostride, x, y - 1);
return (f + b) & 0xff;
}
case 3: // average
{
int a = buffer_get(obuf, ostride, x - cpp, y);
int b = buffer_get(obuf, ostride, x, y - 1);
return (f + ((a + b) / 2)) & 0xff;
}
case 4: // paeth
{
int a = buffer_get(obuf, ostride, x - cpp, y);
int b = buffer_get(obuf, ostride, x, y - 1);
int c = buffer_get(obuf, ostride, x - cpp, y - 1);
return (f + paeth(a, b, c)) & 0xff;
}
default:
return -10;
}
}
static int components_per_pixel(Header const * header)
{
if (header->color_type == 2) {
return 3;
} else if (header->color_type == 6) {
return 4;
} else {
return -1;
}
}
static int decode_idat(uint8_t const * mem, int offset, int length,
png_state * state)
{
int flags = TINFL_FLAG_HAS_MORE_INPUT | TINFL_FLAG_PARSE_ZLIB_HEADER | TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF;
uint8_t const * src = &mem[offset];
size_t src_length = length;
uint8_t * dst = &state->ibuf[state->iindex];
size_t dst_length = IBUF_SIZE - state->iindex;
state->status = tinfl_decompress(&state->decomp,
src, &src_length,
state->ibuf, dst, &dst_length,
flags);
state->iindex += dst_length;
if (!(state->status == TINFL_STATUS_DONE || state->status == TINFL_STATUS_NEEDS_MORE_INPUT)) {
if (state->status < 0) {
state->error = "idat tinfl error neg";
} else {
state->error = "idat tinfl error pos";
}
return -__builtin_abs(state->status);
}
return 0;
}
static int decode_ihdr(uint8_t const * mem, int offset, int length,
Header * header,
png_state * state)
{
if (length != 13) {
state->error = "ihdr length != 13";
return -4;
}
header->width = decode_int32be(mem, offset + 0);
header->height = decode_int32be(mem, offset + 4);
header->bit_depth = mem[offset + 8];
header->color_type = mem[offset + 9];
header->compression_method = mem[offset + 10];
header->filter_method = mem[offset + 11];
header->interlace_method = mem[offset + 12];
return 0;
}
static int decode_iend(uint32_t length,
Header * header,
png_state * state,
uint8_t * obuf, int osize)
{
if (length != 0) {
state->error = "iend length is not zero";
return -6;
}
if (state->status != TINFL_STATUS_DONE) {
int flags = TINFL_FLAG_PARSE_ZLIB_HEADER | TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF;
uint8_t * dst = &state->ibuf[state->iindex];
size_t src_length = 0;
size_t dst_length = IBUF_SIZE - state->iindex;
tinfl_status status;
status = tinfl_decompress(&state->decomp,
nullptr, &src_length,
state->ibuf, dst, &dst_length,
flags);
state->iindex += dst_length;
if (status != TINFL_STATUS_DONE) {
if (status < 0) {
state->error = "iend tinfl error neg";
} else {
state->error = "iend tinfl error pos";
}
return -__builtin_abs(status);
}
}
if (header->bit_depth != 8) {
state->error = "unsupported bit depth";
return -(22 | (header->bit_depth << 8));
}
int cpp = components_per_pixel(header);
if (cpp < 0) {
state->error = "unsupported color type";
return -(3 | (header->color_type << 8));
}
int obpp = cpp * 1;
int ostride = header->width * obpp;
int ibpp = cpp * 1;
int istride = header->width * ibpp + 1;
for (int y = 0; y < header->height; y++) {
int function = state->ibuf[y * istride];
for (int x = 0; x < header->width * cpp; x++) {
int value = reconstruct(function, state->ibuf, istride, obuf, ostride, x, y, cpp);
obuf[y * ostride + x] = value;
}
}
return 0;
}
static int decode_chunk(uint32_t const * crc_table, uint8_t const * mem, int & offset,
Header * header,
png_state * state,
uint8_t * obuf, int osize)
{
uint32_t length = decode_int32be(mem, offset + 0);
uint32_t chunk_type = decode_int32be(mem, offset + 4);
uint32_t a_crc = calculate_crc(crc_table, mem, offset + 4, length + 4);
offset += 8;
int data_offset = offset;
offset += length;
uint32_t e_crc = decode_int32be(mem, offset);
if (e_crc != a_crc) {
state->error = "chunk crc mismatch";
return -5;
}
offset += 4;
if (chunk_type == CT::IHDR) {
return decode_ihdr(mem, data_offset, length, header, state);
} else if (chunk_type == CT::IDAT) {
return decode_idat(mem, data_offset, length, state);
} else if (chunk_type == CT::IEND) {
return decode_iend(length, header, state, obuf, osize);
} else {
return 0;
}
}
extern "C" int decode(uint32_t const * crc_table, uint8_t const * mem, int length,
Header * header,
uint8_t * obuf, int osize,
char const ** error);
int decode(uint32_t const * crc_table, uint8_t const * mem, int length,
Header * header,
uint8_t * obuf, int osize,
char const ** error)
{
*error = 0;
tinfl_init(&_png_state.decomp);
_png_state.error = 0;
_png_state.iindex = 0;
uint8_t const signature[] = {0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A};
for (int i = 0; i < 8; i++) {
if (signature[i] != mem[i]) {
*error = "signature mismatch";
return -(7 | (i << 8) | (mem[i] << 16));
}
}
int offset = 8;
while (offset < length) {
int ret = decode_chunk(crc_table, mem, offset, header, &_png_state, obuf, osize);
if (ret < 0) {
*error = _png_state.error;
return ret;
}
}
if (length != offset) {
*error = "decoded data outside buffer";
return -8;
}
return components_per_pixel(header) * header->width * header->height;
}
#ifdef PNG_MAIN
void * read_file(char const * filename, int * out_size)
{
FILE * f = fopen(filename, "rb");
if (f == NULL) {
fprintf(stderr, "fopen(%s): %s\n", filename, strerror(errno));
return NULL;
}
int fseek_end_ret = fseek(f, 0, SEEK_END);
if (fseek_end_ret < 0) {
fprintf(stderr, "fseek(%s, SEEK_END): %s\n", filename, strerror(errno));
return NULL;
}
size_t size = ftell(f);
if (size < 0) {
fprintf(stderr, "ftell(%s): %s\n", filename, strerror(errno));
return NULL;
}
int fseek_set_ret = fseek(f, 0, SEEK_SET);
if (fseek_set_ret < 0) {
fprintf(stderr, "lseek(%s, SEEK_SET): %s\n", filename, strerror(errno));
return NULL;
}
rewind(f);
void * buf = malloc(size);
size_t read_size = fread(buf, 1, size, f);
if (read_size != size) {
fprintf(stderr, "fread(%s): %s\n", filename, strerror(errno));
return NULL;
}
*out_size = size;
return buf;
}
int main(int argc, char const ** argv)
{
if (argc < 3) {
fprintf(stderr, "usage: %s [input-file] [output-file]\n", argv[0]);
return 1;
}
int isize;
void * ibuf = read_file(argv[1], &isize);
uint32_t crc_table[256];
build_crc_table(crc_table);
uint8_t mem[] = {0x49, 0x45, 0x4E, 0x44};
uint32_t crc = calculate_crc(crc_table, mem, 0, 4);
assert(crc == 0xae426082);
int osize = 8 * 1024 * 1024;
void * obuf = malloc(osize);
Header header{};
char const * error;
int ret = decode(crc_table,
reinterpret_cast<uint8_t *>(ibuf), isize,
&header,
reinterpret_cast<uint8_t *>(obuf), osize,
&error);
if (ret < 0)
return 1;
FILE * f = fopen(argv[2], "wb");
assert(f != NULL);
fwrite(obuf, ret, 1, f);
fclose(f);
return 0;
}
#endif

20
src/stdlib.c Normal file
View File

@ -0,0 +1,20 @@
#include <stddef.h>
void *memset(void *dest, int c, size_t n)
{
unsigned char *s = dest;
size_t k;
for (; n; n--, s++) *s = c;
return dest;
}
void *memcpy(void *restrict dest, const void *restrict src, size_t n)
{
unsigned char *d = dest;
const unsigned char *s = src;
for (; n; n--) *d++ = *s++;
return dest;
}

13
src/stdlib.h Normal file
View File

@ -0,0 +1,13 @@
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
void *memset(void *dest, int c, size_t n);
void *memcpy(void * dest, const void * src, size_t n);
#ifdef __cplusplus
}
#endif