From 817d4db21f183d4e1bdb5c1101f221aa3ed10da0 Mon Sep 17 00:00:00 2001 From: Yuri Sizov Date: Mon, 22 Aug 2022 22:07:02 +0300 Subject: [PATCH] Allow images to be imported "for editor use" and respect editor settings --- core/io/image_loader.h | 1 + core/io/resource_importer.cpp | 9 + editor/editor_node.cpp | 2 + editor/editor_themes.cpp | 8 + editor/import/resource_importer_texture.cpp | 191 +++++++++++++------- modules/svg/image_loader_svg.cpp | 14 +- modules/svg/image_loader_svg.h | 4 + 7 files changed, 167 insertions(+), 62 deletions(-) diff --git a/core/io/image_loader.h b/core/io/image_loader.h index cb64d2310eec..bf78005e4036 100644 --- a/core/io/image_loader.h +++ b/core/io/image_loader.h @@ -52,6 +52,7 @@ public: enum LoaderFlags { FLAG_NONE = 0, FLAG_FORCE_LINEAR = 1, + FLAG_CONVERT_COLORS = 2, }; virtual ~ImageFormatLoader() {} diff --git a/core/io/resource_importer.cpp b/core/io/resource_importer.cpp index aa7f96a04790..d9235223179e 100644 --- a/core/io/resource_importer.cpp +++ b/core/io/resource_importer.cpp @@ -108,6 +108,15 @@ Error ResourceFormatImporter::_get_path_and_type(const String &p_path, PathAndTy } } +#ifdef TOOLS_ENABLED + if (r_path_and_type.metadata && !r_path_and_type.path.is_empty()) { + Dictionary metadata = r_path_and_type.metadata; + if (metadata.has("has_editor_variant")) { + r_path_and_type.path = r_path_and_type.path.get_basename() + ".editor." + r_path_and_type.path.get_extension(); + } + } +#endif + if (r_path_and_type.path.is_empty() || r_path_and_type.type.is_empty()) { return ERR_FILE_CORRUPT; } diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index a2231f4601ad..65de5f78a328 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -3364,6 +3364,8 @@ void EditorNode::add_editor_plugin(EditorPlugin *p_editor, bool p_config_changed Ref icon = p_editor->get_icon(); if (icon.is_valid()) { tb->set_icon(icon); + // Make sure the control is updated if the icon is reimported. + icon->connect("changed", callable_mp((Control *)tb, &Control::update_minimum_size)); } else if (singleton->gui_base->has_theme_icon(p_editor->get_name(), SNAME("EditorIcons"))) { tb->set_icon(singleton->gui_base->get_theme_icon(p_editor->get_name(), SNAME("EditorIcons"))); } diff --git a/editor/editor_themes.cpp b/editor/editor_themes.cpp index 9e983839f960..64ddecc588f3 100644 --- a/editor/editor_themes.cpp +++ b/editor/editor_themes.cpp @@ -447,6 +447,14 @@ Ref create_editor_theme(const Ref p_theme) { // Colors bool dark_theme = EditorSettings::get_singleton()->is_dark_theme(); +#ifdef MODULE_SVG_ENABLED + if (dark_theme) { + ImageLoaderSVG::set_forced_color_map(HashMap()); + } else { + ImageLoaderSVG::set_forced_color_map(EditorColorMap::get()); + } +#endif + // Ensure base colors are in the 0..1 luminance range to avoid 8-bit integer overflow or text rendering issues. // Some places in the editor use 8-bit integer colors. const Color dark_color_1 = base_color.lerp(Color(0, 0, 0, 1), contrast).clamp(); diff --git a/editor/import/resource_importer_texture.cpp b/editor/import/resource_importer_texture.cpp index 17b94ec70621..c06756ff0b16 100644 --- a/editor/import/resource_importer_texture.cpp +++ b/editor/import/resource_importer_texture.cpp @@ -36,6 +36,8 @@ #include "core/version.h" #include "editor/editor_file_system.h" #include "editor/editor_node.h" +#include "editor/editor_scale.h" +#include "editor/editor_settings.h" void ResourceImporterTexture::_texture_reimport_roughness(const Ref &p_tex, const String &p_normal_path, RS::TextureDetectRoughnessChannel p_channel) { ERR_FAIL_COND(p_tex.is_null()); @@ -233,6 +235,10 @@ void ResourceImporterTexture::get_import_options(const String &p_path, Listpush_back(ImportOption(PropertyInfo(Variant::FLOAT, "svg/scale", PROPERTY_HINT_RANGE, "0.001,100,0.001"), 1.0)); + + // Editor use only, applies to SVG. + r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "editor/scale_with_editor_scale"), false)); + r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "editor/convert_colors_with_editor_theme"), false)); } } @@ -447,6 +453,14 @@ Error ResourceImporterTexture::import(const String &p_source_file, const String scale = p_options["svg/scale"]; } + // Editor-specific options. + bool use_editor_scale = p_options.has("editor/scale_with_editor_scale") && p_options["editor/scale_with_editor_scale"]; + bool convert_editor_colors = p_options.has("editor/convert_colors_with_editor_theme") && p_options["editor/convert_colors_with_editor_theme"]; + + // Start importing images. + List> images_imported; + + // Load the normal image. Ref normal_image; Image::RoughnessChannel roughness_channel = Image::ROUGHNESS_CHANNEL_R; if (mipmaps && roughness > 1 && FileAccess::exists(normal_map)) { @@ -456,88 +470,117 @@ Error ResourceImporterTexture::import(const String &p_source_file, const String } } + // Load the main image. Ref image; image.instantiate(); Error err = ImageLoader::load_image(p_source_file, image, nullptr, loader_flags, scale); if (err != OK) { return err; } + images_imported.push_back(image); - Array formats_imported; + // Load the editor-only image. + Ref editor_image; + bool import_editor_image = use_editor_scale || convert_editor_colors; + if (import_editor_image) { + float editor_scale = scale; + if (use_editor_scale) { + editor_scale = scale * EDSCALE; + } - if (size_limit > 0 && (image->get_width() > size_limit || image->get_height() > size_limit)) { - //limit size - if (image->get_width() >= image->get_height()) { - int new_width = size_limit; - int new_height = image->get_height() * new_width / image->get_width(); + int32_t editor_loader_flags = loader_flags; + if (convert_editor_colors) { + editor_loader_flags |= ImageFormatLoader::FLAG_CONVERT_COLORS; + } - image->resize(new_width, new_height, Image::INTERPOLATE_CUBIC); + editor_image.instantiate(); + err = ImageLoader::load_image(p_source_file, editor_image, nullptr, editor_loader_flags, editor_scale); + if (err != OK) { + WARN_PRINT("Failed to import an image resource for editor use from '" + p_source_file + "'"); } else { - int new_height = size_limit; - int new_width = image->get_width() * new_height / image->get_height(); - - image->resize(new_width, new_height, Image::INTERPOLATE_CUBIC); - } - - if (normal == 1) { - image->normalize(); + images_imported.push_back(editor_image); } } - if (fix_alpha_border) { - image->fix_alpha_edges(); - } + for (Ref &target_image : images_imported) { + // Apply the size limit. + if (size_limit > 0 && (target_image->get_width() > size_limit || target_image->get_height() > size_limit)) { + if (target_image->get_width() >= target_image->get_height()) { + int new_width = size_limit; + int new_height = target_image->get_height() * new_width / target_image->get_width(); - if (premult_alpha) { - image->premultiply_alpha(); - } + target_image->resize(new_width, new_height, Image::INTERPOLATE_CUBIC); + } else { + int new_height = size_limit; + int new_width = target_image->get_width() * new_height / target_image->get_height(); - if (normal_map_invert_y) { - // Inverting the green channel can be used to flip a normal map's direction. - // There's no standard when it comes to normal map Y direction, so this is - // sometimes needed when using a normal map exported from another program. - // See . - const int height = image->get_height(); - const int width = image->get_width(); + target_image->resize(new_width, new_height, Image::INTERPOLATE_CUBIC); + } - for (int i = 0; i < width; i++) { - for (int j = 0; j < height; j++) { - const Color color = image->get_pixel(i, j); - image->set_pixel(i, j, Color(color.r, 1 - color.g, color.b)); + if (normal == 1) { + target_image->normalize(); } } - } - if (hdr_clamp_exposure) { - // Clamp HDR exposure following Filament's tonemapping formula. - // This can be used to reduce fireflies in environment maps or reduce the influence - // of the sun from an HDRI panorama on environment lighting (when a DirectionalLight3D is used instead). - const int height = image->get_height(); - const int width = image->get_width(); + // Fix alpha border. + if (fix_alpha_border) { + target_image->fix_alpha_edges(); + } - // These values are chosen arbitrarily and seem to produce good results with 4,096 samples. - const float linear = 4096.0; - const float compressed = 16384.0; + // Premultiply the alpha. + if (premult_alpha) { + target_image->premultiply_alpha(); + } - for (int i = 0; i < width; i++) { - for (int j = 0; j < height; j++) { - const Color color = image->get_pixel(i, j); - const float luma = color.get_luminance(); + // Invert the green channel of the image to flip the normal map it contains. + if (normal_map_invert_y) { + // Inverting the green channel can be used to flip a normal map's direction. + // There's no standard when it comes to normal map Y direction, so this is + // sometimes needed when using a normal map exported from another program. + // See . + const int height = target_image->get_height(); + const int width = target_image->get_width(); - Color clamped_color; - if (luma <= linear) { - clamped_color = color; - } else { - clamped_color = (color / luma) * ((linear * linear - compressed * luma) / (2 * linear - compressed - luma)); + for (int i = 0; i < width; i++) { + for (int j = 0; j < height; j++) { + const Color color = target_image->get_pixel(i, j); + target_image->set_pixel(i, j, Color(color.r, 1 - color.g, color.b)); } + } + } - image->set_pixel(i, j, clamped_color); + // Clamp HDR exposure. + if (hdr_clamp_exposure) { + // Clamp HDR exposure following Filament's tonemapping formula. + // This can be used to reduce fireflies in environment maps or reduce the influence + // of the sun from an HDRI panorama on environment lighting (when a DirectionalLight3D is used instead). + const int height = target_image->get_height(); + const int width = target_image->get_width(); + + // These values are chosen arbitrarily and seem to produce good results with 4,096 samples. + const float linear = 4096.0; + const float compressed = 16384.0; + + for (int i = 0; i < width; i++) { + for (int j = 0; j < height; j++) { + const Color color = target_image->get_pixel(i, j); + const float luma = color.get_luminance(); + + Color clamped_color; + if (luma <= linear) { + clamped_color = color; + } else { + clamped_color = (color / luma) * ((linear * linear - compressed * luma) / (2 * linear - compressed - luma)); + } + + target_image->set_pixel(i, j, clamped_color); + } } } } if (compress_mode == COMPRESS_BASIS_UNIVERSAL && image->get_format() >= Image::FORMAT_RF) { - //basis universal does not support float formats, fall back + // Basis universal does not support float formats, fallback. compress_mode = COMPRESS_VRAM_COMPRESSED; } @@ -547,9 +590,11 @@ Error ResourceImporterTexture::import(const String &p_source_file, const String bool force_normal = normal == 1; bool srgb_friendly_pack = pack_channels == 0; + Array formats_imported; + if (compress_mode == COMPRESS_VRAM_COMPRESSED) { - //must import in all formats, in order of priority (so platform choses the best supported one. IE, etc2 over etc). - //Android, GLES 2.x + // Must import in all formats, in order of priority (so platform choses the best supported one. IE, etc2 over etc). + // Android, GLES 2.x const bool is_hdr = (image->get_format() >= Image::FORMAT_RF && image->get_format() <= Image::FORMAT_RGBE9995); bool is_ldr = (image->get_format() >= Image::FORMAT_L8 && image->get_format() <= Image::FORMAT_RGB565); @@ -557,7 +602,7 @@ Error ResourceImporterTexture::import(const String &p_source_file, const String const bool can_s3tc = ProjectSettings::get_singleton()->get("rendering/textures/vram_compression/import_s3tc"); if (can_bptc) { - //add to the list anyway + // Add to the list anyway. formats_imported.push_back("bptc"); } @@ -566,9 +611,9 @@ Error ResourceImporterTexture::import(const String &p_source_file, const String if (is_hdr && can_compress_hdr) { if (has_alpha) { - //can compress hdr, but hdr with alpha is not compressible + // Can compress HDR, but HDR with alpha is not compressible. if (hdr_compression == 2) { - //but user selected to compress hdr anyway, so force an alpha-less format. + // But user selected to compress HDR anyway, so force an alpha-less format. if (image->get_format() == Image::FORMAT_RGBAF) { image->convert(Image::FORMAT_RGBF); } else if (image->get_format() == Image::FORMAT_RGBAH) { @@ -580,7 +625,7 @@ Error ResourceImporterTexture::import(const String &p_source_file, const String } if (!can_compress_hdr) { - //fallback to RGBE99995 + // Fallback to RGBE99995. if (image->get_format() != Image::FORMAT_RGBE9995) { image->convert(Image::FORMAT_RGBE9995); } @@ -615,16 +660,31 @@ Error ResourceImporterTexture::import(const String &p_source_file, const String EditorNode::add_io_error(vformat(TTR("%s: No suitable desktop VRAM compression algorithm enabled in Project Settings (S3TC or BPTC). This texture may not display correctly on desktop platforms."), p_source_file)); } } else { - //import normally + // Import normally. _save_ctex(image, p_save_path + ".ctex", compress_mode, lossy, Image::COMPRESS_S3TC /*this is ignored */, mipmaps, stream, detect_3d, detect_roughness, detect_normal, force_normal, srgb_friendly_pack, false, mipmap_limit, normal_image, roughness_channel); } + if (editor_image.is_valid()) { + _save_ctex(editor_image, p_save_path + ".editor.ctex", compress_mode, lossy, Image::COMPRESS_S3TC /*this is ignored */, mipmaps, stream, detect_3d, detect_roughness, detect_normal, force_normal, srgb_friendly_pack, false, mipmap_limit, normal_image, roughness_channel); + } + if (r_metadata) { Dictionary metadata; metadata["vram_texture"] = compress_mode == COMPRESS_VRAM_COMPRESSED; if (formats_imported.size()) { metadata["imported_formats"] = formats_imported; } + + if (editor_image.is_valid()) { + metadata["has_editor_variant"] = true; + if (use_editor_scale) { + metadata["editor_scale"] = EDSCALE; + } + if (convert_editor_colors) { + metadata["editor_dark_theme"] = EditorSettings::get_singleton()->is_dark_theme(); + } + } + *r_metadata = metadata; } return OK; @@ -657,13 +717,22 @@ bool ResourceImporterTexture::are_import_settings_valid(const String &p_path) co //will become invalid if formats are missing to import Dictionary metadata = ResourceFormatImporter::get_singleton()->get_resource_metadata(p_path); + if (metadata.has("has_editor_variant")) { + if (metadata.has("editor_scale") && (float)metadata["editor_scale"] != EDSCALE) { + return false; + } + if (metadata.has("editor_dark_theme") && (bool)metadata["editor_dark_theme"] != EditorSettings::get_singleton()->is_dark_theme()) { + return false; + } + } + if (!metadata.has("vram_texture")) { return false; } bool vram = metadata["vram_texture"]; if (!vram) { - return true; //do not care about non vram + return true; // Do not care about non-VRAM. } Vector formats_imported; diff --git a/modules/svg/image_loader_svg.cpp b/modules/svg/image_loader_svg.cpp index 5f839bd9799e..cd6081f91bcb 100644 --- a/modules/svg/image_loader_svg.cpp +++ b/modules/svg/image_loader_svg.cpp @@ -35,6 +35,12 @@ #include +HashMap ImageLoaderSVG::forced_color_map = HashMap(); + +void ImageLoaderSVG::set_forced_color_map(const HashMap &p_color_map) { + forced_color_map = p_color_map; +} + void ImageLoaderSVG::_replace_color_property(const HashMap &p_color_map, const String &p_prefix, String &r_string) { // Replace colors in the SVG based on what is passed in `p_color_map`. // Used to change the colors of editor icons based on the used theme. @@ -138,7 +144,13 @@ void ImageLoaderSVG::get_recognized_extensions(List *p_extensions) const Error ImageLoaderSVG::load_image(Ref p_image, Ref p_fileaccess, uint32_t p_flags, float p_scale) { String svg = p_fileaccess->get_as_utf8_string(); - create_image_from_string(p_image, svg, p_scale, false, HashMap()); + + if (p_flags & FLAG_CONVERT_COLORS) { + create_image_from_string(p_image, svg, p_scale, false, forced_color_map); + } else { + create_image_from_string(p_image, svg, p_scale, false, HashMap()); + } + ERR_FAIL_COND_V(p_image->is_empty(), FAILED); if (p_flags & FLAG_FORCE_LINEAR) { p_image->srgb_to_linear(); diff --git a/modules/svg/image_loader_svg.h b/modules/svg/image_loader_svg.h index fc89b63edb41..e6f73ab18fa1 100644 --- a/modules/svg/image_loader_svg.h +++ b/modules/svg/image_loader_svg.h @@ -34,9 +34,13 @@ #include "core/io/image_loader.h" class ImageLoaderSVG : public ImageFormatLoader { + static HashMap forced_color_map; + void _replace_color_property(const HashMap &p_color_map, const String &p_prefix, String &r_string); public: + static void set_forced_color_map(const HashMap &p_color_map); + void create_image_from_string(Ref p_image, String p_string, float p_scale, bool p_upsample, const HashMap &p_color_map); virtual Error load_image(Ref p_image, Ref p_fileaccess, uint32_t p_flags, float p_scale) override;