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.
This commit is contained in:
Andrei Alexeyev 2021-07-18 18:19:40 +03:00
parent 1840c85273
commit b4e07470d8
No known key found for this signature in database
GPG key ID: 72D26128040B9690
14 changed files with 741 additions and 522 deletions

358
src/pixmap/conversion.c Normal file
View file

@ -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 <laochailan@web.de>.
* Copyright (c) 2012-2019, Andrei Alexeyev <akari@taisei-project.org>.
*/
#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;
}

View file

@ -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 <laochailan@web.de>.
* Copyright (c) 2012-2019, Andrei Alexeyev <akari@taisei-project.org>.
*/
#ifndef IGUARD_pixmap_fileformats_fileformats_h
#define IGUARD_pixmap_fileformats_fileformats_h
#include "taisei.h"
#include <SDL.h>
#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

View file

@ -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,
};
bool pixmap_serialize(SDL_RWops *stream, const Pixmap *pixmap) {
static const uint8_t pxi_magic[] = { 0xe2, 0x99, 0xa5, 0x52, 0x65, 0x69, 0x6d, 0x75 };
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 @@ fail:
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 @@ fail:
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",
};

View file

@ -0,0 +1,6 @@
pixmap_fileformats_src = files(
'internal.c',
'png.c',
'webp.c',
)

View file

@ -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 @@ done:
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",
};

View file

@ -9,7 +9,7 @@
#include "taisei.h"
#include "util.h"
#include "loaders.h"
#include "fileformats.h"
#include <webp/decode.h>
@ -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",
};

View file

@ -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 <laochailan@web.de>.
* Copyright (c) 2012-2019, Andrei Alexeyev <akari@taisei-project.org>.
*/
#ifndef IGUARD_pixmap_loaders_loaders_h
#define IGUARD_pixmap_loaders_loaders_h
#include "taisei.h"
#include <SDL.h>
#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

View file

@ -1,5 +0,0 @@
pixmap_loaders_src = files(
'loader_png.c',
'loader_webp.c',
)

View file

@ -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

View file

@ -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,203 +46,18 @@ 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);
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_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_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;
}
static PixmapLoader *pixmap_loaders[] = {
&pixmap_loader_png,
&pixmap_loader_webp,
NULL,
};
static PixmapLoader *pixmap_loader_for_filename(const char *file) {
static PixmapFileFormatHandler *pixmap_handler_for_filename(const char *file) {
char *ext = strrchr(file, '.');
if(!ext || !*(++ext)) {
return NULL;
}
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) {
if(!SDL_strcasecmp(*l_ext, ext)) {
return l;
return h;
}
}
}
@ -411,18 +65,55 @@ static PixmapLoader *pixmap_loader_for_filename(const char *file) {
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);
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]);
}
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]);
if(!h->probe) {
continue;
}
bool match = h->probe(stream);
if(SDL_RWseek(stream, 0, RW_SEEK_SET) < 0) {
log_sdl_error(LOG_ERROR, "SDL_RWseek");
return NULL;
}
if(match) {
return (*loader)->load(stream, dst, preferred_format);
return h;
}
}
log_error("Image format not recognized");
return false;
return NULL;
}
bool pixmap_load_stream(SDL_RWops *stream, PixmapFileFormat filefmt, Pixmap *dst, PixmapFormat preferred_format) {
PixmapFileFormatHandler *handler = NULL;
if(filefmt == PIXMAP_FILEFORMAT_AUTO) {
handler = pixmap_probe_stream(stream);
} else {
handler = pixmap_handler_for_fileformat(filefmt);
}
if(UNLIKELY(!handler)) {
log_error("Image format not recognized");
return false;
}
if(UNLIKELY(!handler->load)) {
log_error("Can't load images in %s format", NOT_NULL(handler->name));
return false;
}
return handler->load(stream, dst, preferred_format);
}
bool pixmap_load_file(const char *path, Pixmap *dst, PixmapFormat preferred_format) {
@ -434,13 +125,66 @@ bool pixmap_load_file(const char *path, Pixmap *dst, PixmapFormat preferred_form
return false;
}
bool result = pixmap_load_stream(stream, dst, preferred_format);
bool result = pixmap_load_stream(stream, PIXMAP_FILEFORMAT_AUTO, dst, preferred_format);
SDL_RWclose(stream);
return result;
}
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;
}
return handler->save(stream, src, opts);
}
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);
}
bool pixmap_save_file(const char *path, const Pixmap *src, const PixmapSaveOptions *opts) {
PixmapFileFormatHandler *h = NULL;
PixmapFileFormat fmt = PIXMAP_FILEFORMAT_AUTO;
if(opts) {
assert(opts->struct_size >= sizeof(PixmapSaveOptions));
fmt = opts->file_format;
}
if(fmt == PIXMAP_FILEFORMAT_AUTO) {
h = pixmap_handler_for_filename(path);
} else {
h = pixmap_handler_for_fileformat(fmt);
}
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)
);
}
SDL_RWops *stream = vfs_open(path, VFS_MODE_WRITE);
if(UNLIKELY(!stream)) {
log_error("VFS error: %s", vfs_get_error());
return false;
}
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);

View file

@ -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);

View file

@ -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 <laochailan@web.de>.
* Copyright (c) 2012-2019, Andrei Alexeyev <akari@taisei-project.org>.
*/
#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

View file

@ -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 <basisu_transcoder_c_api.h>
@ -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) {

View file

@ -8,8 +8,6 @@
#include "taisei.h"
#include <png.h>
#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;
PixmapPNGSaveOptions opts = PIXMAP_DEFAULT_PNG_SAVE_OPTIONS;
opts.zlib_compression_level = 9;
SDL_RWops *output = vfs_open(tdata->dest_path, VFS_MODE_WRITE);
bool ok = pixmap_save_file(tdata->dest_path, &tdata->image, &opts.base);
if(!output) {
log_error("VFS error: %s", vfs_get_error());
return NULL;
if(LIKELY(ok)) {
char *syspath = vfs_repr(tdata->dest_path, true);
log_info("Saved screenshot as %s", syspath);
free(syspath);
}
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;
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]);
}
if(png_ptr != NULL) {
png_destroy_write_struct(&png_ptr, info_ptr ? &info_ptr : NULL);
}
SDL_RWclose(output);
return NULL;
}