Add animated WebP import

Add animated WebP file loading
Add animated WebP image frames load test assertions
Add libwebpdemux for local libwebp to linuxbsd/detect.py
This commit is contained in:
Spartan322
2025-01-20 23:59:33 -05:00
parent 850d95d6c3
commit 3f2a63752f
12 changed files with 273 additions and 8 deletions

View File

@@ -0,0 +1,66 @@
/**************************************************************************/
/* image_frames_loader_webp.cpp */
/**************************************************************************/
/* This file is part of: */
/* REDOT ENGINE */
/* https://redotengine.org */
/**************************************************************************/
/* Copyright (c) 2024-present Redot Engine contributors */
/* (see REDOT_AUTHORS.md) */
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#include "image_frames_loader_webp.h"
#include "webp_common.h"
static Ref<ImageFrames> _webp_mem_loader_func(const uint8_t *p_webp_data, int p_size, int p_max_frames) {
Ref<ImageFrames> frames;
frames.instantiate();
Error err = WebPCommon::webp_load_image_frames_from_buffer(frames.ptr(), p_webp_data, p_size, p_max_frames);
ERR_FAIL_COND_V(err, Ref<Image>());
return frames;
}
Error ImageFramesLoaderWebP::load_image_frames(Ref<ImageFrames> p_image, Ref<FileAccess> f, BitField<ImageFramesFormatLoader::LoaderFlags> p_flags, float p_scale, int p_max_frames) {
Vector<uint8_t> src_image;
uint64_t src_image_len = f->get_length();
ERR_FAIL_COND_V(src_image_len == 0, ERR_FILE_CORRUPT);
src_image.resize(src_image_len);
uint8_t *w = src_image.ptrw();
f->get_buffer(&w[0], src_image_len);
Error err = WebPCommon::webp_load_image_frames_from_buffer(p_image.ptr(), w, src_image_len, p_max_frames);
return err;
}
void ImageFramesLoaderWebP::get_recognized_extensions(List<String> *p_extensions) const {
p_extensions->push_back("webp");
}
ImageFramesLoaderWebP::ImageFramesLoaderWebP() {
ImageFrames::_webp_mem_loader_func = _webp_mem_loader_func;
}

View File

@@ -0,0 +1,42 @@
/**************************************************************************/
/* image_frames_loader_webp.h */
/**************************************************************************/
/* This file is part of: */
/* REDOT ENGINE */
/* https://redotengine.org */
/**************************************************************************/
/* Copyright (c) 2024-present Redot Engine contributors */
/* (see REDOT_AUTHORS.md) */
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#include "core/io/image_frames_loader.h"
class ImageFramesLoaderWebP : public ImageFramesFormatLoader {
public:
virtual Error load_image_frames(Ref<ImageFrames> p_image, Ref<FileAccess> f, BitField<ImageFramesFormatLoader::LoaderFlags> p_flags, float p_scale, int p_max_frames);
virtual void get_recognized_extensions(List<String> *p_extensions) const;
ImageFramesLoaderWebP();
};

View File

