From b4e07470d8505a7f1f28c127e8a7289611db6f5c Mon Sep 17 00:00:00 2001 From: Andrei Alexeyev Date: Sun, 18 Jul 2021 18:19:40 +0300 Subject: [PATCH] pixmap: refactor * Split conversion-related code into a separate file. * Renamed loaders to fileformat handlers. * Added API for exporting into one of the supported file formats. * Replaced serialization APIs with an "internal" fileformat capable of of storing any Pixmap. It is identical to the serialization format. * Removed PNG writing code from video_screenshot_task, now using the new pixmap export API instead. --- src/pixmap/conversion.c | 358 ++++++++++++++ src/pixmap/fileformats/fileformats.h | 30 ++ .../{serialize.c => fileformats/internal.c} | 45 +- src/pixmap/fileformats/meson.build | 6 + .../loader_png.c => fileformats/png.c} | 150 +++++- .../loader_webp.c => fileformats/webp.c} | 7 +- src/pixmap/loaders/loaders.h | 27 -- src/pixmap/loaders/meson.build | 5 - src/pixmap/meson.build | 6 +- src/pixmap/pixmap.c | 447 ++++-------------- src/pixmap/pixmap.h | 40 +- src/pixmap/serialize.h | 23 - src/resource/texture_loader/basisu_cache.c | 8 +- src/video.c | 75 +-- 14 files changed, 723 insertions(+), 504 deletions(-) create mode 100644 src/pixmap/conversion.c create mode 100644 src/pixmap/fileformats/fileformats.h rename src/pixmap/{serialize.c => fileformats/internal.c} (72%) create mode 100644 src/pixmap/fileformats/meson.build rename src/pixmap/{loaders/loader_png.c => fileformats/png.c} (50%) rename src/pixmap/{loaders/loader_webp.c => fileformats/webp.c} (96%) delete mode 100644 src/pixmap/loaders/loaders.h delete mode 100644 src/pixmap/loaders/meson.build delete mode 100644 src/pixmap/serialize.h diff --git a/src/pixmap/conversion.c b/src/pixmap/conversion.c new file mode 100644 index 0000000000..c322e05991 --- /dev/null +++ b/src/pixmap/conversion.c @@ -0,0 +1,358 @@ +/* + * This software is licensed under the terms of the MIT License. + * See COPYING for further information. + * --- + * Copyright (c) 2011-2019, Lukas Weber . + * Copyright (c) 2012-2019, Andrei Alexeyev . +*/ + +#include "taisei.h" + +#include "pixmap.h" +#include "util.h" + +// NOTE: this is pretty stupid and not at all optimized, patches welcome + +#define _CONV_FUNCNAME convert_u8_to_u8 +#define _CONV_IN_MAX UINT8_MAX +#define _CONV_IN_TYPE uint8_t +#define _CONV_OUT_MAX UINT8_MAX +#define _CONV_OUT_TYPE uint8_t +#include "pixmap_conversion.inc.h" + +#define _CONV_FUNCNAME convert_u8_to_u16 +#define _CONV_IN_MAX UINT8_MAX +#define _CONV_IN_TYPE uint8_t +#define _CONV_OUT_MAX UINT16_MAX +#define _CONV_OUT_TYPE uint16_t +#include "pixmap_conversion.inc.h" + +#define _CONV_FUNCNAME convert_u8_to_u32 +#define _CONV_IN_MAX UINT8_MAX +#define _CONV_IN_TYPE uint8_t +#define _CONV_OUT_MAX UINT32_MAX +#define _CONV_OUT_TYPE uint32_t +#include "pixmap_conversion.inc.h" + +#define _CONV_FUNCNAME convert_u8_to_f32 +#define _CONV_IN_MAX UINT8_MAX +#define _CONV_IN_TYPE uint8_t +#define _CONV_OUT_MAX 1.0f +#define _CONV_OUT_TYPE float +#include "pixmap_conversion.inc.h" + +#define _CONV_FUNCNAME convert_u16_to_u8 +#define _CONV_IN_MAX UINT16_MAX +#define _CONV_IN_TYPE uint16_t +#define _CONV_OUT_MAX UINT8_MAX +#define _CONV_OUT_TYPE uint8_t +#include "pixmap_conversion.inc.h" + +#define _CONV_FUNCNAME convert_u16_to_u16 +#define _CONV_IN_MAX UINT16_MAX +#define _CONV_IN_TYPE uint16_t +#define _CONV_OUT_MAX UINT16_MAX +#define _CONV_OUT_TYPE uint16_t +#include "pixmap_conversion.inc.h" + +#define _CONV_FUNCNAME convert_u16_to_u32 +#define _CONV_IN_MAX UINT16_MAX +#define _CONV_IN_TYPE uint16_t +#define _CONV_OUT_MAX UINT32_MAX +#define _CONV_OUT_TYPE uint32_t +#include "pixmap_conversion.inc.h" + +#define _CONV_FUNCNAME convert_u16_to_f32 +#define _CONV_IN_MAX UINT16_MAX +#define _CONV_IN_TYPE uint16_t +#define _CONV_OUT_MAX 1.0f +#define _CONV_OUT_TYPE float +#include "pixmap_conversion.inc.h" + +#define _CONV_FUNCNAME convert_u32_to_u8 +#define _CONV_IN_MAX UINT32_MAX +#define _CONV_IN_TYPE uint32_t +#define _CONV_OUT_MAX UINT8_MAX +#define _CONV_OUT_TYPE uint8_t +#include "pixmap_conversion.inc.h" + +#define _CONV_FUNCNAME convert_u32_to_u16 +#define _CONV_IN_MAX UINT32_MAX +#define _CONV_IN_TYPE uint32_t +#define _CONV_OUT_MAX UINT16_MAX +#define _CONV_OUT_TYPE uint16_t +#include "pixmap_conversion.inc.h" + +#define _CONV_FUNCNAME convert_u32_to_u32 +#define _CONV_IN_MAX UINT32_MAX +#define _CONV_IN_TYPE uint32_t +#define _CONV_OUT_MAX UINT32_MAX +#define _CONV_OUT_TYPE uint32_t +#include "pixmap_conversion.inc.h" + +#define _CONV_FUNCNAME convert_u32_to_f32 +#define _CONV_IN_MAX UINT32_MAX +#define _CONV_IN_TYPE uint32_t +#define _CONV_OUT_MAX 1.0f +#define _CONV_OUT_TYPE float +#include "pixmap_conversion.inc.h" + +#define _CONV_FUNCNAME convert_f32_to_u8 +#define _CONV_IN_MAX 1.0f +#define _CONV_IN_TYPE float +#define _CONV_OUT_MAX UINT8_MAX +#define _CONV_OUT_TYPE uint8_t +#include "pixmap_conversion.inc.h" + +#define _CONV_FUNCNAME convert_f32_to_u16 +#define _CONV_IN_MAX 1.0f +#define _CONV_IN_TYPE float +#define _CONV_OUT_MAX UINT16_MAX +#define _CONV_OUT_TYPE uint16_t +#include "pixmap_conversion.inc.h" + +#define _CONV_FUNCNAME convert_f32_to_u32 +#define _CONV_IN_MAX 1.0f +#define _CONV_IN_TYPE float +#define _CONV_OUT_MAX UINT32_MAX +#define _CONV_OUT_TYPE uint32_t +#include "pixmap_conversion.inc.h" + +#define _CONV_FUNCNAME convert_f32_to_f32 +#define _CONV_IN_MAX 1.0f +#define _CONV_IN_TYPE float +#define _CONV_OUT_MAX 1.0f +#define _CONV_OUT_TYPE float +#include "pixmap_conversion.inc.h" + +#define DEPTH_FLOAT_BIT (1 << 10) + +typedef void (*convfunc_t)( + size_t in_elements, + size_t out_elements, + size_t num_pixels, + void *vbuf_in, + void *vbuf_out, + int swizzle[4] +); + +#define CONV(in, in_depth, out, out_depth) { convert_##in##_to_##out, in_depth, out_depth } + +struct conversion_def { + convfunc_t func; + uint depth_in; + uint depth_out; +}; + +struct conversion_def conversion_table[] = { + CONV(u8, 8, u8, 8), + CONV(u8, 8, u16, 16), + CONV(u8, 8, u32, 32), + CONV(u8, 8, f32, 32 | DEPTH_FLOAT_BIT), + + CONV(u16, 16, u8, 8), + CONV(u16, 16, u16, 16), + CONV(u16, 16, u32, 32), + CONV(u16, 16, f32, 32 | DEPTH_FLOAT_BIT), + + CONV(u32, 32, u8, 8), + CONV(u32, 16, u16, 16), + CONV(u32, 32, u32, 32), + CONV(u32, 32, f32, 32 | DEPTH_FLOAT_BIT), + + CONV(f32, 32 | DEPTH_FLOAT_BIT, u8, 8), + CONV(f32, 16 | DEPTH_FLOAT_BIT, u16, 16), + CONV(f32, 32 | DEPTH_FLOAT_BIT, u32, 32), + CONV(f32, 32 | DEPTH_FLOAT_BIT, f32, 32 | DEPTH_FLOAT_BIT), + + { 0 } +}; + +static struct conversion_def* find_conversion(uint depth_in, uint depth_out) { + for(struct conversion_def *cv = conversion_table; cv->func; ++cv) { + if(cv->depth_in == depth_in && cv->depth_out == depth_out) { + return cv; + } + } + + log_fatal("Pixmap conversion for %upbc -> %upbc undefined, please add", depth_in, depth_out); +} + +static void pixmap_copy_meta(const Pixmap *src, Pixmap *dst) { + dst->format = src->format; + dst->width = src->width; + dst->height = src->height; + dst->origin = src->origin; +} + +void pixmap_copy(const Pixmap *src, Pixmap *dst) { + assert(dst->data.untyped != NULL); + assert(src->data_size > 0); + assert(dst->data_size >= src->data_size); + + if(!pixmap_format_is_compressed(src->format)) { + assert(src->data_size == pixmap_data_size(src->format, src->width, src->height)); + } + + pixmap_copy_meta(src, dst); + memcpy(dst->data.untyped, src->data.untyped, src->data_size); +} + +void pixmap_copy_alloc(const Pixmap *src, Pixmap *dst) { + dst->data.untyped = pixmap_alloc_buffer_for_copy(src, &dst->data_size); + pixmap_copy(src, dst); +} + +void pixmap_convert(const Pixmap *src, Pixmap *dst, PixmapFormat format) { + size_t num_pixels = src->width * src->height; + size_t pixel_size = PIXMAP_FORMAT_PIXEL_SIZE(format); + + assert(dst->data.untyped != NULL); + pixmap_copy_meta(src, dst); + + if(src->format == format) { + memcpy(dst->data.untyped, src->data.untyped, num_pixels * pixel_size); + return; + } + + dst->format = format; + + struct conversion_def *cv = find_conversion( + PIXMAP_FORMAT_DEPTH(src->format) | (PIXMAP_FORMAT_IS_FLOAT(src->format) * DEPTH_FLOAT_BIT), + PIXMAP_FORMAT_DEPTH(dst->format) | (PIXMAP_FORMAT_IS_FLOAT(dst->format) * DEPTH_FLOAT_BIT) + ); + + cv->func( + PIXMAP_FORMAT_LAYOUT(src->format), + PIXMAP_FORMAT_LAYOUT(dst->format), + num_pixels, + src->data.untyped, + dst->data.untyped, + NULL + ); +} + +static int swizzle_idx(char s) { + switch(s) { + case 'r': return 0; + case 'g': return 1; + case 'b': return 2; + case 'a': return 3; + case '0': return 4; + case '1': return 5; + default: UNREACHABLE; + } +} + +void pixmap_swizzle_inplace(Pixmap *px, SwizzleMask swizzle) { + uint channels = pixmap_format_layout(px->format); + swizzle = swizzle_canonize(swizzle); + + if(!swizzle_is_significant(swizzle, channels)) { + return; + } + + uint cvt_id = pixmap_format_depth(px->format) | (pixmap_format_is_float(px->format) * DEPTH_FLOAT_BIT); + struct conversion_def *cv = find_conversion(cvt_id, cvt_id); + + cv->func( + channels, + channels, + px->width * px->height, + px->data.untyped, + px->data.untyped, + (int[]) { + swizzle_idx(swizzle.r), + swizzle_idx(swizzle.g), + swizzle_idx(swizzle.b), + swizzle_idx(swizzle.a), + } + ); +} + +void pixmap_convert_alloc(const Pixmap *src, Pixmap *dst, PixmapFormat format) { + dst->data.untyped = pixmap_alloc_buffer_for_conversion(src, format, &dst->data_size); + pixmap_convert(src, dst, format); +} + +void pixmap_convert_inplace_realloc(Pixmap *src, PixmapFormat format) { + assert(src->data.untyped != NULL); + + if(src->format == format) { + return; + } + + Pixmap tmp; + pixmap_copy_meta(src, &tmp); + pixmap_convert_alloc(src, &tmp, format); + + free(src->data.untyped); + *src = tmp; +} + +void pixmap_flip_y(const Pixmap *src, Pixmap *dst) { + assert(dst->data.untyped != NULL); + pixmap_copy_meta(src, dst); + + size_t rows = src->height; + size_t row_length = src->width * PIXMAP_FORMAT_PIXEL_SIZE(src->format); + + if(UNLIKELY(row_length == 0)) { + return; + } + + char *cdst = dst->data.untyped; + const char *csrc = src->data.untyped; + + for(size_t row = 0, irow = rows - 1; row < rows; ++row, --irow) { + memcpy(cdst + irow * row_length, csrc + row * row_length, row_length); + } +} + +void pixmap_flip_y_alloc(const Pixmap *src, Pixmap *dst) { + dst->data.untyped = pixmap_alloc_buffer_for_copy(src, &dst->data_size); + pixmap_flip_y(src, dst); +} + +void pixmap_flip_y_inplace(Pixmap *src) { + size_t rows = src->height; + size_t row_length = src->width * PIXMAP_FORMAT_PIXEL_SIZE(src->format); + + if(UNLIKELY(row_length == 0)) { + return; + } + + char *data = src->data.untyped; + char swap_buffer[row_length]; + + for(size_t row = 0; row < rows / 2; ++row) { + memcpy(swap_buffer, data + row * row_length, row_length); + memcpy(data + row * row_length, data + (rows - row - 1) * row_length, row_length); + memcpy(data + (rows - row - 1) * row_length, swap_buffer, row_length); + } +} + +void pixmap_flip_to_origin(const Pixmap *src, Pixmap *dst, PixmapOrigin origin) { + assert(dst->data.untyped != NULL); + + if(src->origin == origin) { + pixmap_copy(src, dst); + } else { + pixmap_flip_y(src, dst); + dst->origin = origin; + } +} + +void pixmap_flip_to_origin_alloc(const Pixmap *src, Pixmap *dst, PixmapOrigin origin) { + dst->data.untyped = pixmap_alloc_buffer_for_copy(src, &dst->data_size); + pixmap_flip_to_origin(src, dst, origin); +} + +void pixmap_flip_to_origin_inplace(Pixmap *src, PixmapOrigin origin) { + if(src->origin == origin) { + return; + } + + pixmap_flip_y_inplace(src); + src->origin = origin; +} diff --git a/src/pixmap/fileformats/fileformats.h b/src/pixmap/fileformats/fileformats.h new file mode 100644 index 0000000000..e7fd5420fa --- /dev/null +++ b/src/pixmap/fileformats/fileformats.h @@ -0,0 +1,30 @@ +/* + * This software is licensed under the terms of the MIT License. + * See COPYING for further information. + * --- + * Copyright (c) 2011-2019, Lukas Weber . + * Copyright (c) 2012-2019, Andrei Alexeyev . + */ + +#ifndef IGUARD_pixmap_fileformats_fileformats_h +#define IGUARD_pixmap_fileformats_fileformats_h + +#include "taisei.h" + +#include + +#include "../pixmap.h" + +typedef struct PixmapFileFormatHandler { + bool (*probe)(SDL_RWops *stream); + bool (*load)(SDL_RWops *stream, Pixmap *pixmap, PixmapFormat preferred_format); + bool (*save)(SDL_RWops *stream, const Pixmap *pixmap, const PixmapSaveOptions *opts); + const char **filename_exts; + const char *name; +} PixmapFileFormatHandler; + +extern PixmapFileFormatHandler pixmap_fileformat_internal; +extern PixmapFileFormatHandler pixmap_fileformat_png; +extern PixmapFileFormatHandler pixmap_fileformat_webp; + +#endif // IGUARD_pixmap_fileformats_fileformats_h diff --git a/src/pixmap/serialize.c b/src/pixmap/fileformats/internal.c similarity index 72% rename from src/pixmap/serialize.c rename to src/pixmap/fileformats/internal.c index 62075679c6..25fcee684a 100644 --- a/src/pixmap/serialize.c +++ b/src/pixmap/fileformats/internal.c @@ -8,15 +8,24 @@ #include "taisei.h" -#include "serialize.h" - +#include "fileformats.h" #include "rwops/rwops_crc32.h" #include "util.h" -static const uint8_t pxs_magic[] = { 0xe2, 0x99, 0xa5, 0x52, 0x65, 0x69, 0x6d, 0x75 }; -#define PXS_CRC32_INIT 42069 +enum { + PXI_VERSION = 1, + PXI_CRC32_INIT = 42069, +}; + +static const uint8_t pxi_magic[] = { 0xe2, 0x99, 0xa5, 0x52, 0x65, 0x69, 0x6d, 0x75 }; -bool pixmap_serialize(SDL_RWops *stream, const Pixmap *pixmap) { +static bool px_internal_probe(SDL_RWops *stream) { + uint8_t magic[sizeof(pxi_magic)] = { 0 }; + SDL_RWread(stream, magic, sizeof(magic), 1); + return !memcmp(magic, pxi_magic, sizeof(magic)); +} + +static bool px_internal_save(SDL_RWops *stream, const Pixmap *pixmap, const PixmapSaveOptions *opts) { SDL_RWops *cstream = NULL; #define CHECK_WRITE(expr, expected_size) \ @@ -32,10 +41,10 @@ bool pixmap_serialize(SDL_RWops *stream, const Pixmap *pixmap) { #define W_U16(stream, val) CHECK_WRITE(SDL_WriteLE16(stream, val), sizeof(uint16_t)) #define W_U8(stream, val) CHECK_WRITE(SDL_WriteU8(stream, val), sizeof(uint8_t)) - W_BYTES(stream, pxs_magic, sizeof(pxs_magic)); - W_U16(stream, PIXMAP_SERIALIZED_VERSION); + W_BYTES(stream, pxi_magic, sizeof(pxi_magic)); + W_U16(stream, PXI_VERSION); - uint32_t crc32 = PXS_CRC32_INIT; + uint32_t crc32 = PXI_CRC32_INIT; cstream = NOT_NULL(SDL_RWWrapCRC32(stream, &crc32, false)); W_U32(cstream, pixmap->width); @@ -60,26 +69,26 @@ bool pixmap_serialize(SDL_RWops *stream, const Pixmap *pixmap) { return false; } -bool pixmap_deserialize(SDL_RWops *stream, Pixmap *pixmap) { +static bool px_internal_load(SDL_RWops *stream, Pixmap *pixmap, PixmapFormat preferred_format) { assume(pixmap->data.untyped == NULL); SDL_RWops *cstream = NULL; - uint8_t magic[sizeof(pxs_magic)] = { 0 }; + uint8_t magic[sizeof(pxi_magic)] = { 0 }; SDL_RWread(stream, magic, 1, sizeof(magic)); - if(memcmp(magic, pxs_magic, sizeof(magic))) { + if(memcmp(magic, pxi_magic, sizeof(magic))) { log_error("Bad magic header"); return false; } uint16_t version = SDL_ReadLE16(stream); - if(version != PIXMAP_SERIALIZED_VERSION) { - log_error("Bad version %u; expected %u", version, PIXMAP_SERIALIZED_VERSION); + if(version != PXI_VERSION) { + log_error("Bad version %u; expected %u", version, PXI_VERSION); return false; } - uint32_t crc32 = PXS_CRC32_INIT; + uint32_t crc32 = PXI_CRC32_INIT; cstream = NOT_NULL(SDL_RWWrapCRC32(stream, &crc32, false)); // TODO validate and verify consistency of data_size/width/height/format @@ -135,3 +144,11 @@ bool pixmap_deserialize(SDL_RWops *stream, Pixmap *pixmap) { return false; } + +PixmapFileFormatHandler pixmap_fileformat_internal = { + .probe = px_internal_probe, + .save = px_internal_save, + .load = px_internal_load, + .filename_exts = (const char*[]) { NULL }, + .name = "Taisei internal", +}; diff --git a/src/pixmap/fileformats/meson.build b/src/pixmap/fileformats/meson.build new file mode 100644 index 0000000000..f1468773ec --- /dev/null +++ b/src/pixmap/fileformats/meson.build @@ -0,0 +1,6 @@ + +pixmap_fileformats_src = files( + 'internal.c', + 'png.c', + 'webp.c', +) diff --git a/src/pixmap/loaders/loader_png.c b/src/pixmap/fileformats/png.c similarity index 50% rename from src/pixmap/loaders/loader_png.c rename to src/pixmap/fileformats/png.c index 1a8efdabcb..96583d8c41 100644 --- a/src/pixmap/loaders/loader_png.c +++ b/src/pixmap/fileformats/png.c @@ -8,14 +8,14 @@ #include "taisei.h" -#include "loaders.h" +#include "fileformats.h" #include "util.h" #include "util/pngcruft.h" -static uchar png_magic[] = { 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A }; +static const uint8_t png_magic[] = { 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A }; static bool px_png_probe(SDL_RWops *stream) { - uchar magic[sizeof(png_magic)] = { 0 }; + uint8_t magic[sizeof(png_magic)] = { 0 }; SDL_RWread(stream, magic, sizeof(magic), 1); return !memcmp(magic, png_magic, sizeof(magic)); } @@ -109,7 +109,7 @@ static bool px_png_load(SDL_RWops *stream, Pixmap *pixmap, PixmapFormat preferre // the GL backend. This is just a slight optimization, not a hard dependency. pixmap->origin = PIXMAP_ORIGIN_BOTTOMLEFT; - size_t pixel_size = PIXMAP_FORMAT_PIXEL_SIZE(pixmap->format); + size_t pixel_size = pixmap_format_pixel_size(pixmap->format); if(pixmap->height > PIXMAP_BUFFER_MAX_SIZE / (pixmap->width * pixel_size)) { error = "The image is too large"; @@ -143,8 +143,146 @@ static bool px_png_load(SDL_RWops *stream, Pixmap *pixmap, PixmapFormat preferre return true; } -PixmapLoader pixmap_loader_png = { +static void px_png_save_apply_conversions( + const Pixmap *src_pixmap, Pixmap *dst_pixmap +) { + PixmapLayout fmt_layout = pixmap_format_layout(src_pixmap->format); + PixmapLayout fmt_depth = pixmap_format_layout(src_pixmap->format); + + if(fmt_depth > 16) { + fmt_depth = 16; + } else if(fmt_depth < 16) { + fmt_depth = 8; + } + + if(fmt_layout == PIXMAP_LAYOUT_RG) { + fmt_layout = PIXMAP_LAYOUT_RGB; + } + + PixmapFormat fmt = PIXMAP_MAKE_FORMAT(fmt_layout, fmt_depth); + + if(fmt == src_pixmap->format) { + *dst_pixmap = *src_pixmap; + } else { + pixmap_convert_alloc(src_pixmap, dst_pixmap, fmt); + } +} + +static bool px_png_save( + SDL_RWops *stream, const Pixmap *src_pixmap, const PixmapSaveOptions *base_opts +) { + if( + pixmap_format_is_compressed(src_pixmap->format) || + pixmap_format_is_float(src_pixmap->format) + ) { + log_error("Can't write %s image to PNG", pixmap_format_name(src_pixmap->format)); + return false; + } + + const PixmapPNGSaveOptions *opts, default_opts = PIXMAP_DEFAULT_PNG_SAVE_OPTIONS; + opts = &default_opts; + + if( + base_opts && + base_opts->file_format == PIXMAP_FILEFORMAT_PNG && + base_opts->struct_size > sizeof(*base_opts) + ) { + assert(base_opts->struct_size == sizeof(*opts)); + opts = UNION_CAST(const PixmapSaveOptions*, const PixmapPNGSaveOptions*, base_opts); + } + + const char *error = NULL; + png_structp png; + png_infop info_ptr; + + Pixmap px = { 0 }; + + png = pngutil_create_write_struct(); + + if(png == NULL) { + error = "pngutil_create_write_struct() failed"; + goto done; + } + + info_ptr = png_create_info_struct(png); + + if(info_ptr == NULL) { + error = "png_create_info_struct() failed"; + goto done; + } + + if(setjmp(png_jmpbuf(png))) { + error = "PNG error"; + goto done; + } + + px_png_save_apply_conversions(src_pixmap, &px); + + size_t pixel_size = pixmap_format_pixel_size(px.format); + + uint png_color; + switch(pixmap_format_layout(px.format)) { + case PIXMAP_LAYOUT_R: png_color = PNG_COLOR_TYPE_GRAY; break; + case PIXMAP_LAYOUT_RGB: png_color = PNG_COLOR_TYPE_RGB; break; + case PIXMAP_LAYOUT_RGBA: png_color = PNG_COLOR_TYPE_RGBA; break; + default: UNREACHABLE; + } + + pngutil_init_rwops_write(png, stream); + + png_set_IHDR( + png, info_ptr, + px.width, px.height, pixmap_format_depth(px.format), + png_color, + PNG_INTERLACE_NONE, + PNG_COMPRESSION_TYPE_DEFAULT, + PNG_FILTER_TYPE_DEFAULT + ); + + if(opts->zlib_compression_level >= 0) { + png_set_compression_level(png, opts->zlib_compression_level); + } + + png_write_info(png, info_ptr); + +#if SDL_BYTEORDER == SDL_LIL_ENDIAN + if(pixmap_format_depth(px.format) > 8) { + png_set_swap(png); + } +#endif + + if(px.origin == PIXMAP_ORIGIN_BOTTOMLEFT) { + for(int row = px.height - 1; row >= 0; --row) { + png_write_row(png, px.data.r8->values + row * px.width * pixel_size); + } + } else { + for(int row = 0; row < px.height; ++row) { + png_write_row(png, px.data.r8->values + row * px.width * pixel_size); + } + } + + png_write_end(png, info_ptr); + +done: + if(error) { + log_error("%s", error); + } + + if(px.data.untyped != src_pixmap->data.untyped) { + free(px.data.untyped); + } + + if(png != NULL) { + png_destroy_write_struct(&png, info_ptr ? &info_ptr : NULL); + } + + return !error; +} + +PixmapFileFormatHandler pixmap_fileformat_png = { .probe = px_png_probe, .load = px_png_load, - .filename_exts = (const char*[]){ "png", NULL }, + .save = px_png_save, + .filename_exts = (const char*[]) { "png", NULL }, + .name = "PNG", }; diff --git a/src/pixmap/loaders/loader_webp.c b/src/pixmap/fileformats/webp.c similarity index 96% rename from src/pixmap/loaders/loader_webp.c rename to src/pixmap/fileformats/webp.c index 88c947fb00..18858db006 100644 --- a/src/pixmap/loaders/loader_webp.c +++ b/src/pixmap/fileformats/webp.c @@ -9,7 +9,7 @@ #include "taisei.h" #include "util.h" -#include "loaders.h" +#include "fileformats.h" #include @@ -130,8 +130,9 @@ static bool px_webp_load(SDL_RWops *stream, Pixmap *pixmap, PixmapFormat preferr return pixmap->data.untyped != NULL; } -PixmapLoader pixmap_loader_webp = { +PixmapFileFormatHandler pixmap_fileformat_webp = { .probe = px_webp_probe, .load = px_webp_load, - .filename_exts = (const char*[]){ "webp", NULL }, + .filename_exts = (const char*[]) { "webp", NULL }, + .name = "WebP", }; diff --git a/src/pixmap/loaders/loaders.h b/src/pixmap/loaders/loaders.h deleted file mode 100644 index 88c1f8dc3a..0000000000 --- a/src/pixmap/loaders/loaders.h +++ /dev/null @@ -1,27 +0,0 @@ -/* - * This software is licensed under the terms of the MIT License. - * See COPYING for further information. - * --- - * Copyright (c) 2011-2019, Lukas Weber . - * Copyright (c) 2012-2019, Andrei Alexeyev . - */ - -#ifndef IGUARD_pixmap_loaders_loaders_h -#define IGUARD_pixmap_loaders_loaders_h - -#include "taisei.h" - -#include - -#include "../pixmap.h" - -typedef struct PixmapLoader { - bool (*probe)(SDL_RWops *stream); - bool (*load)(SDL_RWops *stream, Pixmap *pixmap, PixmapFormat preferred_format); - const char **filename_exts; -} PixmapLoader; - -extern PixmapLoader pixmap_loader_png; -extern PixmapLoader pixmap_loader_webp; - -#endif // IGUARD_pixmap_loaders_loaders_h diff --git a/src/pixmap/loaders/meson.build b/src/pixmap/loaders/meson.build deleted file mode 100644 index 7386fd6b95..0000000000 --- a/src/pixmap/loaders/meson.build +++ /dev/null @@ -1,5 +0,0 @@ - -pixmap_loaders_src = files( - 'loader_png.c', - 'loader_webp.c', -) diff --git a/src/pixmap/meson.build b/src/pixmap/meson.build index 2b4d36368b..0c558b4a68 100644 --- a/src/pixmap/meson.build +++ b/src/pixmap/meson.build @@ -1,8 +1,8 @@ pixmap_src = files( 'pixmap.c', - 'serialize.c', + 'conversion.c', ) -subdir('loaders') -pixmap_src += pixmap_loaders_src +subdir('fileformats') +pixmap_src += pixmap_fileformats_src diff --git a/src/pixmap/pixmap.c b/src/pixmap/pixmap.c index 8a9562bb3a..2d22e9b11c 100644 --- a/src/pixmap/pixmap.c +++ b/src/pixmap/pixmap.c @@ -9,177 +9,16 @@ #include "taisei.h" #include "pixmap.h" -#include "loaders/loaders.h" +#include "fileformats/fileformats.h" #include "util.h" -// NOTE: this is pretty stupid and not at all optimized, patches welcome - -#define _CONV_FUNCNAME convert_u8_to_u8 -#define _CONV_IN_MAX UINT8_MAX -#define _CONV_IN_TYPE uint8_t -#define _CONV_OUT_MAX UINT8_MAX -#define _CONV_OUT_TYPE uint8_t -#include "pixmap_conversion.inc.h" - -#define _CONV_FUNCNAME convert_u8_to_u16 -#define _CONV_IN_MAX UINT8_MAX -#define _CONV_IN_TYPE uint8_t -#define _CONV_OUT_MAX UINT16_MAX -#define _CONV_OUT_TYPE uint16_t -#include "pixmap_conversion.inc.h" - -#define _CONV_FUNCNAME convert_u8_to_u32 -#define _CONV_IN_MAX UINT8_MAX -#define _CONV_IN_TYPE uint8_t -#define _CONV_OUT_MAX UINT32_MAX -#define _CONV_OUT_TYPE uint32_t -#include "pixmap_conversion.inc.h" - -#define _CONV_FUNCNAME convert_u8_to_f32 -#define _CONV_IN_MAX UINT8_MAX -#define _CONV_IN_TYPE uint8_t -#define _CONV_OUT_MAX 1.0f -#define _CONV_OUT_TYPE float -#include "pixmap_conversion.inc.h" - -#define _CONV_FUNCNAME convert_u16_to_u8 -#define _CONV_IN_MAX UINT16_MAX -#define _CONV_IN_TYPE uint16_t -#define _CONV_OUT_MAX UINT8_MAX -#define _CONV_OUT_TYPE uint8_t -#include "pixmap_conversion.inc.h" - -#define _CONV_FUNCNAME convert_u16_to_u16 -#define _CONV_IN_MAX UINT16_MAX -#define _CONV_IN_TYPE uint16_t -#define _CONV_OUT_MAX UINT16_MAX -#define _CONV_OUT_TYPE uint16_t -#include "pixmap_conversion.inc.h" - -#define _CONV_FUNCNAME convert_u16_to_u32 -#define _CONV_IN_MAX UINT16_MAX -#define _CONV_IN_TYPE uint16_t -#define _CONV_OUT_MAX UINT32_MAX -#define _CONV_OUT_TYPE uint32_t -#include "pixmap_conversion.inc.h" - -#define _CONV_FUNCNAME convert_u16_to_f32 -#define _CONV_IN_MAX UINT16_MAX -#define _CONV_IN_TYPE uint16_t -#define _CONV_OUT_MAX 1.0f -#define _CONV_OUT_TYPE float -#include "pixmap_conversion.inc.h" - -#define _CONV_FUNCNAME convert_u32_to_u8 -#define _CONV_IN_MAX UINT32_MAX -#define _CONV_IN_TYPE uint32_t -#define _CONV_OUT_MAX UINT8_MAX -#define _CONV_OUT_TYPE uint8_t -#include "pixmap_conversion.inc.h" - -#define _CONV_FUNCNAME convert_u32_to_u16 -#define _CONV_IN_MAX UINT32_MAX -#define _CONV_IN_TYPE uint32_t -#define _CONV_OUT_MAX UINT16_MAX -#define _CONV_OUT_TYPE uint16_t -#include "pixmap_conversion.inc.h" - -#define _CONV_FUNCNAME convert_u32_to_u32 -#define _CONV_IN_MAX UINT32_MAX -#define _CONV_IN_TYPE uint32_t -#define _CONV_OUT_MAX UINT32_MAX -#define _CONV_OUT_TYPE uint32_t -#include "pixmap_conversion.inc.h" - -#define _CONV_FUNCNAME convert_u32_to_f32 -#define _CONV_IN_MAX UINT32_MAX -#define _CONV_IN_TYPE uint32_t -#define _CONV_OUT_MAX 1.0f -#define _CONV_OUT_TYPE float -#include "pixmap_conversion.inc.h" - -#define _CONV_FUNCNAME convert_f32_to_u8 -#define _CONV_IN_MAX 1.0f -#define _CONV_IN_TYPE float -#define _CONV_OUT_MAX UINT8_MAX -#define _CONV_OUT_TYPE uint8_t -#include "pixmap_conversion.inc.h" - -#define _CONV_FUNCNAME convert_f32_to_u16 -#define _CONV_IN_MAX 1.0f -#define _CONV_IN_TYPE float -#define _CONV_OUT_MAX UINT16_MAX -#define _CONV_OUT_TYPE uint16_t -#include "pixmap_conversion.inc.h" - -#define _CONV_FUNCNAME convert_f32_to_u32 -#define _CONV_IN_MAX 1.0f -#define _CONV_IN_TYPE float -#define _CONV_OUT_MAX UINT32_MAX -#define _CONV_OUT_TYPE uint32_t -#include "pixmap_conversion.inc.h" - -#define _CONV_FUNCNAME convert_f32_to_f32 -#define _CONV_IN_MAX 1.0f -#define _CONV_IN_TYPE float -#define _CONV_OUT_MAX 1.0f -#define _CONV_OUT_TYPE float -#include "pixmap_conversion.inc.h" - -#define DEPTH_FLOAT_BIT (1 << 10) - -typedef void (*convfunc_t)( - size_t in_elements, - size_t out_elements, - size_t num_pixels, - void *vbuf_in, - void *vbuf_out, - int swizzle[4] -); - -#define CONV(in, in_depth, out, out_depth) { convert_##in##_to_##out, in_depth, out_depth } - -struct conversion_def { - convfunc_t func; - uint depth_in; - uint depth_out; +static PixmapFileFormatHandler *fileformat_handlers[] = { + [PIXMAP_FILEFORMAT_INTERNAL] = &pixmap_fileformat_internal, + [PIXMAP_FILEFORMAT_PNG] = &pixmap_fileformat_png, + [PIXMAP_FILEFORMAT_WEBP] = &pixmap_fileformat_webp, }; -struct conversion_def conversion_table[] = { - CONV(u8, 8, u8, 8), - CONV(u8, 8, u16, 16), - CONV(u8, 8, u32, 32), - CONV(u8, 8, f32, 32 | DEPTH_FLOAT_BIT), - - CONV(u16, 16, u8, 8), - CONV(u16, 16, u16, 16), - CONV(u16, 16, u32, 32), - CONV(u16, 16, f32, 32 | DEPTH_FLOAT_BIT), - - CONV(u32, 32, u8, 8), - CONV(u32, 16, u16, 16), - CONV(u32, 32, u32, 32), - CONV(u32, 32, f32, 32 | DEPTH_FLOAT_BIT), - - CONV(f32, 32 | DEPTH_FLOAT_BIT, u8, 8), - CONV(f32, 16 | DEPTH_FLOAT_BIT, u16, 16), - CONV(f32, 32 | DEPTH_FLOAT_BIT, u32, 32), - CONV(f32, 32 | DEPTH_FLOAT_BIT, f32, 32 | DEPTH_FLOAT_BIT), - - { 0 } -}; - -static struct conversion_def* find_conversion(uint depth_in, uint depth_out) { - for(struct conversion_def *cv = conversion_table; cv->func; ++cv) { - if(cv->depth_in == depth_in && cv->depth_out == depth_out) { - return cv; - } - } - - log_fatal("Pixmap conversion for %upbc -> %upbc undefined, please add", depth_in, depth_out); -} - -static uint32_t pixmap_data_size(PixmapFormat format, uint32_t width, uint32_t height) { +uint32_t pixmap_data_size(PixmapFormat format, uint32_t width, uint32_t height) { assert(width >= 1); assert(height >= 1); uint64_t pixel_size = PIXMAP_FORMAT_PIXEL_SIZE(format); @@ -207,240 +46,145 @@ void *pixmap_alloc_buffer_for_conversion(const Pixmap *src, PixmapFormat format, return pixmap_alloc_buffer(format, src->width, src->height, out_bufsize); } -static void pixmap_copy_meta(const Pixmap *src, Pixmap *dst) { - dst->format = src->format; - dst->width = src->width; - dst->height = src->height; - dst->origin = src->origin; -} - -void pixmap_convert(const Pixmap *src, Pixmap *dst, PixmapFormat format) { - size_t num_pixels = src->width * src->height; - size_t pixel_size = PIXMAP_FORMAT_PIXEL_SIZE(format); +static PixmapFileFormatHandler *pixmap_handler_for_filename(const char *file) { + char *ext = strrchr(file, '.'); - assert(dst->data.untyped != NULL); - pixmap_copy_meta(src, dst); + if(!ext || !*(++ext)) { + return NULL; + } - if(src->format == format) { - memcpy(dst->data.untyped, src->data.untyped, num_pixels * pixel_size); - return; + for(int i = 0; i < ARRAY_SIZE(fileformat_handlers); ++i) { + PixmapFileFormatHandler *h = NOT_NULL(fileformat_handlers[i]); + for(const char **l_ext = h->filename_exts; *l_ext; ++l_ext) { + if(!SDL_strcasecmp(*l_ext, ext)) { + return h; + } + } } - dst->format = format; - - struct conversion_def *cv = find_conversion( - PIXMAP_FORMAT_DEPTH(src->format) | (PIXMAP_FORMAT_IS_FLOAT(src->format) * DEPTH_FLOAT_BIT), - PIXMAP_FORMAT_DEPTH(dst->format) | (PIXMAP_FORMAT_IS_FLOAT(dst->format) * DEPTH_FLOAT_BIT) - ); - - cv->func( - PIXMAP_FORMAT_LAYOUT(src->format), - PIXMAP_FORMAT_LAYOUT(dst->format), - num_pixels, - src->data.untyped, - dst->data.untyped, - NULL - ); + return NULL; } -static int swizzle_idx(char s) { - switch(s) { - case 'r': return 0; - case 'g': return 1; - case 'b': return 2; - case 'a': return 3; - case '0': return 4; - case '1': return 5; - default: UNREACHABLE; - } +attr_returns_nonnull +static PixmapFileFormatHandler *pixmap_handler_for_fileformat(PixmapFileFormat ff) { + uint idx = ff; + assert(idx < PIXMAP_NUM_FILEFORMATS); + return NOT_NULL(fileformat_handlers[idx]); } -void pixmap_swizzle_inplace(Pixmap *px, SwizzleMask swizzle) { - uint channels = pixmap_format_layout(px->format); - swizzle = swizzle_canonize(swizzle); - - if(!swizzle_is_significant(swizzle, channels)) { - return; - } +static PixmapFileFormatHandler *pixmap_probe_stream(SDL_RWops *stream) { + for(int i = 0; i < ARRAY_SIZE(fileformat_handlers); ++i) { + PixmapFileFormatHandler *h = NOT_NULL(fileformat_handlers[i]); - uint cvt_id = pixmap_format_depth(px->format) | (pixmap_format_is_float(px->format) * DEPTH_FLOAT_BIT); - struct conversion_def *cv = find_conversion(cvt_id, cvt_id); - - cv->func( - channels, - channels, - px->width * px->height, - px->data.untyped, - px->data.untyped, - (int[]) { - swizzle_idx(swizzle.r), - swizzle_idx(swizzle.g), - swizzle_idx(swizzle.b), - swizzle_idx(swizzle.a), + if(!h->probe) { + continue; } - ); -} - -void pixmap_convert_alloc(const Pixmap *src, Pixmap *dst, PixmapFormat format) { - dst->data.untyped = pixmap_alloc_buffer_for_conversion(src, format, &dst->data_size); - pixmap_convert(src, dst, format); -} -void pixmap_convert_inplace_realloc(Pixmap *src, PixmapFormat format) { - assert(src->data.untyped != NULL); + bool match = h->probe(stream); + if(SDL_RWseek(stream, 0, RW_SEEK_SET) < 0) { + log_sdl_error(LOG_ERROR, "SDL_RWseek"); + return NULL; + } - if(src->format == format) { - return; + if(match) { + return h; + } } - Pixmap tmp; - pixmap_copy_meta(src, &tmp); - pixmap_convert_alloc(src, &tmp, format); - - free(src->data.untyped); - *src = tmp; + return NULL; } -void pixmap_copy(const Pixmap *src, Pixmap *dst) { - assert(dst->data.untyped != NULL); - assert(src->data_size > 0); - assert(dst->data_size >= src->data_size); +bool pixmap_load_stream(SDL_RWops *stream, PixmapFileFormat filefmt, Pixmap *dst, PixmapFormat preferred_format) { + PixmapFileFormatHandler *handler = NULL; - if(!pixmap_format_is_compressed(src->format)) { - assert(src->data_size == pixmap_data_size(src->format, src->width, src->height)); + if(filefmt == PIXMAP_FILEFORMAT_AUTO) { + handler = pixmap_probe_stream(stream); + } else { + handler = pixmap_handler_for_fileformat(filefmt); } - pixmap_copy_meta(src, dst); - memcpy(dst->data.untyped, src->data.untyped, src->data_size); -} - -void pixmap_copy_alloc(const Pixmap *src, Pixmap *dst) { - dst->data.untyped = pixmap_alloc_buffer_for_copy(src, &dst->data_size); - pixmap_copy(src, dst); -} - -void pixmap_flip_y(const Pixmap *src, Pixmap *dst) { - assert(dst->data.untyped != NULL); - pixmap_copy_meta(src, dst); - - size_t rows = src->height; - size_t row_length = src->width * PIXMAP_FORMAT_PIXEL_SIZE(src->format); - - if(UNLIKELY(row_length == 0)) { - return; + if(UNLIKELY(!handler)) { + log_error("Image format not recognized"); + return false; } - char *cdst = dst->data.untyped; - const char *csrc = src->data.untyped; - - for(size_t row = 0, irow = rows - 1; row < rows; ++row, --irow) { - memcpy(cdst + irow * row_length, csrc + row * row_length, row_length); + if(UNLIKELY(!handler->load)) { + log_error("Can't load images in %s format", NOT_NULL(handler->name)); + return false; } -} -void pixmap_flip_y_alloc(const Pixmap *src, Pixmap *dst) { - dst->data.untyped = pixmap_alloc_buffer_for_copy(src, &dst->data_size); - pixmap_flip_y(src, dst); + return handler->load(stream, dst, preferred_format); } -void pixmap_flip_y_inplace(Pixmap *src) { - size_t rows = src->height; - size_t row_length = src->width * PIXMAP_FORMAT_PIXEL_SIZE(src->format); +bool pixmap_load_file(const char *path, Pixmap *dst, PixmapFormat preferred_format) { + log_debug("%s %x", path, preferred_format); + SDL_RWops *stream = vfs_open(path, VFS_MODE_READ | VFS_MODE_SEEKABLE); - if(UNLIKELY(row_length == 0)) { - return; + if(!stream) { + log_error("VFS error: %s", vfs_get_error()); + return false; } - char *data = src->data.untyped; - char swap_buffer[row_length]; - - for(size_t row = 0; row < rows / 2; ++row) { - memcpy(swap_buffer, data + row * row_length, row_length); - memcpy(data + row * row_length, data + (rows - row - 1) * row_length, row_length); - memcpy(data + (rows - row - 1) * row_length, swap_buffer, row_length); - } + bool result = pixmap_load_stream(stream, PIXMAP_FILEFORMAT_AUTO, dst, preferred_format); + SDL_RWclose(stream); + return result; } -void pixmap_flip_to_origin(const Pixmap *src, Pixmap *dst, PixmapOrigin origin) { - assert(dst->data.untyped != NULL); - - if(src->origin == origin) { - pixmap_copy(src, dst); - } else { - pixmap_flip_y(src, dst); - dst->origin = origin; +static bool pixmap_save_stream_internal( + SDL_RWops *stream, const Pixmap *src, const PixmapSaveOptions *opts, + PixmapFileFormatHandler *handler +) { + if(UNLIKELY(!handler->save)) { + log_error("Can't save images in %s format", NOT_NULL(handler->name)); + return false; } -} -void pixmap_flip_to_origin_alloc(const Pixmap *src, Pixmap *dst, PixmapOrigin origin) { - dst->data.untyped = pixmap_alloc_buffer_for_copy(src, &dst->data_size); - pixmap_flip_to_origin(src, dst, origin); + return handler->save(stream, src, opts); } -void pixmap_flip_to_origin_inplace(Pixmap *src, PixmapOrigin origin) { - if(src->origin == origin) { - return; - } - - pixmap_flip_y_inplace(src); - src->origin = origin; +bool pixmap_save_stream(SDL_RWops *stream, const Pixmap *src, const PixmapSaveOptions *opts) { + PixmapFileFormat fmt = opts->file_format; + PixmapFileFormatHandler *h = pixmap_handler_for_fileformat(fmt); + return pixmap_save_stream_internal(stream, src, opts, h); } -static PixmapLoader *pixmap_loaders[] = { - &pixmap_loader_png, - &pixmap_loader_webp, - NULL, -}; - -static PixmapLoader *pixmap_loader_for_filename(const char *file) { - char *ext = strrchr(file, '.'); +bool pixmap_save_file(const char *path, const Pixmap *src, const PixmapSaveOptions *opts) { + PixmapFileFormatHandler *h = NULL; + PixmapFileFormat fmt = PIXMAP_FILEFORMAT_AUTO; - if(!ext || !*(++ext)) { - return NULL; + if(opts) { + assert(opts->struct_size >= sizeof(PixmapSaveOptions)); + fmt = opts->file_format; } - for(PixmapLoader **loader = pixmap_loaders; *loader; ++loader) { - PixmapLoader *l = *loader; - for(const char **l_ext = l->filename_exts; *l_ext; ++l_ext) { - if(!SDL_strcasecmp(*l_ext, ext)) { - return l; - } - } + if(fmt == PIXMAP_FILEFORMAT_AUTO) { + h = pixmap_handler_for_filename(path); + } else { + h = pixmap_handler_for_fileformat(fmt); } - return NULL; -} - -bool pixmap_load_stream(SDL_RWops *stream, Pixmap *dst, PixmapFormat preferred_format) { - for(PixmapLoader **loader = pixmap_loaders; *loader; ++loader) { - bool match = (*loader)->probe(stream); - SDL_RWseek(stream, 0, RW_SEEK_SET); - - if(match) { - return (*loader)->load(stream, dst, preferred_format); - } + if(UNLIKELY(!h)) { + h = NOT_NULL(pixmap_handler_for_fileformat(PIXMAP_FILEFORMAT_INTERNAL)); + log_warn( + "Couldn't determine file format from filename `%s`, assuming %s", + path, NOT_NULL(h->name) + ); } - log_error("Image format not recognized"); - return false; -} - -bool pixmap_load_file(const char *path, Pixmap *dst, PixmapFormat preferred_format) { - log_debug("%s %x", path, preferred_format); - SDL_RWops *stream = vfs_open(path, VFS_MODE_READ | VFS_MODE_SEEKABLE); + SDL_RWops *stream = vfs_open(path, VFS_MODE_WRITE); - if(!stream) { + if(UNLIKELY(!stream)) { log_error("VFS error: %s", vfs_get_error()); return false; } - bool result = pixmap_load_stream(stream, dst, preferred_format); + bool result = pixmap_save_stream_internal(stream, src, opts, h); SDL_RWclose(stream); return result; } bool pixmap_check_filename(const char *path) { - return (bool)pixmap_loader_for_filename(path); + return (bool)pixmap_handler_for_filename(path); } char *pixmap_source_path(const char *prefix, const char *path) { @@ -458,9 +202,10 @@ char *pixmap_source_path(const char *prefix, const char *path) { *dot = 0; } - for(PixmapLoader **loader = pixmap_loaders; *loader; ++loader) { - PixmapLoader *l = *loader; - for(const char **l_ext = l->filename_exts; *l_ext; ++l_ext) { + for(int i = 0; i < ARRAY_SIZE(fileformat_handlers); ++i) { + PixmapFileFormatHandler *h = NOT_NULL(fileformat_handlers[i]); + + for(const char **l_ext = h->filename_exts; *l_ext; ++l_ext) { char ext[strlen(*l_ext) + 2]; ext[0] = '.'; strcpy(ext + 1, *l_ext); diff --git a/src/pixmap/pixmap.h b/src/pixmap/pixmap.h index 95aadf2031..4672aae57c 100644 --- a/src/pixmap/pixmap.h +++ b/src/pixmap/pixmap.h @@ -15,6 +15,40 @@ #define PIXMAP_BUFFER_MAX_SIZE INT32_MAX +typedef enum PixmapFileFormat { + PIXMAP_FILEFORMAT_AUTO = -1, + + // NOTE: these are probed from first to last + PIXMAP_FILEFORMAT_WEBP, + PIXMAP_FILEFORMAT_PNG, + PIXMAP_FILEFORMAT_INTERNAL, + + PIXMAP_NUM_FILEFORMATS +} PixmapFileFormat; + +typedef struct PixmapSaveOptions { + PixmapFileFormat file_format; + size_t struct_size; +} PixmapSaveOptions; + +#define PIXMAP_DEFAULT_SAVE_OPTIONS \ + { \ + .file_format = PIXMAP_FILEFORMAT_AUTO, \ + .struct_size = sizeof(PixmapSaveOptions), \ + } + +typedef struct PixmapPNGSaveOptions { + PixmapSaveOptions base; + int zlib_compression_level; +} PixmapPNGSaveOptions; + +#define PIXMAP_DEFAULT_PNG_SAVE_OPTIONS \ + { \ + .base.file_format = PIXMAP_FILEFORMAT_PNG, \ + .base.struct_size = sizeof(PixmapPNGSaveOptions), \ + .zlib_compression_level = -1, \ + } + typedef enum PixmapLayout { PIXMAP_LAYOUT_R = 1, PIXMAP_LAYOUT_RG, @@ -274,12 +308,16 @@ void pixmap_flip_to_origin_alloc(const Pixmap *src, Pixmap *dst, PixmapOrigin or void pixmap_flip_to_origin_inplace(Pixmap *src, PixmapOrigin origin) attr_nonnull(1); bool pixmap_load_file(const char *path, Pixmap *dst, PixmapFormat preferred_format) attr_nonnull(1, 2) attr_nodiscard; -bool pixmap_load_stream(SDL_RWops *stream, Pixmap *dst, PixmapFormat preferred_format) attr_nonnull(1, 2) attr_nodiscard; +bool pixmap_load_stream(SDL_RWops *stream, PixmapFileFormat filefmt, Pixmap *dst, PixmapFormat preferred_format) attr_nonnull(1, 3) attr_nodiscard; + +bool pixmap_save_file(const char *path, const Pixmap *src, const PixmapSaveOptions *opts) attr_nonnull(1, 2); +bool pixmap_save_stream(SDL_RWops *stream, const Pixmap *src, const PixmapSaveOptions *opts) attr_nonnull(1, 2, 3); bool pixmap_check_filename(const char *path); char *pixmap_source_path(const char *prefix, const char *path) attr_nodiscard; const char *pixmap_format_name(PixmapFormat fmt); +uint32_t pixmap_data_size(PixmapFormat format, uint32_t width, uint32_t height); SwizzleMask swizzle_canonize(SwizzleMask sw_in); bool swizzle_is_valid(SwizzleMask sw); diff --git a/src/pixmap/serialize.h b/src/pixmap/serialize.h deleted file mode 100644 index d9ae7308ae..0000000000 --- a/src/pixmap/serialize.h +++ /dev/null @@ -1,23 +0,0 @@ -/* - * This software is licensed under the terms of the MIT License. - * See COPYING for further information. - * --- - * Copyright (c) 2011-2019, Lukas Weber . - * Copyright (c) 2012-2019, Andrei Alexeyev . -*/ - -#ifndef IGUARD_pixmap_serialize_h -#define IGUARD_pixmap_serialize_h - -#include "taisei.h" - -#include "pixmap.h" - -enum { - PIXMAP_SERIALIZED_VERSION = 1, -}; - -bool pixmap_serialize(SDL_RWops *stream, const Pixmap *pixmap); -bool pixmap_deserialize(SDL_RWops *stream, Pixmap *pixmap); - -#endif // IGUARD_pixmap_serialize_h diff --git a/src/resource/texture_loader/basisu_cache.c b/src/resource/texture_loader/basisu_cache.c index f0d328378a..17a8e7bd49 100644 --- a/src/resource/texture_loader/basisu_cache.c +++ b/src/resource/texture_loader/basisu_cache.c @@ -9,7 +9,7 @@ #include "taisei.h" #include "basisu_cache.h" -#include "pixmap/serialize.h" +#include "pixmap/pixmap.h" #include "rwops/rwops_zstd.h" #include @@ -75,7 +75,7 @@ bool texture_loader_basisu_load_cached( rw = SDL_RWWrapZstdReader(rw, true); - bool deserialize_ok = pixmap_deserialize(rw, out_pixmap); + bool deserialize_ok = pixmap_load_stream(rw, PIXMAP_FILEFORMAT_INTERNAL, out_pixmap, 0); SDL_RWclose(rw); if(!deserialize_ok) { @@ -178,7 +178,9 @@ bool texture_loader_basisu_cache( rw = SDL_RWWrapZstdWriter(rw, RW_ZSTD_LEVEL_DEFAULT, true); - bool serialize_ok = pixmap_serialize(rw, pixmap); + PixmapSaveOptions opts = PIXMAP_DEFAULT_SAVE_OPTIONS; + opts.file_format = PIXMAP_FILEFORMAT_INTERNAL; + bool serialize_ok = pixmap_save_stream(rw, pixmap, &opts); SDL_RWclose(rw); if(!serialize_ok) { diff --git a/src/video.c b/src/video.c index 51c682d143..6e03244634 100644 --- a/src/video.c +++ b/src/video.c @@ -8,8 +8,6 @@ #include "taisei.h" -#include - #include "global.h" #include "video.h" #include "renderer/api.h" @@ -553,77 +551,18 @@ static void *video_screenshot_task(void *arg) { ScreenshotTaskData *tdata = arg; pixmap_convert_inplace_realloc(&tdata->image, PIXMAP_FORMAT_RGB8); - pixmap_flip_to_origin_inplace(&tdata->image, PIXMAP_ORIGIN_BOTTOMLEFT); - - uint width = tdata->image.width; - uint height = tdata->image.height; - uint8_t *pixels = tdata->image.data.untyped; - - SDL_RWops *output = vfs_open(tdata->dest_path, VFS_MODE_WRITE); - - if(!output) { - log_error("VFS error: %s", vfs_get_error()); - return NULL; - } - - char *syspath = vfs_repr(tdata->dest_path, true); - log_info("Saving screenshot as %s", syspath); - free(syspath); - const char *error = NULL; - png_structp png_ptr; - png_infop info_ptr; + PixmapPNGSaveOptions opts = PIXMAP_DEFAULT_PNG_SAVE_OPTIONS; + opts.zlib_compression_level = 9; - png_byte *volatile row_pointers[height]; - memset((void*)row_pointers, 0, sizeof(row_pointers)); - - png_ptr = pngutil_create_write_struct(); - - if(png_ptr == NULL) { - error = "pngutil_create_write_struct() failed"; - goto done; - } - - info_ptr = png_create_info_struct(png_ptr); - - if(info_ptr == NULL) { - error = "png_create_info_struct() failed"; - goto done; - } - - if(setjmp(png_jmpbuf(png_ptr))) { - error = "PNG error"; - goto done; - } - - png_set_IHDR( - png_ptr, info_ptr, width, height, 8, PNG_COLOR_TYPE_RGB, - PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT - ); - - for(int y = 0; y < height; y++) { - row_pointers[y] = png_malloc(png_ptr, width * 3); - memcpy(row_pointers[y], pixels + width * 3 * (height - 1 - y), width * 3); - } - - pngutil_init_rwops_write(png_ptr, output); - png_set_rows(png_ptr, info_ptr, (void*)row_pointers); - png_write_png(png_ptr, info_ptr, PNG_TRANSFORM_IDENTITY, NULL); - -done: - if(error) { - log_error("Couldn't save screenshot: %s", error); - } - - for(int y = 0; y < height; y++) { - png_free(png_ptr, row_pointers[y]); - } + bool ok = pixmap_save_file(tdata->dest_path, &tdata->image, &opts.base); - if(png_ptr != NULL) { - png_destroy_write_struct(&png_ptr, info_ptr ? &info_ptr : NULL); + if(LIKELY(ok)) { + char *syspath = vfs_repr(tdata->dest_path, true); + log_info("Saved screenshot as %s", syspath); + free(syspath); } - SDL_RWclose(output); return NULL; }