Kick SDL_image's ass out and replace JPEG with WebP

This commit is contained in:
Andrei Alexeyev 2018-10-19 00:12:46 +03:00
parent 56049991ac
commit 3937618c84
No known key found for this signature in database
GPG key ID: 363707CD4C7FE8A4
17 changed files with 446 additions and 154 deletions

View file

@ -16,11 +16,11 @@ Installation
Dependencies
^^^^^^^^^^^^
- SDL2 >= 2.0.5, SDL2_mixer, SDL2_image
- SDL2 >= 2.0.5, SDL2_mixer
- zlib
- libzip >= 1.0
- libpng >= 1.5.0
- libjpeg
- libwebpdecoder
- freetype2
- OpenGL >= 3.3, or OpenGL ES >= 3.0
- libshaderc (optional, for OpenGL ES backends)

View file

@ -107,26 +107,27 @@ taisei_c_args += taisei_c_warnargs
static = get_option('static')
dep_freetype = dependency('freetype2', required : true, static : static)
dep_png = dependency('libpng', version : '>=1.5', required : true, static : static)
dep_sdl2 = dependency('sdl2', version : '>=2.0.5', required : true, static : static)
dep_sdl2_mixer = dependency('SDL2_mixer', required : false, static : static)
dep_sdl2_image = dependency('SDL2_image', required : true, static : static)
dep_zlib = dependency('zlib', required : true, static : static)
dep_png = dependency('libpng', version : '>=1.5', required : true, static : static)
dep_webpdecoder = dependency('libwebpdecoder', required : true, static : static)
dep_zip = dependency('libzip', version : '>=1.0', required : false, static : static)
dep_freetype = dependency('freetype2', required : true, static : static)
dep_zlib = dependency('zlib', required : true, static : static)
dep_m = cc.find_library('m', required : false)
dep_cglm = subproject('cglm').get_variable('cglm_dep')
dep_glad = subproject('glad').get_variable('glad_dep')
taisei_deps = [
dep_sdl2,
dep_sdl2_image,
dep_zlib,
dep_png,
dep_cglm,
dep_freetype,
dep_m,
dep_cglm,
dep_png,
dep_sdl2,
dep_webpdecoder,
dep_zlib,
# don't add glad here
]

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 847 KiB

View file

