#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"
#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
#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);
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 *);
void (*WebPFree)(void *ptr);
WebPAnimEncoder *(*WebPAnimEncoderNewInternal)(int, int, const WebPAnimEncoderOptions *, int); int (*WebPAnimEncoderOptionsInitInternal)(WebPAnimEncoderOptions *, int); int (*WebPAnimEncoderAdd)(WebPAnimEncoder *, WebPPicture *, int, const WebPConfig *); int (*WebPAnimEncoderAssemble)(WebPAnimEncoder *, WebPData *); void (*WebPAnimEncoderDelete)(WebPAnimEncoder *);
int (*WebPDemuxGetChunk)(const WebPDemuxer* dmux, const char fourcc[4], int chunk_number, WebPChunkIterator* iter);
void (*WebPDemuxReleaseChunkIterator)(WebPChunkIterator* iter);
WebPMux *(*WebPMuxCreateInternal)(const WebPData*, int, int); 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__
__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))
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 *))
FUNCTION_LOADER_LIBWEBP(WebPFree, void (*)(void *))
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 *))
FUNCTION_LOADER_LIBWEBPDEMUX(WebPDemuxGetChunk, int (*)(const WebPDemuxer *dmux, const char fourcc[4], int chunk_number, WebPChunkIterator *iter))
FUNCTION_LOADER_LIBWEBPDEMUX(WebPDemuxReleaseChunkIterator, void (*)(WebPChunkIterator* iter))
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#endif
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;
}
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) {
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#endif
if (lib.WebPGetFeaturesInternal(raw_data, raw_data_size, &features, WEBP_DECODER_ABI_VERSION) != VP8_STATUS_OK) {
error = "WebPGetFeatures has failed";
goto error;
}
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;
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)
{
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 };
if (dispose_method == WEBP_MUX_DISPOSE_BACKGROUND || iter->blend_method == WEBP_MUX_NO_BLEND) {
uint32_t fill_color = has_alpha ? SDL_MapSurfaceRGBA(canvas, 0, 0, 0, 0) : bgcolor;
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);
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) {
SDL_SetNumberProperty(decoder->props, IMG_PROP_METADATA_FRAME_COUNT_NUMBER, lib.WebPDemuxGetI(decoder->ctx->demuxer, WEBP_FF_FRAME_COUNT));
SDL_SetNumberProperty(decoder->props, IMG_PROP_METADATA_LOOP_COUNT_NUMBER, lib.WebPDemuxGetI(decoder->ctx->demuxer, WEBP_FF_LOOP_COUNT));
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) {
lib.WebPDemuxDelete(decoder->ctx->demuxer);
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
#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;
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
#if !LOAD_WEBP
bool IMG_isWEBP(SDL_IOStream *src)
{
return false;
}
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
#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