From 48b3929cd454ef6355cfc78bef1420aeb90affee Mon Sep 17 00:00:00 2001 From: Zack Buhman Date: Wed, 22 Jul 2026 19:32:29 -0500 Subject: [PATCH] png decoder --- .gitignore | 6 +- src/Makefile | 44 +++++ src/Makefile_linux | 34 ++++ src/png.cpp | 405 +++++++++++++++++++++++++++++++++++++++++++++ src/stdlib.c | 20 +++ src/stdlib.h | 13 ++ 6 files changed, 521 insertions(+), 1 deletion(-) create mode 100644 src/Makefile create mode 100644 src/Makefile_linux create mode 100644 src/png.cpp create mode 100644 src/stdlib.c create mode 100644 src/stdlib.h diff --git a/.gitignore b/.gitignore index 2eee1e5..12c3a82 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,7 @@ *.wasm *.pyc -__pycache__ \ No newline at end of file +__pycache__ +*.o +*.data +.gdb_history +*.elf \ No newline at end of file diff --git a/src/Makefile b/src/Makefile new file mode 100644 index 0000000..e8c1b78 --- /dev/null +++ b/src/Makefile @@ -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 $@ $^ diff --git a/src/Makefile_linux b/src/Makefile_linux new file mode 100644 index 0000000..50cf7cb --- /dev/null +++ b/src/Makefile_linux @@ -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 $@ $^ diff --git a/src/png.cpp b/src/png.cpp new file mode 100644 index 0000000..453473a --- /dev/null +++ b/src/png.cpp @@ -0,0 +1,405 @@ +#include +#include + +#ifdef PNG_MAIN +#include +#include +#include +#include +#include +#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(ibuf), isize, + &header, + reinterpret_cast(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 diff --git a/src/stdlib.c b/src/stdlib.c new file mode 100644 index 0000000..35fb300 --- /dev/null +++ b/src/stdlib.c @@ -0,0 +1,20 @@ +#include + +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; +} diff --git a/src/stdlib.h b/src/stdlib.h new file mode 100644 index 0000000..084bb6c --- /dev/null +++ b/src/stdlib.h @@ -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