|
| 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 | +} |
0 commit comments