Skip to content

Commit

Permalink
Add GIF import
Browse files Browse the repository at this point in the history
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`
  • Loading branch information
Spartan322 committed Oct 29, 2024
1 parent 77eaec7 commit b5995ba
Show file tree
Hide file tree
Showing 45 changed files with 5,679 additions and 0 deletions.
10 changes: 10 additions & 0 deletions core/io/image.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3144,6 +3144,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;

void (*Image::_image_compress_bc_func)(Image *, Image::UsedChannels) = nullptr;
void (*Image::_image_compress_bptc_func)(Image *, Image::UsedChannels) = nullptr;
Expand Down Expand Up @@ -3636,6 +3637,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));
Expand Down Expand Up @@ -4078,6 +4080,14 @@ Error Image::load_ktx_from_buffer(const Vector<uint8_t> &p_array) {
return _load_from_buffer(p_array, _ktx_mem_loader_func);
}

Error Image::load_gif_from_buffer(const Vector<uint8_t> &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());
Expand Down
2 changes: 2 additions & 0 deletions core/io/image.h
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,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;

static void (*_image_compress_bc_func)(Image *, UsedChannels p_channels);
static void (*_image_compress_bptc_func)(Image *, UsedChannels p_channels);
Expand Down Expand Up @@ -416,6 +417,7 @@ class Image : public Resource {
Error load_tga_from_buffer(const Vector<uint8_t> &p_array);
Error load_bmp_from_buffer(const Vector<uint8_t> &p_array);
Error load_ktx_from_buffer(const Vector<uint8_t> &p_array);
Error load_gif_from_buffer(const Vector<uint8_t> &p_array);

Error load_svg_from_buffer(const Vector<uint8_t> &p_array, float scale = 1.0);
Error load_svg_from_string(const String &p_svg_str, float scale = 1.0);
Expand Down
20 changes: 20 additions & 0 deletions doc/classes/AnimatedTexture.xml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,13 @@
<tutorials>
</tutorials>
<methods>
<method name="create_from_image_frames" qualifiers="static">
<return type="AnimatedTexture" />
<param index="0" name="image_frames" type="ImageFrames" />
<description>
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.
</description>
</method>
<method name="get_frame_duration" qualifiers="const">
<return type="float" />
<param index="0" name="frame" type="int" />
Expand All @@ -27,6 +34,12 @@
Returns the given frame's [Texture2D].
</description>
</method>
<method name="make_image_frames" qualifiers="const">
<return type="ImageFrames" />
<description>
Creates a new [ImageFrames] object from contents.
</description>
</method>
<method name="set_frame_duration">
<return type="void" />
<param index="0" name="frame" type="int" />
Expand All @@ -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.
</description>
</method>
<method name="set_from_image_frames">
<return type="void" />
<param index="0" name="image_frames" type="ImageFrames" />
<description>
Replaces the texture's data with a new [ImageFrames]. This function will ignore all frames beyond [constant MAX_FRAMES] - 1.
</description>
</method>
</methods>
<members>
<member name="current_frame" type="int" setter="set_current_frame" getter="get_current_frame">
Expand Down
8 changes: 8 additions & 0 deletions doc/classes/Image.xml
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,14 @@
Creates a new [Image] and loads data from the specified file.
</description>
</method>
<method name="load_gif_from_buffer">
<return type="int" enum="Error" />
<param index="0" name="buffer" type="PackedByteArray" />
<description>
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.
</description>
</method>
<method name="load_jpg_from_buffer">
<return type="int" enum="Error" />
<param index="0" name="buffer" type="PackedByteArray" />
Expand Down
91 changes: 91 additions & 0 deletions doc/classes/ImageFrames.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="ImageFrames" inherits="Resource" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd">
<brief_description>
A container for sequence of [Image]s.
</brief_description>
<description>
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].
</description>
<tutorials>
</tutorials>
<methods>
<method name="get_frame_delay" qualifiers="const">
<return type="float" />
<param index="0" name="frame" type="int" />
<description>
Returns the given frame's duration, in seconds.
</description>
</method>
<method name="get_frame_image" qualifiers="const">
<return type="Image" />
<param index="0" name="frame" type="int" />
<description>
Returns the given frame's [Image].
</description>
</method>
<method name="load">
<return type="int" enum="Error" />
<param index="0" name="path" type="String" />
<description>
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]
</description>
</method>
<method name="load_from_file" qualifiers="static">
<return type="ImageFrames" />
<param index="0" name="path" type="String" />
<description>
Creates a new [ImageFrames] and loads data from the specified file.
</description>
</method>
<method name="load_gif_from_buffer">
<return type="int" enum="Error" />
<param index="0" name="buffer" type="PackedByteArray" />
<description>
Loads an image from the binary contents of a GIF file.
</description>
</method>
<method name="set_frame_delay">
<return type="void" />
<param index="0" name="frame" type="int" />
<param index="1" name="delay" type="float" />
<description>
Sets the delay of any given frame. If set to [code]0[/code], the frame may be skipped if converted into an [AnimatedTexture].
</description>
</method>
<method name="set_frame_image">
<return type="void" />
<param index="0" name="frame" type="int" />
<param index="1" name="image" type="Image" />
<description>
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.
You can define any number of images, but keep in mind that only frames from 0 to [member frame_count] - 1 will be part of the image sequence.
</description>
</method>
</methods>
<members>
<member name="frame_count" type="int" setter="set_frame_count" getter="get_frame_count" default="0">
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.
</member>
</members>
</class>
19 changes: 19 additions & 0 deletions doc/classes/ImageFramesFormatLoader.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="ImageFramesFormatLoader" inherits="RefCounted" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd">
<brief_description>
Base class to add support for specific image sequence formats.
</brief_description>
<description>
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].
</description>
<tutorials>
</tutorials>
<constants>
<constant name="FLAG_NONE" value="0" enum="LoaderFlags" is_bitfield="true">
</constant>
<constant name="FLAG_FORCE_LINEAR" value="1" enum="LoaderFlags" is_bitfield="true">
</constant>
<constant name="FLAG_CONVERT_COLORS" value="2" enum="LoaderFlags" is_bitfield="true">
</constant>
</constants>
</class>
41 changes: 41 additions & 0 deletions doc/classes/ImageFramesFormatLoaderExtension.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="ImageFramesFormatLoaderExtension" inherits="ImageFramesFormatLoader" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd">
<brief_description>
Base class for creating [ImageFramesFormatLoader] extensions (adding support for extra image sequence formats).
</brief_description>
<description>
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.
</description>
<tutorials>
</tutorials>
<methods>
<method name="_get_recognized_extensions" qualifiers="virtual const">
<return type="PackedStringArray" />
<description>
</description>
</method>
<method name="_load_image_frames" qualifiers="virtual">
<return type="int" enum="Error" />
<param index="0" name="image_frames" type="ImageFrames" />
<param index="1" name="fileaccess" type="FileAccess" />
<param index="2" name="flags" type="int" enum="ImageFramesFormatLoader.LoaderFlags" is_bitfield="true" />
<param index="3" name="scale" type="float" />
<description>
Loads the content of [param fileaccess] into the provided [param image_frames].
</description>
</method>
<method name="add_format_loader">
<return type="void" />
<description>
Add this format loader to the engine, allowing it to recognize the file extensions returned by [method _get_recognized_extensions].
</description>
</method>
<method name="remove_format_loader">
<return type="void" />
<description>
Remove this format loader from the engine.
</description>
</method>
</methods>
</class>
29 changes: 29 additions & 0 deletions doc/classes/ResourceImporterAnimatedTexture.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="ResourceImporterAnimatedTexture" inherits="ResourceImporter" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd">
<brief_description>
Imports an animated image for use in 2D rendering.
</brief_description>
<description>
This importer imports [AnimatedTexture] resources. If you need to process the image in scripts in a more convenient way, use [ResourceImporterImageFrames] instead.
</description>
<tutorials>
</tutorials>
<members>
<member name="process/fix_alpha_border" type="bool" setter="" getter="" default="true">
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.
</member>
<member name="process/frame_limit" type="int" setter="" getter="" default="0">
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.
</member>
<member name="process/premult_alpha" type="bool" setter="" getter="" default="false">
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.
</member>
<member name="process/size_limit" type="int" setter="" getter="" default="0">
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).
</member>
</members>
</class>
11 changes: 11 additions & 0 deletions doc/classes/ResourceImporterImageFrames.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="ResourceImporterImageFrames" inherits="ResourceImporter" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd">
<brief_description>
Imports an image sequence for use in scripting, with no rendering capabilities.
</brief_description>
<description>
This importer imports [ImageFrames] resources, as opposed to [AnimatedTexture]. If you need to render the image in 2D, use [ResourceImporterAnimatedTexture] instead.
</description>
<tutorials>
</tutorials>
</class>
10 changes: 10 additions & 0 deletions editor/editor_node.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -6868,6 +6870,14 @@ EditorNode::EditorNode() {
import_texture_atlas.instantiate();
ResourceFormatImporter::get_singleton()->add_importer(import_texture_atlas);

Ref<ResourceImporterAnimatedTexture> import_animated_texture;
import_animated_texture.instantiate();
ResourceFormatImporter::get_singleton()->add_importer(import_animated_texture);

Ref<ResourceImporterImageFrames> import_image_frames;
import_image_frames.instantiate();
ResourceFormatImporter::get_singleton()->add_importer(import_image_frames);

Ref<ResourceImporterDynamicFont> import_font_data_dynamic;
import_font_data_dynamic.instantiate();
ResourceFormatImporter::get_singleton()->add_importer(import_font_data_dynamic);
Expand Down
Loading

0 comments on commit b5995ba

Please sign in to comment.