X-Git-Url: https://git.draconx.ca/gitweb/liblbx.git/blobdiff_plain/4d1c91362ed7719a1371ad977afd95f79821974f..af13a143c8bfcd713216b1382448174e21b9f278:/src/png.c diff --git a/src/png.c b/src/png.c index ee5d0a5..c52db03 100644 --- a/src/png.c +++ b/src/png.c @@ -21,7 +21,9 @@ #include #include #include +#include #include +#include #include @@ -35,18 +37,6 @@ struct file_info { FILE *f; }; -static int is_masked(unsigned char **mask, unsigned width, unsigned height) -{ - for (unsigned y = 0; y < height; y++) { - for (unsigned x = 0; x < width; x++) { - if (mask[y][x] == 0) - return 1; - } - } - - return 0; -} - static void fatal(png_structp png, int err, const char *fmt, ...) { va_list ap; @@ -92,13 +82,33 @@ static void flush_data(png_structp png) } } +static inline unsigned scale6to8(unsigned x) +{ + assert(x <= 0x3f); + + return x*0xff / 0x3f; +} + +/* Scale the 18-bit LBX palette into a 24-bit PNG palette. */ +static void fill_png_palette(png_color *out, const struct lbx_colour *in) +{ + for (unsigned i = 0; i < 256; i++) { + out[i] = (struct png_color_struct) { + .red = scale6to8(in[i].red), + .green = scale6to8(in[i].green), + .blue = scale6to8(in[i].blue), + }; + } +} + static void write_rgba_frame(png_structp png, png_infop info, unsigned char **framedata, unsigned char **mask, - struct lbx_colour *palette) + struct lbx_colour *lbx_palette) { png_uint_32 width = png_get_image_width(png, info); png_uint_32 height = png_get_image_height(png, info); + png_color png_palette[256]; unsigned char (*row)[4]; jmp_buf parent; @@ -120,11 +130,12 @@ write_rgba_frame(png_structp png, png_infop info, } png_write_info(png, info); + fill_png_palette(png_palette, lbx_palette); for (png_uint_32 y = 0; y < height; y++) { for (png_uint_32 x = 0; x < width; x++) { - row[x][0] = palette[framedata[y][x]].red; - row[x][1] = palette[framedata[y][x]].green; - row[x][2] = palette[framedata[y][x]].blue; + row[x][0] = png_palette[framedata[y][x]].red; + row[x][1] = png_palette[framedata[y][x]].green; + row[x][2] = png_palette[framedata[y][x]].blue; row[x][3] = mask[y][x] ? -1 : 0; } png_write_row(png, (void *)row); @@ -135,29 +146,69 @@ write_rgba_frame(png_structp png, png_infop info, } static void -write_index_frame(png_structp png, png_infop info, unsigned char **framedata, - struct lbx_colour *palette) +write_masked_index_frame(png_structp png, png_infop info, + unsigned char **framedata, unsigned char **mask) { - png_color png_palette[256]; + png_uint_32 width = png_get_image_width(png, info); + png_uint_32 height = png_get_image_height(png, info); + unsigned char (*row)[2]; + jmp_buf parent; - for (unsigned i = 0; i < 256; i++) { - png_palette[i] = (png_color) { - .red = palette[i].red, - .green = palette[i].green, - .blue = palette[i].blue, - }; + if (width >= PNG_UINT_32_MAX / sizeof *row) + fatal(png, -1, "image too wide to allocate row buffer"); + + row = png_malloc(png, width * sizeof *row); + + /* + * We need to establish our own error handler to free the row buffer. + * Some care must be taken to save/restore the caller's handler. + */ + memcpy(&parent, &png_jmpbuf(png), sizeof parent); + if (setjmp(png_jmpbuf(png))) { + png_free(png, row); + + memcpy(&png_jmpbuf(png), &parent, sizeof parent); + png_longjmp(png, 1); } + png_write_info(png, info); + for (png_uint_32 y = 0; y < height; y++) { + for (png_uint_32 x = 0; x < width; x++) { + row[x][0] = framedata[y][x]; + row[x][1] = mask[y][x] ? -1 : 0; + } + png_write_row(png, (void *)row); + } + png_write_end(png, NULL); + + memcpy(&png_jmpbuf(png), &parent, sizeof parent); +} + +static void +write_palette_frame(png_structp png, png_infop info, + unsigned char **framedata, struct lbx_colour *lbx_palette) +{ + png_color png_palette[256]; + + fill_png_palette(png_palette, lbx_palette); png_set_PLTE(png, info, png_palette, 256); png_set_rows(png, info, framedata); png_write_png(png, info, PNG_TRANSFORM_IDENTITY, NULL); } +static void +write_index_frame(png_structp png, png_infop info, unsigned char **framedata) +{ + png_set_rows(png, info, framedata); + png_write_png(png, info, PNG_TRANSFORM_IDENTITY, NULL); +} + int img_output_png(FILE *f, const char *filename, unsigned width, unsigned height, unsigned char **framedata, unsigned char **mask, struct lbx_colour *palette) { + bool masked = img_is_masked(mask, width, height); struct file_info file = { filename, f }; png_structp png; png_infop info; @@ -183,7 +234,7 @@ int img_output_png(FILE *f, const char *filename, png_set_error_fn(png, NULL, error_fn, warning_fn); png_set_write_fn(png, &file, write_data, flush_data); - if (is_masked(mask, width, height)) { + if (palette && masked) { /* * Indexed colour mode can only be used for images without * transparency, since indexed PNG requires sacrificing one or @@ -196,13 +247,27 @@ int img_output_png(FILE *f, const char *filename, PNG_FILTER_TYPE_DEFAULT); write_rgba_frame(png, info, framedata, mask, palette); - } else { + } else if (palette) { png_set_IHDR(png, info, width, height, 8, PNG_COLOR_TYPE_PALETTE, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); - write_index_frame(png, info, framedata, palette); + write_palette_frame(png, info, framedata, palette); + } else if (masked) { + png_set_IHDR(png, info, width, height, 8, + PNG_COLOR_TYPE_GRAY_ALPHA, PNG_INTERLACE_NONE, + PNG_COMPRESSION_TYPE_DEFAULT, + PNG_FILTER_TYPE_DEFAULT); + + write_masked_index_frame(png, info, framedata, mask); + } else { + png_set_IHDR(png, info, width, height, 8, + PNG_COLOR_TYPE_GRAY, PNG_INTERLACE_NONE, + PNG_COMPRESSION_TYPE_DEFAULT, + PNG_FILTER_TYPE_DEFAULT); + + write_index_frame(png, info, framedata); } png_destroy_write_struct(&png, &info);