@ -9,7 +9,6 @@
#include "taisei.h"
#include <locale.h>
#include <SDL_image.h>
#include <png.h>
#include "global.h"
@ -106,22 +105,6 @@ static void log_lib_versions(void) {
log_info("Compiled against libpng %s", PNG_LIBPNG_VER_STRING);
log_info("Using libpng %s", png_get_header_ver(NULL));
SDL_version img_version_compiled;
const SDL_version *img_version_linked = IMG_Linked_Version();
SDL_IMAGE_VERSION(&img_version_compiled);
log_info("Compiled against SDL_image %d.%d.%d",
img_version_compiled.major,
img_version_compiled.minor,
img_version_compiled.patch
);
log_info("Using SDL_image %d.%d.%d",
img_version_linked->major,
img_version_linked->minor,
img_version_linked->patch
);
}
void log_system_specs(void) {

View file

@ -8,8 +8,6 @@
#include "taisei.h"
#include <SDL_image.h>
#include "resource.h"
#include "config.h"
#include "video.h"

View file

@ -33,30 +33,6 @@ ResourceHandler texture_res_handler = {
},
};
static const char *texture_image_exts[] = {
// more are usable if you explicitly specify the source in a .tex file,
// but these are the ones we officially support, and are tried in this
// order for source auto-detection.
//
// FIXME: overriding extension in the .tex file currently doesn't work!
".png",
".jpg",
".jpeg",
NULL
};
static char* texture_image_path(const char *name) {
char *p = NULL;
for(const char **ext = texture_image_exts; *ext; ++ext) {
if((p = try_path(TEX_PATH_PREFIX, name, *ext))) {
return p;
}
}
return NULL;
}
char* texture_path(const char *name) {
char *p = NULL;
@ -64,7 +40,7 @@ char* texture_path(const char *name) {
return p;
}
return texture_image_path(name);
return pixmap_source_path(TEX_PATH_PREFIX, name);
}
bool check_texture_path(const char *path) {
@ -72,7 +48,7 @@ bool check_texture_path(const char *path) {
return true;
}
return strendswith_any(path, texture_image_exts);
return pixmap_check_filename(path);
}
typedef struct TexLoadData {
@ -211,7 +187,7 @@ static void* load_texture_begin(const char *path, uint flags) {
if(!source_allocated) {
char *basename = resource_util_basename(TEX_PATH_PREFIX, path);
source_allocated = texture_image_path(basename);
source_allocated = pixmap_source_path(TEX_PATH_PREFIX, basename);
if(!source_allocated) {
log_warn("%s: couldn't infer source path from texture name", basename);

View file

@ -31,3 +31,6 @@ else
# No have_posix check, it might just work.
util_src += files('platform_posix.c')
endif
subdir('pixmap_loaders')
util_src += pixmap_loaders_src

View file

@ -8,10 +8,9 @@
#include "taisei.h"
#include <SDL_image.h>
#include "pixmap.h"
#include "util.h"
#include "pixmap_loaders/loaders.h"
// NOTE: this is pretty stupid and not at all optimized, patches welcome
@ -279,84 +278,52 @@ void pixmap_flip_to_origin_inplace(Pixmap *src, PixmapOrigin origin) {
src->origin = origin;
}
static void quit_sdl_image(void) {
IMG_Quit();
}
static void init_sdl_image(void) {
static bool sdlimage_initialized = false;
if(sdlimage_initialized) {
return;
}
sdlimage_initialized = true;
int want_flags = IMG_INIT_JPG | IMG_INIT_PNG;
int init_flags = IMG_Init(want_flags);
if((want_flags & init_flags) != want_flags) {
log_warn(
"SDL_image doesn't support some of the formats we want. "
"Requested: %i, got: %i. "
"Textures may fail to load",
want_flags,
init_flags
);
}
atexit(quit_sdl_image);
}
static bool pixmap_load_finish(SDL_Surface *surf, Pixmap *dst) {
SDL_Surface *cv_surf = SDL_ConvertSurfaceFormat(surf, SDL_PIXELFORMAT_RGBA32, 0);
SDL_FreeSurface(surf);
if(cv_surf == NULL) {
log_warn("SDL_ConvertSurfaceFormat() failed: %s", SDL_GetError());
return false;
}
// NOTE: 32 in SDL_PIXELFORMAT_RGBA32 is bits per pixel; 8 in PIXMAP_FORMAT_RGBA8 is bits per channel.
dst->format = PIXMAP_FORMAT_RGBA8;
dst->width = cv_surf->w;
dst->height = cv_surf->h;
dst->origin = PIXMAP_ORIGIN_TOPLEFT;
dst->data.rgba8 = pixmap_alloc_buffer_for_copy(dst);
SDL_LockSurface(cv_surf);
memcpy(dst->data.rgba8, cv_surf->pixels, pixmap_data_size(dst));
SDL_UnlockSurface(cv_surf);
SDL_FreeSurface(cv_surf);
return true;
}
bool pixmap_load_stream_tga(SDL_RWops *stream, Pixmap *dst) {
init_sdl_image();
SDL_Surface *surf = IMG_LoadTGA_RW(stream);
return false;
}
if(surf == NULL) {
log_warn("IMG_LoadTGA_RW() failed: %s", IMG_GetError());
return false;
static PixmapLoader *pixmap_loaders[] = {
&pixmap_loader_png,
&pixmap_loader_webp,
NULL,
};
static PixmapLoader* pixmap_loader_for_filename(const char *file) {
char *ext = strrchr(file, '.');
if(!ext || !*(++ext)) {
return NULL;
}
return pixmap_load_finish(surf, dst);
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;
}
}
}
return NULL;
}
bool pixmap_load_stream(SDL_RWops *stream, Pixmap *dst) {
init_sdl_image();
SDL_Surface *surf = IMG_Load_RW(stream, false);
for(PixmapLoader **loader = pixmap_loaders; *loader; ++loader) {
bool match = (*loader)->probe(stream);
SDL_RWseek(stream, 0, RW_SEEK_SET);
if(surf == NULL) {
log_warn("IMG_Load_RW() failed: %s", IMG_GetError());
return false;
if(match) {
return (*loader)->load(stream, dst);
}
}
return pixmap_load_finish(surf, dst);
log_warn("Image format not recognized");
return false;
}
bool pixmap_load_file(const char *path, Pixmap *dst) {
// TODO: Make this work without having to read the whole file into memory
// (that is what the VFS_MODE_SEEKABLE bit currently does with zip archives).
SDL_RWops *stream = vfs_open(path, VFS_MODE_READ | VFS_MODE_SEEKABLE);
if(!stream) {
@ -364,14 +331,44 @@ bool pixmap_load_file(const char *path, Pixmap *dst) {
return false;
}
bool result;
if(strendswith(path, ".tga")) {
result = pixmap_load_stream_tga(stream, dst);
} else {
result = pixmap_load_stream(stream, dst);
}
bool result = pixmap_load_stream(stream, dst);
SDL_RWclose(stream);
return result;
}
bool pixmap_check_filename(const char *path) {
return (bool)pixmap_loader_for_filename(path);
}
char* pixmap_source_path(const char *prefix, const char *path) {
char base_path[strlen(prefix) + strlen(path) + 1];
strcpy(base_path, prefix);
strcpy(base_path + strlen(prefix), path);
if(pixmap_check_filename(base_path) && vfs_query(base_path).exists) {
return strdup(base_path);
}
char *dot = strrchr(path, '.');
if(dot) {
*dot = 0;
}
for(PixmapLoader **loader = pixmap_loaders; *loader; ++loader) {
PixmapLoader *l = *loader;
for(const char **l_ext = l->filename_exts; *l_ext; ++l_ext) {
char ext[strlen(*l_ext) + 2];
ext[0] = '.';
strcpy(ext + 1, *l_ext);
char *p = try_path("", base_path, ext);
if(p != NULL) {
return p;
}
}
}
return NULL;
}

View file

@ -147,3 +147,6 @@ size_t pixmap_data_size(const Pixmap *px) attr_nonnull(1);
bool pixmap_load_file(const char *path, Pixmap *dst) attr_nonnull(1, 2) attr_nodiscard;
bool pixmap_load_stream(SDL_RWops *stream, Pixmap *dst) attr_nonnull(1, 2) attr_nodiscard;
bool pixmap_load_stream_tga(SDL_RWops *stream, Pixmap *dst) attr_nonnull(1, 2) attr_nodiscard;
bool pixmap_check_filename(const char *path);
char* pixmap_source_path(const char *prefix, const char *path);

View file

@ -0,0 +1,135 @@
/*
* This software is licensed under the terms of the MIT-License
* See COPYING for further information.
* ---
* Copyright (c) 2011-2018, Lukas Weber <laochailan@web.de>.
* Copyright (c) 2012-2018, Andrei Alexeyev <akari@alienslab.net>.
*/
#include "taisei.h"
#include "util.h"
#include "loaders.h"
#include "../pngcruft.h"
static uchar png_magic[] = { 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A };
static bool px_png_probe(SDL_RWops *stream) {
uchar magic[sizeof(png_magic)] = { 0 };
SDL_RWread(stream, magic, sizeof(magic), 1);
return !memcmp(magic, png_magic, sizeof(magic));
}
static bool px_png_load(SDL_RWops *stream, Pixmap *pixmap) {
png_structp png = NULL;
png_infop png_info = NULL;
const char *error = NULL;
pixmap->data.untyped = NULL;
if(!(png = pngutil_create_read_struct())) {
error = "png_create_read_struct() failed";
goto done;
}
if(!(png_info = png_create_info_struct(png))) {
error = "png_create_info_struct() failed";
goto done;
}
if(setjmp(png_jmpbuf(png))) {
error = "PNG error";
goto done;
}
pngutil_init_rwops_read(png, stream);
png_read_info(png, png_info);
png_byte color_type = png_get_color_type(png, png_info);
png_byte bit_depth = png_get_bit_depth(png, png_info);
png_set_alpha_mode(png, PNG_ALPHA_PNG, PNG_DEFAULT_sRGB);
if(color_type == PNG_COLOR_TYPE_PALETTE) {
png_set_palette_to_rgb(png);
}
if(png_get_valid(png, png_info, PNG_INFO_tRNS)) {
png_set_tRNS_to_alpha(png);
}
if(color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8) {
png_set_expand_gray_1_2_4_to_8(png);
}
if(color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_GRAY_ALPHA) {
png_set_gray_to_rgb(png);
}
png_set_expand(png);
#if SDL_BYTEORDER == SDL_LIL_ENDIAN
png_set_swap(png);
#endif
int num_passes = png_set_interlace_handling(png);
png_read_update_info(png, png_info);
png_byte channels = png_get_channels(png, png_info);
color_type = png_get_color_type(png, png_info);
bit_depth = png_get_bit_depth(png, png_info);
assert(color_type == PNG_COLOR_TYPE_RGB || color_type == PNG_COLOR_TYPE_RGBA);
assert(bit_depth == 8 || bit_depth == 16);
assert(channels == 3 || channels == 4);
pixmap->width = png_get_image_width(png, png_info);
pixmap->height = png_get_image_height(png, png_info);
pixmap->format = PIXMAP_MAKE_FORMAT(
channels == 3 ? PIXMAP_LAYOUT_RGB : PIXMAP_LAYOUT_RGBA,
bit_depth
);
// NOTE: We read the image upside down here to avoid needing to flip it for
// 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);
if(pixmap->height > PNG_SIZE_MAX/(pixmap->width * pixel_size)) {
error = "The image is too large";
goto done;
}
png_bytep buffer = pixmap->data.untyped = pixmap_alloc_buffer_for_copy(pixmap);
for(int pass = 0; pass < num_passes; ++pass) {
for(int row = pixmap->height - 1; row >= 0; --row) {
png_read_row(png, buffer + row * pixmap->width * pixel_size, NULL);
}
}
done:
if(png != NULL) {
png_destroy_read_struct(
&png,
png_info ? &png_info : NULL,
NULL
);
}
if(error) {
log_warn("Failed to load image: %s", error);
free(pixmap->data.untyped);
pixmap->data.untyped = NULL;
return false;
}
return true;
}
PixmapLoader pixmap_loader_png = {
.probe = px_png_probe,
.load = px_png_load,
.filename_exts = (const char*[]){ "png", NULL },
};

View file

@ -0,0 +1,135 @@
/*
* This software is licensed under the terms of the MIT-License
* See COPYING for further information.
* ---
* Copyright (c) 2011-2018, Lukas Weber <laochailan@web.de>.
* Copyright (c) 2012-2018, Andrei Alexeyev <akari@alienslab.net>.
*/
#include "taisei.h"
#include "util.h"
#include "loaders.h"
#include <webp/decode.h>
static bool px_webp_probe(SDL_RWops *stream) {
// "RIFF", file size (4 bytes), "WEBP", ("VP8 " | "VP8L" | "VP8X")
// https://developers.google.com/speed/webp/docs/riff_container
uchar header[16] = { 0 };
SDL_RWread(stream, header, sizeof(header), 1);
return (
!memcmp(header, "RIFF", 4) &&
!memcmp(header + 8, "WEBP", 4) && (
!memcmp(header + 12, "VP8 ", 4) || // simple lossy
!memcmp(header + 12, "VP8L", 4) || // simple lossless
!memcmp(header + 12, "VP8X", 4) // extended
)
);
}
static inline const char* webp_error_str(VP8StatusCode code) {
static const char* map[] = {
[VP8_STATUS_OK] = "No error",
[VP8_STATUS_OUT_OF_MEMORY] = "Out of memory",
[VP8_STATUS_INVALID_PARAM] = "Invalid parameter",
[VP8_STATUS_BITSTREAM_ERROR] = "Bitstream error",
[VP8_STATUS_UNSUPPORTED_FEATURE] = "Unsupported feature",
[VP8_STATUS_SUSPENDED] = "Suspended",
[VP8_STATUS_USER_ABORT] = "Aborted by user",
[VP8_STATUS_NOT_ENOUGH_DATA] = "Not enough data",
};
if((uint)code < sizeof(map)/sizeof(*map)) {
return map[code];
}
return "Unknown error";
}
static bool px_webp_load(SDL_RWops *stream, Pixmap *pixmap) {
WebPDecoderConfig config;
int status = WebPInitDecoderConfig(&config);
if(!status) {
log_warn("Library ABI mismatch");
return false;
}
// NOTE: We read the image upside down here to avoid needing to flip it for
// the GL backend. This is just a slight optimization, not a hard dependency.
config.options.flip = true;
pixmap->origin = PIXMAP_ORIGIN_BOTTOMLEFT;
// TODO: Make sure this isn't counter-productive with our own inter-resource
// loading parallelism.
config.options.use_threads = true;
// TODO: Investigate the effect of dithering on image quality and decoding
// time. Only relevant for lossy images.
// config.options.dithering_strength = 50;
// config.options.alpha_dithering_strength = 50;
WebPBitstreamFeatures features;
uint8_t buf[BUFSIZ] = { 0 };
size_t data_available = SDL_RWread(stream, buf, 1, sizeof(buf));
status = WebPGetFeatures(buf, data_available, &features);
if(status != VP8_STATUS_OK) {
log_warn("WebPGetFeatures() failed: %s", webp_error_str(status));
return false;
}
WebPIDecoder *idec = WebPINewDecoder(&config.output);
if(idec == NULL) {
log_warn("WebPINewDecoder() failed");
return false;
}
pixmap->width = features.width;
pixmap->height = features.height;
pixmap->format = features.has_alpha ? PIXMAP_FORMAT_RGBA8 : PIXMAP_FORMAT_RGB8;
size_t pixel_size = PIXMAP_FORMAT_PIXEL_SIZE(pixmap->format);
if(pixmap->height > ((size_t)-1)/(pixmap->width * pixel_size)) {
WebPIDelete(idec);
log_warn("The image is too large");
return false;
}
pixmap->data.untyped = pixmap_alloc_buffer_for_copy(pixmap);
config.output.is_external_memory = true;
config.output.colorspace = features.has_alpha ? MODE_RGBA : MODE_RGB;
config.output.u.RGBA.rgba = pixmap->data.untyped;
config.output.u.RGBA.size = pixmap_data_size(pixmap);
config.output.u.RGBA.stride = pixel_size * pixmap->width;
do {
status = WebPIAppend(idec, buf, data_available);
if(status != VP8_STATUS_OK && status != VP8_STATUS_SUSPENDED) {
log_warn("WebPIAppend() failed: %s", webp_error_str(status));
free(pixmap->data.untyped);
pixmap->data.untyped = NULL;
break;
}
data_available = SDL_RWread(stream, buf, 1, sizeof(buf));
} while(data_available > 0);
WebPIDelete(idec);
WebPFreeDecBuffer(&config.output);
return pixmap->data.untyped != NULL;
}
PixmapLoader pixmap_loader_webp = {
.probe = px_webp_probe,
.load = px_webp_load,
.filename_exts = (const char*[]){ "webp", NULL },
};

View file

@ -0,0 +1,22 @@
/*
* This software is licensed under the terms of the MIT-License
* See COPYING for further information.
* ---
* Copyright (c) 2011-2018, Lukas Weber <laochailan@web.de>.
* Copyright (c) 2012-2018, Andrei Alexeyev <akari@alienslab.net>.
*/
#pragma once
#include "taisei.h"
#include <SDL.h>
#include "../pixmap.h"
typedef struct PixmapLoader {
bool (*probe)(SDL_RWops *stream);
bool (*load)(SDL_RWops *stream, Pixmap *pixmap);
const char **filename_exts;
} PixmapLoader;
extern PixmapLoader pixmap_loader_png;
extern PixmapLoader pixmap_loader_webp;

View file

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

View file

@ -11,37 +11,45 @@
#include "pngcruft.h"
#include "log.h"
static void png_rwops_write_data(png_structp png_ptr, png_bytep data, png_size_t length) {
static void pngutil_rwops_write_data(png_structp png_ptr, png_bytep data, png_size_t length) {
SDL_RWops *out = png_get_io_ptr(png_ptr);
SDL_RWwrite(out, data, length, 1);
}
static void png_rwops_flush_data(png_structp png_ptr) {
static void pngutil_rwops_flush_data(png_structp png_ptr) {
// no flush operation in SDL_RWops
}
static void png_rwops_read_data(png_structp png_ptr, png_bytep data, png_size_t length) {
static void pngutil_rwops_read_data(png_structp png_ptr, png_bytep data, png_size_t length) {
SDL_RWops *out = png_get_io_ptr(png_ptr);
SDL_RWread(out, data, length, 1);
}
void png_init_rwops_read(png_structp png, SDL_RWops *rwops) {
png_set_read_fn(png, rwops, png_rwops_read_data);
void pngutil_init_rwops_read(png_structp png, SDL_RWops *rwops) {
png_set_read_fn(png, rwops, pngutil_rwops_read_data);
}
void png_init_rwops_write(png_structp png, SDL_RWops *rwops) {
png_set_write_fn(png, rwops, png_rwops_write_data, png_rwops_flush_data);
void pngutil_init_rwops_write(png_structp png, SDL_RWops *rwops) {
png_set_write_fn(png, rwops, pngutil_rwops_write_data, pngutil_rwops_flush_data);
}
noreturn static void png_error_handler(png_structp png_ptr, png_const_charp error_msg) {
noreturn static void pngutil_error_handler(png_structp png_ptr, png_const_charp error_msg) {
log_warn("PNG error: %s", error_msg);
png_longjmp(png_ptr, 1);
}
static void png_warning_handler(png_structp png_ptr, png_const_charp warning_msg) {
static void pngutil_warning_handler(png_structp png_ptr, png_const_charp warning_msg) {
log_warn("PNG warning: %s", warning_msg);
}
void png_setup_error_handlers(png_structp png) {
png_set_error_fn(png, NULL, png_error_handler, png_warning_handler);
void pngutil_setup_error_handlers(png_structp png) {
png_set_error_fn(png, NULL, pngutil_error_handler, pngutil_warning_handler);
}
png_structp pngutil_create_read_struct(void) {
return png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, pngutil_error_handler, pngutil_warning_handler);
}
png_structp pngutil_create_write_struct(void) {
return png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, pngutil_error_handler, pngutil_warning_handler);
}

View file

@ -12,6 +12,8 @@
#include <png.h>
#include <SDL.h>
void png_init_rwops_read(png_structp png, SDL_RWops *rwops);
void png_init_rwops_write(png_structp png, SDL_RWops *rwops);
void png_setup_error_handlers(png_structp png);
void pngutil_init_rwops_read(png_structp png, SDL_RWops *rwops);
void pngutil_init_rwops_write(png_structp png, SDL_RWops *rwops);
void pngutil_setup_error_handlers(png_structp png);
png_structp pngutil_create_read_struct(void);
png_structp pngutil_create_write_struct(void);

View file

@ -256,36 +256,60 @@ static void* video_screenshot_task(void *arg) {
log_info("Saving screenshot as %s", syspath);
free(syspath);
const char *error = NULL;
png_structp png_ptr;
png_infop info_ptr;
png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
png_setup_error_handlers(png_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
);
png_byte *row_pointers[height];
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);
}
png_init_rwops_write(png_ptr, output);
png_set_rows(png_ptr, info_ptr, row_pointers);
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_warn("Couldn't save screenshot: %s", error);
}
for(int y = 0; y < height; y++) {
png_free(png_ptr, row_pointers[y]);
}
png_destroy_write_struct(&png_ptr, &info_ptr);
SDL_RWclose(output);
if(png_ptr != NULL) {
png_destroy_write_struct(&png_ptr, info_ptr ? &info_ptr : NULL);
}
SDL_RWclose(output);
return NULL;
}