png decoder
This commit is contained in:
parent
f781e5f134
commit
48b3929cd4
4
.gitignore
vendored
4
.gitignore
vendored
@ -1,3 +1,7 @@
|
||||
*.wasm
|
||||
*.pyc
|
||||
__pycache__
|
||||
*.o
|
||||
*.data
|
||||
.gdb_history
|
||||
*.elf
|
||||
44
src/Makefile
Normal file
44
src/Makefile
Normal 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
34
src/Makefile_linux
Normal 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
405
src/png.cpp
Normal 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
20
src/stdlib.c
Normal 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
13
src/stdlib.h
Normal 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
|
||||
Loading…
x
Reference in New Issue
Block a user