1151 lines
40 KiB
C
1151 lines
40 KiB
C
/*
|
|
SDL_image: An example image loading library for use with SDL
|
|
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
|
|
|
|
This software is provided 'as-is', without any express or implied
|
|
warranty. In no event will the authors be held liable for any damages
|
|
arising from the use of this software.
|
|
|
|
Permission is granted to anyone to use this software for any purpose,
|
|
including commercial applications, and to alter it and redistribute it
|
|
freely, subject to the following restrictions:
|
|
|
|
1. The origin of this software must not be misrepresented; you must not
|
|
claim that you wrote the original software. If you use this software
|
|
in a product, an acknowledgment in the product documentation would be
|
|
appreciated but is not required.
|
|
2. Altered source versions must be plainly marked as such, and must not be
|
|
misrepresented as being the original software.
|
|
3. This notice may not be removed or altered from any source distribution.
|
|
*/
|
|
|
|
/* This is a WEBP image file loading framework */
|
|
|
|
#include <SDL3_image/SDL_image.h>
|
|
|
|
#include "IMG.h"
|
|
#include "IMG_webp.h"
|
|
#include "IMG_anim_encoder.h"
|
|
#include "IMG_anim_decoder.h"
|
|
#include "xmlman.h"
|
|
|
|
// We will have the saving WEBP feature by default
|
|
#if !defined(SAVE_WEBP)
|
|
#ifdef LOAD_WEBP
|
|
#define SAVE_WEBP 1
|
|
#else
|
|
#define SAVE_WEBP 0
|
|
#endif
|
|
#endif
|
|
|
|
#ifdef LOAD_WEBP
|
|
|
|
|
|
#if (defined(LOAD_WEBP_DYNAMIC) || defined(LOAD_WEBPDEMUX_DYNAMIC) || defined(LOAD_WEBPMUX_DYNAMIC)) && defined(SDL_ELF_NOTE_DLOPEN)
|
|
|
|
#ifdef LOAD_WEBP_DYNAMIC
|
|
SDL_ELF_NOTE_DLOPEN(
|
|
"webp",
|
|
"Support for WebP images using libwebp",
|
|
SDL_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED,
|
|
LOAD_WEBP_DYNAMIC
|
|
)
|
|
#endif
|
|
|
|
#ifdef LOAD_WEBPDEMUX_DYNAMIC
|
|
SDL_ELF_NOTE_DLOPEN(
|
|
"webp",
|
|
"Support for WebP images using libwebp",
|
|
SDL_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED,
|
|
LOAD_WEBPDEMUX_DYNAMIC
|
|
)
|
|
#endif
|
|
|
|
#ifdef LOAD_WEBPMUX_DYNAMIC
|
|
SDL_ELF_NOTE_DLOPEN(
|
|
"webp",
|
|
"Support for WebP images using libwebp",
|
|
SDL_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED,
|
|
LOAD_WEBPMUX_DYNAMIC
|
|
)
|
|
#endif
|
|
|
|
#endif
|
|
/*=============================================================================
|
|
File: SDL_webp.c
|
|
Purpose: A WEBP loader for the SDL library
|
|
Revision:
|
|
Created by: Michael Bonfils (Murlock) (26 November 2011)
|
|
murlock42@gmail.com
|
|
|
|
=============================================================================*/
|
|
|
|
#ifdef macintosh
|
|
#define MACOS
|
|
#endif
|
|
#include <webp/decode.h>
|
|
#include <webp/demux.h>
|
|
#include <webp/encode.h>
|
|
#include <webp/mux.h>
|
|
#include <webp/types.h>
|
|
|
|
static struct
|
|
{
|
|
int loaded;
|
|
void *handle_libwebpdemux;
|
|
void *handle_libwebp;
|
|
void *handle_libwebpmux;
|
|
|
|
VP8StatusCode (*WebPGetFeaturesInternal)(const uint8_t *data, size_t data_size, WebPBitstreamFeatures *features, int decoder_abi_version);
|
|
uint8_t *(*WebPDecodeRGBInto)(const uint8_t *data, size_t data_size, uint8_t *output_buffer, size_t output_buffer_size, int output_stride);
|
|
uint8_t *(*WebPDecodeRGBAInto)(const uint8_t *data, size_t data_size, uint8_t *output_buffer, size_t output_buffer_size, int output_stride);
|
|
WebPDemuxer *(*WebPDemuxInternal)(const WebPData *data, int allow_partial, WebPDemuxState *state, int version);
|
|
int (*WebPDemuxGetFrame)(const WebPDemuxer *dmux, int frame_number, WebPIterator *iter);
|
|
int (*WebPDemuxNextFrame)(WebPIterator *iter);
|
|
void (*WebPDemuxReleaseIterator)(WebPIterator *iter);
|
|
uint32_t (*WebPDemuxGetI)(const WebPDemuxer *dmux, WebPFormatFeature feature);
|
|
void (*WebPDemuxDelete)(WebPDemuxer *dmux);
|
|
|
|
// Encoding frame functions
|
|
int (*WebPConfigInitInternal)(WebPConfig *, WebPPreset, float, int);
|
|
int (*WebPValidateConfig)(const WebPConfig *);
|
|
int (*WebPPictureInitInternal)(WebPPicture *, int);
|
|
int (*WebPEncode)(const WebPConfig *, WebPPicture *);
|
|
void (*WebPPictureFree)(WebPPicture *);
|
|
int (*WebPPictureImportRGBA)(WebPPicture *, const uint8_t *, int);
|
|
void (*WebPMemoryWriterInit)(WebPMemoryWriter *);
|
|
int (*WebPMemoryWrite)(const uint8_t *, size_t, const WebPPicture *);
|
|
void (*WebPMemoryWriterClear)(WebPMemoryWriter *);
|
|
|
|
// Free function required for cleanup after muxing.
|
|
void (*WebPFree)(void *ptr);
|
|
|
|
// Muxing functions
|
|
WebPAnimEncoder *(*WebPAnimEncoderNewInternal)(int, int, const WebPAnimEncoderOptions *, int); // Export #33 (0x0021), (0x), 0x00003900, None
|
|
int (*WebPAnimEncoderOptionsInitInternal)(WebPAnimEncoderOptions *, int); // Export #34 (0x0022), (0x), 0x000038c0, None
|
|
int (*WebPAnimEncoderAdd)(WebPAnimEncoder *, WebPPicture *, int, const WebPConfig *); // Export #29 (0x001d), (0x), 0x00003e60, None
|
|
int (*WebPAnimEncoderAssemble)(WebPAnimEncoder *, WebPData *); // Export #30 (0x001e), (0x), 0x00004580, None
|
|
void (*WebPAnimEncoderDelete)(WebPAnimEncoder *); // Export #31 (0x001f), (0x), 0x00003cb0, None
|
|
|
|
// Used for extracting EXIF & XMP chunks.
|
|
int (*WebPDemuxGetChunk)(const WebPDemuxer* dmux, const char fourcc[4], int chunk_number, WebPChunkIterator* iter);
|
|
void (*WebPDemuxReleaseChunkIterator)(WebPChunkIterator* iter);
|
|
|
|
// Used for setting EXIF & XMP chunks and for loop count.
|
|
WebPMux *(*WebPMuxCreateInternal)(const WebPData*, int, int); // Export 38 (0x0026), (0x), 0x00006e90, None
|
|
void (*WebPMuxDelete)(WebPMux* mux);
|
|
WebPMuxError (*WebPMuxSetChunk)(WebPMux *mux, const char fourcc[4], const WebPData *chunk_data, int copy_data);
|
|
WebPMuxError (*WebPMuxGetAnimationParams)(const WebPMux *mux, WebPMuxAnimParams *params);
|
|
WebPMuxError (*WebPMuxSetAnimationParams)(WebPMux* mux, const WebPMuxAnimParams* params);
|
|
|
|
WebPMuxError (*WebPMuxAssemble)(WebPMux* mux, WebPData* assembled_data);
|
|
} lib;
|
|
|
|
#if defined(LOAD_WEBP_DYNAMIC) && defined(LOAD_WEBPDEMUX_DYNAMIC) && defined(LOAD_WEBPMUX_DYNAMIC)
|
|
#define FUNCTION_LOADER_LIBWEBP(FUNC, SIG) \
|
|
lib.FUNC = (SIG)SDL_LoadFunction(lib.handle_libwebp, #FUNC); \
|
|
if (lib.FUNC == NULL) { \
|
|
SDL_UnloadObject(lib.handle_libwebp); \
|
|
return false; \
|
|
}
|
|
#define FUNCTION_LOADER_LIBWEBPDEMUX(FUNC, SIG) \
|
|
lib.FUNC = (SIG)SDL_LoadFunction(lib.handle_libwebpdemux, #FUNC); \
|
|
if (lib.FUNC == NULL) { \
|
|
SDL_UnloadObject(lib.handle_libwebpdemux); \
|
|
return false; \
|
|
}
|
|
#define FUNCTION_LOADER_LIBWEBPMUX(FUNC, SIG) \
|
|
lib.FUNC = (SIG)SDL_LoadFunction(lib.handle_libwebpmux, #FUNC); \
|
|
if (lib.FUNC == NULL) { \
|
|
SDL_UnloadObject(lib.handle_libwebpmux); \
|
|
return false; \
|
|
}
|
|
#else
|
|
#define FUNCTION_LOADER_LIBWEBP(FUNC, SIG) \
|
|
lib.FUNC = FUNC; \
|
|
if (lib.FUNC == NULL) { \
|
|
return SDL_SetError("Missing webp.framework"); \
|
|
}
|
|
#define FUNCTION_LOADER_LIBWEBPDEMUX(FUNC, SIG) \
|
|
lib.FUNC = FUNC; \
|
|
if (lib.FUNC == NULL) { \
|
|
return SDL_SetError("Missing webpdemux.framework"); \
|
|
}
|
|
#define FUNCTION_LOADER_LIBWEBPMUX(FUNC, SIG) \
|
|
lib.FUNC = FUNC; \
|
|
if (lib.FUNC == NULL) { \
|
|
return SDL_SetError("Missing webpmux.framework: %s", #FUNC); \
|
|
}
|
|
#endif
|
|
|
|
#ifdef __APPLE__
|
|
/* Need to turn off optimizations so weak framework load check works */
|
|
__attribute__((optnone))
|
|
#endif
|
|
static bool IMG_InitWEBP(void)
|
|
{
|
|
if (lib.loaded == 0) {
|
|
#if defined(LOAD_WEBP_DYNAMIC) && defined(LOAD_WEBPDEMUX_DYNAMIC) && defined(LOAD_WEBPMUX_DYNAMIC)
|
|
lib.handle_libwebp = SDL_LoadObject(LOAD_WEBP_DYNAMIC);
|
|
if (lib.handle_libwebp == NULL) {
|
|
return false;
|
|
}
|
|
lib.handle_libwebpdemux = SDL_LoadObject(LOAD_WEBPDEMUX_DYNAMIC);
|
|
if (lib.handle_libwebpdemux == NULL) {
|
|
return false;
|
|
}
|
|
lib.handle_libwebpmux = SDL_LoadObject(LOAD_WEBPMUX_DYNAMIC);
|
|
if (lib.handle_libwebpmux == NULL) {
|
|
return false;
|
|
}
|
|
#endif
|
|
FUNCTION_LOADER_LIBWEBP(WebPGetFeaturesInternal, VP8StatusCode(*)(const uint8_t *data, size_t data_size, WebPBitstreamFeatures *features, int decoder_abi_version))
|
|
FUNCTION_LOADER_LIBWEBP(WebPDecodeRGBInto, uint8_t *(*)(const uint8_t *data, size_t data_size, uint8_t *output_buffer, size_t output_buffer_size, int output_stride))
|
|
FUNCTION_LOADER_LIBWEBP(WebPDecodeRGBAInto, uint8_t *(*)(const uint8_t *data, size_t data_size, uint8_t *output_buffer, size_t output_buffer_size, int output_stride))
|
|
FUNCTION_LOADER_LIBWEBPDEMUX(WebPDemuxInternal, WebPDemuxer * (*)(const WebPData *, int, WebPDemuxState *, int))
|
|
FUNCTION_LOADER_LIBWEBPDEMUX(WebPDemuxGetFrame, int (*)(const WebPDemuxer *dmux, int frame_number, WebPIterator *iter))
|
|
FUNCTION_LOADER_LIBWEBPDEMUX(WebPDemuxNextFrame, int (*)(WebPIterator *iter))
|
|
FUNCTION_LOADER_LIBWEBPDEMUX(WebPDemuxReleaseIterator, void (*)(WebPIterator *iter))
|
|
FUNCTION_LOADER_LIBWEBPDEMUX(WebPDemuxGetI, uint32_t (*)(const WebPDemuxer *dmux, WebPFormatFeature feature))
|
|
FUNCTION_LOADER_LIBWEBPDEMUX(WebPDemuxDelete, void (*)(WebPDemuxer *dmux))
|
|
|
|
// Encoding frame functions
|
|
FUNCTION_LOADER_LIBWEBP(WebPConfigInitInternal, int (*)(WebPConfig *, WebPPreset, float, int))
|
|
FUNCTION_LOADER_LIBWEBP(WebPValidateConfig, int (*)(const WebPConfig *))
|
|
FUNCTION_LOADER_LIBWEBP(WebPPictureInitInternal, int (*)(WebPPicture *, int))
|
|
FUNCTION_LOADER_LIBWEBP(WebPEncode, int (*)(const WebPConfig *, WebPPicture *))
|
|
FUNCTION_LOADER_LIBWEBP(WebPPictureFree, void (*)(WebPPicture *))
|
|
FUNCTION_LOADER_LIBWEBP(WebPPictureImportRGBA, int (*)(WebPPicture *, const uint8_t *, int))
|
|
|
|
FUNCTION_LOADER_LIBWEBP(WebPMemoryWriterInit, void (*)(WebPMemoryWriter *))
|
|
FUNCTION_LOADER_LIBWEBP(WebPMemoryWrite, int (*)(const uint8_t *, size_t, const WebPPicture *))
|
|
FUNCTION_LOADER_LIBWEBP(WebPMemoryWriterClear, void (*)(WebPMemoryWriter *))
|
|
|
|
// Free function required for cleanup after muxing.
|
|
FUNCTION_LOADER_LIBWEBP(WebPFree, void (*)(void *))
|
|
|
|
// Muxing functions
|
|
FUNCTION_LOADER_LIBWEBPMUX(WebPAnimEncoderNewInternal, WebPAnimEncoder * (*)(int, int, const WebPAnimEncoderOptions *, int))
|
|
FUNCTION_LOADER_LIBWEBPMUX(WebPAnimEncoderOptionsInitInternal, int (*)(WebPAnimEncoderOptions *, int))
|
|
FUNCTION_LOADER_LIBWEBPMUX(WebPAnimEncoderAdd, int (*)(WebPAnimEncoder *, WebPPicture *, int, const WebPConfig *))
|
|
FUNCTION_LOADER_LIBWEBPMUX(WebPAnimEncoderAssemble, int (*)(WebPAnimEncoder *, WebPData *))
|
|
FUNCTION_LOADER_LIBWEBPMUX(WebPAnimEncoderDelete, void (*)(WebPAnimEncoder *))
|
|
|
|
// Used for extracting EXIF & XMP chunks.
|
|
FUNCTION_LOADER_LIBWEBPDEMUX(WebPDemuxGetChunk, int (*)(const WebPDemuxer *dmux, const char fourcc[4], int chunk_number, WebPChunkIterator *iter))
|
|
FUNCTION_LOADER_LIBWEBPDEMUX(WebPDemuxReleaseChunkIterator, void (*)(WebPChunkIterator* iter))
|
|
|
|
// Used for setting EXIF & XMP chunks and for loop count.
|
|
FUNCTION_LOADER_LIBWEBPMUX(WebPMuxCreateInternal, WebPMux * (*)(const WebPData*, int, int))
|
|
FUNCTION_LOADER_LIBWEBPMUX(WebPMuxDelete, void (*)(WebPMux* mux))
|
|
FUNCTION_LOADER_LIBWEBPMUX(WebPMuxSetChunk, WebPMuxError (*)(WebPMux *mux, const char fourcc[4], const WebPData *chunk_data, int copy_data))
|
|
FUNCTION_LOADER_LIBWEBPMUX(WebPMuxGetAnimationParams, WebPMuxError (*)(const WebPMux* mux, WebPMuxAnimParams* params))
|
|
FUNCTION_LOADER_LIBWEBPMUX(WebPMuxSetAnimationParams, WebPMuxError (*)(WebPMux* mux, const WebPMuxAnimParams* params))
|
|
FUNCTION_LOADER_LIBWEBPMUX(WebPMuxAssemble, WebPMuxError (*)(WebPMux* mux, WebPData* assembled_data))
|
|
}
|
|
++lib.loaded;
|
|
|
|
return true;
|
|
}
|
|
#if 0
|
|
void IMG_QuitWEBP(void)
|
|
{
|
|
if (lib.loaded == 0) {
|
|
return;
|
|
}
|
|
if (lib.loaded == 1) {
|
|
#if defined(LOAD_WEBP_DYNAMIC) && defined(LOAD_WEBPDEMUX_DYNAMIC)
|
|
SDL_UnloadObject(lib.handle_libwebp);
|
|
SDL_UnloadObject(lib.handle_libwebpdemux);
|
|
#endif
|
|
}
|
|
--lib.loaded;
|
|
}
|
|
#endif // 0
|
|
|
|
static bool webp_getinfo(SDL_IOStream *src, size_t *datasize)
|
|
{
|
|
Sint64 start, size;
|
|
bool is_WEBP;
|
|
Uint8 magic[20];
|
|
|
|
if (!src) {
|
|
return false;
|
|
}
|
|
|
|
start = SDL_TellIO(src);
|
|
is_WEBP = false;
|
|
if (SDL_ReadIO(src, magic, sizeof(magic)) == sizeof(magic)) {
|
|
if (magic[0] == 'R' &&
|
|
magic[1] == 'I' &&
|
|
magic[2] == 'F' &&
|
|
magic[3] == 'F' &&
|
|
magic[8] == 'W' &&
|
|
magic[9] == 'E' &&
|
|
magic[10] == 'B' &&
|
|
magic[11] == 'P' &&
|
|
magic[12] == 'V' &&
|
|
magic[13] == 'P' &&
|
|
magic[14] == '8' &&
|
|
(magic[15] == ' ' || magic[15] == 'X' || magic[15] == 'L')) {
|
|
is_WEBP = true;
|
|
if (datasize) {
|
|
size = SDL_GetIOSize(src);
|
|
if (size > 0) {
|
|
*datasize = (size_t)(size - start);
|
|
} else {
|
|
*datasize = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
SDL_SeekIO(src, start, SDL_IO_SEEK_SET);
|
|
return is_WEBP;
|
|
}
|
|
|
|
/* See if an image is contained in a data source */
|
|
bool IMG_isWEBP(SDL_IOStream *src)
|
|
{
|
|
return webp_getinfo(src, NULL);
|
|
}
|
|
|
|
SDL_Surface *IMG_LoadWEBP_IO(SDL_IOStream *src)
|
|
{
|
|
Sint64 start;
|
|
const char *error = NULL;
|
|
SDL_Surface *surface = NULL;
|
|
Uint32 format;
|
|
WebPBitstreamFeatures features;
|
|
size_t raw_data_size;
|
|
uint8_t *raw_data = NULL;
|
|
uint8_t *ret;
|
|
|
|
if (!src) {
|
|
/* The error message has been set in SDL_IOFromFile */
|
|
return NULL;
|
|
}
|
|
|
|
start = SDL_TellIO(src);
|
|
|
|
if (!IMG_InitWEBP()) {
|
|
goto error;
|
|
}
|
|
|
|
raw_data_size = -1;
|
|
if (!webp_getinfo(src, &raw_data_size)) {
|
|
error = "Invalid WEBP";
|
|
goto error;
|
|
}
|
|
|
|
raw_data = (uint8_t *)SDL_malloc(raw_data_size);
|
|
if (raw_data == NULL) {
|
|
error = "Failed to allocate enough buffer for WEBP";
|
|
goto error;
|
|
}
|
|
|
|
if (SDL_ReadIO(src, raw_data, raw_data_size) != raw_data_size) {
|
|
error = "Failed to read WEBP";
|
|
goto error;
|
|
}
|
|
|
|
#if 0
|
|
{/* extract size of picture, not interesting since we don't know about alpha channel */
|
|
int width = -1, height = -1;
|
|
if (!WebPGetInfo(raw_data, raw_data_size, &width, &height)) {
|
|
printf("WebPGetInfo has failed\n");
|
|
return NULL;
|
|
} }
|
|
#endif
|
|
|
|
if (lib.WebPGetFeaturesInternal(raw_data, raw_data_size, &features, WEBP_DECODER_ABI_VERSION) != VP8_STATUS_OK) {
|
|
error = "WebPGetFeatures has failed";
|
|
goto error;
|
|
}
|
|
|
|
// Special casing for animated WebP images to extract a single frame.
|
|
if (features.has_animation) {
|
|
if (SDL_SeekIO(src, start, SDL_IO_SEEK_SET) < 0) {
|
|
error = "Failed to seek IO to read animated WebP";
|
|
goto error;
|
|
} else {
|
|
IMG_Animation *animation = IMG_DecodeAsAnimation(src, "webp", 1);
|
|
if (animation && animation->count > 0) {
|
|
SDL_Surface *surf = animation->frames[0];
|
|
if (surf) {
|
|
++surf->refcount;
|
|
if (raw_data) {
|
|
SDL_free(raw_data);
|
|
}
|
|
IMG_FreeAnimation(animation);
|
|
return surf;
|
|
} else {
|
|
error = "Failed to load first frame of animated WebP";
|
|
IMG_FreeAnimation(animation);
|
|
goto error;
|
|
}
|
|
} else {
|
|
IMG_FreeAnimation(animation);
|
|
error = "Received an animated WebP but the animation data was invalid";
|
|
goto error;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (features.has_alpha) {
|
|
format = SDL_PIXELFORMAT_RGBA32;
|
|
} else {
|
|
format = SDL_PIXELFORMAT_RGB24;
|
|
}
|
|
|
|
surface = SDL_CreateSurface(features.width, features.height, format);
|
|
if (surface == NULL) {
|
|
error = "Failed to allocate SDL_Surface";
|
|
goto error;
|
|
}
|
|
|
|
if (features.has_alpha) {
|
|
ret = lib.WebPDecodeRGBAInto(raw_data, raw_data_size, (uint8_t *)surface->pixels, surface->pitch * surface->h, surface->pitch);
|
|
} else {
|
|
ret = lib.WebPDecodeRGBInto(raw_data, raw_data_size, (uint8_t *)surface->pixels, surface->pitch * surface->h, surface->pitch);
|
|
}
|
|
|
|
if (!ret) {
|
|
error = "Failed to decode WEBP";
|
|
goto error;
|
|
}
|
|
|
|
if (raw_data) {
|
|
SDL_free(raw_data);
|
|
}
|
|
|
|
return surface;
|
|
|
|
error:
|
|
if (raw_data) {
|
|
SDL_free(raw_data);
|
|
}
|
|
|
|
if (surface) {
|
|
SDL_DestroySurface(surface);
|
|
}
|
|
|
|
if (error) {
|
|
SDL_SetError("%s", error);
|
|
}
|
|
|
|
SDL_SeekIO(src, start, SDL_IO_SEEK_SET);
|
|
return NULL;
|
|
}
|
|
|
|
struct IMG_AnimationDecoderContext
|
|
{
|
|
WebPDemuxer *demuxer;
|
|
WebPIterator iter;
|
|
SDL_Surface *canvas;
|
|
WebPMuxAnimDispose dispose_method;
|
|
uint32_t bgcolor;
|
|
uint8_t *raw_data;
|
|
size_t raw_data_size;
|
|
WebPDemuxState demux_state;
|
|
SDL_Rect last_rect;
|
|
bool has_alpha;
|
|
};
|
|
|
|
static bool IMG_AnimationDecoderReset_Internal(IMG_AnimationDecoder *decoder)
|
|
{
|
|
lib.WebPDemuxReleaseIterator(&decoder->ctx->iter);
|
|
SDL_zero(decoder->ctx->iter);
|
|
decoder->ctx->dispose_method = WEBP_MUX_DISPOSE_BACKGROUND;
|
|
|
|
/* Reset disposal rect to full canvas */
|
|
decoder->ctx->last_rect.x = 0;
|
|
decoder->ctx->last_rect.y = 0;
|
|
decoder->ctx->last_rect.w = decoder->ctx->canvas->w;
|
|
decoder->ctx->last_rect.h = decoder->ctx->canvas->h;
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool IMG_AnimationDecoderGetNextFrame_Internal(IMG_AnimationDecoder *decoder, SDL_Surface **frame, Uint64 *duration)
|
|
{
|
|
// Get the next frame from the demuxer.
|
|
if (decoder->ctx->iter.frame_num < 1) {
|
|
if (!lib.WebPDemuxGetFrame(decoder->ctx->demuxer, 1, &decoder->ctx->iter)) {
|
|
return SDL_SetError("Failed to get first frame from WEBP demuxer");
|
|
}
|
|
} else {
|
|
if (!lib.WebPDemuxNextFrame(&decoder->ctx->iter)) {
|
|
decoder->status = IMG_DECODER_STATUS_COMPLETE;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
int totalFrames = decoder->ctx->iter.num_frames;
|
|
int availableFrames = totalFrames - (decoder->ctx->iter.frame_num - 1);
|
|
|
|
if (availableFrames < 1) {
|
|
decoder->status = IMG_DECODER_STATUS_COMPLETE;
|
|
return false;
|
|
}
|
|
|
|
SDL_Surface *canvas = decoder->ctx->canvas;
|
|
uint32_t bgcolor = decoder->ctx->bgcolor;
|
|
WebPMuxAnimDispose dispose_method = decoder->ctx->dispose_method;
|
|
WebPIterator *iter = &decoder->ctx->iter;
|
|
SDL_Surface *retval = NULL;
|
|
bool has_alpha = decoder->ctx->has_alpha;
|
|
SDL_Rect last_rect = decoder->ctx->last_rect;
|
|
|
|
SDL_Surface *curr = SDL_CreateSurface(iter->width, iter->height, SDL_PIXELFORMAT_RGBA32);
|
|
if (!curr) {
|
|
return false;
|
|
}
|
|
|
|
if (!lib.WebPDecodeRGBAInto(iter->fragment.bytes, iter->fragment.size,
|
|
(uint8_t *)curr->pixels, curr->pitch * curr->h, curr->pitch)) {
|
|
SDL_DestroySurface(curr);
|
|
return SDL_SetError("Failed to decode frame");
|
|
}
|
|
|
|
SDL_Rect dst = { iter->x_offset, iter->y_offset, iter->width, iter->height };
|
|
/* Correctly handle both Disposal and Blend modes to prevent ghosting */
|
|
if (dispose_method == WEBP_MUX_DISPOSE_BACKGROUND || iter->blend_method == WEBP_MUX_NO_BLEND) {
|
|
/* For alpha WebPs, we clear to transparency regardless of bad bgcolor metadata */
|
|
uint32_t fill_color = has_alpha ? SDL_MapSurfaceRGBA(canvas, 0, 0, 0, 0) : bgcolor;
|
|
|
|
/* If it's a disposal, clear the previous area; if it's NO_BLEND, clear the current area */
|
|
if (dispose_method == WEBP_MUX_DISPOSE_BACKGROUND) {
|
|
SDL_FillSurfaceRect(canvas, &last_rect, fill_color);
|
|
}
|
|
|
|
if (iter->blend_method == WEBP_MUX_NO_BLEND) {
|
|
SDL_FillSurfaceRect(canvas, &dst, fill_color);
|
|
}
|
|
}
|
|
|
|
if (iter->blend_method == WEBP_MUX_BLEND) {
|
|
if (!SDL_SetSurfaceBlendMode(curr, SDL_BLENDMODE_BLEND)) {
|
|
SDL_DestroySurface(curr);
|
|
return false;
|
|
}
|
|
} else {
|
|
if (!SDL_SetSurfaceBlendMode(curr, SDL_BLENDMODE_NONE)) {
|
|
SDL_DestroySurface(curr);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (!SDL_BlitSurface(curr, NULL, canvas, &dst)) {
|
|
SDL_DestroySurface(curr);
|
|
return false;
|
|
}
|
|
SDL_DestroySurface(curr);
|
|
|
|
retval = SDL_DuplicateSurface(canvas);
|
|
if (!retval) {
|
|
return false;
|
|
}
|
|
|
|
*duration = IMG_GetDecoderDuration(decoder, iter->duration, 1000);
|
|
|
|
/* Update state for next frame */
|
|
decoder->ctx->dispose_method = iter->dispose_method;
|
|
decoder->ctx->last_rect = dst;
|
|
|
|
*frame = retval;
|
|
return true;
|
|
}
|
|
|
|
static bool IMG_AnimationDecoderClose_Internal(IMG_AnimationDecoder *decoder)
|
|
{
|
|
if (!decoder->ctx) {
|
|
return false;
|
|
}
|
|
|
|
if (decoder->ctx->canvas) {
|
|
SDL_DestroySurface(decoder->ctx->canvas);
|
|
}
|
|
if (decoder->ctx->demuxer) {
|
|
lib.WebPDemuxDelete(decoder->ctx->demuxer);
|
|
}
|
|
if (decoder->ctx->raw_data) {
|
|
SDL_free(decoder->ctx->raw_data);
|
|
}
|
|
lib.WebPDemuxReleaseIterator(&decoder->ctx->iter);
|
|
SDL_free(decoder->ctx);
|
|
decoder->ctx = NULL;
|
|
return true;
|
|
}
|
|
|
|
bool IMG_CreateWEBPAnimationDecoder(IMG_AnimationDecoder *decoder, SDL_PropertiesID props)
|
|
{
|
|
if (!IMG_InitWEBP()) {
|
|
return SDL_SetError("Failed to initialize WEBP library");
|
|
}
|
|
|
|
if (!webp_getinfo(decoder->src, NULL)) {
|
|
return SDL_SetError("Not a valid WebP file: %s", SDL_GetError());
|
|
}
|
|
|
|
Sint64 stream_size = SDL_GetIOSize(decoder->src);
|
|
if (stream_size <= 0) {
|
|
return SDL_SetError("Stream has no data (size: %" SDL_PRIs64 ")", stream_size);
|
|
}
|
|
|
|
decoder->ctx = (IMG_AnimationDecoderContext *)SDL_calloc(1, sizeof(IMG_AnimationDecoderContext));
|
|
if (!decoder->ctx) {
|
|
return false;
|
|
}
|
|
|
|
decoder->ctx->raw_data_size = (size_t)stream_size;
|
|
decoder->ctx->raw_data = (uint8_t *)SDL_malloc(decoder->ctx->raw_data_size);
|
|
if (!decoder->ctx->raw_data) {
|
|
IMG_AnimationDecoderClose_Internal(decoder);
|
|
return false;
|
|
}
|
|
if (SDL_SeekIO(decoder->src, decoder->start, SDL_IO_SEEK_SET) < 0 ||
|
|
SDL_ReadIO(decoder->src, decoder->ctx->raw_data, decoder->ctx->raw_data_size) != decoder->ctx->raw_data_size) {
|
|
IMG_AnimationDecoderClose_Internal(decoder);
|
|
return false;
|
|
}
|
|
|
|
WebPData wd = { decoder->ctx->raw_data, decoder->ctx->raw_data_size };
|
|
decoder->ctx->demuxer = lib.WebPDemuxInternal(&wd, 0, NULL, WEBP_DEMUX_ABI_VERSION);
|
|
if (!decoder->ctx->demuxer) {
|
|
SDL_SetError("WebPDemux failed to initialize demuxer (not a valid WebP file or corrupted data)");
|
|
IMG_AnimationDecoderClose_Internal(decoder);
|
|
return false;
|
|
}
|
|
|
|
uint32_t width = lib.WebPDemuxGetI(decoder->ctx->demuxer, WEBP_FF_CANVAS_WIDTH);
|
|
uint32_t height = lib.WebPDemuxGetI(decoder->ctx->demuxer, WEBP_FF_CANVAS_HEIGHT);
|
|
uint32_t flags = lib.WebPDemuxGetI(decoder->ctx->demuxer, WEBP_FF_FORMAT_FLAGS);
|
|
|
|
bool ignoreProps = SDL_GetBooleanProperty(props, IMG_PROP_METADATA_IGNORE_PROPS_BOOLEAN, false);
|
|
if (!ignoreProps) {
|
|
// Allow implicit properties to be set which are not globalized but specific to the decoder.
|
|
SDL_SetNumberProperty(decoder->props, IMG_PROP_METADATA_FRAME_COUNT_NUMBER, lib.WebPDemuxGetI(decoder->ctx->demuxer, WEBP_FF_FRAME_COUNT));
|
|
|
|
// Set well-defined properties.
|
|
SDL_SetNumberProperty(decoder->props, IMG_PROP_METADATA_LOOP_COUNT_NUMBER, lib.WebPDemuxGetI(decoder->ctx->demuxer, WEBP_FF_LOOP_COUNT));
|
|
|
|
// Get other well-defined properties and set them in our props.
|
|
WebPChunkIterator xmp_iter;
|
|
if (lib.WebPDemuxGetChunk(decoder->ctx->demuxer, "XMP ", 1, &xmp_iter)) {
|
|
if (xmp_iter.chunk.bytes && xmp_iter.chunk.size > 0) {
|
|
char *desc = __xmlman_GetXMPDescription(xmp_iter.chunk.bytes, xmp_iter.chunk.size);
|
|
char *rights = __xmlman_GetXMPCopyright(xmp_iter.chunk.bytes, xmp_iter.chunk.size);
|
|
char *title = __xmlman_GetXMPTitle(xmp_iter.chunk.bytes, xmp_iter.chunk.size);
|
|
char *creator = __xmlman_GetXMPCreator(xmp_iter.chunk.bytes, xmp_iter.chunk.size);
|
|
char *createdate = __xmlman_GetXMPCreateDate(xmp_iter.chunk.bytes, xmp_iter.chunk.size);
|
|
if (desc) {
|
|
SDL_SetStringProperty(decoder->props, IMG_PROP_METADATA_DESCRIPTION_STRING, desc);
|
|
SDL_free(desc);
|
|
}
|
|
if (rights) {
|
|
SDL_SetStringProperty(decoder->props, IMG_PROP_METADATA_COPYRIGHT_STRING, rights);
|
|
SDL_free(rights);
|
|
}
|
|
if (title) {
|
|
SDL_SetStringProperty(decoder->props, IMG_PROP_METADATA_TITLE_STRING, title);
|
|
SDL_free(title);
|
|
}
|
|
if (creator) {
|
|
SDL_SetStringProperty(decoder->props, IMG_PROP_METADATA_AUTHOR_STRING, creator);
|
|
SDL_free(creator);
|
|
}
|
|
if (createdate) {
|
|
SDL_SetStringProperty(decoder->props, IMG_PROP_METADATA_CREATION_TIME_STRING, createdate);
|
|
SDL_free(createdate);
|
|
}
|
|
}
|
|
lib.WebPDemuxReleaseChunkIterator(&xmp_iter);
|
|
}
|
|
}
|
|
|
|
bool has_alpha = (flags & 0x10) != 0;
|
|
decoder->ctx->has_alpha = has_alpha;
|
|
|
|
decoder->ctx->canvas = SDL_CreateSurface(width, height, has_alpha ? SDL_PIXELFORMAT_RGBA32 : SDL_PIXELFORMAT_RGBX32);
|
|
if (!decoder->ctx->canvas) {
|
|
IMG_AnimationDecoderClose_Internal(decoder);
|
|
return false;
|
|
}
|
|
|
|
uint32_t bgcolor = lib.WebPDemuxGetI(decoder->ctx->demuxer, WEBP_FF_BACKGROUND_COLOR);
|
|
#if SDL_BYTEORDER == SDL_BIG_ENDIAN
|
|
decoder->ctx->bgcolor = SDL_MapSurfaceRGBA(decoder->ctx->canvas,
|
|
(bgcolor >> 8) & 0xFF,
|
|
(bgcolor >> 16) & 0xFF,
|
|
(bgcolor >> 24) & 0xFF,
|
|
(bgcolor >> 0) & 0xFF);
|
|
#else
|
|
decoder->ctx->bgcolor = SDL_MapSurfaceRGBA(decoder->ctx->canvas,
|
|
(bgcolor >> 16) & 0xFF,
|
|
(bgcolor >> 8) & 0xFF,
|
|
(bgcolor >> 0) & 0xFF,
|
|
(bgcolor >> 24) & 0xFF);
|
|
#endif
|
|
|
|
if (has_alpha) {
|
|
SDL_FillSurfaceRect(decoder->ctx->canvas, NULL, SDL_MapSurfaceRGBA(decoder->ctx->canvas, 0, 0, 0, 0));
|
|
} else {
|
|
SDL_FillSurfaceRect(decoder->ctx->canvas, NULL, decoder->ctx->bgcolor);
|
|
}
|
|
|
|
decoder->ctx->dispose_method = WEBP_MUX_DISPOSE_BACKGROUND;
|
|
decoder->ctx->last_rect.x = 0;
|
|
decoder->ctx->last_rect.y = 0;
|
|
decoder->ctx->last_rect.w = width;
|
|
decoder->ctx->last_rect.h = height;
|
|
|
|
SDL_zero(decoder->ctx->iter);
|
|
|
|
decoder->GetNextFrame = IMG_AnimationDecoderGetNextFrame_Internal;
|
|
decoder->Reset = IMG_AnimationDecoderReset_Internal;
|
|
decoder->Close = IMG_AnimationDecoderClose_Internal;
|
|
|
|
return true;
|
|
}
|
|
|
|
#endif // LOAD_WEBP
|
|
|
|
#if SAVE_WEBP
|
|
|
|
static const char *GetWebPEncodingErrorStringInternal(WebPEncodingError error_code)
|
|
{
|
|
switch (error_code) {
|
|
case VP8_ENC_OK:
|
|
return "OK";
|
|
case VP8_ENC_ERROR_OUT_OF_MEMORY:
|
|
return "Out of memory";
|
|
case VP8_ENC_ERROR_BITSTREAM_OUT_OF_MEMORY:
|
|
return "Bitstream out of memory";
|
|
case VP8_ENC_ERROR_NULL_PARAMETER:
|
|
return "Null parameter";
|
|
case VP8_ENC_ERROR_INVALID_CONFIGURATION:
|
|
return "Invalid configuration";
|
|
case VP8_ENC_ERROR_BAD_DIMENSION:
|
|
return "Bad dimension";
|
|
case VP8_ENC_ERROR_PARTITION0_OVERFLOW:
|
|
return "Partition 0 overflow";
|
|
case VP8_ENC_ERROR_PARTITION_OVERFLOW:
|
|
return "Partition overflow";
|
|
case VP8_ENC_ERROR_BAD_WRITE:
|
|
return "Bad write";
|
|
case VP8_ENC_ERROR_FILE_TOO_BIG:
|
|
return "File too big";
|
|
case VP8_ENC_ERROR_USER_ABORT:
|
|
return "User abort";
|
|
default:
|
|
return "Unknown WebP encoding error";
|
|
}
|
|
}
|
|
|
|
bool IMG_SaveWEBP_IO(SDL_Surface *surface, SDL_IOStream *dst, bool closeio, float quality)
|
|
{
|
|
WebPConfig config;
|
|
WebPPicture pic;
|
|
WebPMemoryWriter writer;
|
|
SDL_Surface *converted_surface = NULL;
|
|
bool result = false;
|
|
bool pic_initialized = false;
|
|
bool memorywriter_initialized = false;
|
|
bool converted_surface_locked = false;
|
|
Sint64 start = -1;
|
|
|
|
if (!IMG_VerifyCanSaveSurface(surface)) {
|
|
goto done;
|
|
}
|
|
if (!dst) {
|
|
SDL_InvalidParamError("dst");
|
|
goto done;
|
|
}
|
|
|
|
start = SDL_TellIO(dst);
|
|
|
|
if (!IMG_InitWEBP()) {
|
|
goto done;
|
|
}
|
|
|
|
if (!lib.WebPConfigInitInternal(&config, WEBP_PRESET_DEFAULT, quality, WEBP_ENCODER_ABI_VERSION)) {
|
|
SDL_SetError("Failed to initialize WebPConfig");
|
|
goto done;
|
|
}
|
|
|
|
quality = SDL_clamp(quality, 0.0f, 100.0f);
|
|
|
|
config.lossless = quality == 100.0f;
|
|
config.quality = quality;
|
|
|
|
// TODO: Take a look if the method 4 fits here for us.
|
|
config.method = 4;
|
|
|
|
if (!lib.WebPValidateConfig(&config)) {
|
|
SDL_SetError("Invalid WebP configuration");
|
|
goto done;
|
|
}
|
|
|
|
if (!lib.WebPPictureInitInternal(&pic, WEBP_ENCODER_ABI_VERSION)) {
|
|
SDL_SetError("Failed to initialize WebPPicture");
|
|
goto done;
|
|
}
|
|
pic_initialized = true;
|
|
|
|
pic.width = surface->w;
|
|
pic.height = surface->h;
|
|
|
|
if (surface->format != SDL_PIXELFORMAT_RGBA32) {
|
|
converted_surface = SDL_ConvertSurface(surface, SDL_PIXELFORMAT_RGBA32);
|
|
if (!converted_surface) {
|
|
goto done;
|
|
}
|
|
} else {
|
|
converted_surface = surface;
|
|
}
|
|
|
|
if (SDL_MUSTLOCK(converted_surface)) {
|
|
if (!SDL_LockSurface(converted_surface)) {
|
|
goto done;
|
|
}
|
|
converted_surface_locked = true;
|
|
}
|
|
|
|
if (!lib.WebPPictureImportRGBA(&pic, (const uint8_t *)converted_surface->pixels, converted_surface->pitch)) {
|
|
SDL_SetError("Failed to import RGBA pixels into WebPPicture");
|
|
goto done;
|
|
}
|
|
|
|
if (converted_surface_locked) {
|
|
SDL_UnlockSurface(converted_surface);
|
|
converted_surface_locked = false;
|
|
}
|
|
|
|
lib.WebPMemoryWriterInit(&writer);
|
|
memorywriter_initialized = true;
|
|
pic.writer = (WebPWriterFunction)lib.WebPMemoryWrite;
|
|
pic.custom_ptr = &writer;
|
|
|
|
if (!lib.WebPEncode(&config, &pic)) {
|
|
SDL_SetError("Failed to encode WebP: %s", GetWebPEncodingErrorStringInternal(pic.error_code));
|
|
goto done;
|
|
}
|
|
|
|
if (writer.size > 0) {
|
|
if (SDL_WriteIO(dst, writer.mem, writer.size) != writer.size) {
|
|
goto done;
|
|
}
|
|
} else {
|
|
SDL_SetError("No WebP data generated.");
|
|
goto done;
|
|
}
|
|
|
|
result = true;
|
|
|
|
done:
|
|
if (converted_surface_locked) {
|
|
SDL_UnlockSurface(converted_surface);
|
|
}
|
|
|
|
if (converted_surface && converted_surface != surface) {
|
|
SDL_DestroySurface(converted_surface);
|
|
}
|
|
|
|
if (pic_initialized) {
|
|
lib.WebPPictureFree(&pic);
|
|
}
|
|
|
|
if (memorywriter_initialized) {
|
|
lib.WebPMemoryWriterClear(&writer);
|
|
}
|
|
|
|
if (!result && !closeio && start != -1) {
|
|
SDL_SeekIO(dst, start, SDL_IO_SEEK_SET);
|
|
}
|
|
|
|
if (closeio) {
|
|
result &= SDL_CloseIO(dst);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
bool IMG_SaveWEBP(SDL_Surface *surface, const char *file, float quality)
|
|
{
|
|
if (!IMG_VerifyCanSaveSurface(surface)) {
|
|
return false;
|
|
}
|
|
SDL_IOStream *dst = SDL_IOFromFile(file, "wb");
|
|
if (dst) {
|
|
return IMG_SaveWEBP_IO(surface, dst, true, quality);
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
struct IMG_AnimationEncoderContext
|
|
{
|
|
WebPAnimEncoder *encoder;
|
|
WebPConfig config;
|
|
int timestamp;
|
|
SDL_PropertiesID metadata;
|
|
};
|
|
|
|
static bool IMG_AddWEBPAnimationFrame(IMG_AnimationEncoder *encoder, SDL_Surface *surface, Uint64 duration)
|
|
{
|
|
IMG_AnimationEncoderContext *ctx = encoder->ctx;
|
|
const char *error = NULL;
|
|
WebPPicture pic;
|
|
bool pic_initialized = false;
|
|
SDL_Surface *converted = NULL;
|
|
|
|
if (!ctx->encoder) {
|
|
ctx->encoder = lib.WebPAnimEncoderNewInternal(surface->w, surface->h, NULL, WEBP_MUX_ABI_VERSION);
|
|
if (!ctx->encoder) {
|
|
return SDL_SetError("WebPAnimEncoderNew() failed");
|
|
}
|
|
}
|
|
|
|
if (!lib.WebPPictureInitInternal(&pic, WEBP_ENCODER_ABI_VERSION)) {
|
|
error = "WebPPictureInit() failed";
|
|
goto done;
|
|
}
|
|
pic.use_argb = ctx->config.lossless;
|
|
pic.width = surface->w;
|
|
pic.height = surface->h;
|
|
pic_initialized = true;
|
|
|
|
if (surface->format != SDL_PIXELFORMAT_RGBA32) {
|
|
converted = SDL_ConvertSurface(surface, SDL_PIXELFORMAT_RGBA32);
|
|
if (!converted) {
|
|
error = SDL_GetError();
|
|
goto done;
|
|
}
|
|
surface = converted;
|
|
}
|
|
|
|
if (!lib.WebPPictureImportRGBA(&pic, (const uint8_t *)surface->pixels, surface->pitch)) {
|
|
error = "WebPPictureImportRGBA() failed";
|
|
goto done;
|
|
}
|
|
|
|
if (!lib.WebPAnimEncoderAdd(ctx->encoder, &pic, ctx->timestamp, &ctx->config)) {
|
|
error = GetWebPEncodingErrorStringInternal(pic.error_code);
|
|
goto done;
|
|
}
|
|
ctx->timestamp += (int)IMG_GetEncoderDuration(encoder, duration, 1000);
|
|
|
|
done:
|
|
if (pic_initialized) {
|
|
lib.WebPPictureFree(&pic);
|
|
}
|
|
if (converted) {
|
|
SDL_DestroySurface(converted);
|
|
}
|
|
if (error) {
|
|
SDL_SetError("%s", error);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static bool IMG_CloseWEBPAnimation(IMG_AnimationEncoder *encoder)
|
|
{
|
|
IMG_AnimationEncoderContext *ctx = encoder->ctx;
|
|
const char *error = NULL;
|
|
WebPData data = { NULL, 0 };
|
|
WebPMux *mux = NULL;
|
|
|
|
if (!ctx->encoder) {
|
|
error = "No frames added to animation";
|
|
goto done;
|
|
}
|
|
|
|
if (!lib.WebPAnimEncoderAdd(ctx->encoder, NULL, ctx->timestamp, &ctx->config)) {
|
|
error = "WebPAnimEncoderAdd() failed";
|
|
goto done;
|
|
}
|
|
|
|
if (!IMG_HasMetadata(ctx->metadata)) {
|
|
if (!lib.WebPAnimEncoderAssemble(ctx->encoder, &data)) {
|
|
error = "WebPAnimEncoderAssemble() failed";
|
|
goto done;
|
|
}
|
|
} else {
|
|
WebPMuxError muxErr;
|
|
WebPData pdata = { NULL, 0 };
|
|
if (!lib.WebPAnimEncoderAssemble(ctx->encoder, &pdata)) {
|
|
error = "WebPAnimEncoderAssemble() failed";
|
|
goto done;
|
|
}
|
|
|
|
if (!pdata.bytes || pdata.size < 1) {
|
|
error = "WebPAnimEncoderAssemble() returned invalid data";
|
|
goto done;
|
|
}
|
|
|
|
mux = lib.WebPMuxCreateInternal(&pdata, 1, WEBP_MUX_ABI_VERSION);
|
|
if (!mux) {
|
|
error = "WebPMuxCreateInternal() failed. This usually happens if you tried to encode a single frame only.";
|
|
goto done;
|
|
}
|
|
|
|
WebPMuxAnimParams params;
|
|
muxErr = lib.WebPMuxGetAnimationParams(mux, ¶ms);
|
|
if (muxErr != WEBP_MUX_OK) {
|
|
error = "WebPMuxGetAnimationParams() failed";
|
|
goto done;
|
|
}
|
|
params.loop_count = (int)SDL_max(SDL_GetNumberProperty(ctx->metadata, IMG_PROP_METADATA_LOOP_COUNT_NUMBER, 0), 0);
|
|
muxErr = lib.WebPMuxSetAnimationParams(mux, ¶ms);
|
|
if (muxErr != WEBP_MUX_OK) {
|
|
error = "WebPMuxSetAnimationParams() failed";
|
|
goto done;
|
|
}
|
|
|
|
size_t siz = 0;
|
|
uint8_t *d = __xmlman_ConstructXMPWithRDFDescription(SDL_GetStringProperty(ctx->metadata, IMG_PROP_METADATA_TITLE_STRING, NULL), SDL_GetStringProperty(ctx->metadata, IMG_PROP_METADATA_AUTHOR_STRING, NULL), SDL_GetStringProperty(ctx->metadata, IMG_PROP_METADATA_DESCRIPTION_STRING, NULL), SDL_GetStringProperty(ctx->metadata, IMG_PROP_METADATA_COPYRIGHT_STRING, NULL), SDL_GetStringProperty(ctx->metadata, IMG_PROP_METADATA_CREATION_TIME_STRING, NULL), &siz);
|
|
if (siz < 1 || !d) {
|
|
error = "Failed to construct XMP data";
|
|
goto done;
|
|
}
|
|
WebPData xmp_data = { d, siz };
|
|
muxErr = lib.WebPMuxSetChunk(mux, "XMP ", &xmp_data, 1);
|
|
if (muxErr != WEBP_MUX_OK) {
|
|
SDL_free(d);
|
|
error = "WebPMuxSetChunk() failed for XMP data";
|
|
goto done;
|
|
}
|
|
|
|
SDL_free(d);
|
|
|
|
muxErr = lib.WebPMuxAssemble(mux, &data);
|
|
if (muxErr != WEBP_MUX_OK) {
|
|
error = "WebPMuxAssemble() failed";
|
|
goto done;
|
|
}
|
|
}
|
|
|
|
if (SDL_WriteIO(encoder->dst, data.bytes, data.size) != data.size) {
|
|
error = SDL_GetError();
|
|
goto done;
|
|
}
|
|
|
|
done:
|
|
if (ctx->metadata) {
|
|
SDL_DestroyProperties(ctx->metadata);
|
|
ctx->metadata = 0;
|
|
}
|
|
|
|
if (data.bytes) {
|
|
lib.WebPFree((void *)data.bytes);
|
|
}
|
|
if (mux) {
|
|
lib.WebPMuxDelete(mux);
|
|
}
|
|
if (ctx->encoder) {
|
|
lib.WebPAnimEncoderDelete(ctx->encoder);
|
|
}
|
|
SDL_free(ctx);
|
|
|
|
if (error) {
|
|
SDL_SetError("%s", error);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool IMG_CreateWEBPAnimationEncoder(IMG_AnimationEncoder *encoder, SDL_PropertiesID props)
|
|
{
|
|
if (!IMG_InitWEBP()) {
|
|
return false;
|
|
}
|
|
|
|
IMG_AnimationEncoderContext *ctx = (IMG_AnimationEncoderContext *)SDL_calloc(1, sizeof(*encoder->ctx));
|
|
if (!ctx) {
|
|
return false;
|
|
}
|
|
encoder->ctx = ctx;
|
|
encoder->AddFrame = IMG_AddWEBPAnimationFrame;
|
|
encoder->Close = IMG_CloseWEBPAnimation;
|
|
|
|
float quality;
|
|
if (encoder->quality < 0) {
|
|
quality = 75.0f;
|
|
} else {
|
|
quality = (float)SDL_clamp(encoder->quality, 0, 100);
|
|
}
|
|
if (!lib.WebPConfigInitInternal(&ctx->config, WEBP_PRESET_DEFAULT, quality, WEBP_ENCODER_ABI_VERSION)) {
|
|
SDL_free(ctx);
|
|
return SDL_SetError("WebPConfigInit() failed");
|
|
}
|
|
ctx->config.lossless = (quality == 100.0f);
|
|
ctx->config.quality = quality;
|
|
ctx->config.method = 4;
|
|
|
|
if (!lib.WebPValidateConfig(&ctx->config)) {
|
|
SDL_free(ctx);
|
|
return SDL_SetError("WebPValidateConfig() failed");
|
|
}
|
|
|
|
bool ignoreProps = SDL_GetBooleanProperty(props, IMG_PROP_METADATA_IGNORE_PROPS_BOOLEAN, false);
|
|
if (!ignoreProps) {
|
|
ctx->metadata = SDL_CreateProperties();
|
|
if (!ctx->metadata) {
|
|
SDL_free(ctx);
|
|
return SDL_SetError("Failed to create properties for WebP metadata");
|
|
}
|
|
|
|
if (!SDL_CopyProperties(props, ctx->metadata)) {
|
|
SDL_DestroyProperties(ctx->metadata);
|
|
ctx->metadata = 0;
|
|
SDL_free(ctx);
|
|
return SDL_SetError("Failed to copy properties for WebP metadata");
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
#endif // SAVE_WEBP
|
|
|
|
#if !LOAD_WEBP
|
|
|
|
/* See if an image is contained in a data source */
|
|
bool IMG_isWEBP(SDL_IOStream *src)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
/* Load a WEBP type image from an SDL datasource */
|
|
SDL_Surface *IMG_LoadWEBP_IO(SDL_IOStream *src)
|
|
{
|
|
SDL_SetError("SDL_image built without WEBP support");
|
|
return NULL;
|
|
}
|
|
|
|
bool IMG_CreateWEBPAnimationDecoder(IMG_AnimationDecoder *decoder, SDL_PropertiesID props)
|
|
{
|
|
return SDL_SetError("SDL_image built without WEBP support");
|
|
}
|
|
|
|
#endif // !LOAD_WEBP
|
|
|
|
#if !SAVE_WEBP
|
|
|
|
bool IMG_SaveWEBP_IO(SDL_Surface *surface, SDL_IOStream *dst, bool closeio, float quality)
|
|
{
|
|
return SDL_SetError("SDL_image built without WEBP save support");
|
|
}
|
|
|
|
bool IMG_SaveWEBP(SDL_Surface *surface, const char *file, float quality)
|
|
{
|
|
return SDL_SetError("SDL_image built without WEBP save support");
|
|
}
|
|
|
|
bool IMG_CreateWEBPAnimationEncoder(IMG_AnimationEncoder *encoder, SDL_PropertiesID props)
|
|
{
|
|
return SDL_SetError("SDL_image built without WEBP save support");
|
|
}
|
|
|
|
#endif // !SAVE_WEBP
|