@@ -32,10 +32,12 @@
#include "register_types.h"
#include "image_frames_loader_webp.h"
#include "image_loader_webp.h"
#include "resource_saver_webp.h"
static Ref<ImageLoaderWebP> image_loader_webp;
static Ref<ImageFramesLoaderWebP> image_frames_loader_webp;
static Ref<ResourceSaverWebP> resource_saver_webp;
void initialize_webp_module(ModuleInitializationLevel p_level) {
@@ -46,6 +48,9 @@ void initialize_webp_module(ModuleInitializationLevel p_level) {
image_loader_webp.instantiate();
ImageLoader::add_image_format_loader(image_loader_webp);
image_frames_loader_webp.instantiate();
ImageFramesLoader::add_image_frames_format_loader(image_frames_loader_webp);
resource_saver_webp.instantiate();
ResourceSaver::add_resource_format_saver(resource_saver_webp);
}
@@ -58,6 +63,9 @@ void uninitialize_webp_module(ModuleInitializationLevel p_level) {
ImageLoader::remove_image_format_loader(image_loader_webp);
image_loader_webp.unref();
ImageFramesLoader::remove_image_frames_format_loader(image_frames_loader_webp);
image_frames_loader_webp.unref();
ResourceSaver::remove_resource_format_saver(resource_saver_webp);
resource_saver_webp.unref();
}

View File

@@ -35,7 +35,9 @@
#include "core/config/project_settings.h"
#include <webp/decode.h>
#include <webp/demux.h>
#include <webp/encode.h>
#include <webp/mux_types.h>
namespace WebPCommon {
Vector<uint8_t> _webp_lossy_pack(const Ref<Image> &p_image, float p_quality) {
@@ -165,17 +167,131 @@ Error webp_load_image_from_buffer(Image *p_image, const uint8_t *p_buffer, int p
dst_image.resize(datasize);
uint8_t *dst_w = dst_image.ptrw();
bool errdec = false;
if (features.has_alpha) {
errdec = WebPDecodeRGBAInto(p_buffer, p_buffer_len, dst_w, datasize, 4 * features.width) == nullptr;
} else {
errdec = WebPDecodeRGBInto(p_buffer, p_buffer_len, dst_w, datasize, 3 * features.width) == nullptr;
}
if (!features.has_animation) {
bool errdec = false;
if (features.has_alpha) {
errdec = WebPDecodeRGBAInto(p_buffer, p_buffer_len, dst_w, datasize, 4 * features.width) == nullptr;
} else {
errdec = WebPDecodeRGBInto(p_buffer, p_buffer_len, dst_w, datasize, 3 * features.width) == nullptr;
}
ERR_FAIL_COND_V_MSG(errdec, ERR_FILE_CORRUPT, "Failed decoding WebP image.");
ERR_FAIL_COND_V_MSG(errdec, ERR_FILE_CORRUPT, "Failed decoding WebP image.");
} else {
WebPData webp_data;
WebPDataInit(&webp_data);
webp_data.bytes = p_buffer;
webp_data.size = p_buffer_len;
WebPAnimDecoder *anim_decoder = WebPAnimDecoderNew(&webp_data, nullptr);
if (anim_decoder == nullptr) {
WebPAnimDecoderDelete(anim_decoder);
ERR_FAIL_V_MSG(ERR_FILE_CORRUPT, "Failed decoding animated WebP image.");
}
WebPAnimInfo anim_info;
if (!WebPAnimDecoderGetInfo(anim_decoder, &anim_info)) {
WebPAnimDecoderDelete(anim_decoder);
ERR_FAIL_V_MSG(ERR_FILE_CORRUPT, "Failed decoding WebP animation info.");
}
uint8_t *frame_rgba;
int timestamp;
if (!WebPAnimDecoderGetNext(anim_decoder, &frame_rgba, &timestamp)) {
WebPAnimDecoderDelete(anim_decoder);
ERR_FAIL_V_MSG(ERR_FILE_CORRUPT, "Failed decoding animated WebP initial frame.");
}
memcpy(dst_image.ptrw(), frame_rgba, dst_image.size());
WebPAnimDecoderDelete(anim_decoder);
}
p_image->set_data(features.width, features.height, false, features.has_alpha ? Image::FORMAT_RGBA8 : Image::FORMAT_RGB8, dst_image);
return OK;
}
Error webp_load_image_frames_from_buffer(ImageFrames *p_frames, const uint8_t *p_buffer, int p_buffer_len, int p_max_frames) {
ERR_FAIL_NULL_V(p_frames, ERR_INVALID_PARAMETER);
WebPBitstreamFeatures features;
if (WebPGetFeatures(p_buffer, p_buffer_len, &features) != VP8_STATUS_OK) {
ERR_FAIL_V(ERR_FILE_CORRUPT);
}
if (!features.has_animation) {
p_frames->set_frame_count(1);
Ref<Image> image;
image.instantiate();
if (webp_load_image_from_buffer(image.ptr(), p_buffer, p_buffer_len) != OK) {
return ERR_FILE_CORRUPT;
}
p_frames->set_frame_image(0, image);
return OK;
}
WebPData webp_data;
WebPDataInit(&webp_data);
webp_data.bytes = p_buffer;
webp_data.size = p_buffer_len;
#ifdef THREADS_ENABLED
const bool supports_threads = true;
#else
const bool supports_threads = false;
#endif
WebPAnimDecoderOptions anim_decoder_options;
WebPAnimDecoderOptionsInit(&anim_decoder_options);
anim_decoder_options.color_mode = MODE_RGBA;
anim_decoder_options.use_threads = supports_threads;
WebPAnimDecoder *anim_decoder = WebPAnimDecoderNew(&webp_data, &anim_decoder_options);
if (anim_decoder == nullptr) {
WebPAnimDecoderDelete(anim_decoder);
ERR_FAIL_V_MSG(ERR_FILE_CORRUPT, "Failed decoding animated WebP image.");
}
WebPAnimInfo anim_info;
if (!WebPAnimDecoderGetInfo(anim_decoder, &anim_info)) {
WebPAnimDecoderDelete(anim_decoder);
ERR_FAIL_V_MSG(ERR_FILE_CORRUPT, "Failed decoding WebP animation info.");
}
static const uint32_t NUM_CHANNELS = 4;
const uint64_t rgba_size = anim_info.canvas_width * NUM_CHANNELS * anim_info.canvas_height;
Vector<uint8_t> screen;
screen.resize_zeroed(rgba_size);
const uint32_t frame_count = p_max_frames > 0 ? MIN(anim_info.frame_count, (uint32_t)p_max_frames) : anim_info.frame_count;
p_frames->set_frame_count(frame_count);
p_frames->set_loop_count(anim_info.loop_count);
int previous_timestamp = 0;
for (uint32_t frame_index = 0; p_max_frames > 0 ? frame_count : WebPAnimDecoderHasMoreFrames(anim_decoder); frame_index++) {
if (frame_index >= frame_count) {
WebPAnimDecoderDelete(anim_decoder);
ERR_FAIL_COND_V(frame_index >= frame_count, ERR_FILE_CORRUPT);
}
uint8_t *frame_rgba;
int timestamp;
if (!WebPAnimDecoderGetNext(anim_decoder, &frame_rgba, &timestamp)) {
WebPAnimDecoderDelete(anim_decoder);
ERR_FAIL_V_MSG(ERR_FILE_CORRUPT, vformat("Failed decoding WebP frame %d.", frame_index));
}
memcpy(screen.ptrw(), frame_rgba, screen.size());
Ref<Image> image = memnew(Image(anim_info.canvas_width, anim_info.canvas_height, false, Image::FORMAT_RGBA8, screen));
p_frames->set_frame_image(frame_index, image);
p_frames->set_frame_delay(frame_index, (timestamp - previous_timestamp) / 1000.0);
previous_timestamp = timestamp;
}
WebPAnimDecoderDelete(anim_decoder);
return OK;
}
} // namespace WebPCommon

View File

@@ -33,6 +33,7 @@
#pragma once
#include "core/io/image.h"
#include "core/io/image_frames.h"
namespace WebPCommon {
// Given an image, pack this data into a WebP file.
@@ -43,4 +44,6 @@ Vector<uint8_t> _webp_packer(const Ref<Image> &p_image, float p_quality, bool p_
// Given a WebP file, unpack it into an image.
Ref<Image> _webp_unpack(const Vector<uint8_t> &p_buffer);
Error webp_load_image_from_buffer(Image *p_image, const uint8_t *p_buffer, int p_buffer_len);
// Given a WebP file, unpack it into image frames.
Error webp_load_image_frames_from_buffer(ImageFrames *p_frames, const uint8_t *p_buffer, int p_buffer_len, int p_max_frames);
} //namespace WebPCommon