Mercurial > repos > blastem
diff png.c @ 1648:b7ecd0d6a77b mame_interp
Merge from default
author | Michael Pavone <pavone@retrodev.com> |
---|---|
date | Tue, 25 Dec 2018 11:12:26 -0800 |
parents | 31effaadf877 |
children | 81eebbe6b2e3 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/png.c Tue Dec 25 11:12:26 2018 -0800 @@ -0,0 +1,447 @@ +#include <stdint.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include "zlib/zlib.h" + +static const char png_magic[] = {0x89, 'P', 'N', 'G', '\r', '\n', 0x1A, '\n'}; +static const char ihdr[] = {'I', 'H', 'D', 'R'}; +static const char plte[] = {'P', 'L', 'T', 'E'}; +static const char idat[] = {'I', 'D', 'A', 'T'}; +static const char iend[] = {'I', 'E', 'N', 'D'}; + +enum { + COLOR_GRAY, + COLOR_TRUE = 2, + COLOR_INDEXED, + COLOR_GRAY_ALPHA, + COLOR_TRUE_ALPHA=6 +}; + +static void write_chunk(FILE *f, const char*id, uint8_t *buffer, uint32_t size) +{ + uint8_t tmp[4] = {size >> 24, size >> 16, size >> 8, size}; + uint8_t warn = 0; + warn = warn || (sizeof(tmp) != fwrite(tmp, 1, sizeof(tmp), f)); + warn = warn || (4 != fwrite(id, 1, 4, f)); + if (size) { + warn = warn || (size != fwrite(buffer, 1, size, f)); + } + + uint32_t crc = crc32(0, NULL, 0); + crc = crc32(crc, id, 4); + if (size) { + crc = crc32(crc, buffer, size); + } + tmp[0] = crc >> 24; + tmp[1] = crc >> 16; + tmp[2] = crc >> 8; + tmp[3] = crc; + warn = warn || (sizeof(tmp) != fwrite(tmp, 1, sizeof(tmp), f)); + if (warn) { + fprintf(stderr, "Failure during write of %c%c%c%c chunk\n", id[0], id[1], id[2], id[3]); + } +} + +static void write_header(FILE *f, uint32_t width, uint32_t height, uint8_t color_type) +{ + uint8_t chunk[13] = { + width >> 24, width >> 16, width >> 8, width, + height >> 24, height >> 16, height >> 8, height, + 8, color_type, 0, 0, 0 + }; + if (sizeof(png_magic) != fwrite(png_magic, 1, sizeof(png_magic), f)) { + fputs("Error writing PNG magic\n", stderr); + } + write_chunk(f, ihdr, chunk, sizeof(chunk)); +} + +void save_png24(FILE *f, uint32_t *buffer, uint32_t width, uint32_t height, uint32_t pitch) +{ + uint32_t idat_size = (1 + width*3) * height; + uint8_t *idat_buffer = malloc(idat_size); + uint32_t *pixel = buffer; + uint8_t *cur = idat_buffer; + for (uint32_t y = 0; y < height; y++) + { + //save filter type + *(cur++) = 0; + uint32_t *start = pixel; + for (uint32_t x = 0; x < width; x++, pixel++) + { + uint32_t value = *pixel; + *(cur++) = value >> 16; + *(cur++) = value >> 8; + *(cur++) = value; + } + pixel = start + pitch / sizeof(uint32_t); + } + write_header(f, width, height, COLOR_TRUE); + uLongf compress_buffer_size = idat_size + 5 * (idat_size/16383 + 1) + 3; + uint8_t *compressed = malloc(compress_buffer_size); + compress(compressed, &compress_buffer_size, idat_buffer, idat_size); + free(idat_buffer); + write_chunk(f, idat, compressed, compress_buffer_size); + write_chunk(f, iend, NULL, 0); + free(compressed); +} + +void save_png(FILE *f, uint32_t *buffer, uint32_t width, uint32_t height, uint32_t pitch) +{ + uint32_t palette[256]; + uint8_t pal_buffer[256*3]; + uint32_t num_pal = 0; + uint32_t index_size = (1 + width) * height; + uint8_t *index_buffer = malloc(index_size); + uint8_t *cur = index_buffer; + uint32_t *pixel = buffer; + for (uint32_t y = 0; y < height; y++) + { + //save filter type + *(cur++) = 0; + uint32_t *start = pixel; + for (uint32_t x = 0; x < width; x++, pixel++, cur++) + { + uint32_t value = (*pixel) & 0xFFFFFF; + uint32_t i; + for (i = 0; i < num_pal; i++) + { + if (palette[i] == value) { + break; + } + } + if (i == num_pal) { + if (num_pal == 256) { + free(index_buffer); + save_png24(f, buffer, width, height, pitch); + return; + } + palette[i] = value; + num_pal++; + } + *cur = i; + } + pixel = start + pitch / sizeof(uint32_t); + } + write_header(f, width, height, COLOR_INDEXED); + cur = pal_buffer; + for (uint32_t i = 0; i < num_pal; i++) + { + *(cur++) = palette[i] >> 16; + *(cur++) = palette[i] >> 8; + *(cur++) = palette[i]; + } + write_chunk(f, plte, pal_buffer, num_pal * 3); + uLongf compress_buffer_size = index_size + 5 * (index_size/16383 + 1) + 3; + uint8_t *compressed = malloc(compress_buffer_size); + compress(compressed, &compress_buffer_size, index_buffer, index_size); + free(index_buffer); + write_chunk(f, idat, compressed, compress_buffer_size); + write_chunk(f, iend, NULL, 0); + free(compressed); +} + +typedef uint8_t (*filter_fun)(uint8_t *cur, uint8_t *last, uint8_t bpp, uint32_t x); +typedef uint32_t (*pixel_fun)(uint8_t **cur, uint8_t **last, uint8_t bpp, uint32_t x, filter_fun); + +static uint8_t filter_none(uint8_t *cur, uint8_t *last, uint8_t bpp, uint32_t x) +{ + return *cur; +} + +static uint8_t filter_sub(uint8_t *cur, uint8_t *last, uint8_t bpp, uint32_t x) +{ + if (x) { + return *cur + *(cur - bpp); + } else { + return *cur; + } +} + +static uint8_t filter_up(uint8_t *cur, uint8_t *last, uint8_t bpp, uint32_t x) +{ + if (last) { + return *cur + *last; + } else { + return *cur; + } +} + +static uint8_t filter_avg(uint8_t *cur, uint8_t *last, uint8_t bpp, uint32_t x) +{ + uint8_t prev = x ? *(cur - bpp) : 0; + uint8_t prior = last ? *last : 0; + return *cur + ((prev + prior) >> 1); +} + +static uint8_t paeth(uint8_t a, uint8_t b, uint8_t c) +{ + int32_t p = a + b - c; + int32_t pa = abs(p - a); + int32_t pb = abs(p - b); + int32_t pc = abs(p - c); + if (pa <= pb && pa <= pc) { + return a; + } + if (pb <= pc) { + return b; + } + return c; +} + +static uint8_t filter_paeth(uint8_t *cur, uint8_t *last, uint8_t bpp, uint32_t x) +{ + uint8_t prev, prev_prior; + if (x) { + prev = *(cur - bpp); + prev_prior = *(last - bpp); + } else { + prev = prev_prior = 0; + } + uint8_t prior = last ? *last : 0; + return *cur + paeth(prev, prior, prev_prior); +} + +static uint32_t pixel_gray(uint8_t **cur, uint8_t **last, uint8_t bpp, uint32_t x, filter_fun filter) +{ + uint8_t value = filter(*cur, *last, bpp, x); + (*cur)++; + if (*last) { + (*last)++; + } + return 0xFF000000 | value << 16 | value << 8 | value; +} + +static uint32_t pixel_true(uint8_t **cur, uint8_t **last, uint8_t bpp, uint32_t x, filter_fun filter) +{ + uint8_t red = filter(*cur, *last, bpp, x); + (*cur)++; + if (*last) { + (*last)++; + } + uint8_t green = filter(*cur, *last, bpp, x); + (*cur)++; + if (*last) { + (*last)++; + } + uint8_t blue = filter(*cur, *last, bpp, x); + (*cur)++; + if (*last) { + (*last)++; + } + return 0xFF000000 | red << 16 | green << 8 | blue; +} + +static uint32_t pixel_gray_alpha(uint8_t **cur, uint8_t **last, uint8_t bpp, uint32_t x, filter_fun filter) +{ + uint8_t value = filter(*cur, *last, bpp, x); + (*cur)++; + if (*last) { + (*last)++; + } + uint8_t alpha = filter(*cur, *last, bpp, x); + (*cur)++; + if (*last) { + (*last)++; + } + return alpha << 24 | value << 16 | value << 8 | value; +} + +static uint32_t pixel_true_alpha(uint8_t **cur, uint8_t **last, uint8_t bpp, uint32_t x, filter_fun filter) +{ + uint8_t red = filter(*cur, *last, bpp, x); + (*cur)++; + if (*last) { + (*last)++; + } + uint8_t green = filter(*cur, *last, bpp, x); + (*cur)++; + if (*last) { + (*last)++; + } + uint8_t blue = filter(*cur, *last, bpp, x); + (*cur)++; + if (*last) { + (*last)++; + } + uint8_t alpha = filter(*cur, *last, bpp, x); + (*cur)++; + if (*last) { + (*last)++; + } + return alpha << 24 | red << 16 | green << 8 | blue; +} + +static filter_fun filters[] = {filter_none, filter_sub, filter_up, filter_avg, filter_paeth}; + +#define MIN_CHUNK_SIZE 12 +#define MIN_IHDR_SIZE 0xD +#define MAX_SUPPORTED_DIM 32767 //chosen to avoid possibility of overflow when calculating uncompressed size +uint32_t *load_png(uint8_t *buffer, uint32_t buf_size, uint32_t *width, uint32_t *height) +{ + if (buf_size < sizeof(png_magic) || memcmp(buffer, png_magic, sizeof(png_magic))) { + return NULL; + } + uint32_t cur = sizeof(png_magic); + uint8_t has_header = 0; + uint8_t bits, color_type, comp_type, filter_type, interlace; + uint8_t *idat_buf = NULL; + uint8_t idat_needs_free = 0; + uint32_t idat_size; + uint32_t *out = NULL; + uint32_t *palette = NULL; + while(cur + MIN_CHUNK_SIZE <= buf_size) + { + uint32_t chunk_size = buffer[cur++] << 24; + chunk_size |= buffer[cur++] << 16; + chunk_size |= buffer[cur++] << 8; + chunk_size |= buffer[cur++]; + if (!memcmp(ihdr, buffer + cur, sizeof(ihdr))) { + if (chunk_size < MIN_IHDR_SIZE || cur + MIN_IHDR_SIZE > buf_size) { + return NULL; + } + cur += sizeof(ihdr); + *width = buffer[cur++] << 24; + *width |= buffer[cur++] << 16; + *width |= buffer[cur++] << 8; + *width |= buffer[cur++]; + *height = buffer[cur++] << 24; + *height |= buffer[cur++] << 16; + *height |= buffer[cur++] << 8; + *height |= buffer[cur++]; + if (*width > MAX_SUPPORTED_DIM || *height > MAX_SUPPORTED_DIM) { + return NULL; + } + bits = buffer[cur++]; + if (bits != 8) { + //only support 8-bits per element for now + return NULL; + } + color_type = buffer[cur++]; + if (color_type > COLOR_TRUE_ALPHA || color_type == 1 || color_type == 5) { + //reject invalid color type + return NULL; + } + comp_type = buffer[cur++]; + if (comp_type) { + //only compression type 0 is defined by the spec + return NULL; + } + filter_type = buffer[cur++]; + interlace = buffer[cur++]; + if (interlace) { + //interlacing not supported for now + return NULL; + } + cur += chunk_size - MIN_IHDR_SIZE; + has_header = 1; + } else { + if (!has_header) { + //IHDR is required to be the first chunk, fail if it isn't + break; + } + if (!memcmp(plte, buffer + cur, sizeof(plte))) { + //TODO: implement paletted images + } else if (!memcmp(idat, buffer + cur, sizeof(idat))) { + cur += sizeof(idat); + if (idat_buf) { + if (idat_needs_free) { + idat_buf = realloc(idat_buf, idat_size + chunk_size); + } else { + uint8_t *tmp = idat_buf; + idat_buf = malloc(idat_size + chunk_size); + memcpy(idat_buf, tmp, idat_size); + } + memcpy(idat_buf + idat_size, buffer + cur, chunk_size); + idat_size += chunk_size; + } else { + idat_buf = buffer + cur; + idat_size = chunk_size; + } + cur += chunk_size; + } else if (!memcmp(iend, buffer + cur, sizeof(iend))) { + if (!idat_buf) { + break; + } + if (!palette && color_type == COLOR_INDEXED) { + //indexed color, but no PLTE chunk found + return NULL; + } + uLongf uncompressed_size = *width * *height; + uint8_t bpp; + pixel_fun pixel; + switch (color_type) + { + case COLOR_GRAY: + uncompressed_size *= bits / 8; + bpp = bits/8; + pixel = pixel_gray; + break; + case COLOR_TRUE: + uncompressed_size *= 3 * bits / 8; + bpp = 3 * bits/8; + pixel = pixel_true; + break; + case COLOR_INDEXED: { + uint32_t pixels_per_byte = 8 / bits; + uncompressed_size = (*width / pixels_per_byte) * *height; + if (*width % pixels_per_byte) { + uncompressed_size += *height; + } + bpp = 1; + break; + } + case COLOR_GRAY_ALPHA: + uncompressed_size *= bits / 4; + bpp = bits / 4; + pixel = pixel_gray_alpha; + break; + case COLOR_TRUE_ALPHA: + uncompressed_size *= bits / 2; + bpp = bits / 2; + pixel = pixel_true_alpha; + break; + } + //add filter type byte + uncompressed_size += *height; + uint8_t *decomp_buffer = malloc(uncompressed_size); + if (Z_OK != uncompress(decomp_buffer, &uncompressed_size, idat_buf, idat_size)) { + free(decomp_buffer); + break; + } + out = calloc(*width * *height, sizeof(uint32_t)); + uint32_t *cur_pixel = out; + uint8_t *cur_byte = decomp_buffer; + uint8_t *last_line = NULL; + for (uint32_t y = 0; y < *height; y++) + { + uint8_t filter_type = *(cur_byte++); + if (filter_type >= sizeof(filters)/sizeof(*filters)) { + free(out); + out = NULL; + free(decomp_buffer); + break; + } + filter_fun filter = filters[filter_type]; + uint8_t *line_start = cur_byte; + for (uint32_t x = 0; x < *width; x++) + { + *(cur_pixel++) = pixel(&cur_byte, &last_line, bpp, x, filter); + } + last_line = line_start; + } + free(decomp_buffer); + } else { + //skip uncrecognized chunks + cur += 4 + chunk_size; + } + } + //skip CRC for now + cur += sizeof(uint32_t); + } + if (idat_needs_free) { + free(idat_buf); + } + free(palette); + return out; +}