mirror of
https://github.com/Redot-Engine/redot-engine.git
synced 2025-12-06 07:17:42 -05:00
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:
66
modules/webp/image_frames_loader_webp.cpp
Normal file
66
modules/webp/image_frames_loader_webp.cpp
Normal 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;
|
||||
}
|
||||
42
modules/webp/image_frames_loader_webp.h
Normal file
42
modules/webp/image_frames_loader_webp.h
Normal 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();
|
||||
};
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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, ×tamp)) {
|
||||
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, ×tamp)) {
|
||||
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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user