From 89ca9c5e6770dc1801635166ad98e4adb48d689d Mon Sep 17 00:00:00 2001 From: Spartan322 Date: Tue, 29 Oct 2024 14:41:40 -0400 Subject: [PATCH] Add GIF import Add GIF file loading Add `ResourceFormatLoader` for `AnimatedTexture` Add `ImageFrames` resource for handling image sequence Add `ImageFramesLoader` for resource loading image sequences Add `ResourceFormatLoader` for `ImageFrames` --- COPYRIGHT.txt | 5 + core/io/image.cpp | 10 + core/io/image.h | 2 + core/io/image_frames.cpp | 137 ++ core/io/image_frames.h | 82 ++ core/io/image_frames_loader.cpp | 212 +++ core/io/image_frames_loader.h | 114 ++ core/register_core_types.cpp | 12 + doc/classes/AnimatedTexture.xml | 20 + doc/classes/Image.xml | 8 + doc/classes/ImageFrames.xml | 91 ++ doc/classes/ImageFramesFormatLoader.xml | 17 + .../ImageFramesFormatLoaderExtension.xml | 42 + .../ResourceImporterAnimatedTexture.xml | 33 + doc/classes/ResourceImporterImageFrames.xml | 11 + editor/editor_node.cpp | 10 + .../resource_importer_animated_texture.cpp | 164 +++ .../resource_importer_animated_texture.h | 59 + .../import/resource_importer_image_frames.cpp | 99 ++ .../import/resource_importer_image_frames.h | 60 + editor/register_editor_types.cpp | 4 + main/main.cpp | 2 + modules/gif/SCsub | 28 + modules/gif/config.py | 6 + modules/gif/gif_common.cpp | 244 +++ modules/gif/gif_common.h | 51 + modules/gif/image_frames_loader_gif.cpp | 66 + modules/gif/image_frames_loader_gif.h | 45 + modules/gif/image_loader_gif.cpp | 66 + modules/gif/image_loader_gif.h | 45 + modules/gif/register_types.cpp | 63 + modules/gif/register_types.h | 41 + scene/register_scene_types.cpp | 9 + scene/resources/animated_texture.cpp | 46 + scene/resources/animated_texture.h | 6 + .../resource_format_animated_texture.cpp | 98 ++ .../resource_format_animated_texture.h | 46 + thirdparty/README.md | 19 + thirdparty/giflib/COPYING | 19 + thirdparty/giflib/dgif_lib.c | 1312 +++++++++++++++++ thirdparty/giflib/egif_lib.c | 1163 +++++++++++++++ thirdparty/giflib/gif_err.c | 97 ++ thirdparty/giflib/gif_hash.c | 128 ++ thirdparty/giflib/gif_hash.h | 43 + thirdparty/giflib/gif_lib.h | 292 ++++ thirdparty/giflib/gif_lib_private.h | 72 + thirdparty/giflib/gifalloc.c | 425 ++++++ thirdparty/giflib/openbsd-reallocarray.c | 73 + 48 files changed, 5697 insertions(+) create mode 100644 core/io/image_frames.cpp create mode 100644 core/io/image_frames.h create mode 100644 core/io/image_frames_loader.cpp create mode 100644 core/io/image_frames_loader.h create mode 100644 doc/classes/ImageFrames.xml create mode 100644 doc/classes/ImageFramesFormatLoader.xml create mode 100644 doc/classes/ImageFramesFormatLoaderExtension.xml create mode 100644 doc/classes/ResourceImporterAnimatedTexture.xml create mode 100644 doc/classes/ResourceImporterImageFrames.xml create mode 100644 editor/import/resource_importer_animated_texture.cpp create mode 100644 editor/import/resource_importer_animated_texture.h create mode 100644 editor/import/resource_importer_image_frames.cpp create mode 100644 editor/import/resource_importer_image_frames.h create mode 100644 modules/gif/SCsub create mode 100644 modules/gif/config.py create mode 100644 modules/gif/gif_common.cpp create mode 100644 modules/gif/gif_common.h create mode 100644 modules/gif/image_frames_loader_gif.cpp create mode 100644 modules/gif/image_frames_loader_gif.h create mode 100644 modules/gif/image_loader_gif.cpp create mode 100644 modules/gif/image_loader_gif.h create mode 100644 modules/gif/register_types.cpp create mode 100644 modules/gif/register_types.h create mode 100644 scene/resources/resource_format_animated_texture.cpp create mode 100644 scene/resources/resource_format_animated_texture.h create mode 100644 thirdparty/giflib/COPYING create mode 100644 thirdparty/giflib/dgif_lib.c create mode 100644 thirdparty/giflib/egif_lib.c create mode 100644 thirdparty/giflib/gif_err.c create mode 100644 thirdparty/giflib/gif_hash.c create mode 100644 thirdparty/giflib/gif_hash.h create mode 100644 thirdparty/giflib/gif_lib.h create mode 100644 thirdparty/giflib/gif_lib_private.h create mode 100644 thirdparty/giflib/gifalloc.c create mode 100644 thirdparty/giflib/openbsd-reallocarray.c diff --git a/COPYRIGHT.txt b/COPYRIGHT.txt index d8c2ffa5ccc..a89073f0355 100644 --- a/COPYRIGHT.txt +++ b/COPYRIGHT.txt @@ -254,6 +254,11 @@ Comment: The FreeType Project Copyright: 1996-2023, David Turner, Robert Wilhelm, and Werner Lemberg. License: FTL +Files: ./thirdparty/giflib/ +Comment: giflib +Copyright: 1997-2024, Eric S. Raymond +License: Expat + Files: ./thirdparty/glad/ Comment: glad Copyright: 2013-2022, David Herberth diff --git a/core/io/image.cpp b/core/io/image.cpp index 3e5ae837f5b..547346f7a77 100644 --- a/core/io/image.cpp +++ b/core/io/image.cpp @@ -109,6 +109,7 @@ ImageMemLoadFunc Image::_tga_mem_loader_func = nullptr; ImageMemLoadFunc Image::_bmp_mem_loader_func = nullptr; ScalableImageMemLoadFunc Image::_svg_scalable_mem_loader_func = nullptr; ImageMemLoadFunc Image::_ktx_mem_loader_func = nullptr; +ImageMemLoadFunc Image::_gif_mem_loader_func = nullptr; // External VRAM compression function pointers. @@ -3649,6 +3650,7 @@ void Image::_bind_methods() { ClassDB::bind_method(D_METHOD("load_tga_from_buffer", "buffer"), &Image::load_tga_from_buffer); ClassDB::bind_method(D_METHOD("load_bmp_from_buffer", "buffer"), &Image::load_bmp_from_buffer); ClassDB::bind_method(D_METHOD("load_ktx_from_buffer", "buffer"), &Image::load_ktx_from_buffer); + ClassDB::bind_method(D_METHOD("load_gif_from_buffer", "buffer"), &Image::load_gif_from_buffer); ClassDB::bind_method(D_METHOD("load_svg_from_buffer", "buffer", "scale"), &Image::load_svg_from_buffer, DEFVAL(1.0)); ClassDB::bind_method(D_METHOD("load_svg_from_string", "svg_str", "scale"), &Image::load_svg_from_string, DEFVAL(1.0)); @@ -4091,6 +4093,14 @@ Error Image::load_ktx_from_buffer(const Vector &p_array) { return _load_from_buffer(p_array, _ktx_mem_loader_func); } +Error Image::load_gif_from_buffer(const Vector &p_array) { + ERR_FAIL_NULL_V_MSG( + _gif_mem_loader_func, + ERR_UNAVAILABLE, + "The GIF module isn't enabled. Recompile the Redot editor or export template binary with the `module_gif_enabled=yes` SCons option."); + return _load_from_buffer(p_array, _gif_mem_loader_func); +} + void Image::convert_rg_to_ra_rgba8() { ERR_FAIL_COND(format != FORMAT_RGBA8); ERR_FAIL_COND(data.is_empty()); diff --git a/core/io/image.h b/core/io/image.h index d11e9b9c9c2..6ddd048d4c5 100644 --- a/core/io/image.h +++ b/core/io/image.h @@ -204,6 +204,7 @@ class Image : public Resource { static ImageMemLoadFunc _bmp_mem_loader_func; static ScalableImageMemLoadFunc _svg_scalable_mem_loader_func; static ImageMemLoadFunc _ktx_mem_loader_func; + static ImageMemLoadFunc _gif_mem_loader_func; // External VRAM compression function pointers. @@ -405,6 +406,7 @@ class Image : public Resource { Error load_tga_from_buffer(const Vector &p_array); Error load_bmp_from_buffer(const Vector &p_array); Error load_ktx_from_buffer(const Vector &p_array); + Error load_gif_from_buffer(const Vector &p_array); Error load_svg_from_buffer(const Vector &p_array, float scale = 1.0); Error load_svg_from_string(const String &p_svg_str, float scale = 1.0); diff --git a/core/io/image_frames.cpp b/core/io/image_frames.cpp new file mode 100644 index 00000000000..407d7f7424d --- /dev/null +++ b/core/io/image_frames.cpp @@ -0,0 +1,137 @@ +/**************************************************************************/ +/* image_frames.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.h" +#include "core/io/image_frames_loader.h" +#include "core/io/resource_loader.h" + +ImageFramesMemLoadFunc ImageFrames::_gif_mem_loader_func = nullptr; + +void ImageFrames::set_frame_count(int p_frames) { + ERR_FAIL_COND(p_frames < 1); + + frames.resize(p_frames); +} + +int ImageFrames::get_frame_count() const { + return frames.size(); +} + +void ImageFrames::set_frame_image(int p_frame, Ref p_image) { + ERR_FAIL_INDEX(p_frame, frames.size()); + + frames.write[p_frame].image = p_image; +} + +Ref ImageFrames::get_frame_image(int p_frame) const { + ERR_FAIL_INDEX_V(p_frame, frames.size(), Ref()); + + return frames[p_frame].image; +} + +void ImageFrames::set_frame_delay(int p_frame, float p_delay) { + ERR_FAIL_INDEX(p_frame, frames.size()); + + frames.write[p_frame].delay = p_delay; +} + +float ImageFrames::get_frame_delay(int p_frame) const { + ERR_FAIL_INDEX_V(p_frame, frames.size(), 0); + + return frames[p_frame].delay; +} + +Error ImageFrames::_load_from_buffer(const Vector &p_array, ImageFramesMemLoadFunc p_loader, int p_max_frames) { + int buffer_size = p_array.size(); + + ERR_FAIL_COND_V(buffer_size == 0, ERR_INVALID_PARAMETER); + ERR_FAIL_NULL_V(p_loader, ERR_INVALID_PARAMETER); + + const uint8_t *r = p_array.ptr(); + + Ref img_frames = p_loader(r, buffer_size, p_max_frames); + ERR_FAIL_COND_V(!img_frames.is_valid(), ERR_PARSE_ERROR); + + copy_internals_from(img_frames); + + return OK; +} + +Error ImageFrames::load(const String &p_path) { +#ifdef DEBUG_ENABLED + if (p_path.begins_with("res://") && ResourceLoader::exists(p_path)) { + WARN_PRINT("Loaded resource as image frames file, this will not work on export: '" + p_path + "'. Instead, import the image frames file as an ImageFrames resource and load it normally as a resource."); + } +#endif + return ImageFramesLoader::load_image_frames(p_path, this); +} + +Ref ImageFrames::load_from_file(const String &p_path) { +#ifdef DEBUG_ENABLED + if (p_path.begins_with("res://") && ResourceLoader::exists(p_path)) { + WARN_PRINT("Loaded resource as image frames file, this will not work on export: '" + p_path + "'. Instead, import the image frames file as an ImageFrames resource and load it normally as a resource."); + } +#endif + Ref img_frames; + img_frames.instantiate(); + Error err = ImageFramesLoader::load_image_frames(p_path, img_frames); + if (err != OK) { + ERR_FAIL_V_MSG(Ref(), vformat("Failed to load image frames. Error %d", err)); + } + return img_frames; +} + +Error ImageFrames::load_gif_from_buffer(const PackedByteArray &p_array, int p_max_frames) { + ERR_FAIL_NULL_V_MSG( + _gif_mem_loader_func, + ERR_UNAVAILABLE, + "The GIF module isn't enabled. Recompile the Redot editor or export template binary with the `module_gif_enabled=yes` SCons option."); + return _load_from_buffer(p_array, _gif_mem_loader_func, p_max_frames); +} + +void ImageFrames::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_frame_count", "frames"), &ImageFrames::set_frame_count); + ClassDB::bind_method(D_METHOD("get_frame_count"), &ImageFrames::get_frame_count); + + ClassDB::bind_method(D_METHOD("set_frame_image", "frame", "image"), &ImageFrames::set_frame_image); + ClassDB::bind_method(D_METHOD("get_frame_image", "frame"), &ImageFrames::get_frame_image); + + ClassDB::bind_method(D_METHOD("set_frame_delay", "frame", "delay"), &ImageFrames::set_frame_delay); + ClassDB::bind_method(D_METHOD("get_frame_delay", "frame"), &ImageFrames::get_frame_delay); + + ClassDB::bind_method(D_METHOD("load", "path"), &ImageFrames::load); + ClassDB::bind_static_method("ImageFrames", D_METHOD("load_from_file", "path"), &ImageFrames::load_from_file); + + ClassDB::bind_method(D_METHOD("load_gif_from_buffer", "buffer", "max_frames"), &ImageFrames::load_gif_from_buffer); + + ADD_PROPERTY(PropertyInfo(Variant::INT, "frame_count", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), "set_frame_count", "get_frame_count"); +} diff --git a/core/io/image_frames.h b/core/io/image_frames.h new file mode 100644 index 00000000000..2a658177ff3 --- /dev/null +++ b/core/io/image_frames.h @@ -0,0 +1,82 @@ +/**************************************************************************/ +/* image_frames.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. */ +/**************************************************************************/ + +#ifndef IMAGE_FRAMES_H +#define IMAGE_FRAMES_H + +#include "core/io/image.h" +#include "core/io/resource.h" +#include "core/variant/variant.h" + +class ImageFrames; +typedef Ref (*ImageFramesMemLoadFunc)(const uint8_t *p_png, int p_size, int p_max_frames); + +class ImageFrames : public Resource { + GDCLASS(ImageFrames, Resource); + +public: + static ImageFramesMemLoadFunc _gif_mem_loader_func; + +private: + struct Frame { + Ref image; + float delay = 1.0; + }; + + Vector frames; + + Error _load_from_buffer(const Vector &p_array, ImageFramesMemLoadFunc p_loader, int p_max_frames); + + void copy_internals_from(const Ref &p_frames) { + ERR_FAIL_COND_MSG(p_frames.is_null(), "Cannot copy image internals: invalid ImageFrames object."); + frames = p_frames->frames; + } + +protected: + static void _bind_methods(); + +public: + void set_frame_count(int p_frames); + int get_frame_count() const; + + void set_frame_image(int p_frame, Ref p_image); + Ref get_frame_image(int p_frame) const; + + void set_frame_delay(int p_frame, float p_delay); + float get_frame_delay(int p_frame) const; + + Error load(const String &p_path); + static Ref load_from_file(const String &p_path); + Error load_gif_from_buffer(const PackedByteArray &p_array, int p_max_frames = 0); +}; + +#endif // IMAGE_FRAMES_H diff --git a/core/io/image_frames_loader.cpp b/core/io/image_frames_loader.cpp new file mode 100644 index 00000000000..f1112d4ff74 --- /dev/null +++ b/core/io/image_frames_loader.cpp @@ -0,0 +1,212 @@ +/**************************************************************************/ +/* image_frames_loader.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.h" + +void ImageFramesFormatLoader::_bind_methods() { + BIND_BITFIELD_FLAG(FLAG_NONE); + BIND_BITFIELD_FLAG(FLAG_FORCE_LINEAR); +} + +bool ImageFramesFormatLoader::recognize(const String &p_extension) const { + List extensions; + get_recognized_extensions(&extensions); + for (const String &E : extensions) { + if (E.nocasecmp_to(p_extension) == 0) { + return true; + } + } + + return false; +} + +Error ImageFramesFormatLoaderExtension::load_image_frames(Ref p_image_frames, Ref p_fileaccess, BitField p_flags, float p_scale, int p_max_frames) { + Error err = ERR_UNAVAILABLE; + GDVIRTUAL_CALL(_load_image_frames, p_image_frames, p_fileaccess, p_flags, p_scale, p_max_frames, err); + return err; +} + +void ImageFramesFormatLoaderExtension::get_recognized_extensions(List *p_extension) const { + PackedStringArray ext; + if (GDVIRTUAL_CALL(_get_recognized_extensions, ext)) { + for (int i = 0; i < ext.size(); i++) { + p_extension->push_back(ext[i]); + } + } +} + +void ImageFramesFormatLoaderExtension::add_format_loader() { + ImageFramesLoader::add_image_frames_format_loader(this); +} + +void ImageFramesFormatLoaderExtension::remove_format_loader() { + ImageFramesLoader::remove_image_frames_format_loader(this); +} + +void ImageFramesFormatLoaderExtension::_bind_methods() { + GDVIRTUAL_BIND(_get_recognized_extensions); + GDVIRTUAL_BIND(_load_image_frames, "image_frames", "fileaccess", "flags", "scale", "max_frames"); + ClassDB::bind_method(D_METHOD("add_format_loader"), &ImageFramesFormatLoaderExtension::add_format_loader); + ClassDB::bind_method(D_METHOD("remove_format_loader"), &ImageFramesFormatLoaderExtension::remove_format_loader); +} + +Error ImageFramesLoader::load_image_frames(const String &p_file, Ref p_image_frames, Ref p_custom, BitField p_flags, float p_scale, int p_max_frames) { + ERR_FAIL_COND_V_MSG(p_image_frames.is_null(), ERR_INVALID_PARAMETER, "Can't load image frames: invalid ImageFrames object."); + + Ref f = p_custom; + if (f.is_null()) { + Error err; + f = FileAccess::open(p_file, FileAccess::READ, &err); + ERR_FAIL_COND_V_MSG(f.is_null(), err, "Error opening file '" + p_file + "'."); + } + + String extension = p_file.get_extension(); + + for (int i = 0; i < loader.size(); i++) { + if (!loader[i]->recognize(extension)) { + continue; + } + Error err = loader.write[i]->load_image_frames(p_image_frames, f, p_flags, p_scale); + if (err != OK) { + ERR_PRINT("Error loading image frames: " + p_file); + } + + if (err != ERR_FILE_UNRECOGNIZED) { + return err; + } + } + + return ERR_FILE_UNRECOGNIZED; +} + +void ImageFramesLoader::get_recognized_extensions(List *p_extensions) { + for (int i = 0; i < loader.size(); i++) { + loader[i]->get_recognized_extensions(p_extensions); + } +} + +Ref ImageFramesLoader::recognize(const String &p_extension) { + for (int i = 0; i < loader.size(); i++) { + if (loader[i]->recognize(p_extension)) { + return loader[i]; + } + } + + return nullptr; +} + +Vector> ImageFramesLoader::loader; + +void ImageFramesLoader::add_image_frames_format_loader(Ref p_loader) { + loader.push_back(p_loader); +} + +void ImageFramesLoader::remove_image_frames_format_loader(Ref p_loader) { + loader.erase(p_loader); +} + +void ImageFramesLoader::cleanup() { + while (loader.size()) { + remove_image_frames_format_loader(loader[0]); + } +} + +///////////////// + +Ref ResourceFormatLoaderImageFrames::load(const String &p_path, const String &p_original_path, Error *r_error, bool p_use_sub_threads, float *r_progress, CacheMode p_cache_mode) { + Ref f = FileAccess::open(p_path, FileAccess::READ); + if (f.is_null()) { + if (r_error) { + *r_error = ERR_CANT_OPEN; + } + return Ref(); + } + + uint8_t header[5] = { 0, 0, 0, 0, 0 }; + f->get_buffer(header, 5); + + bool unrecognized = header[0] != 'R' || header[1] != 'D' || header[2] != 'I' || header[3] != 'M' || header[4] != 'F'; + if (unrecognized) { + if (r_error) { + *r_error = ERR_FILE_UNRECOGNIZED; + } + ERR_FAIL_V(Ref()); + } + + String extension = f->get_pascal_string(); + + int idx = -1; + + for (int i = 0; i < ImageFramesLoader::loader.size(); i++) { + if (ImageFramesLoader::loader[i]->recognize(extension)) { + idx = i; + break; + } + } + + if (idx == -1) { + if (r_error) { + *r_error = ERR_FILE_UNRECOGNIZED; + } + ERR_FAIL_V(Ref()); + } + + Ref image; + image.instantiate(); + + Error err = ImageFramesLoader::loader.write[idx]->load_image_frames(image, f); + + if (err != OK) { + if (r_error) { + *r_error = err; + } + return Ref(); + } + + if (r_error) { + *r_error = OK; + } + + return image; +} + +void ResourceFormatLoaderImageFrames::get_recognized_extensions(List *p_extensions) const { + p_extensions->push_back("image_frames"); +} + +bool ResourceFormatLoaderImageFrames::handles_type(const String &p_type) const { + return p_type == "ImageFrames"; +} + +String ResourceFormatLoaderImageFrames::get_resource_type(const String &p_path) const { + return p_path.get_extension().to_lower() == "image_frames" ? "ImageFrames" : String(); +} diff --git a/core/io/image_frames_loader.h b/core/io/image_frames_loader.h new file mode 100644 index 00000000000..22ee55e731c --- /dev/null +++ b/core/io/image_frames_loader.h @@ -0,0 +1,114 @@ +/**************************************************************************/ +/* image_frames_loader.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. */ +/**************************************************************************/ + +#ifndef IMAGE_FRAMES_LOADER_H +#define IMAGE_FRAMES_LOADER_H + +#include "core/io/file_access.h" +#include "core/io/image_frames.h" +#include "core/io/resource_loader.h" +#include "core/string/ustring.h" +#include "core/templates/list.h" +#include "core/variant/binder_common.h" +#include "scene/resources/resource_format_animated_texture.h" + +class ImageFramesLoader; + +class ImageFramesFormatLoader : public RefCounted { + GDCLASS(ImageFramesFormatLoader, RefCounted); + + friend class ImageFramesLoader; + friend class ResourceFormatLoaderImageFrames; + friend class ResourceFormatLoaderAnimatedTexture; + +public: + enum LoaderFlags { + FLAG_NONE = 0, + FLAG_FORCE_LINEAR = 1, + }; + +protected: + static void _bind_methods(); + + virtual Error load_image_frames(Ref p_image, Ref p_fileaccess, BitField p_flags = FLAG_NONE, float p_scale = 1.0, int p_max_frames = 0) = 0; + virtual void get_recognized_extensions(List *p_extensions) const = 0; + bool recognize(const String &p_extension) const; + +public: + virtual ~ImageFramesFormatLoader() {} +}; + +VARIANT_BITFIELD_CAST(ImageFramesFormatLoader::LoaderFlags); + +class ImageFramesFormatLoaderExtension : public ImageFramesFormatLoader { + GDCLASS(ImageFramesFormatLoaderExtension, ImageFramesFormatLoader); + +protected: + static void _bind_methods(); + +public: + virtual Error load_image_frames(Ref p_image, Ref p_fileaccess, BitField p_flags = FLAG_NONE, float p_scale = 1.0, int p_max_frames = 0) override; + virtual void get_recognized_extensions(List *p_extensions) const override; + + void add_format_loader(); + void remove_format_loader(); + + GDVIRTUAL0RC(PackedStringArray, _get_recognized_extensions); + GDVIRTUAL5R(Error, _load_image_frames, Ref, Ref, BitField, float, int); +}; + +class ImageFramesLoader { + static Vector> loader; + friend class ResourceFormatLoaderImageFrames; + friend class ResourceFormatLoaderAnimatedTexture; + +protected: +public: + static Error load_image_frames(const String &p_file, Ref p_image, Ref p_custom = Ref(), BitField p_flags = ImageFramesFormatLoader::FLAG_NONE, float p_scale = 1.0, int p_max_frames = 0); + static void get_recognized_extensions(List *p_extensions); + static Ref recognize(const String &p_extension); + + static void add_image_frames_format_loader(Ref p_loader); + static void remove_image_frames_format_loader(Ref p_loader); + + static void cleanup(); +}; + +class ResourceFormatLoaderImageFrames : public ResourceFormatLoader { +public: + virtual Ref load(const String &p_path, const String &p_original_path = "", Error *r_error = nullptr, bool p_use_sub_threads = false, float *r_progress = nullptr, CacheMode p_cache_mode = CACHE_MODE_REUSE) override; + virtual void get_recognized_extensions(List *p_extensions) const override; + virtual bool handles_type(const String &p_type) const override; + virtual String get_resource_type(const String &p_path) const override; +}; + +#endif // IMAGE_FRAMES_LOADER_H diff --git a/core/register_core_types.cpp b/core/register_core_types.cpp index 0f400e5db8a..d34920ea512 100644 --- a/core/register_core_types.cpp +++ b/core/register_core_types.cpp @@ -48,6 +48,8 @@ #include "core/io/dir_access.h" #include "core/io/dtls_server.h" #include "core/io/http_client.h" +#include "core/io/image_frames.h" +#include "core/io/image_frames_loader.h" #include "core/io/image_loader.h" #include "core/io/json.h" #include "core/io/marshalls.h" @@ -88,6 +90,7 @@ static Ref resource_loader_binary; static Ref resource_format_importer; static Ref resource_format_importer_saver; static Ref resource_format_image; +static Ref resource_loader_image_frames; static Ref resource_format_po; static Ref resource_format_saver_crypto; static Ref resource_format_loader_crypto; @@ -157,6 +160,9 @@ void register_core_types() { resource_format_image.instantiate(); ResourceLoader::add_resource_format_loader(resource_format_image); + resource_loader_image_frames.instantiate(); + ResourceLoader::add_resource_format_loader(resource_loader_image_frames); + GDREGISTER_CLASS(Object); GDREGISTER_ABSTRACT_CLASS(Script); @@ -170,6 +176,7 @@ void register_core_types() { GDREGISTER_CLASS(Resource); GDREGISTER_VIRTUAL_CLASS(MissingResource); GDREGISTER_CLASS(Image); + GDREGISTER_CLASS(ImageFrames); GDREGISTER_CLASS(Shortcut); GDREGISTER_ABSTRACT_CLASS(InputEvent); @@ -266,6 +273,8 @@ void register_core_types() { GDREGISTER_ABSTRACT_CLASS(ImageFormatLoader); GDREGISTER_CLASS(ImageFormatLoaderExtension); + GDREGISTER_ABSTRACT_CLASS(ImageFramesFormatLoader); + GDREGISTER_CLASS(ImageFramesFormatLoaderExtension); GDREGISTER_ABSTRACT_CLASS(ResourceImporter); GDREGISTER_CLASS(GDExtension); @@ -409,6 +418,9 @@ void unregister_core_types() { ResourceLoader::remove_resource_format_loader(resource_format_image); resource_format_image.unref(); + ResourceLoader::remove_resource_format_loader(resource_loader_image_frames); + resource_loader_image_frames.unref(); + ResourceSaver::remove_resource_format_saver(resource_saver_binary); resource_saver_binary.unref(); diff --git a/doc/classes/AnimatedTexture.xml b/doc/classes/AnimatedTexture.xml index d443541e268..16d3a028fd8 100644 --- a/doc/classes/AnimatedTexture.xml +++ b/doc/classes/AnimatedTexture.xml @@ -13,6 +13,13 @@ + + + + + Creates a new [AnimatedTexture] and initializes it by allocating and setting the data from an [ImageFrames]. This function will ignore all frames beyond [constant MAX_FRAMES] - 1. + + @@ -27,6 +34,12 @@ Returns the given frame's [Texture2D]. + + + + Creates a new [ImageFrames] object from contents. + + @@ -44,6 +57,13 @@ You can define any number of textures up to [constant MAX_FRAMES], but keep in mind that only frames from 0 to [member frames] - 1 will be part of the animation. + + + + + Replaces the texture's data with a new [ImageFrames]. This function will ignore all frames beyond [constant MAX_FRAMES] - 1. + + diff --git a/doc/classes/Image.xml b/doc/classes/Image.xml index 16a8a7c1bda..61b0c8d0981 100644 --- a/doc/classes/Image.xml +++ b/doc/classes/Image.xml @@ -353,6 +353,14 @@ Creates a new [Image] and loads data from the specified file. + + + + + Loads an image from the binary contents of a GIF file. + [b]Note:[/b] This method is only available in engine builds with the GIF module enabled. By default, the GIF module is enabled, but it can be disabled at build-time using the [code]module_gif_enabled=no[/code] SCons option. + + diff --git a/doc/classes/ImageFrames.xml b/doc/classes/ImageFrames.xml new file mode 100644 index 00000000000..7ef458e4a73 --- /dev/null +++ b/doc/classes/ImageFrames.xml @@ -0,0 +1,91 @@ + + + + A container for sequence of [Image]s. + + + A container of [Image]s used to load and arrange a sequence of frames. Each frame can specify a delay for animated images. + Can be used to load animated image formats externally. + Supported animated image formats are [url=https://www.w3.org/Graphics/GIF/spec-gif89a.txt]GIF[/url] ([code].gif[/code]) and any format exposed via a GDExtension plugin. + An [ImageTexture] is not meant to be operated from within the editor interface directly, and is mostly useful for rendering images on screen dynamically via code. If you need to generate images procedurally from within the editor, consider saving and importing images as custom texture resources implementing a new [EditorImportPlugin]. + + + + + + + + + Returns the given frame's duration, in seconds. + + + + + + + Returns the given frame's [Image]. + + + + + + + Loads a sequence of image frames from file [param path]. + [b]Warning:[/b] This method should only be used in the editor or in cases when you need to load external images at run-time, such as images located at the [code]user://[/code] directory, and may not work in exported projects. + [codeblock] + var frames = ImageFrames.load_from_file("res://animated.gif") + var animated_texture = AnimatedTexture.create_from_image_frames(frames) + $Sprite2D.texture = animated_texture + [/codeblock] + This way, textures can be created at run-time by loading images both from within the editor and externally. + [b]Warning:[/b] Prefer to load imported textures with [method @GDScript.load] over loading them from within the filesystem dynamically with [method ImageFrames.load], as it may not work in exported projects: + [codeblock] + var animated_texture = load("res://animated.gif") + $Sprite2D.texture = texture + [/codeblock] + This is because images have to be imported as an [AnimatedTexture] first to be loaded with [method @GDScript.load]. If you'd still like to load an animated image file just like any other [Resource], import it as an [ImageFrames] resource instead, and then load it normally using the [method @GDScript.load] method. + [b]Note:[/b] The image frame can be create from an imported texture using the [method AnimatedTexture.create_from_image_frames] method: + [codeblock] + var texture = load("res://animated.gif") + var image: AnimatedTexture = AnimatedTexture.create_from_image_frames(texture) + [/codeblock] + + + + + + + Creates a new [ImageFrames] and loads data from the specified file. + + + + + + + + Loads an image from the binary contents of a GIF file. + + + + + + + + Sets the delay of any given frame. If set to [code]0[/code], the frame may be skipped if converted into an [AnimatedTexture]. + + + + + + + + Assigns an [Image] to the given frame. Frame IDs start at 0, so the first frame has ID 0, and the last frame has ID [member frame_count] - 1. + + + + + + Number of frames to use in the animation. While you can create the frames independently with [method set_frame_image], you need to set this value for the animation to take new frames into account. + + + diff --git a/doc/classes/ImageFramesFormatLoader.xml b/doc/classes/ImageFramesFormatLoader.xml new file mode 100644 index 00000000000..9d754fe5d9a --- /dev/null +++ b/doc/classes/ImageFramesFormatLoader.xml @@ -0,0 +1,17 @@ + + + + Base class to add support for specific image sequence formats. + + + The engine supports multiple image sequence formats out of the box, but you can choose to implement support for additional image sequence formats by extending [ImageFramesFormatLoaderExtension]. + + + + + + + + + + diff --git a/doc/classes/ImageFramesFormatLoaderExtension.xml b/doc/classes/ImageFramesFormatLoaderExtension.xml new file mode 100644 index 00000000000..88951b68ea0 --- /dev/null +++ b/doc/classes/ImageFramesFormatLoaderExtension.xml @@ -0,0 +1,42 @@ + + + + Base class for creating [ImageFramesFormatLoader] extensions (adding support for extra image sequence formats). + + + The engine supports multiple image sequence formats out of the box, but you can choose to implement support for additional image sequence formats by extending this class. + Be sure to respect the documented return types and values. You should create an instance of it, and call [method add_format_loader] to register that loader during the initialization phase. + + + + + + + + + + + + + + + + + + Loads the content of [param fileaccess] into the provided [param image_frames]. + + + + + + Add this format loader to the engine, allowing it to recognize the file extensions returned by [method _get_recognized_extensions]. + + + + + + Remove this format loader from the engine. + + + + diff --git a/doc/classes/ResourceImporterAnimatedTexture.xml b/doc/classes/ResourceImporterAnimatedTexture.xml new file mode 100644 index 00000000000..7d5616751a5 --- /dev/null +++ b/doc/classes/ResourceImporterAnimatedTexture.xml @@ -0,0 +1,33 @@ + + + + Imports an animated image for use in 2D rendering. + + + This importer imports [AnimatedTexture] resources. If you need to process the image in scripts in a more convenient way, use [ResourceImporterImageFrames] instead. + + + + + + If [code]true[/code], puts pixels of the same surrounding color in transition from transparent to opaque areas for all textures. For textures displayed with bilinear filtering, this helps to reduce the outline effect when exporting images from an image editor. + It's recommended to leave this enabled (as it is by default), unless this causes issues for a particular animated image. + + + If set to a value greater than [code]0[/code], the frames to read is limited on import to a value smaller than or equal to the value specified here. + This can be used to reduce memory usage at the cost of truncated animations. + + + Some HDR images you can find online may be broken and contain sRGB color data (instead of linear color data). It is advised not to use those files. If you absolutely have to, enabling [member process/hdr_as_srgb] will make them look correct. + [b]Warning:[/b] Enabling [member process/hdr_as_srgb] on well-formatted HDR images will cause the resulting image to look too dark, so leave this on [code]false[/code] if unsure. + + + An alternative to fixing darkened borders with [member process/fix_alpha_border] is to use premultiplied alpha. By enabling this option, all the textures will be converted to this format. A premultiplied alpha texture requires specific materials to be displayed correctly: + A [CanvasItemMaterial] will need to be created and configured to use the [constant CanvasItemMaterial.BLEND_MODE_PREMULT_ALPHA] blend mode on [CanvasItem]s that use this texture. In custom [code]@canvas_item[/code] shaders, [code]render_mode blend_premul_alpha;[/code] should be used. + + + If set to a value greater than [code]0[/code], the size of each individual texture is limited on import to a value smaller than or equal to the value specified here. For non-square textures, the size limit affects the longer dimension, with the shorter dimension scaled to preserve aspect ratio. Resizing is performed using cubic interpolation. + This can be used to reduce memory usage without affecting the source images, or avoid issues with textures not displaying on mobile/web platforms (as these usually can't display textures larger than 4096×4096). + + + diff --git a/doc/classes/ResourceImporterImageFrames.xml b/doc/classes/ResourceImporterImageFrames.xml new file mode 100644 index 00000000000..ab6be7181fe --- /dev/null +++ b/doc/classes/ResourceImporterImageFrames.xml @@ -0,0 +1,11 @@ + + + + Imports an image sequence for use in scripting, with no rendering capabilities. + + + This importer imports [ImageFrames] resources, as opposed to [AnimatedTexture]. If you need to render the image in 2D, use [ResourceImporterAnimatedTexture] instead. + + + + diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index b3a2ba04a31..a79aa91a3e6 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -48,6 +48,8 @@ #include "core/string/translation_server.h" #include "core/version.h" #include "editor/editor_string_names.h" +#include "editor/import/resource_importer_animated_texture.h" +#include "editor/import/resource_importer_image_frames.h" #include "editor/plugins/editor_context_menu_plugin.h" #include "main/main.h" #include "scene/3d/bone_attachment_3d.h" @@ -6873,6 +6875,14 @@ EditorNode::EditorNode() { import_texture_atlas.instantiate(); ResourceFormatImporter::get_singleton()->add_importer(import_texture_atlas); + Ref import_animated_texture; + import_animated_texture.instantiate(); + ResourceFormatImporter::get_singleton()->add_importer(import_animated_texture); + + Ref import_image_frames; + import_image_frames.instantiate(); + ResourceFormatImporter::get_singleton()->add_importer(import_image_frames); + Ref import_font_data_dynamic; import_font_data_dynamic.instantiate(); ResourceFormatImporter::get_singleton()->add_importer(import_font_data_dynamic); diff --git a/editor/import/resource_importer_animated_texture.cpp b/editor/import/resource_importer_animated_texture.cpp new file mode 100644 index 00000000000..d0afdfb5cba --- /dev/null +++ b/editor/import/resource_importer_animated_texture.cpp @@ -0,0 +1,164 @@ +/**************************************************************************/ +/* resource_importer_animated_texture.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 "resource_importer_animated_texture.h" + +#include "core/io/image.h" +#include "core/io/image_frames_loader.h" +#include "core/typedefs.h" +#include "scene/resources/animated_texture.h" + +String ResourceImporterAnimatedTexture::get_importer_name() const { + return "animated_texture"; +} + +String ResourceImporterAnimatedTexture::get_visible_name() const { + return "AnimatedTexture"; +} + +void ResourceImporterAnimatedTexture::get_recognized_extensions(List *p_extensions) const { + ImageFramesLoader::get_recognized_extensions(p_extensions); +} + +String ResourceImporterAnimatedTexture::get_save_extension() const { + return "atex"; +} + +String ResourceImporterAnimatedTexture::get_resource_type() const { + return "AnimatedTexture"; +} + +bool ResourceImporterAnimatedTexture::get_option_visibility(const String &p_path, const String &p_option, const HashMap &p_options) const { + return true; +} + +int ResourceImporterAnimatedTexture::get_preset_count() const { + return 0; +} + +String ResourceImporterAnimatedTexture::get_preset_name(int p_idx) const { + return String(); +} + +void ResourceImporterAnimatedTexture::get_import_options(const String &p_path, List *r_options, int p_preset) const { + r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "process/fix_alpha_border"), true)); + r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "process/premult_alpha"), false)); + r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "process/hdr_as_srgb"), false)); + r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "process/size_limit", PROPERTY_HINT_RANGE, "0,4096,1"), 0)); + r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "process/frame_limit", PROPERTY_HINT_RANGE, vformat("0, %d, 1", AnimatedTexture::MAX_FRAMES)), 0)); +} + +Error ResourceImporterAnimatedTexture::import(ResourceUID::ID p_source_id, const String &p_source_file, const String &p_save_path, const HashMap &p_options, List *r_platform_variants, List *r_gen_files, Variant *r_metadata) { + // Parse import options. + int32_t loader_flags = ImageFramesFormatLoader::FLAG_NONE; + + // Processing. + const bool fix_alpha_border = p_options["process/fix_alpha_border"]; + const bool premult_alpha = p_options["process/premult_alpha"]; + const int size_limit = p_options["process/size_limit"]; + const bool hdr_as_srgb = p_options["process/hdr_as_srgb"]; + if (hdr_as_srgb) { + loader_flags |= ImageFramesFormatLoader::FLAG_FORCE_LINEAR; + } + const int frame_limit = p_options["process/frame_limit"]; + + Ref image_frames; + image_frames.instantiate(); + Error err = ImageFramesLoader::load_image_frames(p_source_file, image_frames, Ref(), loader_flags); + if (err != OK) { + return err; + } + + int frame_count = frame_limit <= 0 ? AnimatedTexture::MAX_FRAMES : frame_limit; + frame_count = MIN(frame_count, MIN(image_frames->get_frame_count(), AnimatedTexture::MAX_FRAMES)); + + Ref f = FileAccess::open(p_save_path + ".atex", FileAccess::WRITE, &err); + ERR_FAIL_COND_V_MSG(f.is_null(), ERR_CANT_CREATE, "Cannot create file in path '" + p_save_path + ".atex'."); + + const uint8_t header[4] = { 'R', 'D', 'A', 'T' }; + f->store_buffer(header, 4); // Redot Animated Texture. + f->store_32(loader_flags); + f->store_32(frame_count); + + int width = image_frames->get_frame_image(0)->get_width(); + int height = image_frames->get_frame_image(0)->get_height(); + int new_width = width; + int new_height = height; + if (size_limit > 0) { + // Apply the size limit. + if (width > size_limit || height > size_limit) { + if (width >= height) { + new_width = size_limit; + new_height = height * new_width / width; + } else { + new_height = size_limit; + new_width = width * new_height / height; + } + } + } + + // We already assume image frames already contains at least one frame, + // and that all frames have the same size. + f->store_32(new_width); + f->store_32(new_height); + + for (int current_frame = 0; current_frame < frame_count; current_frame++) { + Ref image = image_frames->get_frame_image(current_frame); + image->convert(Image::FORMAT_RGBA8); + if (width != new_width || height != new_height) { + image->resize(new_width, new_height, Image::INTERPOLATE_CUBIC); + } + + // Fix alpha border. + if (fix_alpha_border) { + image->fix_alpha_edges(); + } + + // Premultiply the alpha. + if (premult_alpha) { + image->premultiply_alpha(); + } + + // Frame image data. + Vector data = image->get_data(); + f->store_32(data.size()); + f->store_buffer(data.ptr(), data.size()); + // Frame delay data. + const real_t delay = image_frames->get_frame_delay(current_frame); + f->store_real(delay); + } + + return OK; +} + +ResourceImporterAnimatedTexture::ResourceImporterAnimatedTexture() { +} diff --git a/editor/import/resource_importer_animated_texture.h b/editor/import/resource_importer_animated_texture.h new file mode 100644 index 00000000000..dd93bf107d3 --- /dev/null +++ b/editor/import/resource_importer_animated_texture.h @@ -0,0 +1,59 @@ +/**************************************************************************/ +/* resource_importer_animated_texture.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. */ +/**************************************************************************/ + +#ifndef RESOURCE_IMPORTER_ANIMATED_TEXTURE_H +#define RESOURCE_IMPORTER_ANIMATED_TEXTURE_H + +#include "core/io/resource_importer.h" + +class ResourceImporterAnimatedTexture : public ResourceImporter { + GDCLASS(ResourceImporterAnimatedTexture, ResourceImporter); + +public: + virtual String get_importer_name() const override; + virtual String get_visible_name() const override; + virtual void get_recognized_extensions(List *p_extensions) const override; + virtual String get_save_extension() const override; + virtual String get_resource_type() const override; + + virtual int get_preset_count() const override; + virtual String get_preset_name(int p_idx) const override; + + virtual void get_import_options(const String &p_path, List *r_options, int p_preset = 0) const override; + virtual bool get_option_visibility(const String &p_path, const String &p_option, const HashMap &p_options) const override; + + virtual Error import(ResourceUID::ID p_source_id, const String &p_source_file, const String &p_save_path, const HashMap &p_options, List *r_platform_variants, List *r_gen_files = nullptr, Variant *r_metadata = nullptr) override; + + ResourceImporterAnimatedTexture(); +}; + +#endif // RESOURCE_IMPORTER_ANIMATED_TEXTURE_H diff --git a/editor/import/resource_importer_image_frames.cpp b/editor/import/resource_importer_image_frames.cpp new file mode 100644 index 00000000000..05be78d5076 --- /dev/null +++ b/editor/import/resource_importer_image_frames.cpp @@ -0,0 +1,99 @@ +/**************************************************************************/ +/* resource_importer_image_frames.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 "resource_importer_image_frames.h" + +#include "core/io/file_access.h" +#include "core/io/image_frames_loader.h" + +String ResourceImporterImageFrames::get_importer_name() const { + return "image_frames"; +} + +String ResourceImporterImageFrames::get_visible_name() const { + return "ImageFrames"; +} + +void ResourceImporterImageFrames::get_recognized_extensions(List *p_extensions) const { + ImageFramesLoader::get_recognized_extensions(p_extensions); +} + +String ResourceImporterImageFrames::get_save_extension() const { + return "image_frames"; +} + +String ResourceImporterImageFrames::get_resource_type() const { + return "ImageFrames"; +} + +bool ResourceImporterImageFrames::get_option_visibility(const String &p_path, const String &p_option, const HashMap &p_options) const { + return true; +} + +int ResourceImporterImageFrames::get_preset_count() const { + return 0; +} + +String ResourceImporterImageFrames::get_preset_name(int p_idx) const { + return String(); +} + +void ResourceImporterImageFrames::get_import_options(const String &p_path, List *r_options, int p_preset) const { +} + +Error ResourceImporterImageFrames::import(ResourceUID::ID p_source_id, const String &p_source_file, const String &p_save_path, const HashMap &p_options, List *r_platform_variants, List *r_gen_files, Variant *r_metadata) { + Ref f = FileAccess::open(p_source_file, FileAccess::READ); + + ERR_FAIL_COND_V_MSG(f.is_null(), ERR_CANT_OPEN, "Cannot open file from path '" + p_source_file + "'."); + uint64_t len = f->get_length(); + + Vector data; + data.resize(len); + + f->get_buffer(data.ptrw(), len); + + f = FileAccess::open(p_save_path + ".image_frames", FileAccess::WRITE); + ERR_FAIL_COND_V_MSG(f.is_null(), ERR_CANT_CREATE, "Cannot create file in path '" + p_save_path + ".image_frames'."); + + //save the header RDIM + const uint8_t header[5] = { 'R', 'D', 'I', 'M', 'F' }; + f->store_buffer(header, 5); + //SAVE the extension (so it can be recognized by the loader later + f->store_pascal_string(p_source_file.get_extension().to_lower()); + //SAVE the actual image + f->store_buffer(data.ptr(), len); + + return OK; +} + +ResourceImporterImageFrames::ResourceImporterImageFrames() { +} diff --git a/editor/import/resource_importer_image_frames.h b/editor/import/resource_importer_image_frames.h new file mode 100644 index 00000000000..0ef8289b811 --- /dev/null +++ b/editor/import/resource_importer_image_frames.h @@ -0,0 +1,60 @@ +/**************************************************************************/ +/* resource_importer_image_frames.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. */ +/**************************************************************************/ + +#ifndef RESOURCE_IMPORTER_IMAGE_FRAMES_H +#define RESOURCE_IMPORTER_IMAGE_FRAMES_H + +#include "core/io/image.h" +#include "core/io/resource_importer.h" + +class ResourceImporterImageFrames : public ResourceImporter { + GDCLASS(ResourceImporterImageFrames, ResourceImporter); + +public: + virtual String get_importer_name() const override; + virtual String get_visible_name() const override; + virtual void get_recognized_extensions(List *p_extensions) const override; + virtual String get_save_extension() const override; + virtual String get_resource_type() const override; + + virtual int get_preset_count() const override; + virtual String get_preset_name(int p_idx) const override; + + virtual void get_import_options(const String &p_path, List *r_options, int p_preset = 0) const override; + virtual bool get_option_visibility(const String &p_path, const String &p_option, const HashMap &p_options) const override; + + virtual Error import(ResourceUID::ID p_source_id, const String &p_source_file, const String &p_save_path, const HashMap &p_options, List *r_platform_variants, List *r_gen_files = nullptr, Variant *r_metadata = nullptr) override; + + ResourceImporterImageFrames(); +}; + +#endif // RESOURCE_IMPORTER_IMAGE_FRAMES_H diff --git a/editor/register_editor_types.cpp b/editor/register_editor_types.cpp index 78fe6dc12f4..297b7ed335c 100644 --- a/editor/register_editor_types.cpp +++ b/editor/register_editor_types.cpp @@ -58,11 +58,13 @@ #include "editor/import/3d/resource_importer_obj.h" #include "editor/import/3d/resource_importer_scene.h" #include "editor/import/editor_import_plugin.h" +#include "editor/import/resource_importer_animated_texture.h" #include "editor/import/resource_importer_bitmask.h" #include "editor/import/resource_importer_bmfont.h" #include "editor/import/resource_importer_csv_translation.h" #include "editor/import/resource_importer_dynamic_font.h" #include "editor/import/resource_importer_image.h" +#include "editor/import/resource_importer_image_frames.h" #include "editor/import/resource_importer_imagefont.h" #include "editor/import/resource_importer_layered_texture.h" #include "editor/import/resource_importer_shader_file.h" @@ -193,11 +195,13 @@ void register_editor_types() { GDREGISTER_ABSTRACT_CLASS(EditorDebuggerSession); // Required to document import options in the class reference. + GDREGISTER_CLASS(ResourceImporterAnimatedTexture); GDREGISTER_CLASS(ResourceImporterBitMap); GDREGISTER_CLASS(ResourceImporterBMFont); GDREGISTER_CLASS(ResourceImporterCSVTranslation); GDREGISTER_CLASS(ResourceImporterDynamicFont); GDREGISTER_CLASS(ResourceImporterImage); + GDREGISTER_CLASS(ResourceImporterImageFrames); GDREGISTER_CLASS(ResourceImporterImageFont); GDREGISTER_CLASS(ResourceImporterLayeredTexture); GDREGISTER_CLASS(ResourceImporterOBJ); diff --git a/main/main.cpp b/main/main.cpp index 8c5e0624365..95aee4f3613 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -45,6 +45,7 @@ #include "core/io/file_access_pack.h" #include "core/io/file_access_zip.h" #include "core/io/image.h" +#include "core/io/image_frames_loader.h" #include "core/io/image_loader.h" #include "core/io/ip.h" #include "core/io/resource_loader.h" @@ -4618,6 +4619,7 @@ void Main::cleanup(bool p_force) { #endif ImageLoader::cleanup(); + ImageFramesLoader::cleanup(); GDExtensionManager::get_singleton()->deinitialize_extensions(GDExtension::INITIALIZATION_LEVEL_SCENE); uninitialize_modules(MODULE_INITIALIZATION_LEVEL_SCENE); diff --git a/modules/gif/SCsub b/modules/gif/SCsub new file mode 100644 index 00000000000..c385e65f21c --- /dev/null +++ b/modules/gif/SCsub @@ -0,0 +1,28 @@ +#!/usr/bin/env python +from misc.utility.scons_hints import * + +Import('env') +Import('env_modules') + +env_gif = env_modules.Clone() + +# Thirdparty source files +thirdparty_dir = "#thirdparty/giflib/" +thirdparty_sources = [ + "gif_err.c", + "dgif_lib.c", + "egif_lib.c", + "gifalloc.c", + "gif_hash.c", + "openbsd-reallocarray.c" +] +thirdparty_sources = [thirdparty_dir + file for file in thirdparty_sources] + +env_gif.Prepend(CPPPATH=[thirdparty_dir]) + +env_thirdparty = env_gif.Clone() +env_thirdparty.disable_warnings() +env_thirdparty.add_source_files(env.modules_sources, thirdparty_sources) + +# Godot's own source files +env_gif.add_source_files(env.modules_sources, "*.cpp") diff --git a/modules/gif/config.py b/modules/gif/config.py new file mode 100644 index 00000000000..d22f9454ed2 --- /dev/null +++ b/modules/gif/config.py @@ -0,0 +1,6 @@ +def can_build(env, platform): + return True + + +def configure(env): + pass diff --git a/modules/gif/gif_common.cpp b/modules/gif/gif_common.cpp new file mode 100644 index 00000000000..336c34762a8 --- /dev/null +++ b/modules/gif/gif_common.cpp @@ -0,0 +1,244 @@ +/**************************************************************************/ +/* gif_common.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 "gif_common.h" +#include "core/error/error_list.h" +#include "core/error/error_macros.h" +#include "core/io/image.h" +#include "core/io/image_frames.h" +#include "core/string/ustring.h" +#include "core/templates/vector.h" +#include "core/variant/variant.h" +#include +#include +#include +#include + +struct GifFileTypeRAII { + GifFileType *file_type; + int error = 0; + + GifFileTypeRAII(void *p_user_ptr, InputFunc p_read_func) { + file_type = DGifOpen(p_user_ptr, p_read_func, &error); + } + + ~GifFileTypeRAII() { + int gif_err = 0; + if (!DGifCloseFile(file_type, &error)) { + ERR_PRINT(GifErrorString(gif_err)); + } + } +}; + +struct GifBuffer { + uint8_t *data; + int64_t size; + int index; +}; + +int gif_read_buffer(GifFileType *p_gif, GifByteType *p_data, int p_length) { + GifBuffer *gif_data = (GifBuffer *)(p_gif->UserData); + if (gif_data->index + p_length > gif_data->size) { + p_length = gif_data->size - gif_data->index; + } + + memcpy(p_data, &gif_data->data[gif_data->index], p_length); + gif_data->index += p_length; + return p_length; +} + +template +Error gif_load_from_buffer_t(T *p_dest, const uint8_t *p_buffer, int p_buffer_len, int p_max_frames) { + ERR_FAIL_NULL_V(p_dest, ERR_INVALID_PARAMETER); + + const int RGBA = 4; + GifBuffer buffer = { const_cast(p_buffer), p_buffer_len, 0 }; + GifFileTypeRAII gif(&buffer, gif_read_buffer); + + ERR_FAIL_COND_V_MSG(!gif.file_type, FAILED, vformat("Failed to open GIF buffer: %s", GifErrorString(gif.error))); + ERR_FAIL_COND_V_MSG(DGifSlurp(gif.file_type) == GIF_ERROR, FAILED, + vformat("Failed to read GIF buffer: %s", GifErrorString(gif.file_type->Error))); + + GifImageDesc &img_desc = gif.file_type->Image; + ERR_FAIL_COND_V_MSG(img_desc.Width <= 0, FAILED, "GIF Image width must be greater than 0."); + ERR_FAIL_COND_V_MSG(img_desc.Height <= 0, FAILED, "GIF Image height must be greater than 0."); + ERR_FAIL_COND_V_MSG(img_desc.Width > Image::MAX_WIDTH, FAILED, "GIF Image width cannot be greater than " + itos(Image::MAX_WIDTH) + "."); + ERR_FAIL_COND_V_MSG(img_desc.Height > Image::MAX_HEIGHT, FAILED, "GIF Image height cannot be greater than " + itos(Image::MAX_HEIGHT) + "."); + ERR_FAIL_COND_V_MSG(img_desc.Width * img_desc.Height > Image::MAX_PIXELS, FAILED, "Too many pixels for a GIF Image, maximum is " + itos(Image::MAX_PIXELS)); + + if constexpr (std::is_same_v) { + static_cast(p_dest)->set_frame_count(gif.file_type->ImageCount); + } + + int image_size = gif.file_type->SWidth * gif.file_type->SHeight * RGBA; + Vector screen; + screen.resize(image_size); + + ColorMapObject *common_map = gif.file_type->SColorMap; + int last_undisposed_frame = -1; + for (int current_frame = 0; current_frame < gif.file_type->ImageCount; ++current_frame) { + const SavedImage ¤t_frame_image = gif.file_type->SavedImages[current_frame]; + const GifImageDesc ¤t_frame_desc = current_frame_image.ImageDesc; + const ColorMapObject *current_color_map = current_frame_desc.ColorMap ? current_frame_desc.ColorMap : common_map; + + GraphicsControlBlock gcb; + ERR_FAIL_COND_V_MSG(DGifSavedExtensionToGCB(gif.file_type, current_frame, &gcb) == GIF_ERROR, FAILED, + vformat("Failed to extract GIF Frame Graphics Control Block: %s", GifErrorString(gif.file_type->Error))); + + for (int y = 0; y < current_frame_desc.Height; ++y) { + for (int x = 0; x < current_frame_desc.Width; ++x) { + uint8_t color_index = current_frame_image.RasterBits[y * current_frame_desc.Width + x]; + if (!current_color_map) { + continue; + } + + int write_y = y + img_desc.Top; + int write_x = x + img_desc.Left; + int write_index = (write_y * gif.file_type->SWidth + write_x) * RGBA; + if (color_index == gcb.TransparentColor) { + screen.write[write_index] = 0; + screen.write[write_index + 1] = 0; + screen.write[write_index + 2] = 0; + screen.write[write_index + 3] = 0; + continue; + } + + GifColorType rgb = current_color_map->Colors[color_index]; + screen.write[write_index] = rgb.Red; + screen.write[write_index + 1] = rgb.Green; + screen.write[write_index + 2] = rgb.Blue; + screen.write[write_index + 3] = 255; + } + } + + PackedByteArray frame_data; + frame_data.resize(image_size); + memcpy(frame_data.ptrw(), screen.ptr(), image_size); + + float delay = gcb.DelayTime / 100.0; + if (delay == 0) { + delay = 0.05; + } + + if constexpr (std::is_same_v) { + static_cast(p_dest)->set_data(gif.file_type->SWidth, gif.file_type->SHeight, false, Image::FORMAT_RGBA8, frame_data); + } else if (std::is_same_v) { + Ref img; + img.instantiate(); + img->set_data(gif.file_type->SWidth, gif.file_type->SHeight, false, Image::FORMAT_RGBA8, frame_data); + static_cast(p_dest)->set_frame_image(current_frame, img); + static_cast(p_dest)->set_frame_delay(current_frame, delay); + } + + // What should happen after the frame has been drawn. + switch (gcb.DisposalMode) { + // Make the area of the current frame transparent. + case DISPOSE_BACKGROUND: { + for (int y = 0; y < img_desc.Height; y++) { + int write_y = y + img_desc.Top; + int write_index = (write_y * gif.file_type->SWidth + img_desc.Left) * RGBA; + memset(&screen.write[write_index], 0, img_desc.Width * RGBA); + } + } break; + // Reset the screen to the last undisposed frame. + case DISPOSE_PREVIOUS: { + int row_size = img_desc.Width * RGBA; + + // Clear the frame. + if (last_undisposed_frame == -1) { + for (int y = 0; y < img_desc.Height; y++) { + int write_y = y + img_desc.Top; + int write_index = (write_y * gif.file_type->SWidth + img_desc.Left) * RGBA; + memset(&screen.write[write_index], 0, row_size); + } + } else { + PackedByteArray last_frame_data; + if constexpr (std::is_same_v) { + last_frame_data = static_cast(p_dest)->get_data(); + } else if (std::is_same_v) { + last_frame_data = static_cast(p_dest)->get_frame_image(last_undisposed_frame)->get_data(); + for (int y = 0; y < img_desc.Height; y++) { + int write_y = y + img_desc.Top; + int write_index = (write_y * gif.file_type->SWidth + img_desc.Left) * RGBA; + memcpy(&screen.write[write_index], &last_frame_data.ptr()[write_index], row_size); + } + } + } + } break; + default: { + last_undisposed_frame = gif.file_type->ImageCount - 1; + } + } + + if (gif.file_type->ImageCount == p_max_frames && gif.file_type->ImageCount > 0) { + break; + } + } + + ERR_FAIL_COND_V_MSG(gif.file_type->ImageCount == 0, FAILED, "No frames found."); + + return OK; +} + +namespace GIFCommon { +Ref _gif_unpack(const Vector &p_buffer) { + int size = p_buffer.size(); + ERR_FAIL_COND_V(size <= 0, Ref()); + const uint8_t *r = p_buffer.ptr(); + + Ref img; + Error err = gif_load_image_from_buffer(*img, r, size); + + ERR_FAIL_COND_V_MSG(err != OK, Ref(), "Failed decoding GIF image."); + return img; +} + +Ref _gif_animated_unpack(const Vector &p_buffer, int p_max_frames) { + int size = p_buffer.size(); + ERR_FAIL_COND_V(size <= 0, Ref()); + const uint8_t *r = p_buffer.ptr(); + + Ref texture; + Error err = gif_load_image_frames_from_buffer(*texture, r, size, p_max_frames); + + ERR_FAIL_COND_V_MSG(err != OK, Ref(), "Failed decoding animated GIF image."); + return texture; +} + +Error gif_load_image_from_buffer(Image *p_image, const uint8_t *p_buffer, int p_buffer_len) { + return gif_load_from_buffer_t(p_image, p_buffer, p_buffer_len, 1); +} + +Error gif_load_image_frames_from_buffer(ImageFrames *p_image_frames, const uint8_t *p_buffer, int p_buffer_len, int p_max_frames) { + return gif_load_from_buffer_t(p_image_frames, p_buffer, p_buffer_len, p_max_frames); +} +} //namespace GIFCommon diff --git a/modules/gif/gif_common.h b/modules/gif/gif_common.h new file mode 100644 index 00000000000..3b1866a0ada --- /dev/null +++ b/modules/gif/gif_common.h @@ -0,0 +1,51 @@ +/**************************************************************************/ +/* gif_common.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. */ +/**************************************************************************/ + +#ifndef GIF_COMMON_H +#define GIF_COMMON_H + +#include "core/error/error_list.h" +#include "core/io/image.h" +#include "core/io/image_frames.h" +#include "core/variant/variant.h" + +struct GifFileType; + +namespace GIFCommon { +// Given a GIF file, unpack it into an image. +Ref _gif_unpack(const Vector &p_buffer); +Ref _gif_animated_unpack(const Vector &p_buffer, int p_max_frames); +Error gif_load_image_from_buffer(Image *p_image, const uint8_t *p_buffer, int p_buffer_len); +Error gif_load_image_frames_from_buffer(ImageFrames *p_image_frames, const uint8_t *p_buffer, int p_buffer_len, int p_max_frames); +} //namespace GIFCommon + +#endif // GIF_COMMON_H diff --git a/modules/gif/image_frames_loader_gif.cpp b/modules/gif/image_frames_loader_gif.cpp new file mode 100644 index 00000000000..3d452b50e63 --- /dev/null +++ b/modules/gif/image_frames_loader_gif.cpp @@ -0,0 +1,66 @@ +/**************************************************************************/ +/* image_frames_loader_gif.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_gif.h" + +#include "modules/gif/gif_common.h" + +static Ref _animated_gif_mem_loader_func(const uint8_t *p_gif_data, int p_size, int p_max_frames) { + Ref texture; + texture.instantiate(); + Error err = GIFCommon::gif_load_image_frames_from_buffer(texture.ptr(), p_gif_data, p_size, p_max_frames); + ERR_FAIL_COND_V(err, Ref()); + return texture; +} + +Error ImageFramesLoaderGIF::load_image_frames(Ref p_image_frames, Ref f, BitField p_flags, float p_scale, int p_max_frames) { + Vector 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 = GIFCommon::gif_load_image_frames_from_buffer(p_image_frames.ptr(), w, src_image_len, p_max_frames); + + return err; +} + +void ImageFramesLoaderGIF::get_recognized_extensions(List *p_extensions) const { + p_extensions->push_back("gif"); +} + +ImageFramesLoaderGIF::ImageFramesLoaderGIF() { + ImageFrames::_gif_mem_loader_func = _animated_gif_mem_loader_func; +} diff --git a/modules/gif/image_frames_loader_gif.h b/modules/gif/image_frames_loader_gif.h new file mode 100644 index 00000000000..8c93755dbac --- /dev/null +++ b/modules/gif/image_frames_loader_gif.h @@ -0,0 +1,45 @@ +/**************************************************************************/ +/* image_frames_loader_gif.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. */ +/**************************************************************************/ + +#ifndef IMAGE_FRAMES_LOADER_GIF_H +#define IMAGE_FRAMES_LOADER_GIF_H + +#include "core/io/image_frames_loader.h" + +class ImageFramesLoaderGIF : public ImageFramesFormatLoader { +public: + virtual Error load_image_frames(Ref p_image, Ref f, BitField p_flags, float p_scale, int p_max_frames); + virtual void get_recognized_extensions(List *p_extensions) const; + ImageFramesLoaderGIF(); +}; + +#endif // IMAGE_FRAMES_LOADER_GIF_H diff --git a/modules/gif/image_loader_gif.cpp b/modules/gif/image_loader_gif.cpp new file mode 100644 index 00000000000..ca25da18cef --- /dev/null +++ b/modules/gif/image_loader_gif.cpp @@ -0,0 +1,66 @@ +/**************************************************************************/ +/* image_loader_gif.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_loader_gif.h" + +#include "gif_common.h" + +static Ref _gif_mem_loader_func(const uint8_t *p_gif_data, int p_size) { + Ref img; + img.instantiate(); + Error err = GIFCommon::gif_load_image_from_buffer(img.ptr(), p_gif_data, p_size); + ERR_FAIL_COND_V(err, Ref()); + return img; +} + +Error ImageLoaderGIF::load_image(Ref p_image, Ref f, BitField p_flags, float p_scale) { + Vector 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 = GIFCommon::gif_load_image_from_buffer(p_image.ptr(), w, src_image_len); + + return err; +} + +void ImageLoaderGIF::get_recognized_extensions(List *p_extensions) const { + p_extensions->push_back("gif"); +} + +ImageLoaderGIF::ImageLoaderGIF() { + Image::_gif_mem_loader_func = _gif_mem_loader_func; +} diff --git a/modules/gif/image_loader_gif.h b/modules/gif/image_loader_gif.h new file mode 100644 index 00000000000..7e9bf7b5e55 --- /dev/null +++ b/modules/gif/image_loader_gif.h @@ -0,0 +1,45 @@ +/**************************************************************************/ +/* image_loader_gif.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. */ +/**************************************************************************/ + +#ifndef IMAGE_LOADER_GIF_H +#define IMAGE_LOADER_GIF_H + +#include "core/io/image_loader.h" + +class ImageLoaderGIF : public ImageFormatLoader { +public: + virtual Error load_image(Ref p_image, Ref f, BitField p_flags, float p_scale); + virtual void get_recognized_extensions(List *p_extensions) const; + ImageLoaderGIF(); +}; + +#endif // IMAGE_LOADER_GIF_H diff --git a/modules/gif/register_types.cpp b/modules/gif/register_types.cpp new file mode 100644 index 00000000000..dce62584e74 --- /dev/null +++ b/modules/gif/register_types.cpp @@ -0,0 +1,63 @@ +/**************************************************************************/ +/* register_types.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 "register_types.h" + +#include "image_frames_loader_gif.h" +#include "image_loader_gif.h" + +static Ref image_loader_gif; +static Ref image_frames_loader_gif; + +void initialize_gif_module(ModuleInitializationLevel p_level) { + if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) { + return; + } + + image_loader_gif.instantiate(); + ImageLoader::add_image_format_loader(image_loader_gif); + + image_frames_loader_gif.instantiate(); + ImageFramesLoader::add_image_frames_format_loader(image_frames_loader_gif); +} + +void uninitialize_gif_module(ModuleInitializationLevel p_level) { + if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) { + return; + } + + ImageLoader::remove_image_format_loader(image_loader_gif); + image_loader_gif.unref(); + + ImageFramesLoader::remove_image_frames_format_loader(image_frames_loader_gif); + image_frames_loader_gif.unref(); +} diff --git a/modules/gif/register_types.h b/modules/gif/register_types.h new file mode 100644 index 00000000000..6a0a5a14571 --- /dev/null +++ b/modules/gif/register_types.h @@ -0,0 +1,41 @@ +/**************************************************************************/ +/* register_types.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. */ +/**************************************************************************/ + +#ifndef GIF_REGISTER_TYPES_H +#define GIF_REGISTER_TYPES_H + +#include "modules/register_module_types.h" + +void initialize_gif_module(ModuleInitializationLevel p_level); +void uninitialize_gif_module(ModuleInitializationLevel p_level); + +#endif // GIF_REGISTER_TYPES_H diff --git a/scene/register_scene_types.cpp b/scene/register_scene_types.cpp index 8767a593560..b7bf6047d30 100644 --- a/scene/register_scene_types.cpp +++ b/scene/register_scene_types.cpp @@ -136,6 +136,7 @@ #include "scene/resources/physics_material.h" #include "scene/resources/placeholder_textures.h" #include "scene/resources/portable_compressed_texture.h" +#include "scene/resources/resource_format_animated_texture.h" #include "scene/resources/resource_format_text.h" #include "scene/resources/shader_include.h" #include "scene/resources/skeleton_profile.h" @@ -316,6 +317,7 @@ static Ref resource_loader_text; static Ref resource_loader_stream_texture; static Ref resource_loader_texture_layered; static Ref resource_loader_texture_3d; +static Ref resource_loader_animated_texture; static Ref resource_saver_shader; static Ref resource_loader_shader; @@ -341,6 +343,9 @@ void register_scene_types() { resource_loader_texture_3d.instantiate(); ResourceLoader::add_resource_format_loader(resource_loader_texture_3d); + resource_loader_animated_texture.instantiate(); + ResourceLoader::add_resource_format_loader(resource_loader_animated_texture); + resource_saver_text.instantiate(); ResourceSaver::add_resource_format_saver(resource_saver_text, true); @@ -939,6 +944,7 @@ void register_scene_types() { GDREGISTER_VIRTUAL_CLASS(Texture3D); GDREGISTER_CLASS(ImageTexture3D); GDREGISTER_CLASS(CompressedTexture3D); + GDREGISTER_CLASS(ImageFrames); GDREGISTER_CLASS(Cubemap); GDREGISTER_CLASS(CubemapArray); GDREGISTER_CLASS(Texture2DArray); @@ -1255,6 +1261,9 @@ void unregister_scene_types() { ResourceLoader::remove_resource_format_loader(resource_loader_texture_3d); resource_loader_texture_3d.unref(); + ResourceLoader::remove_resource_format_loader(resource_loader_animated_texture); + resource_loader_animated_texture.unref(); + ResourceLoader::remove_resource_format_loader(resource_loader_stream_texture); resource_loader_stream_texture.unref(); diff --git a/scene/resources/animated_texture.cpp b/scene/resources/animated_texture.cpp index 34e10a6be3a..8c3dacfec33 100644 --- a/scene/resources/animated_texture.cpp +++ b/scene/resources/animated_texture.cpp @@ -31,6 +31,10 @@ /**************************************************************************/ #include "animated_texture.h" +#include "core/error/error_macros.h" +#include "core/io/image.h" +#include "core/io/image_frames.h" +#include "scene/resources/image_texture.h" void AnimatedTexture::_update_proxy() { RWLockRead r(rw_lock); @@ -226,6 +230,44 @@ bool AnimatedTexture::is_pixel_opaque(int p_x, int p_y) const { return true; } +Ref AnimatedTexture::create_from_image_frames(const Ref &p_image_frames) { + ERR_FAIL_COND_V_MSG(p_image_frames.is_null(), Ref(), "Invalid image frames: null"); + + Ref animated_texture; + animated_texture.instantiate(); + animated_texture->set_from_image_frames(p_image_frames); + return animated_texture; +} + +void AnimatedTexture::set_from_image_frames(const Ref &p_image_frames) { + ERR_FAIL_COND_MSG(p_image_frames.is_null(), "Invalid image frames"); + if (p_image_frames->get_frame_count() > MAX_FRAMES) { + WARN_PRINT(vformat("ImageFrames' frame count %d is larger than '%d': all excess frames will be dropped.", p_image_frames->get_frame_count(), MAX_FRAMES)); + } + + RWLockWrite w(rw_lock); + frame_count = MIN(p_image_frames->get_frame_count(), MAX_FRAMES); + for (int frame_index = 0; frame_index < frame_count; frame_index++) { + Ref frame = p_image_frames->get_frame_image(frame_index); + frames[frame_index].texture = ImageTexture::create_from_image(frame); + frames[frame_index].duration = p_image_frames->get_frame_delay(frame_index); + } +} + +Ref AnimatedTexture::make_image_frames() const { + Ref image_frames; + image_frames.instantiate(); + image_frames->set_frame_count(frame_count); + + RWLockRead r(rw_lock); + for (int frame_index = 0; frame_index < frame_count; frame_index++) { + ERR_CONTINUE(frames[frame_index].texture.is_null()); + image_frames->set_frame_image(frame_index, frames[frame_index].texture->get_image()); + image_frames->set_frame_delay(frame_index, frames[frame_index].duration); + } + return image_frames; +} + void AnimatedTexture::_validate_property(PropertyInfo &p_property) const { String prop = p_property.name; if (prop.begins_with("frame_")) { @@ -258,6 +300,10 @@ void AnimatedTexture::_bind_methods() { ClassDB::bind_method(D_METHOD("set_frame_duration", "frame", "duration"), &AnimatedTexture::set_frame_duration); ClassDB::bind_method(D_METHOD("get_frame_duration", "frame"), &AnimatedTexture::get_frame_duration); + ClassDB::bind_static_method("AnimatedTexture", D_METHOD("create_from_image_frames", "image_frames"), &AnimatedTexture::create_from_image_frames); + ClassDB::bind_method(D_METHOD("set_from_image_frames", "image_frames"), &AnimatedTexture::set_from_image_frames); + ClassDB::bind_method(D_METHOD("make_image_frames"), &AnimatedTexture::make_image_frames); + ADD_PROPERTY(PropertyInfo(Variant::INT, "frames", PROPERTY_HINT_RANGE, "1," + itos(MAX_FRAMES), PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), "set_frames", "get_frames"); ADD_PROPERTY(PropertyInfo(Variant::INT, "current_frame", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "set_current_frame", "get_current_frame"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "pause"), "set_pause", "get_pause"); diff --git a/scene/resources/animated_texture.h b/scene/resources/animated_texture.h index b80606e8183..16778273baf 100644 --- a/scene/resources/animated_texture.h +++ b/scene/resources/animated_texture.h @@ -33,6 +33,7 @@ #ifndef ANIMATED_TEXTURE_H #define ANIMATED_TEXTURE_H +#include "core/io/image_frames.h" #include "scene/resources/texture.h" class AnimatedTexture : public Texture2D { @@ -105,6 +106,11 @@ class AnimatedTexture : public Texture2D { bool is_pixel_opaque(int p_x, int p_y) const override; + void set_from_image_frames(const Ref &p_image_frames); + static Ref create_from_image_frames(const Ref &p_image_frames); + + Ref make_image_frames() const; + AnimatedTexture(); ~AnimatedTexture(); }; diff --git a/scene/resources/resource_format_animated_texture.cpp b/scene/resources/resource_format_animated_texture.cpp new file mode 100644 index 00000000000..a2d77e6f9f0 --- /dev/null +++ b/scene/resources/resource_format_animated_texture.cpp @@ -0,0 +1,98 @@ +/**************************************************************************/ +/* resource_format_animated_texture.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 "resource_format_animated_texture.h" + +#include "scene/resources/animated_texture.h" +#include "scene/resources/image_texture.h" + +Ref ResourceFormatLoaderAnimatedTexture::load(const String &p_path, const String &p_original_path, Error *r_error, bool p_use_sub_threads, float *r_progress, CacheMode p_cache_mode) { + Ref f = FileAccess::open(p_path, FileAccess::READ); + if (!f->is_open()) { + if (r_error) { + *r_error = ERR_CANT_OPEN; + } + return Ref(); + } + + uint8_t header[4] = { 0, 0, 0, 0 }; + f->get_buffer(header, 4); + + bool unrecognized = header[0] != 'R' || header[1] != 'D' || header[2] != 'A' || header[3] != 'T'; + if (unrecognized) { + if (r_error) { + *r_error = ERR_FILE_UNRECOGNIZED; + } + ERR_FAIL_V(Ref()); + } + + Ref atex; + atex.instantiate(); + + [[maybe_unused]] uint32_t tex_flags = f->get_32(); + uint32_t frame_count = f->get_32(); + atex->set_frames(frame_count); + + uint32_t width = f->get_32(); + uint32_t height = f->get_32(); + + for (size_t current_frame = 0; current_frame < frame_count; current_frame++) { + // Frame image data. + LocalVector data; + uint32_t frame_byte_length = f->get_32(); + data.resize(frame_byte_length); + f->get_buffer(data.ptr(), frame_byte_length); + + Ref image; + image.instantiate(); + image->set_data(width, height, false, Image::FORMAT_RGBA8, data); + Ref frame = ImageTexture::create_from_image(image); + atex->set_frame_texture(current_frame, frame); + + // Frame delay data. + atex->set_frame_duration(current_frame, f->get_real()); + } + + return atex; +} + +void ResourceFormatLoaderAnimatedTexture::get_recognized_extensions(List *p_extensions) const { + p_extensions->push_back("atex"); +} + +bool ResourceFormatLoaderAnimatedTexture::handles_type(const String &p_type) const { + return p_type == "AnimatedTexture"; +} + +String ResourceFormatLoaderAnimatedTexture::get_resource_type(const String &p_path) const { + return p_path.get_extension().to_lower() == "atex" ? "AnimatedTexture" : String(); +} diff --git a/scene/resources/resource_format_animated_texture.h b/scene/resources/resource_format_animated_texture.h new file mode 100644 index 00000000000..586524b35d5 --- /dev/null +++ b/scene/resources/resource_format_animated_texture.h @@ -0,0 +1,46 @@ +/**************************************************************************/ +/* resource_format_animated_texture.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. */ +/**************************************************************************/ + +#ifndef RESOURCE_FORMAT_ANIMATED_TEXTURE_H +#define RESOURCE_FORMAT_ANIMATED_TEXTURE_H + +#include "core/io/resource_loader.h" + +class ResourceFormatLoaderAnimatedTexture : public ResourceFormatLoader { +public: + virtual Ref load(const String &p_path, const String &p_original_path = "", Error *r_error = nullptr, bool p_use_sub_threads = false, float *r_progress = nullptr, CacheMode p_cache_mode = CACHE_MODE_REUSE) override; + virtual void get_recognized_extensions(List *p_extensions) const override; + virtual bool handles_type(const String &p_type) const override; + virtual String get_resource_type(const String &p_path) const override; +}; + +#endif // RESOURCE_FORMAT_ANIMATED_TEXTURE_H diff --git a/thirdparty/README.md b/thirdparty/README.md index 7a95e3c7240..d5626bc4fd1 100644 --- a/thirdparty/README.md +++ b/thirdparty/README.md @@ -332,6 +332,25 @@ Files extracted from upstream source: - `LICENSE.TXT` and `docs/FTL.TXT` +## giflib + +- Upstream: http://sourceforge.net/projects/giflib +- Version: 5.2.2 (7cad0c7f5aaf5723b814549c024cac8c7d735077, 2024) +- License: MIT + +Files extracted from upstream source: + +- gif_err.c +- gif_lib.h +- dgif_lib.c +- egif_lib.c +- gifalloc.c +- gif_hash.{c,h} +- gif_lib_private.h +- openbsd-reallocarray.c +- COPYING + + ## glad - Upstream: https://github.com/Dav1dde/glad diff --git a/thirdparty/giflib/COPYING b/thirdparty/giflib/COPYING new file mode 100644 index 00000000000..b9c0b501260 --- /dev/null +++ b/thirdparty/giflib/COPYING @@ -0,0 +1,19 @@ +The GIFLIB distribution is Copyright (c) 1997 Eric S. Raymond + +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. diff --git a/thirdparty/giflib/dgif_lib.c b/thirdparty/giflib/dgif_lib.c new file mode 100644 index 00000000000..cbcf23f930e --- /dev/null +++ b/thirdparty/giflib/dgif_lib.c @@ -0,0 +1,1312 @@ +/****************************************************************************** + +dgif_lib.c - GIF decoding + +The functions here and in egif_lib.c are partitioned carefully so that +if you only require one of read and write capability, only one of these +two modules will be linked. Preserve this property! + +SPDX-License-Identifier: MIT + +*****************************************************************************/ + +#include +#include +#include +#include +#include +#include + +#ifdef _WIN32 +#include +#else +#include +#endif /* _WIN32 */ + +#include "gif_lib.h" +#include "gif_lib_private.h" + +/* compose unsigned little endian value */ +#define UNSIGNED_LITTLE_ENDIAN(lo, hi) ((lo) | ((hi) << 8)) + +/* avoid extra function call in case we use fread (TVT) */ +static int InternalRead(GifFileType *gif, GifByteType *buf, int len) { + // fprintf(stderr, "### Read: %d\n", len); + return (((GifFilePrivateType *)gif->Private)->Read + ? ((GifFilePrivateType *)gif->Private)->Read(gif, buf, len) + : fread(buf, 1, len, + ((GifFilePrivateType *)gif->Private)->File)); +} + +static int DGifGetWord(GifFileType *GifFile, GifWord *Word); +static int DGifSetupDecompress(GifFileType *GifFile); +static int DGifDecompressLine(GifFileType *GifFile, GifPixelType *Line, + int LineLen); +static int DGifGetPrefixChar(const GifPrefixType *Prefix, int Code, + int ClearCode); +static int DGifDecompressInput(GifFileType *GifFile, int *Code); +static int DGifBufferedInput(GifFileType *GifFile, GifByteType *Buf, + GifByteType *NextByte); + +/****************************************************************************** + Open a new GIF file for read, given by its name. + Returns dynamically allocated GifFileType pointer which serves as the GIF + info record. +******************************************************************************/ +GifFileType *DGifOpenFileName(const char *FileName, int *Error) { + int FileHandle; + GifFileType *GifFile; + + if ((FileHandle = open(FileName, O_RDONLY)) == -1) { + if (Error != NULL) { + *Error = D_GIF_ERR_OPEN_FAILED; + } + return NULL; + } + + GifFile = DGifOpenFileHandle(FileHandle, Error); + return GifFile; +} + +/****************************************************************************** + Update a new GIF file, given its file handle. + Returns dynamically allocated GifFileType pointer which serves as the GIF + info record. +******************************************************************************/ +GifFileType *DGifOpenFileHandle(int FileHandle, int *Error) { + char Buf[GIF_STAMP_LEN + 1]; + GifFileType *GifFile; + GifFilePrivateType *Private; + FILE *f; + + GifFile = (GifFileType *)malloc(sizeof(GifFileType)); + if (GifFile == NULL) { + if (Error != NULL) { + *Error = D_GIF_ERR_NOT_ENOUGH_MEM; + } + (void)close(FileHandle); + return NULL; + } + + /*@i1@*/ memset(GifFile, '\0', sizeof(GifFileType)); + + /* Belt and suspenders, in case the null pointer isn't zero */ + GifFile->SavedImages = NULL; + GifFile->SColorMap = NULL; + + Private = (GifFilePrivateType *)calloc(1, sizeof(GifFilePrivateType)); + if (Private == NULL) { + if (Error != NULL) { + *Error = D_GIF_ERR_NOT_ENOUGH_MEM; + } + (void)close(FileHandle); + free((char *)GifFile); + return NULL; + } + + /*@i1@*/ memset(Private, '\0', sizeof(GifFilePrivateType)); + +#ifdef _WIN32 + _setmode(FileHandle, O_BINARY); /* Make sure it is in binary mode. */ +#endif /* _WIN32 */ + + f = fdopen(FileHandle, "rb"); /* Make it into a stream: */ + + /*@-mustfreeonly@*/ + GifFile->Private = (void *)Private; + Private->FileHandle = FileHandle; + Private->File = f; + Private->FileState = FILE_STATE_READ; + Private->Read = NULL; /* don't use alternate input method (TVT) */ + GifFile->UserData = NULL; /* TVT */ + /*@=mustfreeonly@*/ + + /* Let's see if this is a GIF file: */ + /* coverity[check_return] */ + if (InternalRead(GifFile, (unsigned char *)Buf, GIF_STAMP_LEN) != + GIF_STAMP_LEN) { + if (Error != NULL) { + *Error = D_GIF_ERR_READ_FAILED; + } + (void)fclose(f); + free((char *)Private); + free((char *)GifFile); + return NULL; + } + + /* Check for GIF prefix at start of file */ + Buf[GIF_STAMP_LEN] = 0; + if (strncmp(GIF_STAMP, Buf, GIF_VERSION_POS) != 0) { + if (Error != NULL) { + *Error = D_GIF_ERR_NOT_GIF_FILE; + } + (void)fclose(f); + free((char *)Private); + free((char *)GifFile); + return NULL; + } + + if (DGifGetScreenDesc(GifFile) == GIF_ERROR) { + (void)fclose(f); + free((char *)Private); + free((char *)GifFile); + return NULL; + } + + GifFile->Error = 0; + + /* What version of GIF? */ + Private->gif89 = (Buf[GIF_VERSION_POS + 1] == '9'); + + return GifFile; +} + +/****************************************************************************** + GifFileType constructor with user supplied input function (TVT) +******************************************************************************/ +GifFileType *DGifOpen(void *userData, InputFunc readFunc, int *Error) { + char Buf[GIF_STAMP_LEN + 1]; + GifFileType *GifFile; + GifFilePrivateType *Private; + + GifFile = (GifFileType *)malloc(sizeof(GifFileType)); + if (GifFile == NULL) { + if (Error != NULL) { + *Error = D_GIF_ERR_NOT_ENOUGH_MEM; + } + return NULL; + } + + memset(GifFile, '\0', sizeof(GifFileType)); + + /* Belt and suspenders, in case the null pointer isn't zero */ + GifFile->SavedImages = NULL; + GifFile->SColorMap = NULL; + + Private = (GifFilePrivateType *)calloc(1, sizeof(GifFilePrivateType)); + if (!Private) { + if (Error != NULL) { + *Error = D_GIF_ERR_NOT_ENOUGH_MEM; + } + free((char *)GifFile); + return NULL; + } + /*@i1@*/ memset(Private, '\0', sizeof(GifFilePrivateType)); + + GifFile->Private = (void *)Private; + Private->FileHandle = 0; + Private->File = NULL; + Private->FileState = FILE_STATE_READ; + + Private->Read = readFunc; /* TVT */ + GifFile->UserData = userData; /* TVT */ + + /* Lets see if this is a GIF file: */ + /* coverity[check_return] */ + if (InternalRead(GifFile, (unsigned char *)Buf, GIF_STAMP_LEN) != + GIF_STAMP_LEN) { + if (Error != NULL) { + *Error = D_GIF_ERR_READ_FAILED; + } + free((char *)Private); + free((char *)GifFile); + return NULL; + } + + /* Check for GIF prefix at start of file */ + Buf[GIF_STAMP_LEN] = '\0'; + if (strncmp(GIF_STAMP, Buf, GIF_VERSION_POS) != 0) { + if (Error != NULL) { + *Error = D_GIF_ERR_NOT_GIF_FILE; + } + free((char *)Private); + free((char *)GifFile); + return NULL; + } + + if (DGifGetScreenDesc(GifFile) == GIF_ERROR) { + free((char *)Private); + free((char *)GifFile); + if (Error != NULL) { + *Error = D_GIF_ERR_NO_SCRN_DSCR; + } + return NULL; + } + + GifFile->Error = 0; + + /* What version of GIF? */ + Private->gif89 = (Buf[GIF_VERSION_POS + 1] == '9'); + + return GifFile; +} + +/****************************************************************************** + This routine should be called before any other DGif calls. Note that + this routine is called automatically from DGif file open routines. +******************************************************************************/ +int DGifGetScreenDesc(GifFileType *GifFile) { + int BitsPerPixel; + bool SortFlag; + GifByteType Buf[3]; + GifFilePrivateType *Private = (GifFilePrivateType *)GifFile->Private; + + if (!IS_READABLE(Private)) { + /* This file was NOT open for reading: */ + GifFile->Error = D_GIF_ERR_NOT_READABLE; + return GIF_ERROR; + } + + /* Put the screen descriptor into the file: */ + if (DGifGetWord(GifFile, &GifFile->SWidth) == GIF_ERROR || + DGifGetWord(GifFile, &GifFile->SHeight) == GIF_ERROR) { + return GIF_ERROR; + } + + if (InternalRead(GifFile, Buf, 3) != 3) { + GifFile->Error = D_GIF_ERR_READ_FAILED; + GifFreeMapObject(GifFile->SColorMap); + GifFile->SColorMap = NULL; + return GIF_ERROR; + } + GifFile->SColorResolution = (((Buf[0] & 0x70) + 1) >> 4) + 1; + SortFlag = (Buf[0] & 0x08) != 0; + BitsPerPixel = (Buf[0] & 0x07) + 1; + GifFile->SBackGroundColor = Buf[1]; + GifFile->AspectByte = Buf[2]; + if (Buf[0] & 0x80) { /* Do we have global color map? */ + int i; + + GifFile->SColorMap = GifMakeMapObject(1 << BitsPerPixel, NULL); + if (GifFile->SColorMap == NULL) { + GifFile->Error = D_GIF_ERR_NOT_ENOUGH_MEM; + return GIF_ERROR; + } + + /* Get the global color map: */ + GifFile->SColorMap->SortFlag = SortFlag; + for (i = 0; i < GifFile->SColorMap->ColorCount; i++) { + /* coverity[check_return] */ + if (InternalRead(GifFile, Buf, 3) != 3) { + GifFreeMapObject(GifFile->SColorMap); + GifFile->SColorMap = NULL; + GifFile->Error = D_GIF_ERR_READ_FAILED; + return GIF_ERROR; + } + GifFile->SColorMap->Colors[i].Red = Buf[0]; + GifFile->SColorMap->Colors[i].Green = Buf[1]; + GifFile->SColorMap->Colors[i].Blue = Buf[2]; + } + } else { + GifFile->SColorMap = NULL; + } + + /* + * No check here for whether the background color is in range for the + * screen color map. Possibly there should be. + */ + + return GIF_OK; +} + +const char *DGifGetGifVersion(GifFileType *GifFile) { + GifFilePrivateType *Private = (GifFilePrivateType *)GifFile->Private; + + if (Private->gif89) { + return GIF89_STAMP; + } else { + return GIF87_STAMP; + } +} + +/****************************************************************************** + This routine should be called before any attempt to read an image. +******************************************************************************/ +int DGifGetRecordType(GifFileType *GifFile, GifRecordType *Type) { + GifByteType Buf; + GifFilePrivateType *Private = (GifFilePrivateType *)GifFile->Private; + + if (!IS_READABLE(Private)) { + /* This file was NOT open for reading: */ + GifFile->Error = D_GIF_ERR_NOT_READABLE; + return GIF_ERROR; + } + + /* coverity[check_return] */ + if (InternalRead(GifFile, &Buf, 1) != 1) { + GifFile->Error = D_GIF_ERR_READ_FAILED; + return GIF_ERROR; + } + + // fprintf(stderr, "### DGifGetRecordType: %02x\n", Buf); + switch (Buf) { + case DESCRIPTOR_INTRODUCER: + *Type = IMAGE_DESC_RECORD_TYPE; + break; + case EXTENSION_INTRODUCER: + *Type = EXTENSION_RECORD_TYPE; + break; + case TERMINATOR_INTRODUCER: + *Type = TERMINATE_RECORD_TYPE; + break; + default: + *Type = UNDEFINED_RECORD_TYPE; + GifFile->Error = D_GIF_ERR_WRONG_RECORD; + return GIF_ERROR; + } + + return GIF_OK; +} + +int DGifGetImageHeader(GifFileType *GifFile) { + unsigned int BitsPerPixel; + GifByteType Buf[3]; + GifFilePrivateType *Private = (GifFilePrivateType *)GifFile->Private; + + if (!IS_READABLE(Private)) { + /* This file was NOT open for reading: */ + GifFile->Error = D_GIF_ERR_NOT_READABLE; + return GIF_ERROR; + } + + if (DGifGetWord(GifFile, &GifFile->Image.Left) == GIF_ERROR || + DGifGetWord(GifFile, &GifFile->Image.Top) == GIF_ERROR || + DGifGetWord(GifFile, &GifFile->Image.Width) == GIF_ERROR || + DGifGetWord(GifFile, &GifFile->Image.Height) == GIF_ERROR) { + return GIF_ERROR; + } + if (InternalRead(GifFile, Buf, 1) != 1) { + GifFile->Error = D_GIF_ERR_READ_FAILED; + GifFreeMapObject(GifFile->Image.ColorMap); + GifFile->Image.ColorMap = NULL; + return GIF_ERROR; + } + BitsPerPixel = (Buf[0] & 0x07) + 1; + GifFile->Image.Interlace = (Buf[0] & 0x40) ? true : false; + + /* Setup the colormap */ + if (GifFile->Image.ColorMap) { + GifFreeMapObject(GifFile->Image.ColorMap); + GifFile->Image.ColorMap = NULL; + } + /* Does this image have local color map? */ + if (Buf[0] & 0x80) { + unsigned int i; + + GifFile->Image.ColorMap = + GifMakeMapObject(1 << BitsPerPixel, NULL); + if (GifFile->Image.ColorMap == NULL) { + GifFile->Error = D_GIF_ERR_NOT_ENOUGH_MEM; + return GIF_ERROR; + } + + /* Get the image local color map: */ + for (i = 0; i < GifFile->Image.ColorMap->ColorCount; i++) { + /* coverity[check_return] */ + if (InternalRead(GifFile, Buf, 3) != 3) { + GifFreeMapObject(GifFile->Image.ColorMap); + GifFile->Error = D_GIF_ERR_READ_FAILED; + GifFile->Image.ColorMap = NULL; + return GIF_ERROR; + } + GifFile->Image.ColorMap->Colors[i].Red = Buf[0]; + GifFile->Image.ColorMap->Colors[i].Green = Buf[1]; + GifFile->Image.ColorMap->Colors[i].Blue = Buf[2]; + } + } + + Private->PixelCount = + (long)GifFile->Image.Width * (long)GifFile->Image.Height; + + /* Reset decompress algorithm parameters. */ + return DGifSetupDecompress(GifFile); +} + +/****************************************************************************** + This routine should be called before any attempt to read an image. + Note it is assumed the Image desc. header has been read. +******************************************************************************/ +int DGifGetImageDesc(GifFileType *GifFile) { + GifFilePrivateType *Private = (GifFilePrivateType *)GifFile->Private; + SavedImage *sp; + + if (!IS_READABLE(Private)) { + /* This file was NOT open for reading: */ + GifFile->Error = D_GIF_ERR_NOT_READABLE; + return GIF_ERROR; + } + + if (DGifGetImageHeader(GifFile) == GIF_ERROR) { + return GIF_ERROR; + } + + if (GifFile->SavedImages) { + SavedImage *new_saved_images = (SavedImage *)reallocarray( + GifFile->SavedImages, (GifFile->ImageCount + 1), + sizeof(SavedImage)); + if (new_saved_images == NULL) { + GifFile->Error = D_GIF_ERR_NOT_ENOUGH_MEM; + return GIF_ERROR; + } + GifFile->SavedImages = new_saved_images; + } else { + if ((GifFile->SavedImages = + (SavedImage *)malloc(sizeof(SavedImage))) == NULL) { + GifFile->Error = D_GIF_ERR_NOT_ENOUGH_MEM; + return GIF_ERROR; + } + } + + sp = &GifFile->SavedImages[GifFile->ImageCount]; + memcpy(&sp->ImageDesc, &GifFile->Image, sizeof(GifImageDesc)); + if (GifFile->Image.ColorMap != NULL) { + sp->ImageDesc.ColorMap = + GifMakeMapObject(GifFile->Image.ColorMap->ColorCount, + GifFile->Image.ColorMap->Colors); + if (sp->ImageDesc.ColorMap == NULL) { + GifFile->Error = D_GIF_ERR_NOT_ENOUGH_MEM; + return GIF_ERROR; + } + } + sp->RasterBits = (unsigned char *)NULL; + sp->ExtensionBlockCount = 0; + sp->ExtensionBlocks = (ExtensionBlock *)NULL; + + GifFile->ImageCount++; + + return GIF_OK; +} + +/****************************************************************************** + Get one full scanned line (Line) of length LineLen from GIF file. +******************************************************************************/ +int DGifGetLine(GifFileType *GifFile, GifPixelType *Line, int LineLen) { + GifByteType *Dummy; + GifFilePrivateType *Private = (GifFilePrivateType *)GifFile->Private; + + if (!IS_READABLE(Private)) { + /* This file was NOT open for reading: */ + GifFile->Error = D_GIF_ERR_NOT_READABLE; + return GIF_ERROR; + } + + if (!LineLen) { + LineLen = GifFile->Image.Width; + } + + if ((Private->PixelCount -= LineLen) > 0xffff0000UL) { + GifFile->Error = D_GIF_ERR_DATA_TOO_BIG; + return GIF_ERROR; + } + + if (DGifDecompressLine(GifFile, Line, LineLen) == GIF_OK) { + if (Private->PixelCount == 0) { + /* We probably won't be called any more, so let's clean + * up everything before we return: need to flush out all + * the rest of image until an empty block (size 0) + * detected. We use GetCodeNext. + */ + do { + if (DGifGetCodeNext(GifFile, &Dummy) == + GIF_ERROR) { + return GIF_ERROR; + } + } while (Dummy != NULL); + } + return GIF_OK; + } else { + return GIF_ERROR; + } +} + +/****************************************************************************** + Put one pixel (Pixel) into GIF file. +******************************************************************************/ +int DGifGetPixel(GifFileType *GifFile, GifPixelType Pixel) { + GifByteType *Dummy; + GifFilePrivateType *Private = (GifFilePrivateType *)GifFile->Private; + + if (!IS_READABLE(Private)) { + /* This file was NOT open for reading: */ + GifFile->Error = D_GIF_ERR_NOT_READABLE; + return GIF_ERROR; + } + if (--Private->PixelCount > 0xffff0000UL) { + GifFile->Error = D_GIF_ERR_DATA_TOO_BIG; + return GIF_ERROR; + } + + if (DGifDecompressLine(GifFile, &Pixel, 1) == GIF_OK) { + if (Private->PixelCount == 0) { + /* We probably won't be called any more, so let's clean + * up everything before we return: need to flush out all + * the rest of image until an empty block (size 0) + * detected. We use GetCodeNext. + */ + do { + if (DGifGetCodeNext(GifFile, &Dummy) == + GIF_ERROR) { + return GIF_ERROR; + } + } while (Dummy != NULL); + } + return GIF_OK; + } else { + return GIF_ERROR; + } +} + +/****************************************************************************** + Get an extension block (see GIF manual) from GIF file. This routine only + returns the first data block, and DGifGetExtensionNext should be called + after this one until NULL extension is returned. + The Extension should NOT be freed by the user (not dynamically allocated). + Note it is assumed the Extension description header has been read. +******************************************************************************/ +int DGifGetExtension(GifFileType *GifFile, int *ExtCode, + GifByteType **Extension) { + GifByteType Buf; + GifFilePrivateType *Private = (GifFilePrivateType *)GifFile->Private; + + // fprintf(stderr, "### -> DGifGetExtension:\n"); + if (!IS_READABLE(Private)) { + /* This file was NOT open for reading: */ + GifFile->Error = D_GIF_ERR_NOT_READABLE; + return GIF_ERROR; + } + + /* coverity[check_return] */ + if (InternalRead(GifFile, &Buf, 1) != 1) { + GifFile->Error = D_GIF_ERR_READ_FAILED; + return GIF_ERROR; + } + *ExtCode = Buf; + // fprintf(stderr, "### <- DGifGetExtension: %02x, about to call + // next\n", Buf); + + return DGifGetExtensionNext(GifFile, Extension); +} + +/****************************************************************************** + Get a following extension block (see GIF manual) from GIF file. This + routine should be called until NULL Extension is returned. + The Extension should NOT be freed by the user (not dynamically allocated). +******************************************************************************/ +int DGifGetExtensionNext(GifFileType *GifFile, GifByteType **Extension) { + GifByteType Buf; + GifFilePrivateType *Private = (GifFilePrivateType *)GifFile->Private; + + // fprintf(stderr, "### -> DGifGetExtensionNext\n"); + if (InternalRead(GifFile, &Buf, 1) != 1) { + GifFile->Error = D_GIF_ERR_READ_FAILED; + return GIF_ERROR; + } + // fprintf(stderr, "### DGifGetExtensionNext sees %d\n", Buf); + + if (Buf > 0) { + *Extension = Private->Buf; /* Use private unused buffer. */ + (*Extension)[0] = + Buf; /* Pascal strings notation (pos. 0 is len.). */ + /* coverity[tainted_data,check_return] */ + if (InternalRead(GifFile, &((*Extension)[1]), Buf) != Buf) { + GifFile->Error = D_GIF_ERR_READ_FAILED; + return GIF_ERROR; + } + } else { + *Extension = NULL; + } + // fprintf(stderr, "### <- DGifGetExtensionNext: %p\n", Extension); + + return GIF_OK; +} + +/****************************************************************************** + Extract a Graphics Control Block from raw extension data +******************************************************************************/ + +int DGifExtensionToGCB(const size_t GifExtensionLength, + const GifByteType *GifExtension, + GraphicsControlBlock *GCB) { + if (GifExtensionLength != 4) { + return GIF_ERROR; + } + + GCB->DisposalMode = (GifExtension[0] >> 2) & 0x07; + GCB->UserInputFlag = (GifExtension[0] & 0x02) != 0; + GCB->DelayTime = + UNSIGNED_LITTLE_ENDIAN(GifExtension[1], GifExtension[2]); + if (GifExtension[0] & 0x01) { + GCB->TransparentColor = (int)GifExtension[3]; + } else { + GCB->TransparentColor = NO_TRANSPARENT_COLOR; + } + + return GIF_OK; +} + +/****************************************************************************** + Extract the Graphics Control Block for a saved image, if it exists. +******************************************************************************/ + +int DGifSavedExtensionToGCB(GifFileType *GifFile, int ImageIndex, + GraphicsControlBlock *GCB) { + int i; + + if (ImageIndex < 0 || ImageIndex > GifFile->ImageCount - 1) { + return GIF_ERROR; + } + + GCB->DisposalMode = DISPOSAL_UNSPECIFIED; + GCB->UserInputFlag = false; + GCB->DelayTime = 0; + GCB->TransparentColor = NO_TRANSPARENT_COLOR; + + for (i = 0; i < GifFile->SavedImages[ImageIndex].ExtensionBlockCount; + i++) { + ExtensionBlock *ep = + &GifFile->SavedImages[ImageIndex].ExtensionBlocks[i]; + if (ep->Function == GRAPHICS_EXT_FUNC_CODE) { + return DGifExtensionToGCB(ep->ByteCount, ep->Bytes, + GCB); + } + } + + return GIF_ERROR; +} + +/****************************************************************************** + This routine should be called last, to close the GIF file. +******************************************************************************/ +int DGifCloseFile(GifFileType *GifFile, int *ErrorCode) { + GifFilePrivateType *Private; + + if (GifFile == NULL || GifFile->Private == NULL) { + return GIF_ERROR; + } + + if (GifFile->Image.ColorMap) { + GifFreeMapObject(GifFile->Image.ColorMap); + GifFile->Image.ColorMap = NULL; + } + + if (GifFile->SColorMap) { + GifFreeMapObject(GifFile->SColorMap); + GifFile->SColorMap = NULL; + } + + if (GifFile->SavedImages) { + GifFreeSavedImages(GifFile); + GifFile->SavedImages = NULL; + } + + GifFreeExtensions(&GifFile->ExtensionBlockCount, + &GifFile->ExtensionBlocks); + + Private = (GifFilePrivateType *)GifFile->Private; + + if (!IS_READABLE(Private)) { + /* This file was NOT open for reading: */ + if (ErrorCode != NULL) { + *ErrorCode = D_GIF_ERR_NOT_READABLE; + } + free((char *)GifFile->Private); + free(GifFile); + return GIF_ERROR; + } + + if (Private->File && (fclose(Private->File) != 0)) { + if (ErrorCode != NULL) { + *ErrorCode = D_GIF_ERR_CLOSE_FAILED; + } + free((char *)GifFile->Private); + free(GifFile); + return GIF_ERROR; + } + + free((char *)GifFile->Private); + free(GifFile); + if (ErrorCode != NULL) { + *ErrorCode = D_GIF_SUCCEEDED; + } + return GIF_OK; +} + +/****************************************************************************** + Get 2 bytes (word) from the given file: +******************************************************************************/ +static int DGifGetWord(GifFileType *GifFile, GifWord *Word) { + unsigned char c[2]; + + /* coverity[check_return] */ + if (InternalRead(GifFile, c, 2) != 2) { + GifFile->Error = D_GIF_ERR_READ_FAILED; + return GIF_ERROR; + } + + *Word = (GifWord)UNSIGNED_LITTLE_ENDIAN(c[0], c[1]); + return GIF_OK; +} + +/****************************************************************************** + Get the image code in compressed form. This routine can be called if the + information needed to be piped out as is. Obviously this is much faster + than decoding and encoding again. This routine should be followed by calls + to DGifGetCodeNext, until NULL block is returned. + The block should NOT be freed by the user (not dynamically allocated). +******************************************************************************/ +int DGifGetCode(GifFileType *GifFile, int *CodeSize, GifByteType **CodeBlock) { + GifFilePrivateType *Private = (GifFilePrivateType *)GifFile->Private; + + if (!IS_READABLE(Private)) { + /* This file was NOT open for reading: */ + GifFile->Error = D_GIF_ERR_NOT_READABLE; + return GIF_ERROR; + } + + *CodeSize = Private->BitsPerPixel; + + return DGifGetCodeNext(GifFile, CodeBlock); +} + +/****************************************************************************** + Continue to get the image code in compressed form. This routine should be + called until NULL block is returned. + The block should NOT be freed by the user (not dynamically allocated). +******************************************************************************/ +int DGifGetCodeNext(GifFileType *GifFile, GifByteType **CodeBlock) { + GifByteType Buf; + GifFilePrivateType *Private = (GifFilePrivateType *)GifFile->Private; + + /* coverity[tainted_data_argument] */ + /* coverity[check_return] */ + if (InternalRead(GifFile, &Buf, 1) != 1) { + GifFile->Error = D_GIF_ERR_READ_FAILED; + return GIF_ERROR; + } + + /* coverity[lower_bounds] */ + if (Buf > 0) { + *CodeBlock = Private->Buf; /* Use private unused buffer. */ + (*CodeBlock)[0] = + Buf; /* Pascal strings notation (pos. 0 is len.). */ + /* coverity[tainted_data] */ + if (InternalRead(GifFile, &((*CodeBlock)[1]), Buf) != Buf) { + GifFile->Error = D_GIF_ERR_READ_FAILED; + return GIF_ERROR; + } + } else { + *CodeBlock = NULL; + Private->Buf[0] = 0; /* Make sure the buffer is empty! */ + Private->PixelCount = + 0; /* And local info. indicate image read. */ + } + + return GIF_OK; +} + +/****************************************************************************** + Setup the LZ decompression for this image: +******************************************************************************/ +static int DGifSetupDecompress(GifFileType *GifFile) { + int i, BitsPerPixel; + GifByteType CodeSize; + GifPrefixType *Prefix; + GifFilePrivateType *Private = (GifFilePrivateType *)GifFile->Private; + + /* coverity[check_return] */ + if (InternalRead(GifFile, &CodeSize, 1) < + 1) { /* Read Code size from file. */ + GifFile->Error = D_GIF_ERR_READ_FAILED; + return GIF_ERROR; /* Failed to read Code size. */ + } + BitsPerPixel = CodeSize; + + /* this can only happen on a severely malformed GIF */ + if (BitsPerPixel > 8) { + GifFile->Error = + D_GIF_ERR_READ_FAILED; /* somewhat bogus error code */ + return GIF_ERROR; /* Failed to read Code size. */ + } + + Private->Buf[0] = 0; /* Input Buffer empty. */ + Private->BitsPerPixel = BitsPerPixel; + Private->ClearCode = (1 << BitsPerPixel); + Private->EOFCode = Private->ClearCode + 1; + Private->RunningCode = Private->EOFCode + 1; + Private->RunningBits = BitsPerPixel + 1; /* Number of bits per code. */ + Private->MaxCode1 = 1 << Private->RunningBits; /* Max. code + 1. */ + Private->StackPtr = 0; /* No pixels on the pixel stack. */ + Private->LastCode = NO_SUCH_CODE; + Private->CrntShiftState = 0; /* No information in CrntShiftDWord. */ + Private->CrntShiftDWord = 0; + + Prefix = Private->Prefix; + for (i = 0; i <= LZ_MAX_CODE; i++) { + Prefix[i] = NO_SUCH_CODE; + } + + return GIF_OK; +} + +/****************************************************************************** + The LZ decompression routine: + This version decompress the given GIF file into Line of length LineLen. + This routine can be called few times (one per scan line, for example), in + order the complete the whole image. +******************************************************************************/ +static int DGifDecompressLine(GifFileType *GifFile, GifPixelType *Line, + int LineLen) { + int i = 0; + int j, CrntCode, EOFCode, ClearCode, CrntPrefix, LastCode, StackPtr; + GifByteType *Stack, *Suffix; + GifPrefixType *Prefix; + GifFilePrivateType *Private = (GifFilePrivateType *)GifFile->Private; + + StackPtr = Private->StackPtr; + Prefix = Private->Prefix; + Suffix = Private->Suffix; + Stack = Private->Stack; + EOFCode = Private->EOFCode; + ClearCode = Private->ClearCode; + LastCode = Private->LastCode; + + if (StackPtr > LZ_MAX_CODE) { + return GIF_ERROR; + } + + if (StackPtr != 0) { + /* Let pop the stack off before continueing to read the GIF + * file: */ + while (StackPtr != 0 && i < LineLen) { + Line[i++] = Stack[--StackPtr]; + } + } + + while (i < LineLen) { /* Decode LineLen items. */ + if (DGifDecompressInput(GifFile, &CrntCode) == GIF_ERROR) { + return GIF_ERROR; + } + + if (CrntCode == EOFCode) { + /* Note however that usually we will not be here as we + * will stop decoding as soon as we got all the pixel, + * or EOF code will not be read at all, and + * DGifGetLine/Pixel clean everything. */ + GifFile->Error = D_GIF_ERR_EOF_TOO_SOON; + return GIF_ERROR; + } else if (CrntCode == ClearCode) { + /* We need to start over again: */ + for (j = 0; j <= LZ_MAX_CODE; j++) { + Prefix[j] = NO_SUCH_CODE; + } + Private->RunningCode = Private->EOFCode + 1; + Private->RunningBits = Private->BitsPerPixel + 1; + Private->MaxCode1 = 1 << Private->RunningBits; + LastCode = Private->LastCode = NO_SUCH_CODE; + } else { + /* Its regular code - if in pixel range simply add it to + * output stream, otherwise trace to codes linked list + * until the prefix is in pixel range: */ + if (CrntCode < ClearCode) { + /* This is simple - its pixel scalar, so add it + * to output: */ + Line[i++] = CrntCode; + } else { + /* Its a code to needed to be traced: trace the + * linked list until the prefix is a pixel, + * while pushing the suffix pixels on our stack. + * If we done, pop the stack in reverse (thats + * what stack is good for!) order to output. */ + if (Prefix[CrntCode] == NO_SUCH_CODE) { + CrntPrefix = LastCode; + + /* Only allowed if CrntCode is exactly + * the running code: In that case + * CrntCode = XXXCode, CrntCode or the + * prefix code is last code and the + * suffix char is exactly the prefix of + * last code! */ + if (CrntCode == + Private->RunningCode - 2) { + Suffix[Private->RunningCode - + 2] = Stack[StackPtr++] = + DGifGetPrefixChar( + Prefix, LastCode, + ClearCode); + } else { + Suffix[Private->RunningCode - + 2] = Stack[StackPtr++] = + DGifGetPrefixChar( + Prefix, CrntCode, + ClearCode); + } + } else { + CrntPrefix = CrntCode; + } + + /* Now (if image is O.K.) we should not get a + * NO_SUCH_CODE during the trace. As we might + * loop forever, in case of defective image, we + * use StackPtr as loop counter and stop before + * overflowing Stack[]. */ + while (StackPtr < LZ_MAX_CODE && + CrntPrefix > ClearCode && + CrntPrefix <= LZ_MAX_CODE) { + Stack[StackPtr++] = Suffix[CrntPrefix]; + CrntPrefix = Prefix[CrntPrefix]; + } + if (StackPtr >= LZ_MAX_CODE || + CrntPrefix > LZ_MAX_CODE) { + GifFile->Error = D_GIF_ERR_IMAGE_DEFECT; + return GIF_ERROR; + } + /* Push the last character on stack: */ + Stack[StackPtr++] = CrntPrefix; + + /* Now lets pop all the stack into output: */ + while (StackPtr != 0 && i < LineLen) { + Line[i++] = Stack[--StackPtr]; + } + } + if (LastCode != NO_SUCH_CODE && + Private->RunningCode - 2 < (LZ_MAX_CODE + 1) && + Prefix[Private->RunningCode - 2] == NO_SUCH_CODE) { + Prefix[Private->RunningCode - 2] = LastCode; + + if (CrntCode == Private->RunningCode - 2) { + /* Only allowed if CrntCode is exactly + * the running code: In that case + * CrntCode = XXXCode, CrntCode or the + * prefix code is last code and the + * suffix char is exactly the prefix of + * last code! */ + Suffix[Private->RunningCode - 2] = + DGifGetPrefixChar(Prefix, LastCode, + ClearCode); + } else { + Suffix[Private->RunningCode - 2] = + DGifGetPrefixChar(Prefix, CrntCode, + ClearCode); + } + } + LastCode = CrntCode; + } + } + + Private->LastCode = LastCode; + Private->StackPtr = StackPtr; + + return GIF_OK; +} + +/****************************************************************************** + Routine to trace the Prefixes linked list until we get a prefix which is + not code, but a pixel value (less than ClearCode). Returns that pixel value. + If image is defective, we might loop here forever, so we limit the loops to + the maximum possible if image O.k. - LZ_MAX_CODE times. +******************************************************************************/ +static int DGifGetPrefixChar(const GifPrefixType *Prefix, int Code, + int ClearCode) { + int i = 0; + + while (Code > ClearCode && i++ <= LZ_MAX_CODE) { + if (Code > LZ_MAX_CODE) { + return NO_SUCH_CODE; + } + Code = Prefix[Code]; + } + return Code; +} + +/****************************************************************************** + Interface for accessing the LZ codes directly. Set Code to the real code + (12bits), or to -1 if EOF code is returned. +******************************************************************************/ +int DGifGetLZCodes(GifFileType *GifFile, int *Code) { + GifByteType *CodeBlock; + GifFilePrivateType *Private = (GifFilePrivateType *)GifFile->Private; + + if (!IS_READABLE(Private)) { + /* This file was NOT open for reading: */ + GifFile->Error = D_GIF_ERR_NOT_READABLE; + return GIF_ERROR; + } + + if (DGifDecompressInput(GifFile, Code) == GIF_ERROR) { + return GIF_ERROR; + } + + if (*Code == Private->EOFCode) { + /* Skip rest of codes (hopefully only NULL terminating block): + */ + do { + if (DGifGetCodeNext(GifFile, &CodeBlock) == GIF_ERROR) { + return GIF_ERROR; + } + } while (CodeBlock != NULL); + + *Code = -1; + } else if (*Code == Private->ClearCode) { + /* We need to start over again: */ + Private->RunningCode = Private->EOFCode + 1; + Private->RunningBits = Private->BitsPerPixel + 1; + Private->MaxCode1 = 1 << Private->RunningBits; + } + + return GIF_OK; +} + +/****************************************************************************** + The LZ decompression input routine: + This routine is responsable for the decompression of the bit stream from + 8 bits (bytes) packets, into the real codes. + Returns GIF_OK if read successfully. +******************************************************************************/ +static int DGifDecompressInput(GifFileType *GifFile, int *Code) { + static const unsigned short CodeMasks[] = { + 0x0000, 0x0001, 0x0003, 0x0007, 0x000f, 0x001f, 0x003f, + 0x007f, 0x00ff, 0x01ff, 0x03ff, 0x07ff, 0x0fff}; + + GifFilePrivateType *Private = (GifFilePrivateType *)GifFile->Private; + + GifByteType NextByte; + + /* The image can't contain more than LZ_BITS per code. */ + if (Private->RunningBits > LZ_BITS) { + GifFile->Error = D_GIF_ERR_IMAGE_DEFECT; + return GIF_ERROR; + } + + while (Private->CrntShiftState < Private->RunningBits) { + /* Needs to get more bytes from input stream for next code: */ + if (DGifBufferedInput(GifFile, Private->Buf, &NextByte) == + GIF_ERROR) { + return GIF_ERROR; + } + Private->CrntShiftDWord |= ((unsigned long)NextByte) + << Private->CrntShiftState; + Private->CrntShiftState += 8; + } + *Code = Private->CrntShiftDWord & CodeMasks[Private->RunningBits]; + + Private->CrntShiftDWord >>= Private->RunningBits; + Private->CrntShiftState -= Private->RunningBits; + + /* If code cannot fit into RunningBits bits, must raise its size. Note + * however that codes above 4095 are used for special signaling. + * If we're using LZ_BITS bits already and we're at the max code, just + * keep using the table as it is, don't increment Private->RunningCode. + */ + if (Private->RunningCode < LZ_MAX_CODE + 2 && + ++Private->RunningCode > Private->MaxCode1 && + Private->RunningBits < LZ_BITS) { + Private->MaxCode1 <<= 1; + Private->RunningBits++; + } + return GIF_OK; +} + +/****************************************************************************** + This routines read one GIF data block at a time and buffers it internally + so that the decompression routine could access it. + The routine returns the next byte from its internal buffer (or read next + block in if buffer empty) and returns GIF_OK if succesful. +******************************************************************************/ +static int DGifBufferedInput(GifFileType *GifFile, GifByteType *Buf, + GifByteType *NextByte) { + if (Buf[0] == 0) { + /* Needs to read the next buffer - this one is empty: */ + /* coverity[check_return] */ + if (InternalRead(GifFile, Buf, 1) != 1) { + GifFile->Error = D_GIF_ERR_READ_FAILED; + return GIF_ERROR; + } + /* There shouldn't be any empty data blocks here as the LZW spec + * says the LZW termination code should come first. Therefore + * we shouldn't be inside this routine at that point. + */ + if (Buf[0] == 0) { + GifFile->Error = D_GIF_ERR_IMAGE_DEFECT; + return GIF_ERROR; + } + if (InternalRead(GifFile, &Buf[1], Buf[0]) != Buf[0]) { + GifFile->Error = D_GIF_ERR_READ_FAILED; + return GIF_ERROR; + } + *NextByte = Buf[1]; + Buf[1] = 2; /* We use now the second place as last char read! */ + Buf[0]--; + } else { + *NextByte = Buf[Buf[1]++]; + Buf[0]--; + } + + return GIF_OK; +} + +/****************************************************************************** + This routine is called in case of error during parsing image. We need to + decrease image counter and reallocate memory for saved images. Not decreasing + ImageCount may lead to null pointer dereference, because the last element in + SavedImages may point to the spoilt image and null pointer buffers. +*******************************************************************************/ +void DGifDecreaseImageCounter(GifFileType *GifFile) { + GifFile->ImageCount--; + if (GifFile->SavedImages[GifFile->ImageCount].RasterBits != NULL) { + free(GifFile->SavedImages[GifFile->ImageCount].RasterBits); + } + + // Realloc array according to the new image counter. + SavedImage *correct_saved_images = (SavedImage *)reallocarray( + GifFile->SavedImages, GifFile->ImageCount, sizeof(SavedImage)); + if (correct_saved_images != NULL) { + GifFile->SavedImages = correct_saved_images; + } +} + +/****************************************************************************** + This routine reads an entire GIF into core, hanging all its state info off + the GifFileType pointer. Call DGifOpenFileName() or DGifOpenFileHandle() + first to initialize I/O. Its inverse is EGifSpew(). +*******************************************************************************/ +int DGifSlurp(GifFileType *GifFile) { + size_t ImageSize; + GifRecordType RecordType; + SavedImage *sp; + GifByteType *ExtData; + int ExtFunction; + + GifFile->ExtensionBlocks = NULL; + GifFile->ExtensionBlockCount = 0; + + do { + if (DGifGetRecordType(GifFile, &RecordType) == GIF_ERROR) { + return (GIF_ERROR); + } + + switch (RecordType) { + case IMAGE_DESC_RECORD_TYPE: + if (DGifGetImageDesc(GifFile) == GIF_ERROR) { + return (GIF_ERROR); + } + + sp = &GifFile->SavedImages[GifFile->ImageCount - 1]; + /* Allocate memory for the image */ + if (sp->ImageDesc.Width <= 0 || + sp->ImageDesc.Height <= 0 || + sp->ImageDesc.Width > + (INT_MAX / sp->ImageDesc.Height)) { + DGifDecreaseImageCounter(GifFile); + return GIF_ERROR; + } + ImageSize = sp->ImageDesc.Width * sp->ImageDesc.Height; + + if (ImageSize > (SIZE_MAX / sizeof(GifPixelType))) { + DGifDecreaseImageCounter(GifFile); + return GIF_ERROR; + } + sp->RasterBits = (unsigned char *)reallocarray( + NULL, ImageSize, sizeof(GifPixelType)); + + if (sp->RasterBits == NULL) { + DGifDecreaseImageCounter(GifFile); + return GIF_ERROR; + } + + if (sp->ImageDesc.Interlace) { + int i, j; + /* + * The way an interlaced image should be read - + * offsets and jumps... + */ + static const int InterlacedOffset[] = {0, 4, 2, + 1}; + static const int InterlacedJumps[] = {8, 8, 4, + 2}; + /* Need to perform 4 passes on the image */ + for (i = 0; i < 4; i++) { + for (j = InterlacedOffset[i]; + j < sp->ImageDesc.Height; + j += InterlacedJumps[i]) { + if (DGifGetLine( + GifFile, + sp->RasterBits + + j * sp->ImageDesc + .Width, + sp->ImageDesc.Width) == + GIF_ERROR) { + DGifDecreaseImageCounter( + GifFile); + return GIF_ERROR; + } + } + } + } else { + if (DGifGetLine(GifFile, sp->RasterBits, + ImageSize) == GIF_ERROR) { + DGifDecreaseImageCounter(GifFile); + return GIF_ERROR; + } + } + + if (GifFile->ExtensionBlocks) { + sp->ExtensionBlocks = GifFile->ExtensionBlocks; + sp->ExtensionBlockCount = + GifFile->ExtensionBlockCount; + + GifFile->ExtensionBlocks = NULL; + GifFile->ExtensionBlockCount = 0; + } + break; + + case EXTENSION_RECORD_TYPE: + if (DGifGetExtension(GifFile, &ExtFunction, &ExtData) == + GIF_ERROR) { + return (GIF_ERROR); + } + /* Create an extension block with our data */ + if (ExtData != NULL) { + if (GifAddExtensionBlock( + &GifFile->ExtensionBlockCount, + &GifFile->ExtensionBlocks, ExtFunction, + ExtData[0], &ExtData[1]) == GIF_ERROR) { + return (GIF_ERROR); + } + } + for (;;) { + if (DGifGetExtensionNext(GifFile, &ExtData) == + GIF_ERROR) { + return (GIF_ERROR); + } + if (ExtData == NULL) { + break; + } + /* Continue the extension block */ + if (GifAddExtensionBlock( + &GifFile->ExtensionBlockCount, + &GifFile->ExtensionBlocks, + CONTINUE_EXT_FUNC_CODE, ExtData[0], + &ExtData[1]) == GIF_ERROR) { + return (GIF_ERROR); + } + } + break; + + case TERMINATE_RECORD_TYPE: + break; + + default: /* Should be trapped by DGifGetRecordType */ + break; + } + } while (RecordType != TERMINATE_RECORD_TYPE); + + /* Sanity check for corrupted file */ + if (GifFile->ImageCount == 0) { + GifFile->Error = D_GIF_ERR_NO_IMAG_DSCR; + return (GIF_ERROR); + } + + return (GIF_OK); +} + +/* end */ diff --git a/thirdparty/giflib/egif_lib.c b/thirdparty/giflib/egif_lib.c new file mode 100644 index 00000000000..15268682868 --- /dev/null +++ b/thirdparty/giflib/egif_lib.c @@ -0,0 +1,1163 @@ +/****************************************************************************** + +egif_lib.c - GIF encoding + +The functions here and in dgif_lib.c are partitioned carefully so that +if you only require one of read and write capability, only one of these +two modules will be linked. Preserve this property! + +SPDX-License-Identifier: MIT + +*****************************************************************************/ + +#include +#include +#include +#include +#include + +#ifdef _WIN32 +#include +#else +#include +#include +#endif /* _WIN32 */ +#include + +#include "gif_lib.h" +#include "gif_lib_private.h" + +/* Masks given codes to BitsPerPixel, to make sure all codes are in range: */ +/*@+charint@*/ +static const GifPixelType CodeMask[] = {0x00, 0x01, 0x03, 0x07, 0x0f, + 0x1f, 0x3f, 0x7f, 0xff}; +/*@-charint@*/ + +static int EGifPutWord(int Word, GifFileType *GifFile); +static int EGifSetupCompress(GifFileType *GifFile); +static int EGifCompressLine(GifFileType *GifFile, const GifPixelType *Line, + const int LineLen); +static int EGifCompressOutput(GifFileType *GifFile, int Code); +static int EGifBufferedOutput(GifFileType *GifFile, GifByteType *Buf, int c); + +/* extract bytes from an unsigned word */ +#define LOBYTE(x) ((x)&0xff) +#define HIBYTE(x) (((x) >> 8) & 0xff) + +#ifndef S_IREAD +#define S_IREAD S_IRUSR +#endif + +#ifndef S_IWRITE +#define S_IWRITE S_IWUSR +#endif +/****************************************************************************** + Open a new GIF file for write, specified by name. If TestExistance then + if the file exists this routines fails (returns NULL). + Returns a dynamically allocated GifFileType pointer which serves as the GIF + info record. The Error member is cleared if successful. +******************************************************************************/ +GifFileType *EGifOpenFileName(const char *FileName, const bool TestExistence, + int *Error) { + + int FileHandle; + GifFileType *GifFile; + + if (TestExistence) { + FileHandle = open(FileName, O_WRONLY | O_CREAT | O_EXCL, + S_IREAD | S_IWRITE); + } else { + FileHandle = open(FileName, O_WRONLY | O_CREAT | O_TRUNC, + S_IREAD | S_IWRITE); + } + + if (FileHandle == -1) { + if (Error != NULL) { + *Error = E_GIF_ERR_OPEN_FAILED; + } + return NULL; + } + GifFile = EGifOpenFileHandle(FileHandle, Error); + if (GifFile == (GifFileType *)NULL) { + (void)close(FileHandle); + } + return GifFile; +} + +/****************************************************************************** + Update a new GIF file, given its file handle, which must be opened for + write in binary mode. + Returns dynamically allocated a GifFileType pointer which serves as the GIF + info record. + Only fails on a memory allocation error. +******************************************************************************/ +GifFileType *EGifOpenFileHandle(const int FileHandle, int *Error) { + GifFileType *GifFile; + GifFilePrivateType *Private; + FILE *f; + + GifFile = (GifFileType *)malloc(sizeof(GifFileType)); + if (GifFile == NULL) { + return NULL; + } + + memset(GifFile, '\0', sizeof(GifFileType)); + + Private = (GifFilePrivateType *)malloc(sizeof(GifFilePrivateType)); + if (Private == NULL) { + free(GifFile); + if (Error != NULL) { + *Error = E_GIF_ERR_NOT_ENOUGH_MEM; + } + return NULL; + } + /*@i1@*/ memset(Private, '\0', sizeof(GifFilePrivateType)); + if ((Private->HashTable = _InitHashTable()) == NULL) { + free(GifFile); + free(Private); + if (Error != NULL) { + *Error = E_GIF_ERR_NOT_ENOUGH_MEM; + } + return NULL; + } + +#ifdef _WIN32 + _setmode(FileHandle, O_BINARY); /* Make sure it is in binary mode. */ +#endif /* _WIN32 */ + + f = fdopen(FileHandle, "wb"); /* Make it into a stream: */ + + GifFile->Private = (void *)Private; + Private->FileHandle = FileHandle; + Private->File = f; + Private->FileState = FILE_STATE_WRITE; + Private->gif89 = false; + + Private->Write = (OutputFunc)0; /* No user write routine (MRB) */ + GifFile->UserData = (void *)NULL; /* No user write handle (MRB) */ + + GifFile->Error = 0; + + return GifFile; +} + +/****************************************************************************** + Output constructor that takes user supplied output function. + Basically just a copy of EGifOpenFileHandle. (MRB) +******************************************************************************/ +GifFileType *EGifOpen(void *userData, OutputFunc writeFunc, int *Error) { + GifFileType *GifFile; + GifFilePrivateType *Private; + + GifFile = (GifFileType *)malloc(sizeof(GifFileType)); + if (GifFile == NULL) { + if (Error != NULL) { + *Error = E_GIF_ERR_NOT_ENOUGH_MEM; + } + return NULL; + } + + memset(GifFile, '\0', sizeof(GifFileType)); + + Private = (GifFilePrivateType *)malloc(sizeof(GifFilePrivateType)); + if (Private == NULL) { + free(GifFile); + if (Error != NULL) { + *Error = E_GIF_ERR_NOT_ENOUGH_MEM; + } + return NULL; + } + + memset(Private, '\0', sizeof(GifFilePrivateType)); + + Private->HashTable = _InitHashTable(); + if (Private->HashTable == NULL) { + free(GifFile); + free(Private); + if (Error != NULL) { + *Error = E_GIF_ERR_NOT_ENOUGH_MEM; + } + return NULL; + } + + GifFile->Private = (void *)Private; + Private->FileHandle = 0; + Private->File = (FILE *)0; + Private->FileState = FILE_STATE_WRITE; + + Private->Write = writeFunc; /* User write routine (MRB) */ + GifFile->UserData = userData; /* User write handle (MRB) */ + + Private->gif89 = false; /* initially, write GIF87 */ + + GifFile->Error = 0; + + return GifFile; +} + +/****************************************************************************** + Routine to compute the GIF version that will be written on output. +******************************************************************************/ +const char *EGifGetGifVersion(GifFileType *GifFile) { + GifFilePrivateType *Private = (GifFilePrivateType *)GifFile->Private; + int i, j; + + /* + * Bulletproofing - always write GIF89 if we need to. + * Note, we don't clear the gif89 flag here because + * users of the sequential API might have called EGifSetGifVersion() + * in order to set that flag. + */ + for (i = 0; i < GifFile->ImageCount; i++) { + for (j = 0; j < GifFile->SavedImages[i].ExtensionBlockCount; + j++) { + int function = + GifFile->SavedImages[i].ExtensionBlocks[j].Function; + + if (function == COMMENT_EXT_FUNC_CODE || + function == GRAPHICS_EXT_FUNC_CODE || + function == PLAINTEXT_EXT_FUNC_CODE || + function == APPLICATION_EXT_FUNC_CODE) { + Private->gif89 = true; + } + } + } + for (i = 0; i < GifFile->ExtensionBlockCount; i++) { + int function = GifFile->ExtensionBlocks[i].Function; + + if (function == COMMENT_EXT_FUNC_CODE || + function == GRAPHICS_EXT_FUNC_CODE || + function == PLAINTEXT_EXT_FUNC_CODE || + function == APPLICATION_EXT_FUNC_CODE) { + Private->gif89 = true; + } + } + + if (Private->gif89) { + return GIF89_STAMP; + } else { + return GIF87_STAMP; + } +} + +/****************************************************************************** + Set the GIF version. In the extremely unlikely event that there is ever + another version, replace the bool argument with an enum in which the + GIF87 value is 0 (numerically the same as bool false) and the GIF89 value + is 1 (numerically the same as bool true). That way we'll even preserve + object-file compatibility! +******************************************************************************/ +void EGifSetGifVersion(GifFileType *GifFile, const bool gif89) { + GifFilePrivateType *Private = (GifFilePrivateType *)GifFile->Private; + + Private->gif89 = gif89; +} + +/****************************************************************************** + All writes to the GIF should go through this. +******************************************************************************/ +static int InternalWrite(GifFileType *GifFileOut, const unsigned char *buf, + size_t len) { + GifFilePrivateType *Private = (GifFilePrivateType *)GifFileOut->Private; + if (Private->Write) { + return Private->Write(GifFileOut, buf, len); + } else { + return fwrite(buf, 1, len, Private->File); + } +} + +/****************************************************************************** + This routine should be called before any other EGif calls, immediately + following the GIF file opening. +******************************************************************************/ +int EGifPutScreenDesc(GifFileType *GifFile, const int Width, const int Height, + const int ColorRes, const int BackGround, + const ColorMapObject *ColorMap) { + GifByteType Buf[3]; + GifFilePrivateType *Private = (GifFilePrivateType *)GifFile->Private; + const char *write_version; + GifFile->SColorMap = NULL; + + if (Private->FileState & FILE_STATE_SCREEN) { + /* If already has screen descriptor - something is wrong! */ + GifFile->Error = E_GIF_ERR_HAS_SCRN_DSCR; + return GIF_ERROR; + } + if (!IS_WRITEABLE(Private)) { + /* This file was NOT open for writing: */ + GifFile->Error = E_GIF_ERR_NOT_WRITEABLE; + return GIF_ERROR; + } + + write_version = EGifGetGifVersion(GifFile); + + /* First write the version prefix into the file. */ + if (InternalWrite(GifFile, (unsigned char *)write_version, + strlen(write_version)) != strlen(write_version)) { + GifFile->Error = E_GIF_ERR_WRITE_FAILED; + return GIF_ERROR; + } + + GifFile->SWidth = Width; + GifFile->SHeight = Height; + GifFile->SColorResolution = ColorRes; + GifFile->SBackGroundColor = BackGround; + if (ColorMap) { + GifFile->SColorMap = + GifMakeMapObject(ColorMap->ColorCount, ColorMap->Colors); + if (GifFile->SColorMap == NULL) { + GifFile->Error = E_GIF_ERR_NOT_ENOUGH_MEM; + return GIF_ERROR; + } + } else { + GifFile->SColorMap = NULL; + } + + /* + * Put the logical screen descriptor into the file: + */ + /* Logical Screen Descriptor: Dimensions */ + (void)EGifPutWord(Width, GifFile); + (void)EGifPutWord(Height, GifFile); + + /* Logical Screen Descriptor: Packed Fields */ + /* Note: We have actual size of the color table default to the largest + * possible size (7+1 == 8 bits) because the decoder can use it to + * decide how to display the files. + */ + Buf[0] = + (ColorMap ? 0x80 : 0x00) | /* Yes/no global colormap */ + ((ColorRes - 1) << 4) | /* Bits allocated to each primary color */ + (ColorMap ? ColorMap->BitsPerPixel - 1 + : 0x07); /* Actual size of the + color table. */ + if (ColorMap != NULL && ColorMap->SortFlag) { + Buf[0] |= 0x08; + } + Buf[1] = + BackGround; /* Index into the ColorTable for background color */ + Buf[2] = GifFile->AspectByte; /* Pixel Aspect Ratio */ + InternalWrite(GifFile, Buf, 3); + + /* If we have Global color map - dump it also: */ + if (ColorMap != NULL) { + int i; + for (i = 0; i < ColorMap->ColorCount; i++) { + /* Put the ColorMap out also: */ + Buf[0] = ColorMap->Colors[i].Red; + Buf[1] = ColorMap->Colors[i].Green; + Buf[2] = ColorMap->Colors[i].Blue; + if (InternalWrite(GifFile, Buf, 3) != 3) { + GifFile->Error = E_GIF_ERR_WRITE_FAILED; + return GIF_ERROR; + } + } + } + + /* Mark this file as has screen descriptor, and no pixel written yet: */ + Private->FileState |= FILE_STATE_SCREEN; + + return GIF_OK; +} + +/****************************************************************************** + This routine should be called before any attempt to dump an image - any + call to any of the pixel dump routines. +******************************************************************************/ +int EGifPutImageDesc(GifFileType *GifFile, const int Left, const int Top, + const int Width, const int Height, const bool Interlace, + const ColorMapObject *ColorMap) { + GifByteType Buf[3]; + GifFilePrivateType *Private = (GifFilePrivateType *)GifFile->Private; + + if (Private->FileState & FILE_STATE_IMAGE && + Private->PixelCount > 0xffff0000UL) { + /* If already has active image descriptor - something is wrong! + */ + GifFile->Error = E_GIF_ERR_HAS_IMAG_DSCR; + return GIF_ERROR; + } + if (!IS_WRITEABLE(Private)) { + /* This file was NOT open for writing: */ + GifFile->Error = E_GIF_ERR_NOT_WRITEABLE; + return GIF_ERROR; + } + GifFile->Image.Left = Left; + GifFile->Image.Top = Top; + GifFile->Image.Width = Width; + GifFile->Image.Height = Height; + GifFile->Image.Interlace = Interlace; + if (ColorMap != GifFile->Image.ColorMap) { + if (ColorMap) { + if (GifFile->Image.ColorMap != NULL) { + GifFreeMapObject(GifFile->Image.ColorMap); + GifFile->Image.ColorMap = NULL; + } + GifFile->Image.ColorMap = GifMakeMapObject( + ColorMap->ColorCount, ColorMap->Colors); + if (GifFile->Image.ColorMap == NULL) { + GifFile->Error = E_GIF_ERR_NOT_ENOUGH_MEM; + return GIF_ERROR; + } + } else { + GifFile->Image.ColorMap = NULL; + } + } + + /* Put the image descriptor into the file: */ + Buf[0] = DESCRIPTOR_INTRODUCER; /* Image separator character. */ + InternalWrite(GifFile, Buf, 1); + (void)EGifPutWord(Left, GifFile); + (void)EGifPutWord(Top, GifFile); + (void)EGifPutWord(Width, GifFile); + (void)EGifPutWord(Height, GifFile); + Buf[0] = (ColorMap ? 0x80 : 0x00) | (Interlace ? 0x40 : 0x00) | + (ColorMap ? ColorMap->BitsPerPixel - 1 : 0); + InternalWrite(GifFile, Buf, 1); + + /* If we have Global color map - dump it also: */ + if (ColorMap != NULL) { + int i; + for (i = 0; i < ColorMap->ColorCount; i++) { + /* Put the ColorMap out also: */ + Buf[0] = ColorMap->Colors[i].Red; + Buf[1] = ColorMap->Colors[i].Green; + Buf[2] = ColorMap->Colors[i].Blue; + if (InternalWrite(GifFile, Buf, 3) != 3) { + GifFile->Error = E_GIF_ERR_WRITE_FAILED; + return GIF_ERROR; + } + } + } + if (GifFile->SColorMap == NULL && GifFile->Image.ColorMap == NULL) { + GifFile->Error = E_GIF_ERR_NO_COLOR_MAP; + return GIF_ERROR; + } + + /* Mark this file as has screen descriptor: */ + Private->FileState |= FILE_STATE_IMAGE; + Private->PixelCount = (long)Width * (long)Height; + + /* Reset compress algorithm parameters. */ + (void)EGifSetupCompress(GifFile); + + return GIF_OK; +} + +/****************************************************************************** + Put one full scanned line (Line) of length LineLen into GIF file. +******************************************************************************/ +int EGifPutLine(GifFileType *GifFile, GifPixelType *Line, int LineLen) { + int i; + GifPixelType Mask; + GifFilePrivateType *Private = (GifFilePrivateType *)GifFile->Private; + + if (!IS_WRITEABLE(Private)) { + /* This file was NOT open for writing: */ + GifFile->Error = E_GIF_ERR_NOT_WRITEABLE; + return GIF_ERROR; + } + + if (!LineLen) { + LineLen = GifFile->Image.Width; + } + if (Private->PixelCount < (unsigned)LineLen) { + GifFile->Error = E_GIF_ERR_DATA_TOO_BIG; + return GIF_ERROR; + } + Private->PixelCount -= LineLen; + + /* Make sure the codes are not out of bit range, as we might generate + * wrong code (because of overflow when we combine them) in this case: + */ + Mask = CodeMask[Private->BitsPerPixel]; + for (i = 0; i < LineLen; i++) { + Line[i] &= Mask; + } + + return EGifCompressLine(GifFile, Line, LineLen); +} + +/****************************************************************************** + Put one pixel (Pixel) into GIF file. +******************************************************************************/ +int EGifPutPixel(GifFileType *GifFile, GifPixelType Pixel) { + GifFilePrivateType *Private = (GifFilePrivateType *)GifFile->Private; + + if (!IS_WRITEABLE(Private)) { + /* This file was NOT open for writing: */ + GifFile->Error = E_GIF_ERR_NOT_WRITEABLE; + return GIF_ERROR; + } + + if (Private->PixelCount == 0) { + GifFile->Error = E_GIF_ERR_DATA_TOO_BIG; + return GIF_ERROR; + } + --Private->PixelCount; + + /* Make sure the code is not out of bit range, as we might generate + * wrong code (because of overflow when we combine them) in this case: + */ + Pixel &= CodeMask[Private->BitsPerPixel]; + + return EGifCompressLine(GifFile, &Pixel, 1); +} + +/****************************************************************************** + Put a comment into GIF file using the GIF89 comment extension block. +******************************************************************************/ +int EGifPutComment(GifFileType *GifFile, const char *Comment) { + unsigned int length; + char *buf; + + length = strlen(Comment); + if (length <= 255) { + return EGifPutExtension(GifFile, COMMENT_EXT_FUNC_CODE, length, + Comment); + } else { + buf = (char *)Comment; + if (EGifPutExtensionLeader(GifFile, COMMENT_EXT_FUNC_CODE) == + GIF_ERROR) { + return GIF_ERROR; + } + + /* Break the comment into 255 byte sub blocks */ + while (length > 255) { + if (EGifPutExtensionBlock(GifFile, 255, buf) == + GIF_ERROR) { + return GIF_ERROR; + } + buf = buf + 255; + length -= 255; + } + /* Output any partial block and the clear code. */ + if (length > 0) { + if (EGifPutExtensionBlock(GifFile, length, buf) == + GIF_ERROR) { + return GIF_ERROR; + } + } + if (EGifPutExtensionTrailer(GifFile) == GIF_ERROR) { + return GIF_ERROR; + } + } + return GIF_OK; +} + +/****************************************************************************** + Begin an extension block (see GIF manual). More + extensions can be dumped using EGifPutExtensionBlock until + EGifPutExtensionTrailer is invoked. +******************************************************************************/ +int EGifPutExtensionLeader(GifFileType *GifFile, const int ExtCode) { + GifByteType Buf[3]; + GifFilePrivateType *Private = (GifFilePrivateType *)GifFile->Private; + + if (!IS_WRITEABLE(Private)) { + /* This file was NOT open for writing: */ + GifFile->Error = E_GIF_ERR_NOT_WRITEABLE; + return GIF_ERROR; + } + + Buf[0] = EXTENSION_INTRODUCER; + Buf[1] = ExtCode; + InternalWrite(GifFile, Buf, 2); + + return GIF_OK; +} + +/****************************************************************************** + Put extension block data (see GIF manual) into a GIF file. +******************************************************************************/ +int EGifPutExtensionBlock(GifFileType *GifFile, const int ExtLen, + const void *Extension) { + GifByteType Buf; + GifFilePrivateType *Private = (GifFilePrivateType *)GifFile->Private; + + if (!IS_WRITEABLE(Private)) { + /* This file was NOT open for writing: */ + GifFile->Error = E_GIF_ERR_NOT_WRITEABLE; + return GIF_ERROR; + } + + Buf = ExtLen; + InternalWrite(GifFile, &Buf, 1); + InternalWrite(GifFile, Extension, ExtLen); + + return GIF_OK; +} + +/****************************************************************************** + Put a terminating block (see GIF manual) into a GIF file. +******************************************************************************/ +int EGifPutExtensionTrailer(GifFileType *GifFile) { + + GifByteType Buf; + GifFilePrivateType *Private = (GifFilePrivateType *)GifFile->Private; + + if (!IS_WRITEABLE(Private)) { + /* This file was NOT open for writing: */ + GifFile->Error = E_GIF_ERR_NOT_WRITEABLE; + return GIF_ERROR; + } + + /* Write the block terminator */ + Buf = 0; + InternalWrite(GifFile, &Buf, 1); + + return GIF_OK; +} + +/****************************************************************************** + Put an extension block (see GIF manual) into a GIF file. + Warning: This function is only useful for Extension blocks that have at + most one subblock. Extensions with more than one subblock need to use the + EGifPutExtension{Leader,Block,Trailer} functions instead. +******************************************************************************/ +int EGifPutExtension(GifFileType *GifFile, const int ExtCode, const int ExtLen, + const void *Extension) { + + GifByteType Buf[3]; + GifFilePrivateType *Private = (GifFilePrivateType *)GifFile->Private; + + if (!IS_WRITEABLE(Private)) { + /* This file was NOT open for writing: */ + GifFile->Error = E_GIF_ERR_NOT_WRITEABLE; + return GIF_ERROR; + } + + if (ExtCode == 0) { + InternalWrite(GifFile, (GifByteType *)&ExtLen, 1); + } else { + Buf[0] = EXTENSION_INTRODUCER; + Buf[1] = ExtCode; /* Extension Label */ + Buf[2] = ExtLen; /* Extension length */ + InternalWrite(GifFile, Buf, 3); + } + InternalWrite(GifFile, Extension, ExtLen); + Buf[0] = 0; + InternalWrite(GifFile, Buf, 1); + + return GIF_OK; +} + +/****************************************************************************** + Render a Graphics Control Block as raw extension data +******************************************************************************/ + +size_t EGifGCBToExtension(const GraphicsControlBlock *GCB, + GifByteType *GifExtension) { + GifExtension[0] = 0; + GifExtension[0] |= + (GCB->TransparentColor == NO_TRANSPARENT_COLOR) ? 0x00 : 0x01; + GifExtension[0] |= GCB->UserInputFlag ? 0x02 : 0x00; + GifExtension[0] |= ((GCB->DisposalMode & 0x07) << 2); + GifExtension[1] = LOBYTE(GCB->DelayTime); + GifExtension[2] = HIBYTE(GCB->DelayTime); + GifExtension[3] = (char)GCB->TransparentColor; + return 4; +} + +/****************************************************************************** + Replace the Graphics Control Block for a saved image, if it exists. +******************************************************************************/ + +int EGifGCBToSavedExtension(const GraphicsControlBlock *GCB, + GifFileType *GifFile, int ImageIndex) { + int i; + size_t Len; + GifByteType buf[sizeof(GraphicsControlBlock)]; /* a bit dodgy... */ + + if (ImageIndex < 0 || ImageIndex > GifFile->ImageCount - 1) { + return GIF_ERROR; + } + + for (i = 0; i < GifFile->SavedImages[ImageIndex].ExtensionBlockCount; + i++) { + ExtensionBlock *ep = + &GifFile->SavedImages[ImageIndex].ExtensionBlocks[i]; + if (ep->Function == GRAPHICS_EXT_FUNC_CODE) { + EGifGCBToExtension(GCB, ep->Bytes); + return GIF_OK; + } + } + + Len = EGifGCBToExtension(GCB, (GifByteType *)buf); + if (GifAddExtensionBlock( + &GifFile->SavedImages[ImageIndex].ExtensionBlockCount, + &GifFile->SavedImages[ImageIndex].ExtensionBlocks, + GRAPHICS_EXT_FUNC_CODE, Len, + (unsigned char *)buf) == GIF_ERROR) { + return (GIF_ERROR); + } + + return (GIF_OK); +} + +/****************************************************************************** + Put the image code in compressed form. This routine can be called if the + information needed to be piped out as is. Obviously this is much faster + than decoding and encoding again. This routine should be followed by calls + to EGifPutCodeNext, until NULL block is given. + The block should NOT be freed by the user (not dynamically allocated). +******************************************************************************/ +int EGifPutCode(GifFileType *GifFile, int CodeSize, + const GifByteType *CodeBlock) { + GifFilePrivateType *Private = (GifFilePrivateType *)GifFile->Private; + + if (!IS_WRITEABLE(Private)) { + /* This file was NOT open for writing: */ + GifFile->Error = E_GIF_ERR_NOT_WRITEABLE; + return GIF_ERROR; + } + + /* No need to dump code size as Compression set up does any for us: */ + /* + * Buf = CodeSize; + * if (InternalWrite(GifFile, &Buf, 1) != 1) { + * GifFile->Error = E_GIF_ERR_WRITE_FAILED; + * return GIF_ERROR; + * } + */ + + return EGifPutCodeNext(GifFile, CodeBlock); +} + +/****************************************************************************** + Continue to put the image code in compressed form. This routine should be + called with blocks of code as read via DGifGetCode/DGifGetCodeNext. If + given buffer pointer is NULL, empty block is written to mark end of code. +******************************************************************************/ +int EGifPutCodeNext(GifFileType *GifFile, const GifByteType *CodeBlock) { + GifByteType Buf; + GifFilePrivateType *Private = (GifFilePrivateType *)GifFile->Private; + + if (CodeBlock != NULL) { + if (InternalWrite(GifFile, CodeBlock, CodeBlock[0] + 1) != + (unsigned)(CodeBlock[0] + 1)) { + GifFile->Error = E_GIF_ERR_WRITE_FAILED; + return GIF_ERROR; + } + } else { + Buf = 0; + if (InternalWrite(GifFile, &Buf, 1) != 1) { + GifFile->Error = E_GIF_ERR_WRITE_FAILED; + return GIF_ERROR; + } + Private->PixelCount = + 0; /* And local info. indicate image read. */ + } + + return GIF_OK; +} + +/****************************************************************************** + This routine should be called last, to close the GIF file. +******************************************************************************/ +int EGifCloseFile(GifFileType *GifFile, int *ErrorCode) { + GifByteType Buf; + GifFilePrivateType *Private; + FILE *File; + + if (GifFile == NULL) { + return GIF_ERROR; + } + + Private = (GifFilePrivateType *)GifFile->Private; + if (Private == NULL) { + return GIF_ERROR; + } else if (!IS_WRITEABLE(Private)) { + /* This file was NOT open for writing: */ + if (ErrorCode != NULL) { + *ErrorCode = E_GIF_ERR_NOT_WRITEABLE; + } + free(GifFile); + return GIF_ERROR; + } else { + File = Private->File; + + Buf = TERMINATOR_INTRODUCER; + InternalWrite(GifFile, &Buf, 1); + + if (GifFile->Image.ColorMap) { + GifFreeMapObject(GifFile->Image.ColorMap); + GifFile->Image.ColorMap = NULL; + } + if (GifFile->SColorMap) { + GifFreeMapObject(GifFile->SColorMap); + GifFile->SColorMap = NULL; + } + if (Private->HashTable) { + free((char *)Private->HashTable); + } + free((char *)Private); + + if (File && fclose(File) != 0) { + if (ErrorCode != NULL) { + *ErrorCode = E_GIF_ERR_CLOSE_FAILED; + } + free(GifFile); + return GIF_ERROR; + } + + free(GifFile); + if (ErrorCode != NULL) { + *ErrorCode = E_GIF_SUCCEEDED; + } + } + return GIF_OK; +} + +/****************************************************************************** + Put 2 bytes (a word) into the given file in little-endian order: +******************************************************************************/ +static int EGifPutWord(int Word, GifFileType *GifFile) { + unsigned char c[2]; + + c[0] = LOBYTE(Word); + c[1] = HIBYTE(Word); + if (InternalWrite(GifFile, c, 2) == 2) { + return GIF_OK; + } else { + return GIF_ERROR; + } +} + +/****************************************************************************** + Setup the LZ compression for this image: +******************************************************************************/ +static int EGifSetupCompress(GifFileType *GifFile) { + int BitsPerPixel; + GifByteType Buf; + GifFilePrivateType *Private = (GifFilePrivateType *)GifFile->Private; + + /* Test and see what color map to use, and from it # bits per pixel: */ + if (GifFile->Image.ColorMap) { + BitsPerPixel = GifFile->Image.ColorMap->BitsPerPixel; + } else if (GifFile->SColorMap) { + BitsPerPixel = GifFile->SColorMap->BitsPerPixel; + } else { + GifFile->Error = E_GIF_ERR_NO_COLOR_MAP; + return GIF_ERROR; + } + + Buf = BitsPerPixel = (BitsPerPixel < 2 ? 2 : BitsPerPixel); + InternalWrite(GifFile, &Buf, 1); /* Write the Code size to file. */ + + Private->Buf[0] = 0; /* Nothing was output yet. */ + Private->BitsPerPixel = BitsPerPixel; + Private->ClearCode = (1 << BitsPerPixel); + Private->EOFCode = Private->ClearCode + 1; + Private->RunningCode = Private->EOFCode + 1; + Private->RunningBits = BitsPerPixel + 1; /* Number of bits per code. */ + Private->MaxCode1 = 1 << Private->RunningBits; /* Max. code + 1. */ + Private->CrntCode = FIRST_CODE; /* Signal that this is first one! */ + Private->CrntShiftState = 0; /* No information in CrntShiftDWord. */ + Private->CrntShiftDWord = 0; + + /* Clear hash table and send Clear to make sure the decoder do the same. + */ + _ClearHashTable(Private->HashTable); + + if (EGifCompressOutput(GifFile, Private->ClearCode) == GIF_ERROR) { + GifFile->Error = E_GIF_ERR_DISK_IS_FULL; + return GIF_ERROR; + } + return GIF_OK; +} + +/****************************************************************************** + The LZ compression routine: + This version compresses the given buffer Line of length LineLen. + This routine can be called a few times (one per scan line, for example), in + order to complete the whole image. +******************************************************************************/ +static int EGifCompressLine(GifFileType *GifFile, const GifPixelType *Line, + const int LineLen) { + int i = 0, CrntCode; + GifHashTableType *HashTable; + GifFilePrivateType *Private = (GifFilePrivateType *)GifFile->Private; + + HashTable = Private->HashTable; + + if (Private->CrntCode == FIRST_CODE) { /* Its first time! */ + CrntCode = Line[i++]; + } else { + CrntCode = + Private->CrntCode; /* Get last code in compression. */ + } + while (i < LineLen) { /* Decode LineLen items. */ + GifPixelType Pixel = + Line[i++]; /* Get next pixel from stream. */ + /* Form a new unique key to search hash table for the code + * combines CrntCode as Prefix string with Pixel as postfix + * char. + */ + int NewCode; + unsigned long NewKey = (((uint32_t)CrntCode) << 8) + Pixel; + if ((NewCode = _ExistsHashTable(HashTable, NewKey)) >= 0) { + /* This Key is already there, or the string is old one, + * so simple take new code as our CrntCode: + */ + CrntCode = NewCode; + } else { + /* Put it in hash table, output the prefix code, and + * make our CrntCode equal to Pixel. + */ + if (EGifCompressOutput(GifFile, CrntCode) == + GIF_ERROR) { + GifFile->Error = E_GIF_ERR_DISK_IS_FULL; + return GIF_ERROR; + } + CrntCode = Pixel; + + /* If however the HashTable if full, we send a clear + * first and Clear the hash table. + */ + if (Private->RunningCode >= LZ_MAX_CODE) { + /* Time to do some clearance: */ + if (EGifCompressOutput(GifFile, + Private->ClearCode) == + GIF_ERROR) { + GifFile->Error = E_GIF_ERR_DISK_IS_FULL; + return GIF_ERROR; + } + Private->RunningCode = Private->EOFCode + 1; + Private->RunningBits = + Private->BitsPerPixel + 1; + Private->MaxCode1 = 1 << Private->RunningBits; + _ClearHashTable(HashTable); + } else { + /* Put this unique key with its relative Code in + * hash table: */ + _InsertHashTable(HashTable, NewKey, + Private->RunningCode++); + } + } + } + + /* Preserve the current state of the compression algorithm: */ + Private->CrntCode = CrntCode; + + if (Private->PixelCount == 0) { + /* We are done - output last Code and flush output buffers: */ + if (EGifCompressOutput(GifFile, CrntCode) == GIF_ERROR) { + GifFile->Error = E_GIF_ERR_DISK_IS_FULL; + return GIF_ERROR; + } + if (EGifCompressOutput(GifFile, Private->EOFCode) == + GIF_ERROR) { + GifFile->Error = E_GIF_ERR_DISK_IS_FULL; + return GIF_ERROR; + } + if (EGifCompressOutput(GifFile, FLUSH_OUTPUT) == GIF_ERROR) { + GifFile->Error = E_GIF_ERR_DISK_IS_FULL; + return GIF_ERROR; + } + } + + return GIF_OK; +} + +/****************************************************************************** + The LZ compression output routine: + This routine is responsible for the compression of the bit stream into + 8 bits (bytes) packets. + Returns GIF_OK if written successfully. +******************************************************************************/ +static int EGifCompressOutput(GifFileType *GifFile, const int Code) { + GifFilePrivateType *Private = (GifFilePrivateType *)GifFile->Private; + int retval = GIF_OK; + + if (Code == FLUSH_OUTPUT) { + while (Private->CrntShiftState > 0) { + /* Get Rid of what is left in DWord, and flush it. */ + if (EGifBufferedOutput(GifFile, Private->Buf, + Private->CrntShiftDWord & + 0xff) == GIF_ERROR) { + retval = GIF_ERROR; + } + Private->CrntShiftDWord >>= 8; + Private->CrntShiftState -= 8; + } + Private->CrntShiftState = 0; /* For next time. */ + if (EGifBufferedOutput(GifFile, Private->Buf, FLUSH_OUTPUT) == + GIF_ERROR) { + retval = GIF_ERROR; + } + } else { + Private->CrntShiftDWord |= ((long)Code) + << Private->CrntShiftState; + Private->CrntShiftState += Private->RunningBits; + while (Private->CrntShiftState >= 8) { + /* Dump out full bytes: */ + if (EGifBufferedOutput(GifFile, Private->Buf, + Private->CrntShiftDWord & + 0xff) == GIF_ERROR) { + retval = GIF_ERROR; + } + Private->CrntShiftDWord >>= 8; + Private->CrntShiftState -= 8; + } + } + + /* If code cannt fit into RunningBits bits, must raise its size. Note */ + /* however that codes above 4095 are used for special signaling. */ + if (Private->RunningCode >= Private->MaxCode1 && Code <= 4095) { + Private->MaxCode1 = 1 << ++Private->RunningBits; + } + + return retval; +} + +/****************************************************************************** + This routines buffers the given characters until 255 characters are ready + to be output. If Code is equal to -1 the buffer is flushed (EOF). + The buffer is Dumped with first byte as its size, as GIF format requires. + Returns GIF_OK if written successfully. +******************************************************************************/ +static int EGifBufferedOutput(GifFileType *GifFile, GifByteType *Buf, int c) { + if (c == FLUSH_OUTPUT) { + /* Flush everything out. */ + if (Buf[0] != 0 && InternalWrite(GifFile, Buf, Buf[0] + 1) != + (unsigned)(Buf[0] + 1)) { + GifFile->Error = E_GIF_ERR_WRITE_FAILED; + return GIF_ERROR; + } + /* Mark end of compressed data, by an empty block (see GIF doc): + */ + Buf[0] = 0; + if (InternalWrite(GifFile, Buf, 1) != 1) { + GifFile->Error = E_GIF_ERR_WRITE_FAILED; + return GIF_ERROR; + } + } else { + if (Buf[0] == 255) { + /* Dump out this buffer - it is full: */ + if (InternalWrite(GifFile, Buf, Buf[0] + 1) != + (unsigned)(Buf[0] + 1)) { + GifFile->Error = E_GIF_ERR_WRITE_FAILED; + return GIF_ERROR; + } + Buf[0] = 0; + } + Buf[++Buf[0]] = c; + } + + return GIF_OK; +} + +/****************************************************************************** + This routine writes to disk an in-core representation of a GIF previously + created by DGifSlurp(). +******************************************************************************/ + +static int EGifWriteExtensions(GifFileType *GifFileOut, + ExtensionBlock *ExtensionBlocks, + int ExtensionBlockCount) { + if (ExtensionBlocks) { + int j; + + for (j = 0; j < ExtensionBlockCount; j++) { + ExtensionBlock *ep = &ExtensionBlocks[j]; + if (ep->Function != CONTINUE_EXT_FUNC_CODE) { + if (EGifPutExtensionLeader(GifFileOut, + ep->Function) == + GIF_ERROR) { + return (GIF_ERROR); + } + } + if (EGifPutExtensionBlock(GifFileOut, ep->ByteCount, + ep->Bytes) == GIF_ERROR) { + return (GIF_ERROR); + } + if (j == ExtensionBlockCount - 1 || + (ep + 1)->Function != CONTINUE_EXT_FUNC_CODE) { + if (EGifPutExtensionTrailer(GifFileOut) == + GIF_ERROR) { + return (GIF_ERROR); + } + } + } + } + + return (GIF_OK); +} + +int EGifSpew(GifFileType *GifFileOut) { + int i, j; + + if (EGifPutScreenDesc(GifFileOut, GifFileOut->SWidth, + GifFileOut->SHeight, GifFileOut->SColorResolution, + GifFileOut->SBackGroundColor, + GifFileOut->SColorMap) == GIF_ERROR) { + return (GIF_ERROR); + } + + for (i = 0; i < GifFileOut->ImageCount; i++) { + SavedImage *sp = &GifFileOut->SavedImages[i]; + int SavedHeight = sp->ImageDesc.Height; + int SavedWidth = sp->ImageDesc.Width; + + /* this allows us to delete images by nuking their rasters */ + if (sp->RasterBits == NULL) { + continue; + } + + if (EGifWriteExtensions(GifFileOut, sp->ExtensionBlocks, + sp->ExtensionBlockCount) == GIF_ERROR) { + return (GIF_ERROR); + } + + if (EGifPutImageDesc(GifFileOut, sp->ImageDesc.Left, + sp->ImageDesc.Top, SavedWidth, SavedHeight, + sp->ImageDesc.Interlace, + sp->ImageDesc.ColorMap) == GIF_ERROR) { + return (GIF_ERROR); + } + + if (sp->ImageDesc.Interlace) { + /* + * The way an interlaced image should be written - + * offsets and jumps... + */ + static const int InterlacedOffset[] = {0, 4, 2, 1}; + static const int InterlacedJumps[] = {8, 8, 4, 2}; + int k; + /* Need to perform 4 passes on the images: */ + for (k = 0; k < 4; k++) { + for (j = InterlacedOffset[k]; j < SavedHeight; + j += InterlacedJumps[k]) { + if (EGifPutLine( + GifFileOut, + sp->RasterBits + j * SavedWidth, + SavedWidth) == GIF_ERROR) { + return (GIF_ERROR); + } + } + } + } else { + for (j = 0; j < SavedHeight; j++) { + if (EGifPutLine(GifFileOut, + sp->RasterBits + j * SavedWidth, + SavedWidth) == GIF_ERROR) { + return (GIF_ERROR); + } + } + } + } + + if (EGifWriteExtensions(GifFileOut, GifFileOut->ExtensionBlocks, + GifFileOut->ExtensionBlockCount) == GIF_ERROR) { + return (GIF_ERROR); + } + + if (EGifCloseFile(GifFileOut, NULL) == GIF_ERROR) { + return (GIF_ERROR); + } + + return (GIF_OK); +} + +/* end */ diff --git a/thirdparty/giflib/gif_err.c b/thirdparty/giflib/gif_err.c new file mode 100644 index 00000000000..0226194e000 --- /dev/null +++ b/thirdparty/giflib/gif_err.c @@ -0,0 +1,97 @@ +/***************************************************************************** + +gif_err.c - handle error reporting for the GIF library. + +SPDX-License-Identifier: MIT + +****************************************************************************/ + +#include + +#include "gif_lib.h" +#include "gif_lib_private.h" + +/***************************************************************************** + Return a string description of the last GIF error +*****************************************************************************/ +const char *GifErrorString(int ErrorCode) { + const char *Err; + + switch (ErrorCode) { + case E_GIF_ERR_OPEN_FAILED: + Err = "Failed to open given file"; + break; + case E_GIF_ERR_WRITE_FAILED: + Err = "Failed to write to given file"; + break; + case E_GIF_ERR_HAS_SCRN_DSCR: + Err = "Screen descriptor has already been set"; + break; + case E_GIF_ERR_HAS_IMAG_DSCR: + Err = "Image descriptor is still active"; + break; + case E_GIF_ERR_NO_COLOR_MAP: + Err = "Neither global nor local color map"; + break; + case E_GIF_ERR_DATA_TOO_BIG: + Err = "Number of pixels bigger than width * height"; + break; + case E_GIF_ERR_NOT_ENOUGH_MEM: + Err = "Failed to allocate required memory"; + break; + case E_GIF_ERR_DISK_IS_FULL: + Err = "Write failed (disk full?)"; + break; + case E_GIF_ERR_CLOSE_FAILED: + Err = "Failed to close given file"; + break; + case E_GIF_ERR_NOT_WRITEABLE: + Err = "Given file was not opened for write"; + break; + case D_GIF_ERR_OPEN_FAILED: + Err = "Failed to open given file"; + break; + case D_GIF_ERR_READ_FAILED: + Err = "Failed to read from given file"; + break; + case D_GIF_ERR_NOT_GIF_FILE: + Err = "Data is not in GIF format"; + break; + case D_GIF_ERR_NO_SCRN_DSCR: + Err = "No screen descriptor detected"; + break; + case D_GIF_ERR_NO_IMAG_DSCR: + Err = "No Image Descriptor detected"; + break; + case D_GIF_ERR_NO_COLOR_MAP: + Err = "Neither global nor local color map"; + break; + case D_GIF_ERR_WRONG_RECORD: + Err = "Wrong record type detected"; + break; + case D_GIF_ERR_DATA_TOO_BIG: + Err = "Number of pixels bigger than width * height"; + break; + case D_GIF_ERR_NOT_ENOUGH_MEM: + Err = "Failed to allocate required memory"; + break; + case D_GIF_ERR_CLOSE_FAILED: + Err = "Failed to close given file"; + break; + case D_GIF_ERR_NOT_READABLE: + Err = "Given file was not opened for read"; + break; + case D_GIF_ERR_IMAGE_DEFECT: + Err = "Image is defective, decoding aborted"; + break; + case D_GIF_ERR_EOF_TOO_SOON: + Err = "Image EOF detected before image complete"; + break; + default: + Err = NULL; + break; + } + return Err; +} + +/* end */ diff --git a/thirdparty/giflib/gif_hash.c b/thirdparty/giflib/gif_hash.c new file mode 100644 index 00000000000..ad777cd0bca --- /dev/null +++ b/thirdparty/giflib/gif_hash.c @@ -0,0 +1,128 @@ +/***************************************************************************** + +gif_hash.c -- module to support the following operations: + +1. InitHashTable - initialize hash table. +2. ClearHashTable - clear the hash table to an empty state. +2. InsertHashTable - insert one item into data structure. +3. ExistsHashTable - test if item exists in data structure. + +This module is used to hash the GIF codes during encoding. + +SPDX-License-Identifier: MIT + +*****************************************************************************/ + +#include +#include +#include +#include +#include + +#include "gif_hash.h" +#include "gif_lib.h" +#include "gif_lib_private.h" + +/* #define DEBUG_HIT_RATE Debug number of misses per hash Insert/Exists. */ + +#ifdef DEBUG_HIT_RATE +static long NumberOfTests = 0, NumberOfMisses = 0; +#endif /* DEBUG_HIT_RATE */ + +static int KeyItem(uint32_t Item); + +/****************************************************************************** + Initialize HashTable - allocate the memory needed and clear it. * +******************************************************************************/ +GifHashTableType *_InitHashTable(void) { + GifHashTableType *HashTable; + + if ((HashTable = (GifHashTableType *)malloc( + sizeof(GifHashTableType))) == NULL) { + return NULL; + } + + _ClearHashTable(HashTable); + + return HashTable; +} + +/****************************************************************************** + Routine to clear the HashTable to an empty state. * + This part is a little machine depended. Use the commented part otherwise. * +******************************************************************************/ +void _ClearHashTable(GifHashTableType *HashTable) { + memset(HashTable->HTable, 0xFF, HT_SIZE * sizeof(uint32_t)); +} + +/****************************************************************************** + Routine to insert a new Item into the HashTable. The data is assumed to be * + new one. * +******************************************************************************/ +void _InsertHashTable(GifHashTableType *HashTable, uint32_t Key, int Code) { + int HKey = KeyItem(Key); + uint32_t *HTable = HashTable->HTable; + +#ifdef DEBUG_HIT_RATE + NumberOfTests++; + NumberOfMisses++; +#endif /* DEBUG_HIT_RATE */ + + while (HT_GET_KEY(HTable[HKey]) != 0xFFFFFL) { +#ifdef DEBUG_HIT_RATE + NumberOfMisses++; +#endif /* DEBUG_HIT_RATE */ + HKey = (HKey + 1) & HT_KEY_MASK; + } + HTable[HKey] = HT_PUT_KEY(Key) | HT_PUT_CODE(Code); +} + +/****************************************************************************** + Routine to test if given Key exists in HashTable and if so returns its code * + Returns the Code if key was found, -1 if not. * +******************************************************************************/ +int _ExistsHashTable(GifHashTableType *HashTable, uint32_t Key) { + int HKey = KeyItem(Key); + uint32_t *HTable = HashTable->HTable, HTKey; + +#ifdef DEBUG_HIT_RATE + NumberOfTests++; + NumberOfMisses++; +#endif /* DEBUG_HIT_RATE */ + + while ((HTKey = HT_GET_KEY(HTable[HKey])) != 0xFFFFFL) { +#ifdef DEBUG_HIT_RATE + NumberOfMisses++; +#endif /* DEBUG_HIT_RATE */ + if (Key == HTKey) { + return HT_GET_CODE(HTable[HKey]); + } + HKey = (HKey + 1) & HT_KEY_MASK; + } + + return -1; +} + +/****************************************************************************** + Routine to generate an HKey for the hashtable out of the given unique key. * + The given Key is assumed to be 20 bits as follows: lower 8 bits are the * + new postfix character, while the upper 12 bits are the prefix code. * + Because the average hit ratio is only 2 (2 hash references per entry), * + evaluating more complex keys (such as twin prime keys) does not worth it! * +******************************************************************************/ +static int KeyItem(uint32_t Item) { + return ((Item >> 12) ^ Item) & HT_KEY_MASK; +} + +#ifdef DEBUG_HIT_RATE +/****************************************************************************** + Debugging routine to print the hit ratio - number of times the hash table * + was tested per operation. This routine was used to test the KeyItem routine * +******************************************************************************/ +void HashTablePrintHitRatio(void) { + printf("Hash Table Hit Ratio is %ld/%ld = %ld%%.\n", NumberOfMisses, + NumberOfTests, NumberOfMisses * 100 / NumberOfTests); +} +#endif /* DEBUG_HIT_RATE */ + +/* end */ diff --git a/thirdparty/giflib/gif_hash.h b/thirdparty/giflib/gif_hash.h new file mode 100644 index 00000000000..e393d8060cd --- /dev/null +++ b/thirdparty/giflib/gif_hash.h @@ -0,0 +1,43 @@ +/****************************************************************************** + +gif_hash.h - magfic constants and declarations for GIF LZW + +SPDX-License-Identifier: MIT + +******************************************************************************/ + +#ifndef _GIF_HASH_H_ +#define _GIF_HASH_H_ + +#ifndef _WIN32 +#include +#endif /* _WIN32 */ +#include + +#define HT_SIZE 8192 /* 12bits = 4096 or twice as big! */ +#define HT_KEY_MASK 0x1FFF /* 13bits keys */ +#define HT_KEY_NUM_BITS 13 /* 13bits keys */ +#define HT_MAX_KEY 8191 /* 13bits - 1, maximal code possible */ +#define HT_MAX_CODE 4095 /* Biggest code possible in 12 bits. */ + +/* The 32 bits of the long are divided into two parts for the key & code: */ +/* 1. The code is 12 bits as our compression algorithm is limited to 12bits */ +/* 2. The key is 12 bits Prefix code + 8 bit new char or 20 bits. */ +/* The key is the upper 20 bits. The code is the lower 12. */ +#define HT_GET_KEY(l) (l >> 12) +#define HT_GET_CODE(l) (l & 0x0FFF) +#define HT_PUT_KEY(l) (l << 12) +#define HT_PUT_CODE(l) (l & 0x0FFF) + +typedef struct GifHashTableType { + uint32_t HTable[HT_SIZE]; +} GifHashTableType; + +GifHashTableType *_InitHashTable(void); +void _ClearHashTable(GifHashTableType *HashTable); +void _InsertHashTable(GifHashTableType *HashTable, uint32_t Key, int Code); +int _ExistsHashTable(GifHashTableType *HashTable, uint32_t Key); + +#endif /* _GIF_HASH_H_ */ + +/* end */ diff --git a/thirdparty/giflib/gif_lib.h b/thirdparty/giflib/gif_lib.h new file mode 100644 index 00000000000..b05c9ff3eb3 --- /dev/null +++ b/thirdparty/giflib/gif_lib.h @@ -0,0 +1,292 @@ +/****************************************************************************** + +gif_lib.h - service library for decoding and encoding GIF images + +SPDX-License-Identifier: MIT + +*****************************************************************************/ + +#ifndef _GIF_LIB_H_ +#define _GIF_LIB_H_ 1 + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +#define GIFLIB_MAJOR 5 +#define GIFLIB_MINOR 2 +#define GIFLIB_RELEASE 2 + +#define GIF_ERROR 0 +#define GIF_OK 1 + +#include +#include + +#define GIF_STAMP "GIFVER" /* First chars in file - GIF stamp. */ +#define GIF_STAMP_LEN sizeof(GIF_STAMP) - 1 +#define GIF_VERSION_POS 3 /* Version first character in stamp. */ +#define GIF87_STAMP "GIF87a" /* First chars in file - GIF stamp. */ +#define GIF89_STAMP "GIF89a" /* First chars in file - GIF stamp. */ + +typedef unsigned char GifPixelType; +typedef unsigned char *GifRowType; +typedef unsigned char GifByteType; +typedef unsigned int GifPrefixType; +typedef int GifWord; + +typedef struct GifColorType { + GifByteType Red, Green, Blue; +} GifColorType; + +typedef struct ColorMapObject { + int ColorCount; + int BitsPerPixel; + bool SortFlag; + GifColorType *Colors; /* on malloc(3) heap */ +} ColorMapObject; + +typedef struct GifImageDesc { + GifWord Left, Top, Width, Height; /* Current image dimensions. */ + bool Interlace; /* Sequential/Interlaced lines. */ + ColorMapObject *ColorMap; /* The local color map */ +} GifImageDesc; + +typedef struct ExtensionBlock { + int ByteCount; + GifByteType *Bytes; /* on malloc(3) heap */ + int Function; /* The block function code */ +#define CONTINUE_EXT_FUNC_CODE 0x00 /* continuation subblock */ +#define COMMENT_EXT_FUNC_CODE 0xfe /* comment */ +#define GRAPHICS_EXT_FUNC_CODE 0xf9 /* graphics control (GIF89) */ +#define PLAINTEXT_EXT_FUNC_CODE 0x01 /* plaintext */ +#define APPLICATION_EXT_FUNC_CODE 0xff /* application block (GIF89) */ +} ExtensionBlock; + +typedef struct SavedImage { + GifImageDesc ImageDesc; + GifByteType *RasterBits; /* on malloc(3) heap */ + int ExtensionBlockCount; /* Count of extensions before image */ + ExtensionBlock *ExtensionBlocks; /* Extensions before image */ +} SavedImage; + +typedef struct GifFileType { + GifWord SWidth, SHeight; /* Size of virtual canvas */ + GifWord SColorResolution; /* How many colors can we generate? */ + GifWord SBackGroundColor; /* Background color for virtual canvas */ + GifByteType AspectByte; /* Used to compute pixel aspect ratio */ + ColorMapObject *SColorMap; /* Global colormap, NULL if nonexistent. */ + int ImageCount; /* Number of current image (both APIs) */ + GifImageDesc Image; /* Current image (low-level API) */ + SavedImage *SavedImages; /* Image sequence (high-level API) */ + int ExtensionBlockCount; /* Count extensions past last image */ + ExtensionBlock *ExtensionBlocks; /* Extensions past last image */ + int Error; /* Last error condition reported */ + void *UserData; /* hook to attach user data (TVT) */ + void *Private; /* Don't mess with this! */ +} GifFileType; + +#define GIF_ASPECT_RATIO(n) ((n) + 15.0 / 64.0) + +typedef enum { + UNDEFINED_RECORD_TYPE, + SCREEN_DESC_RECORD_TYPE, + IMAGE_DESC_RECORD_TYPE, /* Begin with ',' */ + EXTENSION_RECORD_TYPE, /* Begin with '!' */ + TERMINATE_RECORD_TYPE /* Begin with ';' */ +} GifRecordType; + +/* func type to read gif data from arbitrary sources (TVT) */ +typedef int (*InputFunc)(GifFileType *, GifByteType *, int); + +/* func type to write gif data to arbitrary targets. + * Returns count of bytes written. (MRB) + */ +typedef int (*OutputFunc)(GifFileType *, const GifByteType *, int); + +/****************************************************************************** + GIF89 structures +******************************************************************************/ + +typedef struct GraphicsControlBlock { + int DisposalMode; +#define DISPOSAL_UNSPECIFIED 0 /* No disposal specified. */ +#define DISPOSE_DO_NOT 1 /* Leave image in place */ +#define DISPOSE_BACKGROUND 2 /* Set area too background color */ +#define DISPOSE_PREVIOUS 3 /* Restore to previous content */ + bool UserInputFlag; /* User confirmation required before disposal */ + int DelayTime; /* pre-display delay in 0.01sec units */ + int TransparentColor; /* Palette index for transparency, -1 if none */ +#define NO_TRANSPARENT_COLOR -1 +} GraphicsControlBlock; + +/****************************************************************************** + GIF encoding routines +******************************************************************************/ + +/* Main entry points */ +GifFileType *EGifOpenFileName(const char *GifFileName, + const bool GifTestExistence, int *Error); +GifFileType *EGifOpenFileHandle(const int GifFileHandle, int *Error); +GifFileType *EGifOpen(void *userPtr, OutputFunc writeFunc, int *Error); +int EGifSpew(GifFileType *GifFile); +const char *EGifGetGifVersion(GifFileType *GifFile); /* new in 5.x */ +int EGifCloseFile(GifFileType *GifFile, int *ErrorCode); + +#define E_GIF_SUCCEEDED 0 +#define E_GIF_ERR_OPEN_FAILED 1 /* And EGif possible errors. */ +#define E_GIF_ERR_WRITE_FAILED 2 +#define E_GIF_ERR_HAS_SCRN_DSCR 3 +#define E_GIF_ERR_HAS_IMAG_DSCR 4 +#define E_GIF_ERR_NO_COLOR_MAP 5 +#define E_GIF_ERR_DATA_TOO_BIG 6 +#define E_GIF_ERR_NOT_ENOUGH_MEM 7 +#define E_GIF_ERR_DISK_IS_FULL 8 +#define E_GIF_ERR_CLOSE_FAILED 9 +#define E_GIF_ERR_NOT_WRITEABLE 10 + +/* These are legacy. You probably do not want to call them directly */ +int EGifPutScreenDesc(GifFileType *GifFile, const int GifWidth, + const int GifHeight, const int GifColorRes, + const int GifBackGround, + const ColorMapObject *GifColorMap); +int EGifPutImageDesc(GifFileType *GifFile, const int GifLeft, const int GifTop, + const int GifWidth, const int GifHeight, + const bool GifInterlace, + const ColorMapObject *GifColorMap); +void EGifSetGifVersion(GifFileType *GifFile, const bool gif89); +int EGifPutLine(GifFileType *GifFile, GifPixelType *GifLine, int GifLineLen); +int EGifPutPixel(GifFileType *GifFile, const GifPixelType GifPixel); +int EGifPutComment(GifFileType *GifFile, const char *GifComment); +int EGifPutExtensionLeader(GifFileType *GifFile, const int GifExtCode); +int EGifPutExtensionBlock(GifFileType *GifFile, const int GifExtLen, + const void *GifExtension); +int EGifPutExtensionTrailer(GifFileType *GifFile); +int EGifPutExtension(GifFileType *GifFile, const int GifExtCode, + const int GifExtLen, const void *GifExtension); +int EGifPutCode(GifFileType *GifFile, int GifCodeSize, + const GifByteType *GifCodeBlock); +int EGifPutCodeNext(GifFileType *GifFile, const GifByteType *GifCodeBlock); + +/****************************************************************************** + GIF decoding routines +******************************************************************************/ + +/* Main entry points */ +GifFileType *DGifOpenFileName(const char *GifFileName, int *Error); +GifFileType *DGifOpenFileHandle(int GifFileHandle, int *Error); +int DGifSlurp(GifFileType *GifFile); +GifFileType *DGifOpen(void *userPtr, InputFunc readFunc, + int *Error); /* new one (TVT) */ +int DGifCloseFile(GifFileType *GifFile, int *ErrorCode); + +#define D_GIF_SUCCEEDED 0 +#define D_GIF_ERR_OPEN_FAILED 101 /* And DGif possible errors. */ +#define D_GIF_ERR_READ_FAILED 102 +#define D_GIF_ERR_NOT_GIF_FILE 103 +#define D_GIF_ERR_NO_SCRN_DSCR 104 +#define D_GIF_ERR_NO_IMAG_DSCR 105 +#define D_GIF_ERR_NO_COLOR_MAP 106 +#define D_GIF_ERR_WRONG_RECORD 107 +#define D_GIF_ERR_DATA_TOO_BIG 108 +#define D_GIF_ERR_NOT_ENOUGH_MEM 109 +#define D_GIF_ERR_CLOSE_FAILED 110 +#define D_GIF_ERR_NOT_READABLE 111 +#define D_GIF_ERR_IMAGE_DEFECT 112 +#define D_GIF_ERR_EOF_TOO_SOON 113 + +/* These are legacy. You probably do not want to call them directly */ +int DGifGetScreenDesc(GifFileType *GifFile); +int DGifGetRecordType(GifFileType *GifFile, GifRecordType *GifType); +int DGifGetImageHeader(GifFileType *GifFile); +int DGifGetImageDesc(GifFileType *GifFile); +int DGifGetLine(GifFileType *GifFile, GifPixelType *GifLine, int GifLineLen); +int DGifGetPixel(GifFileType *GifFile, GifPixelType GifPixel); +int DGifGetExtension(GifFileType *GifFile, int *GifExtCode, + GifByteType **GifExtension); +int DGifGetExtensionNext(GifFileType *GifFile, GifByteType **GifExtension); +int DGifGetCode(GifFileType *GifFile, int *GifCodeSize, + GifByteType **GifCodeBlock); +int DGifGetCodeNext(GifFileType *GifFile, GifByteType **GifCodeBlock); +int DGifGetLZCodes(GifFileType *GifFile, int *GifCode); +const char *DGifGetGifVersion(GifFileType *GifFile); + +/****************************************************************************** + Error handling and reporting. +******************************************************************************/ +extern const char *GifErrorString(int ErrorCode); /* new in 2012 - ESR */ + +/***************************************************************************** + Everything below this point is new after version 1.2, supporting `slurp + mode' for doing I/O in two big belts with all the image-bashing in core. +******************************************************************************/ + +/****************************************************************************** + Color map handling from gif_alloc.c +******************************************************************************/ + +extern ColorMapObject *GifMakeMapObject(int ColorCount, + const GifColorType *ColorMap); +extern void GifFreeMapObject(ColorMapObject *Object); +extern ColorMapObject *GifUnionColorMap(const ColorMapObject *ColorIn1, + const ColorMapObject *ColorIn2, + GifPixelType ColorTransIn2[]); +extern int GifBitSize(int n); + +/****************************************************************************** + Support for the in-core structures allocation (slurp mode). +******************************************************************************/ + +extern void GifApplyTranslation(SavedImage *Image, + const GifPixelType Translation[]); +extern int GifAddExtensionBlock(int *ExtensionBlock_Count, + ExtensionBlock **ExtensionBlocks, int Function, + unsigned int Len, unsigned char ExtData[]); +extern void GifFreeExtensions(int *ExtensionBlock_Count, + ExtensionBlock **ExtensionBlocks); +extern SavedImage *GifMakeSavedImage(GifFileType *GifFile, + const SavedImage *CopyFrom); +extern void GifFreeSavedImages(GifFileType *GifFile); + +/****************************************************************************** + 5.x functions for GIF89 graphics control blocks +******************************************************************************/ + +int DGifExtensionToGCB(const size_t GifExtensionLength, + const GifByteType *GifExtension, + GraphicsControlBlock *GCB); +size_t EGifGCBToExtension(const GraphicsControlBlock *GCB, + GifByteType *GifExtension); + +int DGifSavedExtensionToGCB(GifFileType *GifFile, int ImageIndex, + GraphicsControlBlock *GCB); +int EGifGCBToSavedExtension(const GraphicsControlBlock *GCB, + GifFileType *GifFile, int ImageIndex); + +/****************************************************************************** + The library's internal utility font +******************************************************************************/ + +#define GIF_FONT_WIDTH 8 +#define GIF_FONT_HEIGHT 8 +extern const unsigned char GifAsciiTable8x8[][GIF_FONT_WIDTH]; + +extern void GifDrawText8x8(SavedImage *Image, const int x, const int y, + const char *legend, const int color); + +extern void GifDrawBox(SavedImage *Image, const int x, const int y, const int w, + const int d, const int color); + +extern void GifDrawRectangle(SavedImage *Image, const int x, const int y, + const int w, const int d, const int color); + +extern void GifDrawBoxedText8x8(SavedImage *Image, const int x, const int y, + const char *legend, const int border, + const int bg, const int fg); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif /* _GIF_LIB_H */ + +/* end */ diff --git a/thirdparty/giflib/gif_lib_private.h b/thirdparty/giflib/gif_lib_private.h new file mode 100644 index 00000000000..19578d4530c --- /dev/null +++ b/thirdparty/giflib/gif_lib_private.h @@ -0,0 +1,72 @@ +/**************************************************************************** + +gif_lib_private.h - internal giflib routines and structures + +SPDX-License-Identifier: MIT + +****************************************************************************/ + +#ifndef _GIF_LIB_PRIVATE_H +#define _GIF_LIB_PRIVATE_H + +#include "gif_hash.h" +#include "gif_lib.h" + +#ifndef SIZE_MAX +#define SIZE_MAX UINTPTR_MAX +#endif + +#define EXTENSION_INTRODUCER 0x21 +#define DESCRIPTOR_INTRODUCER 0x2c +#define TERMINATOR_INTRODUCER 0x3b + +#define LZ_MAX_CODE 4095 /* Biggest code possible in 12 bits. */ +#define LZ_BITS 12 + +#define FLUSH_OUTPUT 4096 /* Impossible code, to signal flush. */ +#define FIRST_CODE 4097 /* Impossible code, to signal first. */ +#define NO_SUCH_CODE 4098 /* Impossible code, to signal empty. */ + +#define FILE_STATE_WRITE 0x01 +#define FILE_STATE_SCREEN 0x02 +#define FILE_STATE_IMAGE 0x04 +#define FILE_STATE_READ 0x08 + +#define IS_READABLE(Private) (Private->FileState & FILE_STATE_READ) +#define IS_WRITEABLE(Private) (Private->FileState & FILE_STATE_WRITE) + +typedef struct GifFilePrivateType { + GifWord FileState, FileHandle, /* Where all this data goes to! */ + BitsPerPixel, /* Bits per pixel (Codes uses at least this + 1). */ + ClearCode, /* The CLEAR LZ code. */ + EOFCode, /* The EOF LZ code. */ + RunningCode, /* The next code algorithm can generate. */ + RunningBits, /* The number of bits required to represent + RunningCode. */ + MaxCode1, /* 1 bigger than max. possible code, in RunningBits bits. + */ + LastCode, /* The code before the current code. */ + CrntCode, /* Current algorithm code. */ + StackPtr, /* For character stack (see below). */ + CrntShiftState; /* Number of bits in CrntShiftDWord. */ + unsigned long CrntShiftDWord; /* For bytes decomposition into codes. */ + unsigned long PixelCount; /* Number of pixels in image. */ + FILE *File; /* File as stream. */ + InputFunc Read; /* function to read gif input (TVT) */ + OutputFunc Write; /* function to write gif output (MRB) */ + GifByteType Buf[256]; /* Compressed input is buffered here. */ + GifByteType Stack[LZ_MAX_CODE]; /* Decoded pixels are stacked here. */ + GifByteType Suffix[LZ_MAX_CODE + 1]; /* So we can trace the codes. */ + GifPrefixType Prefix[LZ_MAX_CODE + 1]; + GifHashTableType *HashTable; + bool gif89; +} GifFilePrivateType; + +#ifndef HAVE_REALLOCARRAY +extern void *openbsd_reallocarray(void *optr, size_t nmemb, size_t size); +#define reallocarray openbsd_reallocarray +#endif + +#endif /* _GIF_LIB_PRIVATE_H */ + +/* end */ diff --git a/thirdparty/giflib/gifalloc.c b/thirdparty/giflib/gifalloc.c new file mode 100644 index 00000000000..47c653930f6 --- /dev/null +++ b/thirdparty/giflib/gifalloc.c @@ -0,0 +1,425 @@ +/***************************************************************************** + + GIF construction tools + +SPDX-License-Identifier: MIT + +****************************************************************************/ + +#include +#include +#include + +#include "gif_lib.h" +#include "gif_lib_private.h" + +#define MAX(x, y) (((x) > (y)) ? (x) : (y)) + +/****************************************************************************** + Miscellaneous utility functions +******************************************************************************/ + +/* return smallest bitfield size n will fit in */ +int GifBitSize(int n) { + register int i; + + for (i = 1; i <= 8; i++) { + if ((1 << i) >= n) { + break; + } + } + return (i); +} + +/****************************************************************************** + Color map object functions +******************************************************************************/ + +/* + * Allocate a color map of given size; initialize with contents of + * ColorMap if that pointer is non-NULL. + */ +ColorMapObject *GifMakeMapObject(int ColorCount, const GifColorType *ColorMap) { + ColorMapObject *Object; + + /*** FIXME: Our ColorCount has to be a power of two. Is it necessary to + * make the user know that or should we automatically round up instead? + */ + if (ColorCount != (1 << GifBitSize(ColorCount))) { + return ((ColorMapObject *)NULL); + } + + Object = (ColorMapObject *)malloc(sizeof(ColorMapObject)); + if (Object == (ColorMapObject *)NULL) { + return ((ColorMapObject *)NULL); + } + + Object->Colors = + (GifColorType *)calloc(ColorCount, sizeof(GifColorType)); + if (Object->Colors == (GifColorType *)NULL) { + free(Object); + return ((ColorMapObject *)NULL); + } + + Object->ColorCount = ColorCount; + Object->BitsPerPixel = GifBitSize(ColorCount); + Object->SortFlag = false; + + if (ColorMap != NULL) { + memcpy((char *)Object->Colors, (char *)ColorMap, + ColorCount * sizeof(GifColorType)); + } + + return (Object); +} + +/******************************************************************************* + Free a color map object +*******************************************************************************/ +void GifFreeMapObject(ColorMapObject *Object) { + if (Object != NULL) { + (void)free(Object->Colors); + (void)free(Object); + } +} + +#ifdef DEBUG +void DumpColorMap(ColorMapObject *Object, FILE *fp) { + if (Object != NULL) { + int i, j, Len = Object->ColorCount; + + for (i = 0; i < Len; i += 4) { + for (j = 0; j < 4 && j < Len; j++) { + (void)fprintf(fp, "%3d: %02x %02x %02x ", + i + j, Object->Colors[i + j].Red, + Object->Colors[i + j].Green, + Object->Colors[i + j].Blue); + } + (void)fprintf(fp, "\n"); + } + } +} +#endif /* DEBUG */ + +/******************************************************************************* + Compute the union of two given color maps and return it. If result can't + fit into 256 colors, NULL is returned, the allocated union otherwise. + ColorIn1 is copied as is to ColorUnion, while colors from ColorIn2 are + copied iff they didn't exist before. ColorTransIn2 maps the old + ColorIn2 into the ColorUnion color map table./ +*******************************************************************************/ +ColorMapObject *GifUnionColorMap(const ColorMapObject *ColorIn1, + const ColorMapObject *ColorIn2, + GifPixelType ColorTransIn2[]) { + int i, j, CrntSlot, RoundUpTo, NewGifBitSize; + ColorMapObject *ColorUnion; + + /* + * We don't worry about duplicates within either color map; if + * the caller wants to resolve those, he can perform unions + * with an empty color map. + */ + + /* Allocate table which will hold the result for sure. */ + ColorUnion = GifMakeMapObject( + MAX(ColorIn1->ColorCount, ColorIn2->ColorCount) * 2, NULL); + + if (ColorUnion == NULL) { + return (NULL); + } + + /* + * Copy ColorIn1 to ColorUnion. + */ + for (i = 0; i < ColorIn1->ColorCount; i++) { + ColorUnion->Colors[i] = ColorIn1->Colors[i]; + } + CrntSlot = ColorIn1->ColorCount; + + /* + * Potentially obnoxious hack: + * + * Back CrntSlot down past all contiguous {0, 0, 0} slots at the end + * of table 1. This is very useful if your display is limited to + * 16 colors. + */ + while (ColorIn1->Colors[CrntSlot - 1].Red == 0 && + ColorIn1->Colors[CrntSlot - 1].Green == 0 && + ColorIn1->Colors[CrntSlot - 1].Blue == 0) { + CrntSlot--; + } + + /* Copy ColorIn2 to ColorUnion (use old colors if they exist): */ + for (i = 0; i < ColorIn2->ColorCount && CrntSlot <= 256; i++) { + /* Let's see if this color already exists: */ + for (j = 0; j < ColorIn1->ColorCount; j++) { + if (memcmp(&ColorIn1->Colors[j], &ColorIn2->Colors[i], + sizeof(GifColorType)) == 0) { + break; + } + } + + if (j < ColorIn1->ColorCount) { + ColorTransIn2[i] = j; /* color exists in Color1 */ + } else { + /* Color is new - copy it to a new slot: */ + ColorUnion->Colors[CrntSlot] = ColorIn2->Colors[i]; + ColorTransIn2[i] = CrntSlot++; + } + } + + if (CrntSlot > 256) { + GifFreeMapObject(ColorUnion); + return ((ColorMapObject *)NULL); + } + + NewGifBitSize = GifBitSize(CrntSlot); + RoundUpTo = (1 << NewGifBitSize); + + if (RoundUpTo != ColorUnion->ColorCount) { + register GifColorType *Map = ColorUnion->Colors; + + /* + * Zero out slots up to next power of 2. + * We know these slots exist because of the way ColorUnion's + * start dimension was computed. + */ + for (j = CrntSlot; j < RoundUpTo; j++) { + Map[j].Red = Map[j].Green = Map[j].Blue = 0; + } + + /* perhaps we can shrink the map? */ + if (RoundUpTo < ColorUnion->ColorCount) { + GifColorType *new_map = (GifColorType *)reallocarray( + Map, RoundUpTo, sizeof(GifColorType)); + if (new_map == NULL) { + GifFreeMapObject(ColorUnion); + return ((ColorMapObject *)NULL); + } + ColorUnion->Colors = new_map; + } + } + + ColorUnion->ColorCount = RoundUpTo; + ColorUnion->BitsPerPixel = NewGifBitSize; + + return (ColorUnion); +} + +/******************************************************************************* + Apply a given color translation to the raster bits of an image +*******************************************************************************/ +void GifApplyTranslation(SavedImage *Image, const GifPixelType Translation[]) { + register int i; + register int RasterSize = + Image->ImageDesc.Height * Image->ImageDesc.Width; + + for (i = 0; i < RasterSize; i++) { + Image->RasterBits[i] = Translation[Image->RasterBits[i]]; + } +} + +/****************************************************************************** + Extension record functions +******************************************************************************/ +int GifAddExtensionBlock(int *ExtensionBlockCount, + ExtensionBlock **ExtensionBlocks, int Function, + unsigned int Len, unsigned char ExtData[]) { + ExtensionBlock *ep; + + if (*ExtensionBlocks == NULL) { + *ExtensionBlocks = + (ExtensionBlock *)malloc(sizeof(ExtensionBlock)); + } else { + ExtensionBlock *ep_new = (ExtensionBlock *)reallocarray( + *ExtensionBlocks, (*ExtensionBlockCount + 1), + sizeof(ExtensionBlock)); + if (ep_new == NULL) { + return (GIF_ERROR); + } + *ExtensionBlocks = ep_new; + } + + if (*ExtensionBlocks == NULL) { + return (GIF_ERROR); + } + + ep = &(*ExtensionBlocks)[(*ExtensionBlockCount)++]; + + ep->Function = Function; + ep->ByteCount = Len; + ep->Bytes = (GifByteType *)malloc(ep->ByteCount); + if (ep->Bytes == NULL) { + return (GIF_ERROR); + } + + if (ExtData != NULL) { + memcpy(ep->Bytes, ExtData, Len); + } + + return (GIF_OK); +} + +void GifFreeExtensions(int *ExtensionBlockCount, + ExtensionBlock **ExtensionBlocks) { + ExtensionBlock *ep; + + if (*ExtensionBlocks == NULL) { + return; + } + + for (ep = *ExtensionBlocks; + ep < (*ExtensionBlocks + *ExtensionBlockCount); ep++) { + (void)free((char *)ep->Bytes); + } + (void)free((char *)*ExtensionBlocks); + *ExtensionBlocks = NULL; + *ExtensionBlockCount = 0; +} + +/****************************************************************************** + Image block allocation functions +******************************************************************************/ + +/* Private Function: + * Frees the last image in the GifFile->SavedImages array + */ +void FreeLastSavedImage(GifFileType *GifFile) { + SavedImage *sp; + + if ((GifFile == NULL) || (GifFile->SavedImages == NULL)) { + return; + } + + /* Remove one SavedImage from the GifFile */ + GifFile->ImageCount--; + sp = &GifFile->SavedImages[GifFile->ImageCount]; + + /* Deallocate its Colormap */ + if (sp->ImageDesc.ColorMap != NULL) { + GifFreeMapObject(sp->ImageDesc.ColorMap); + sp->ImageDesc.ColorMap = NULL; + } + + /* Deallocate the image data */ + if (sp->RasterBits != NULL) { + free((char *)sp->RasterBits); + } + + /* Deallocate any extensions */ + GifFreeExtensions(&sp->ExtensionBlockCount, &sp->ExtensionBlocks); + + /*** FIXME: We could realloc the GifFile->SavedImages structure but is + * there a point to it? Saves some memory but we'd have to do it every + * time. If this is used in GifFreeSavedImages then it would be + * inefficient (The whole array is going to be deallocated.) If we just + * use it when we want to free the last Image it's convenient to do it + * here. + */ +} + +/* + * Append an image block to the SavedImages array + */ +SavedImage *GifMakeSavedImage(GifFileType *GifFile, + const SavedImage *CopyFrom) { + // cppcheck-suppress ctunullpointer + if (GifFile->SavedImages == NULL) { + GifFile->SavedImages = (SavedImage *)malloc(sizeof(SavedImage)); + } else { + SavedImage *newSavedImages = (SavedImage *)reallocarray( + GifFile->SavedImages, (GifFile->ImageCount + 1), + sizeof(SavedImage)); + if (newSavedImages == NULL) { + return ((SavedImage *)NULL); + } + GifFile->SavedImages = newSavedImages; + } + if (GifFile->SavedImages == NULL) { + return ((SavedImage *)NULL); + } else { + SavedImage *sp = &GifFile->SavedImages[GifFile->ImageCount++]; + + if (CopyFrom != NULL) { + memcpy((char *)sp, CopyFrom, sizeof(SavedImage)); + + /* + * Make our own allocated copies of the heap fields in + * the copied record. This guards against potential + * aliasing problems. + */ + + /* first, the local color map */ + if (CopyFrom->ImageDesc.ColorMap != NULL) { + sp->ImageDesc.ColorMap = GifMakeMapObject( + CopyFrom->ImageDesc.ColorMap->ColorCount, + CopyFrom->ImageDesc.ColorMap->Colors); + if (sp->ImageDesc.ColorMap == NULL) { + FreeLastSavedImage(GifFile); + return (SavedImage *)(NULL); + } + } + + /* next, the raster */ + sp->RasterBits = (unsigned char *)reallocarray( + NULL, + (CopyFrom->ImageDesc.Height * + CopyFrom->ImageDesc.Width), + sizeof(GifPixelType)); + if (sp->RasterBits == NULL) { + FreeLastSavedImage(GifFile); + return (SavedImage *)(NULL); + } + memcpy(sp->RasterBits, CopyFrom->RasterBits, + sizeof(GifPixelType) * + CopyFrom->ImageDesc.Height * + CopyFrom->ImageDesc.Width); + + /* finally, the extension blocks */ + if (CopyFrom->ExtensionBlocks != NULL) { + sp->ExtensionBlocks = + (ExtensionBlock *)reallocarray( + NULL, CopyFrom->ExtensionBlockCount, + sizeof(ExtensionBlock)); + if (sp->ExtensionBlocks == NULL) { + FreeLastSavedImage(GifFile); + return (SavedImage *)(NULL); + } + memcpy(sp->ExtensionBlocks, + CopyFrom->ExtensionBlocks, + sizeof(ExtensionBlock) * + CopyFrom->ExtensionBlockCount); + } + } else { + memset((char *)sp, '\0', sizeof(SavedImage)); + } + + return (sp); + } +} + +void GifFreeSavedImages(GifFileType *GifFile) { + SavedImage *sp; + + if ((GifFile == NULL) || (GifFile->SavedImages == NULL)) { + return; + } + for (sp = GifFile->SavedImages; + sp < GifFile->SavedImages + GifFile->ImageCount; sp++) { + if (sp->ImageDesc.ColorMap != NULL) { + GifFreeMapObject(sp->ImageDesc.ColorMap); + sp->ImageDesc.ColorMap = NULL; + } + + if (sp->RasterBits != NULL) { + free((char *)sp->RasterBits); + } + + GifFreeExtensions(&sp->ExtensionBlockCount, + &sp->ExtensionBlocks); + } + free((char *)GifFile->SavedImages); + GifFile->SavedImages = NULL; +} + +/* end */ diff --git a/thirdparty/giflib/openbsd-reallocarray.c b/thirdparty/giflib/openbsd-reallocarray.c new file mode 100644 index 00000000000..5f5367c7b87 --- /dev/null +++ b/thirdparty/giflib/openbsd-reallocarray.c @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2008 Otto Moerbeek + * SPDX-License-Identifier: MIT + */ + +#include +#include +#include +#include + +#ifndef SIZE_MAX +#define SIZE_MAX UINTPTR_MAX +#endif + +/* + * This is sqrt(SIZE_MAX+1), as s1*s2 <= SIZE_MAX + * if both s1 < MUL_NO_OVERFLOW and s2 < MUL_NO_OVERFLOW + */ +#define MUL_NO_OVERFLOW ((size_t)1 << (sizeof(size_t) * 4)) + +void *openbsd_reallocarray(void *optr, size_t nmemb, size_t size) { + if ((nmemb >= MUL_NO_OVERFLOW || size >= MUL_NO_OVERFLOW) && + nmemb > 0 && SIZE_MAX / nmemb < size) { + errno = ENOMEM; + return NULL; + } + /* + * Head off variations in realloc behavior on different + * platforms (reported by MarkR ) + * + * The behaviour of reallocarray is implementation-defined if + * nmemb or size is zero. It can return NULL or non-NULL + * depending on the platform. + * https://www.securecoding.cert.org/confluence/display/c/MEM04-C.Beware+of+zero-lengthallocations + * + * Here are some extracts from realloc man pages on different platforms. + * + * void realloc( void memblock, size_t size ); + * + * Windows: + * + * If there is not enough available memory to expand the block + * to the given size, the original block is left unchanged, + * and NULL is returned. If size is zero, then the block + * pointed to by memblock is freed; the return value is NULL, + * and memblock is left pointing at a freed block. + * + * OpenBSD: + * + * If size or nmemb is equal to 0, a unique pointer to an + * access protected, zero sized object is returned. Access via + * this pointer will generate a SIGSEGV exception. + * + * Linux: + * + * If size was equal to 0, either NULL or a pointer suitable + * to be passed to free() is returned. + * + * OS X: + * + * If size is zero and ptr is not NULL, a new, minimum sized + * object is allocated and the original object is freed. + * + * It looks like images with zero width or height can trigger + * this, and fuzzing behaviour will differ by platform, so + * fuzzing on one platform may not detect zero-size allocation + * problems on other platforms. + */ + if (size == 0 || nmemb == 0) { + return NULL; + } + return realloc(optr, size * nmemb); +}