Allow images to be imported "for editor use" and respect editor settings

This commit is contained in:
Yuri Sizov 2022-08-22 22:07:02 +03:00
parent 6b92dbfce2
commit 817d4db21f
7 changed files with 167 additions and 62 deletions

View file

@ -52,6 +52,7 @@ public:
enum LoaderFlags {
FLAG_NONE = 0,
FLAG_FORCE_LINEAR = 1,
FLAG_CONVERT_COLORS = 2,
};
virtual ~ImageFormatLoader() {}

View file

@ -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;
}

View file

@ -3364,6 +3364,8 @@ void EditorNode::add_editor_plugin(EditorPlugin *p_editor, bool p_config_changed
Ref<Texture2D> 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")));
}

View file

@ -447,6 +447,14 @@ Ref<Theme> create_editor_theme(const Ref<Theme> 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<Color, Color>());
} 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();

View file

@ -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<CompressedTexture2D> &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, List<Impo
if (p_path.get_extension() == "svg") {
r_options->push_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<Ref<Image>> images_imported;
// Load the normal image.
Ref<Image> 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;
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<Image> 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<Image> &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 <http://wiki.polycount.com/wiki/Normal_Map_Technical_Details#Common_Swizzle_Coordinates>.
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 <http://wiki.polycount.com/wiki/Normal_Map_Technical_Details#Common_Swizzle_Coordinates>.
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<String> formats_imported;

View file

@ -35,6 +35,12 @@
#include <thorvg.h>
HashMap<Color, Color> ImageLoaderSVG::forced_color_map = HashMap<Color, Color>();
void ImageLoaderSVG::set_forced_color_map(const HashMap<Color, Color> &p_color_map) {
forced_color_map = p_color_map;
}
void ImageLoaderSVG::_replace_color_property(const HashMap<Color, Color> &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<String> *p_extensions) const
Error ImageLoaderSVG::load_image(Ref<Image> p_image, Ref<FileAccess> 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<Color, Color>());
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<Color, Color>());
}
ERR_FAIL_COND_V(p_image->is_empty(), FAILED);
if (p_flags & FLAG_FORCE_LINEAR) {
p_image->srgb_to_linear();

View file

@ -34,9 +34,13 @@
#include "core/io/image_loader.h"
class ImageLoaderSVG : public ImageFormatLoader {
static HashMap<Color, Color> forced_color_map;
void _replace_color_property(const HashMap<Color, Color> &p_color_map, const String &p_prefix, String &r_string);
public:
static void set_forced_color_map(const HashMap<Color, Color> &p_color_map);
void create_image_from_string(Ref<Image> p_image, String p_string, float p_scale, bool p_upsample, const HashMap<Color, Color> &p_color_map);
virtual Error load_image(Ref<Image> p_image, Ref<FileAccess> p_fileaccess, uint32_t p_flags, float p_scale) override;