Kick SDL_image's ass out and replace JPEG with WebP
This commit is contained in:
parent
56049991ac
commit
3937618c84
17 changed files with 446 additions and 154 deletions
|
@ -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)
|
||||
|
|
19
meson.build
19
meson.build
|
@ -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 |
BIN
resources/gfx/stage6/spellbg_toe.webp
Normal file
BIN
resources/gfx/stage6/spellbg_toe.webp
Normal file
Binary file not shown.
After Width: | Height: | Size: 847 KiB |
17
src/main.c
17
src/main.c
|
@ -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) {
|
||||
|
|
|
@ -8,8 +8,6 @@
|
|||
|
||||
#include "taisei.h"
|
||||
|
||||
#include <SDL_image.h>
|
||||
|
||||
#include "resource.h"
|
||||
#include "config.h"
|
||||
#include "video.h"
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
135
src/util/pixmap_loaders/loader_png.c
Normal file
135
src/util/pixmap_loaders/loader_png.c
Normal 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 },
|
||||
};
|
135
src/util/pixmap_loaders/loader_webp.c
Normal file
135
src/util/pixmap_loaders/loader_webp.c
Normal 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 },
|
||||
};
|
22
src/util/pixmap_loaders/loaders.h
Normal file
22
src/util/pixmap_loaders/loaders.h
Normal 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;
|
5
src/util/pixmap_loaders/meson.build
Normal file
5
src/util/pixmap_loaders/meson.build
Normal file
|
@ -0,0 +1,5 @@
|
|||
|
||||
pixmap_loaders_src = files(
|
||||
'loader_png.c',
|
||||
'loader_webp.c',
|
||||
)
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
40
src/video.c
40
src/video.c
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue