Skip to content

Commit b9f4ce3

Browse files
committed
Add Lottie resource importer using ThorVG
Add lottie importer for `.lot` to import lottie as CompressedTexture2D
1 parent 215acd5 commit b9f4ce3

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

67 files changed

+29160
-1
lines changed

editor/doc_tools.cpp

+29
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
#include "core/string/translation_server.h"
4141
#include "editor/editor_settings.h"
4242
#include "editor/export/editor_export_platform.h"
43+
#include "editor/import/resource_importer_texture.h"
4344
#include "scene/resources/theme.h"
4445
#include "scene/theme/theme_db.h"
4546

@@ -464,6 +465,34 @@ void DocTools::generate(BitField<GenerateFlags> p_flags) {
464465
} else if (name == "ProjectSettings") {
465466
ProjectSettings::get_singleton()->get_property_list(&properties);
466467
own_properties = properties;
468+
} else if (name == "ResourceImporterLottie") {
469+
import_option = true;
470+
471+
List<ResourceImporter::ImportOption> texture_options;
472+
ResourceImporter *texture_importer = memnew(ResourceImporterTexture);
473+
texture_importer->get_import_options("", &texture_options);
474+
475+
ResourceImporter *resimp = Object::cast_to<ResourceImporter>(ClassDB::instantiate(name));
476+
List<ResourceImporter::ImportOption> options;
477+
resimp->get_import_options("", &options);
478+
for (const ResourceImporter::ImportOption &option : options) {
479+
const PropertyInfo &prop = option.option;
480+
bool own_property = true;
481+
for (const ResourceImporter::ImportOption &texture_option : texture_options) {
482+
if (texture_option.option.name == prop.name) {
483+
own_property = false;
484+
break;
485+
}
486+
}
487+
if (!own_property) {
488+
continue;
489+
}
490+
properties.push_back(prop);
491+
import_options_default[prop.name] = option.default_value;
492+
}
493+
own_properties = properties;
494+
memdelete(resimp);
495+
memdelete(texture_importer);
467496
} else if (ClassDB::is_parent_class(name, "ResourceImporter") && name != "EditorImportPlugin" && ClassDB::can_instantiate(name)) {
468497
import_option = true;
469498
ResourceImporter *resimp = Object::cast_to<ResourceImporter>(ClassDB::instantiate(name));

modules/svg/SCsub

+25-1
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,21 @@ thirdparty_sources = [
5959
"src/renderer/sw_engine/tvgSwStroke.cpp",
6060
]
6161

62+
if env.editor_build:
63+
thirdparty_sources += [
64+
"src/renderer/tvgAnimation.cpp",
65+
# Lottie loader
66+
"src/loaders/lottie/tvgLottieAnimation.cpp",
67+
"src/loaders/lottie/tvgLottieBuilder.cpp",
68+
# "src/loaders/lottie/tvgLottieExpressions.cpp",
69+
"src/loaders/lottie/tvgLottieInterpolator.cpp",
70+
"src/loaders/lottie/tvgLottieLoader.cpp",
71+
"src/loaders/lottie/tvgLottieModel.cpp",
72+
"src/loaders/lottie/tvgLottieModifier.cpp",
73+
"src/loaders/lottie/tvgLottieParser.cpp",
74+
"src/loaders/lottie/tvgLottieParserHandler.cpp",
75+
]
76+
6277
if env["module_webp_enabled"]:
6378
thirdparty_sources += ["src/loaders/external_webp/tvgWebpLoader.cpp"]
6479
env_svg.Append(CPPDEFINES=["THORVG_WEBP_LOADER_SUPPORT"])
@@ -72,6 +87,9 @@ env_svg.Append(CPPDEFINES=["TVG_STATIC"])
7287
# Explicit support for embedded images in svg.
7388
env_svg.Append(CPPDEFINES=["THORVG_FILE_IO_SUPPORT"])
7489

90+
if env.editor_build:
91+
env_svg.Append(CPPDEFINES=["THORVG_LOTTIE_LOADER_SUPPORT", "LOTTIE_ENABLED"])
92+
7593
env_thirdparty = env_svg.Clone()
7694
env_thirdparty.disable_warnings()
7795
env_thirdparty.Prepend(
@@ -85,6 +103,9 @@ env_thirdparty.Prepend(
85103
thirdparty_dir + "src/loaders/jpg",
86104
]
87105
)
106+
107+
if env.editor_build:
108+
env_thirdparty.Prepend(CPPPATH=[thirdparty_dir + "src/loaders/lottie"])
88109
if env["builtin_libpng"]:
89110
env_thirdparty.Prepend(CPPEXTPATH=["#thirdparty/libpng"])
90111
if env["module_webp_enabled"]:
@@ -99,7 +120,10 @@ env.modules_sources += thirdparty_obj
99120

100121
module_obj = []
101122

102-
env_svg.add_source_files(module_obj, "*.cpp")
123+
env_svg.add_source_files(module_obj, ["register_types.cpp", "image_loader_svg.cpp"])
124+
if env.editor_build:
125+
env_svg.add_source_files(module_obj, "editor/*.cpp")
126+
103127
env.modules_sources += module_obj
104128

105129
# Needed to force rebuilding the module files when the thirdparty library is updated.

modules/svg/config.py

+10
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,13 @@ def can_build(env, platform):
44

55
def configure(env):
66
pass
7+
8+
9+
def get_doc_classes():
10+
return [
11+
"ResourceImporterLottie",
12+
]
13+
14+
15+
def get_doc_path():
16+
return "doc_classes"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?xml version="1.0" encoding="UTF-8" ?>
2+
<class name="ResourceImporterLottie" inherits="ResourceImporterTexture" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd">
3+
<brief_description>
4+
Import a Lottie animation as a sprite sheet.
5+
</brief_description>
6+
<description>
7+
This imports a Lottie animation to a [CompressedTexture2D] with support for Lottie JSON.
8+
[b]Note:[/b] This importer only recognizes files with [code].lot[/code] extension. You have to rename Lottie JSON files extension to [code].lot[/code] to import them.
9+
</description>
10+
<tutorials>
11+
</tutorials>
12+
<members>
13+
<member name="lottie/begin" type="float" setter="" getter="" default="0">
14+
The start of the Lottie animation in the range [code]0.0[/code] to [code]1.0[/code].
15+
</member>
16+
<member name="lottie/columns" type="int" setter="" getter="" default="0">
17+
The number of columns of the sprite sheet. If it is [code]0[/code], the number of columns will be automatically calculated to be close to the number of rows.
18+
</member>
19+
<member name="lottie/end" type="float" setter="" getter="" default="1">
20+
The end of the Lottie animation in the range [code]0.0[/code] to [code]1.0[/code]. This value will be clamped to be at least [member lottie/begin].
21+
</member>
22+
<member name="lottie/fps" type="float" setter="" getter="" default="30">
23+
The frame rate Lottie animation should be rendered at. Higher values result in a larger image.
24+
</member>
25+
<member name="lottie/scale" type="float" setter="" getter="" default="1">
26+
The scale the Lottie animation should be rendered at, with [code]1.0[/code] being the original size. Higher values result in a larger image.
27+
</member>
28+
<member name="lottie/size_limit" type="int" setter="" getter="" default="2048">
29+
If set to a value greater than [code]0[/code], the size of the imported Lottie texture is limited on import to a value smaller than or equal to the value specified here.
30+
[b]Note:[/b] Importing large Lottie textures in editor is slow. This setting ensures import speed and ignores [member lottie/scale].
31+
</member>
32+
</members>
33+
</class>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
/**************************************************************************/
2+
/* resource_importer_lottie.cpp */
3+
/**************************************************************************/
4+
/* This file is part of: */
5+
/* GODOT ENGINE */
6+
/* https://godotengine.org */
7+
/**************************************************************************/
8+
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
9+
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
10+
/* */
11+
/* Permission is hereby granted, free of charge, to any person obtaining */
12+
/* a copy of this software and associated documentation files (the */
13+
/* "Software"), to deal in the Software without restriction, including */
14+
/* without limitation the rights to use, copy, modify, merge, publish, */
15+
/* distribute, sublicense, and/or sell copies of the Software, and to */
16+
/* permit persons to whom the Software is furnished to do so, subject to */
17+
/* the following conditions: */
18+
/* */
19+
/* The above copyright notice and this permission notice shall be */
20+
/* included in all copies or substantial portions of the Software. */
21+
/* */
22+
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
23+
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
24+
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
25+
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
26+
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
27+
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
28+
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
29+
/**************************************************************************/
30+
31+
#include "resource_importer_lottie.h"
32+
33+
#include "core/io/dir_access.h"
34+
#include "core/io/json.h"
35+
36+
#include <thorvg.h>
37+
38+
static Ref<Image> lottie_to_sprite_sheet(Ref<JSON> p_json, float p_begin, float p_end, float p_fps, int p_columns, float p_scale, int p_size_limit, Size2i *r_sprite_size, int *r_columns, int *r_frame_count) {
39+
std::unique_ptr<tvg::SwCanvas> sw_canvas = tvg::SwCanvas::gen();
40+
std::unique_ptr<tvg::Animation> animation = tvg::Animation::gen();
41+
tvg::Picture *picture = animation->picture();
42+
tvg::Result res = sw_canvas->push(tvg::cast(picture));
43+
ERR_FAIL_COND_V(res != tvg::Result::Success, Ref<Image>());
44+
45+
String lottie_str = p_json->get_parsed_text();
46+
if (lottie_str.is_empty()) {
47+
// Set p_sort_keys to false, otherwise ThorVG can't load it.
48+
lottie_str = JSON::stringify(p_json->get_data(), "", false);
49+
}
50+
51+
res = picture->load(lottie_str.utf8().get_data(), lottie_str.utf8().size(), "lottie", true);
52+
ERR_FAIL_COND_V_MSG(res != tvg::Result::Success, Ref<Image>(), "Failed to load Lottie.");
53+
54+
float origin_width, origin_height;
55+
picture->size(&origin_width, &origin_height);
56+
57+
p_end = CLAMP(p_end, p_begin, 1);
58+
int total_frame_count = animation->totalFrame();
59+
int frame_count = MAX(1, animation->duration() * CLAMP(p_end - p_begin, 0, 1) * p_fps);
60+
int sheet_columns = p_columns <= 0 ? Math::ceil(Math::sqrt((float)frame_count)) : p_columns;
61+
int sheet_rows = Math::ceil(((float)frame_count) / sheet_columns);
62+
Vector2 texture_size = Vector2(origin_width * sheet_columns * p_scale, origin_height * sheet_rows * p_scale);
63+
64+
const uint32_t max_dimension = 16384;
65+
if (p_size_limit <= 0) {
66+
p_size_limit = max_dimension;
67+
}
68+
if (texture_size[texture_size.max_axis_index()] > p_size_limit) {
69+
p_scale = p_size_limit / MAX(origin_width * sheet_columns, origin_height * sheet_rows);
70+
}
71+
uint32_t width = MAX(1, Math::round(origin_width * p_scale));
72+
uint32_t height = MAX(1, Math::round(origin_height * p_scale));
73+
picture->size(width, height);
74+
75+
uint32_t *buffer = (uint32_t *)memalloc(sizeof(uint32_t) * width * height);
76+
memset(buffer, 0, sizeof(uint32_t) * width * height);
77+
78+
sw_canvas->sync();
79+
res = sw_canvas->target(buffer, width, width, height, tvg::SwCanvas::ARGB8888S);
80+
if (res != tvg::Result::Success) {
81+
memfree(buffer);
82+
ERR_FAIL_V_MSG(Ref<Image>(), "Couldn't set target on ThorVG canvas.");
83+
}
84+
85+
Ref<Image> image = Image::create_empty(width * sheet_columns, height * sheet_rows, false, Image::FORMAT_RGBA8);
86+
87+
for (int row = 0; row < sheet_rows; row++) {
88+
for (int column = 0; column < sheet_columns; column++) {
89+
if (row * sheet_columns + column >= frame_count) {
90+
break;
91+
}
92+
float progress = ((float)(row * sheet_columns + column)) / frame_count;
93+
float current_frame = total_frame_count * (p_begin + (p_end - p_begin) * progress);
94+
95+
animation->frame(current_frame);
96+
res = sw_canvas->update(picture);
97+
if (res != tvg::Result::Success) {
98+
memfree(buffer);
99+
ERR_FAIL_V_MSG(Ref<Image>(), "Couldn't update ThorVG pictures on canvas.");
100+
}
101+
res = sw_canvas->draw();
102+
if (res != tvg::Result::Success) {
103+
WARN_PRINT_ONCE("Couldn't draw ThorVG pictures on canvas.");
104+
}
105+
res = sw_canvas->sync();
106+
if (res != tvg::Result::Success) {
107+
memfree(buffer);
108+
ERR_FAIL_V_MSG(Ref<Image>(), "Couldn't sync ThorVG canvas.");
109+
}
110+
111+
for (uint32_t y = 0; y < height; y++) {
112+
for (uint32_t x = 0; x < width; x++) {
113+
uint32_t n = buffer[y * width + x];
114+
Color color;
115+
color.set_r8((n >> 16) & 0xff);
116+
color.set_g8((n >> 8) & 0xff);
117+
color.set_b8(n & 0xff);
118+
color.set_a8((n >> 24) & 0xff);
119+
image->set_pixel(x + width * column, y + height * row, color);
120+
}
121+
}
122+
sw_canvas->clear(false);
123+
}
124+
}
125+
memfree(buffer);
126+
if (r_sprite_size) {
127+
*r_sprite_size = Size2i(width, height);
128+
}
129+
if (r_columns) {
130+
*r_columns = sheet_columns;
131+
}
132+
if (r_frame_count) {
133+
*r_frame_count = frame_count;
134+
}
135+
return image;
136+
}
137+
138+
String ResourceImporterLottie::get_importer_name() const {
139+
return "lottie_texture";
140+
}
141+
142+
String ResourceImporterLottie::get_visible_name() const {
143+
return "Texture2D";
144+
}
145+
146+
void ResourceImporterLottie::get_import_options(const String &p_path, List<ImportOption> *r_options, int p_preset) const {
147+
r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "lottie/size_limit", PROPERTY_HINT_RANGE, "0,4096,1,or_greater"), 2048));
148+
r_options->push_back(ImportOption(PropertyInfo(Variant::FLOAT, "lottie/scale", PROPERTY_HINT_RANGE, "0,1,0.001,or_greater"), 1));
149+
r_options->push_back(ImportOption(PropertyInfo(Variant::FLOAT, "lottie/begin", PROPERTY_HINT_RANGE, "0,1,0.001"), 0));
150+
r_options->push_back(ImportOption(PropertyInfo(Variant::FLOAT, "lottie/end", PROPERTY_HINT_RANGE, "0,1,0.001"), 1));
151+
r_options->push_back(ImportOption(PropertyInfo(Variant::FLOAT, "lottie/fps", PROPERTY_HINT_RANGE, "0,60,0.1,or_greater"), 30));
152+
r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "lottie/columns", PROPERTY_HINT_RANGE, "0,16,1,or_greater"), 0));
153+
ResourceImporterTexture::get_import_options(p_path, r_options, p_preset);
154+
}
155+
156+
void ResourceImporterLottie::get_recognized_extensions(List<String> *p_extensions) const {
157+
p_extensions->push_back("lot");
158+
}
159+
160+
Error ResourceImporterLottie::import(ResourceUID::ID p_source_id, const String &p_source_file, const String &p_save_path, const HashMap<StringName, Variant> &p_options, List<String> *r_platform_variants, List<String> *r_gen_files, Variant *r_metadata) {
161+
Error err = OK;
162+
Ref<JSON> lottie_json;
163+
lottie_json.instantiate();
164+
String Lottie_str = FileAccess::get_file_as_string(p_source_file, &err);
165+
ERR_FAIL_COND_V(err != OK, err);
166+
lottie_json->parse(Lottie_str, true);
167+
ERR_FAIL_COND_V(lottie_json.is_null(), ERR_INVALID_DATA);
168+
169+
const int size_limit = p_options["lottie/size_limit"];
170+
const float scale = p_options["lottie/scale"];
171+
const float begin = p_options["lottie/begin"];
172+
const float end = p_options["lottie/end"];
173+
const float fps = p_options["lottie/fps"];
174+
const int columns = p_options["lottie/columns"];
175+
176+
Size2i sprite_size;
177+
int column_r;
178+
int frame_count;
179+
Ref<Image> image = lottie_to_sprite_sheet(lottie_json, begin, end, fps, columns, scale, size_limit, &sprite_size, &column_r, &frame_count);
180+
ERR_FAIL_COND_V(image.is_null(), ERR_INVALID_DATA);
181+
String tmp_image = p_save_path + ".tmp.png";
182+
err = image->save_png(tmp_image);
183+
if (err == OK) {
184+
err = ResourceImporterTexture::import(p_source_id, tmp_image, p_save_path, p_options, r_platform_variants, r_gen_files, r_metadata);
185+
Ref<DirAccess> d = DirAccess::create(DirAccess::ACCESS_RESOURCES);
186+
err = d->remove(tmp_image);
187+
}
188+
return err;
189+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/**************************************************************************/
2+
/* resource_importer_lottie.h */
3+
/**************************************************************************/
4+
/* This file is part of: */
5+
/* GODOT ENGINE */
6+
/* https://godotengine.org */
7+
/**************************************************************************/
8+
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
9+
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
10+
/* */
11+
/* Permission is hereby granted, free of charge, to any person obtaining */
12+
/* a copy of this software and associated documentation files (the */
13+
/* "Software"), to deal in the Software without restriction, including */
14+
/* without limitation the rights to use, copy, modify, merge, publish, */
15+
/* distribute, sublicense, and/or sell copies of the Software, and to */
16+
/* permit persons to whom the Software is furnished to do so, subject to */
17+
/* the following conditions: */
18+
/* */
19+
/* The above copyright notice and this permission notice shall be */
20+
/* included in all copies or substantial portions of the Software. */
21+
/* */
22+
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
23+
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
24+
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
25+
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
26+
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
27+
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
28+
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
29+
/**************************************************************************/
30+
31+
#pragma once
32+
33+
#include "core/io/resource_importer.h"
34+
#include "editor/import/resource_importer_texture.h"
35+
36+
class ResourceImporterLottie : public ResourceImporterTexture {
37+
GDCLASS(ResourceImporterLottie, ResourceImporterTexture);
38+
39+
public:
40+
virtual String get_importer_name() const override;
41+
virtual String get_visible_name() const override;
42+
virtual void get_import_options(const String &p_path, List<ImportOption> *r_options, int p_preset = 0) const override;
43+
virtual void get_recognized_extensions(List<String> *p_extensions) const override;
44+
virtual Error import(ResourceUID::ID p_source_id, const String &p_source_file, const String &p_save_path, const HashMap<StringName, Variant> &p_options, List<String> *r_platform_variants, List<String> *r_gen_files = nullptr, Variant *r_metadata = nullptr) override;
45+
};

0 commit comments

Comments
 (0)