Skip to content

Implement EditorTextureImportPlugin to extend texture importer #105342

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions doc/classes/EditorPlugin.xml
Original file line number Diff line number Diff line change
Expand Up @@ -544,6 +544,15 @@
If [param first_priority] is [code]true[/code], the new import plugin is inserted first in the list and takes precedence over pre-existing plugins.
</description>
</method>
<method name="add_texture_post_import_plugin">
<return type="void" />
<param index="0" name="importer" type="EditorTexturePostImportPlugin" />
<param index="1" name="first_priority" type="bool" default="false" />
<description>
Add a [EditorTexturePostImportPlugin]. These plugins allow customizing the import process of [CompressedTexture2D].
If [param first_priority] is [code]true[/code], the new import plugin is inserted first in the list and takes precedence over pre-existing plugins.
</description>
</method>
<method name="add_tool_menu_item">
<return type="void" />
<param index="0" name="name" type="String" />
Expand Down Expand Up @@ -732,6 +741,13 @@
Remove the [EditorScenePostImportPlugin], added with [method add_scene_post_import_plugin].
</description>
</method>
<method name="remove_texture_post_import_plugin">
<return type="void" />
<param index="0" name="importer" type="EditorTexturePostImportPlugin" />
<description>
Remove the [EditorTexturePostImportPlugin], added with [method add_texture_post_import_plugin].
</description>
</method>
<method name="remove_tool_menu_item">
<return type="void" />
<param index="0" name="name" type="String" />
Expand Down
94 changes: 94 additions & 0 deletions doc/classes/EditorTexturePostImportPlugin.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="EditorTexturePostImportPlugin" inherits="RefCounted" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd">
<brief_description>
Plugin to control and modify the process of importing a [CompressedTexture2D].
</brief_description>
<description>
This plugin type exists to extend [ResourceImporterTexture] and modify the process of importing [CompressedTexture2D], allowing to load custom file format, add import options, and change the image when processing.
</description>
<tutorials>
</tutorials>
<methods>
<method name="_get_import_options" qualifiers="virtual const">
<return type="void" />
<param index="0" name="path" type="String" />
<param index="1" name="preset_index" type="int" />
<description>
Override to add import options. These will appear in the Texture2D import dock on the editor. Add options via [method add_import_option] and [method add_import_option_advanced].
</description>
</method>
<method name="_get_option_visibility" qualifiers="virtual const">
<return type="Variant" />
<param index="0" name="path" type="String" />
<param index="1" name="option" type="String" />
<description>
Override to change the visibility of import option. Should return [code]true[/code] to show the given option, [code]false[/code] to hide the given option, or [code]null[/code] to ignore.
</description>
</method>
<method name="_get_recognized_extensions" qualifiers="virtual const">
<return type="PackedStringArray" />
<description>
Gets the file extensions that will be added to the recognized extensions list of texture importer.
</description>
</method>
<method name="_load_image" qualifiers="virtual const">
<return type="Image" />
<param index="0" name="source_file" type="String" />
<description>
Override to add import options. These will appear in the Texture2D import dock on the editor. Add options via [method add_import_option] and [method add_import_option_advanced].
</description>
</method>
<method name="_post_process" qualifiers="virtual const">
<return type="Image" />
<param index="0" name="image" type="Image" />
<description>
Post process the image. This function is called after the image has been processed, such as fix alpha border, apply size limit. See also [ResourceImporterTexture].
</description>
</method>
<method name="_pre_process" qualifiers="virtual const">
<return type="Image" />
<param index="0" name="image" type="Image" />
<description>
Pre Process the image. This function is called right after the image loader loaded the image and no changes have been made.
</description>
</method>
<method name="add_import_option">
<return type="void" />
<param index="0" name="name" type="String" />
<param index="1" name="value" type="Variant" />
<description>
Add a specific import option (name and default value only). This function can only be called from [method _get_import_options].
</description>
</method>
<method name="add_import_option_advanced">
<return type="void" />
<param index="0" name="type" type="int" enum="Variant.Type" />
<param index="1" name="name" type="String" />
<param index="2" name="default_value" type="Variant" />
<param index="3" name="hint" type="int" enum="PropertyHint" default="0" />
<param index="4" name="hint_string" type="String" default="&quot;&quot;" />
<param index="5" name="usage_flags" type="int" default="6" />
<description>
Add a specific import option. This function can only be called from [method _get_import_options].
</description>
</method>
<method name="get_option_value" qualifiers="const">
<return type="Variant" />
<param index="0" name="name" type="StringName" />
<description>
Query the value of an option. This function can only be called from [method _get_option_visibility], [method _load_image], [method _pre_process] and [method _post_process].
</description>
</method>
</methods>
<constants>
<constant name="PRESET_DETECT" value="0" enum="Preset">
The 2D/3D (Auto-Detect) preset of texture importer. Can be accessed in [method _get_import_options].
</constant>
<constant name="PRESET_2D" value="1" enum="Preset">
The 2D preset of texture importer. Can be accessed in [method _get_import_options].
</constant>
<constant name="PRESET_3D" value="2" enum="Preset">
The 3D preset of texture importer. Can be accessed in [method _get_import_options].
</constant>
</constants>
</class>
1 change: 1 addition & 0 deletions editor/editor_node.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8381,6 +8381,7 @@ EditorNode::~EditorNode() {
EditorInspector::cleanup_plugins();
EditorTranslationParser::get_singleton()->clean_parsers();
ResourceImporterScene::clean_up_importer_plugins();
ResourceImporterTexture::clean_up_importer_plugins();
EditorContextMenuPluginManager::cleanup();

remove_print_handler(&print_handler);
Expand Down
141 changes: 138 additions & 3 deletions editor/import/resource_importer_texture.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,90 @@
#include "editor/themes/editor_theme_manager.h"
#include "scene/resources/compressed_texture.h"

Variant EditorTexturePostImportPlugin::get_option_value(const StringName &p_name) const {
ERR_FAIL_COND_V_MSG(current_options == nullptr, Variant(), "get_option_value() called from a function where option values are not available.");
ERR_FAIL_COND_V_MSG(!current_options->has(p_name), Variant(), "get_option_value() called with unexisting option argument: " + String(p_name));
return (*current_options)[p_name];
}

void EditorTexturePostImportPlugin::add_import_option(const String &p_name, const Variant &p_default_value) {
ERR_FAIL_NULL_MSG(current_option_list, "add_import_option() can only be called from get_import_options().");
add_import_option_advanced(p_default_value.get_type(), p_name, p_default_value);
}

void EditorTexturePostImportPlugin::add_import_option_advanced(Variant::Type p_type, const String &p_name, const Variant &p_default_value, PropertyHint p_hint, const String &p_hint_string, int p_usage_flags) {
ERR_FAIL_NULL_MSG(current_option_list, "add_import_option_advanced() can only be called from get_import_options().");
current_option_list->push_back(ResourceImporter::ImportOption(PropertyInfo(p_type, p_name, p_hint, p_hint_string, p_usage_flags), p_default_value));
}

void EditorTexturePostImportPlugin::get_recognized_extensions(List<String> *p_extensions) const {
Vector<String> extensions;
GDVIRTUAL_CALL(_get_recognized_extensions, extensions);
for (int i = 0; i < extensions.size(); i++) {
p_extensions->push_back(extensions[i]);
}
}

void EditorTexturePostImportPlugin::get_import_options(const String &p_path, List<ResourceImporter::ImportOption> *r_options, Preset p_preset) const {
current_option_list = r_options;
GDVIRTUAL_CALL(_get_import_options, p_path, p_preset);
current_option_list = nullptr;
}

Variant EditorTexturePostImportPlugin::get_option_visibility(const String &p_path, const String &p_option, const HashMap<StringName, Variant> &p_options) const {
current_options = &p_options;
Variant ret;
GDVIRTUAL_CALL(_get_option_visibility, p_path, p_option, ret);
current_options = nullptr;
return ret;
}

Ref<Image> EditorTexturePostImportPlugin::load_image(const String &p_source_file, bool *r_use_custom_loader, const HashMap<StringName, Variant> &p_options) const {
Ref<Image> image;
current_options = &p_options;
bool use_custom_loader = GDVIRTUAL_CALL(_load_image, p_source_file, image);
current_options = nullptr;
if (r_use_custom_loader) {
*r_use_custom_loader = use_custom_loader;
}
return image;
}

Ref<Image> EditorTexturePostImportPlugin::pre_process(Ref<Image> p_image, const HashMap<StringName, Variant> &p_options) const {
Ref<Image> image = p_image;
current_options = &p_options;
GDVIRTUAL_CALL(_pre_process, p_image, image);
current_options = nullptr;
return image;
}

Ref<Image> EditorTexturePostImportPlugin::post_process(Ref<Image> p_image, const HashMap<StringName, Variant> &p_options) const {
Ref<Image> image = p_image;
current_options = &p_options;
GDVIRTUAL_CALL(_post_process, p_image, image);
current_options = nullptr;
return image;
}

void EditorTexturePostImportPlugin::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_option_value", "name"), &EditorTexturePostImportPlugin::get_option_value);
ClassDB::bind_method(D_METHOD("add_import_option", "name", "value"), &EditorTexturePostImportPlugin::add_import_option);
ClassDB::bind_method(D_METHOD("add_import_option_advanced", "type", "name", "default_value", "hint", "hint_string", "usage_flags"), &EditorTexturePostImportPlugin::add_import_option_advanced, DEFVAL(PROPERTY_HINT_NONE), DEFVAL(""), DEFVAL(PROPERTY_USAGE_DEFAULT));

BIND_ENUM_CONSTANT(PRESET_DETECT);
BIND_ENUM_CONSTANT(PRESET_2D);
BIND_ENUM_CONSTANT(PRESET_3D);

GDVIRTUAL_BIND(_get_recognized_extensions);
GDVIRTUAL_BIND(_get_import_options, "path", "preset_index");
GDVIRTUAL_BIND(_get_option_visibility, "path", "option");
GDVIRTUAL_BIND(_load_image, "source_file");
GDVIRTUAL_BIND(_pre_process, "image");
GDVIRTUAL_BIND(_post_process, "image");
}

///////////////////////////////////////////////////////

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());

Expand Down Expand Up @@ -170,6 +254,9 @@ String ResourceImporterTexture::get_visible_name() const {

void ResourceImporterTexture::get_recognized_extensions(List<String> *p_extensions) const {
ImageLoader::get_recognized_extensions(p_extensions);
for (const Ref<EditorTexturePostImportPlugin> &plugin : texture_import_plugins) {
plugin->get_recognized_extensions(p_extensions);
}
}

String ResourceImporterTexture::get_save_extension() const {
Expand Down Expand Up @@ -209,6 +296,13 @@ bool ResourceImporterTexture::get_option_visibility(const String &p_path, const
return p_options["mipmaps/generate"];
}

for (const Ref<EditorTexturePostImportPlugin> &plugin : texture_import_plugins) {
Variant ret = plugin->get_option_visibility(p_path, p_option, p_options);
if (ret.get_type() == Variant::BOOL && !ret) {
return false;
}
}

return true;
}

Expand Down Expand Up @@ -256,6 +350,10 @@ void ResourceImporterTexture::get_import_options(const String &p_path, List<Impo
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));
}

for (const Ref<EditorTexturePostImportPlugin> &plugin : texture_import_plugins) {
plugin->get_import_options(p_path, r_options);
}
}

void ResourceImporterTexture::save_to_ctex_format(Ref<FileAccess> f, const Ref<Image> &p_image, CompressMode p_compress_mode, Image::UsedChannels p_channels, Image::CompressMode p_compress_format, float p_lossy_quality) {
Expand Down Expand Up @@ -338,6 +436,23 @@ void ResourceImporterTexture::save_to_ctex_format(Ref<FileAccess> f, const Ref<I
}
}

void ResourceImporterTexture::add_post_import_plugin(Ref<EditorTexturePostImportPlugin> p_plugin, bool p_first_priority) {
ERR_FAIL_COND(p_plugin.is_null());
if (p_first_priority) {
texture_import_plugins.insert(0, p_plugin);
} else {
texture_import_plugins.push_back(p_plugin);
}
}

void ResourceImporterTexture::remove_post_import_plugin(Ref<EditorTexturePostImportPlugin> p_importer) {
texture_import_plugins.erase(p_importer);
}

void ResourceImporterTexture::clean_up_importer_plugins() {
texture_import_plugins.clear();
}

void ResourceImporterTexture::_save_ctex(const Ref<Image> &p_image, const String &p_to_path, CompressMode p_compress_mode, float p_lossy_quality, Image::CompressMode p_vram_compression, bool p_mipmaps, bool p_streamable, bool p_detect_3d, bool p_detect_roughness, bool p_detect_normal, bool p_force_normal, bool p_srgb_friendly, bool p_force_po2_for_compressed, uint32_t p_limit_mipmap, const Ref<Image> &p_normal, Image::RoughnessChannel p_roughness_channel) {
Ref<FileAccess> f = FileAccess::open(p_to_path, FileAccess::WRITE);
ERR_FAIL_COND(f.is_null());
Expand Down Expand Up @@ -559,10 +674,24 @@ Error ResourceImporterTexture::import(ResourceUID::ID p_source_id, 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;
Error err = OK;
bool use_custom_loader = false;

for (const Ref<EditorTexturePostImportPlugin> &plugin : texture_import_plugins) {
image = plugin->load_image(p_source_file, &use_custom_loader, p_options);
}
ERR_FAIL_COND_V_MSG(image.is_null(), ERR_INVALID_DATA, "The returned image of _load_image() is null.");

if (!use_custom_loader) {
err = ImageLoader::load_image(p_source_file, image, nullptr, loader_flags, scale);
ERR_FAIL_COND_V(err != OK, err);
}

for (const Ref<EditorTexturePostImportPlugin> &plugin : texture_import_plugins) {
image = plugin->pre_process(image, p_options);
}
ERR_FAIL_COND_V_MSG(image.is_null(), ERR_INVALID_DATA, "The returned image of _pre_process() is null.");

images_imported.push_back(image);

// Load the editor-only image.
Expand Down Expand Up @@ -636,6 +765,11 @@ Error ResourceImporterTexture::import(ResourceUID::ID p_source_id, const String
}
}

for (const Ref<EditorTexturePostImportPlugin> &plugin : texture_import_plugins) {
image = plugin->post_process(image, p_options);
}
ERR_FAIL_COND_V_MSG(image.is_null(), ERR_INVALID_DATA, "The returned image of _post_process() is null.");

bool detect_3d = int(p_options["detect_3d/compress_to"]) > 0;
bool detect_roughness = roughness == 0;
bool detect_normal = normal == 0;
Expand Down Expand Up @@ -837,6 +971,7 @@ bool ResourceImporterTexture::are_import_settings_valid(const String &p_path, co
}

ResourceImporterTexture *ResourceImporterTexture::singleton = nullptr;
Vector<Ref<EditorTexturePostImportPlugin>> ResourceImporterTexture::texture_import_plugins;

ResourceImporterTexture::ResourceImporterTexture(bool p_singleton) {
// This should only be set through the EditorNode.
Expand Down
46 changes: 46 additions & 0 deletions editor/import/resource_importer_texture.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,50 @@

class CompressedTexture2D;

class EditorTexturePostImportPlugin : public RefCounted {
GDCLASS(EditorTexturePostImportPlugin, RefCounted);

public:
enum Preset {
PRESET_DETECT,
PRESET_2D,
PRESET_3D,
};

private:
mutable const HashMap<StringName, Variant> *current_options = nullptr;
mutable List<ResourceImporter::ImportOption> *current_option_list = nullptr;

protected:
GDVIRTUAL0RC(Vector<String>, _get_recognized_extensions)
GDVIRTUAL2C(_get_import_options, String, int)
GDVIRTUAL2RC(Variant, _get_option_visibility, String, String)
GDVIRTUAL1RC(Ref<Image>, _load_image, String)
GDVIRTUAL1RC(Ref<Image>, _pre_process, Ref<Image>)
GDVIRTUAL1RC(Ref<Image>, _post_process, Ref<Image>)

static void _bind_methods();

public:
Variant get_option_value(const StringName &p_name) const;
void add_import_option(const String &p_name, const Variant &p_default_value);
void add_import_option_advanced(Variant::Type p_type, const String &p_name, const Variant &p_default_value, PropertyHint p_hint = PROPERTY_HINT_NONE, const String &p_hint_string = String(), int p_usage_flags = PROPERTY_USAGE_DEFAULT);

virtual void get_recognized_extensions(List<String> *p_extensions) const;
virtual void get_import_options(const String &p_path, List<ResourceImporter::ImportOption> *r_options, Preset p_preset = PRESET_DETECT) const;
virtual Variant get_option_visibility(const String &p_path, const String &p_option, const HashMap<StringName, Variant> &p_options) const;

virtual Ref<Image> load_image(const String &p_source_file, bool *r_use_custom_loader, const HashMap<StringName, Variant> &p_options) const;
virtual Ref<Image> pre_process(Ref<Image> p_image, const HashMap<StringName, Variant> &p_options) const;
virtual Ref<Image> post_process(Ref<Image> p_image, const HashMap<StringName, Variant> &p_options) const;
};
VARIANT_ENUM_CAST(EditorTexturePostImportPlugin::Preset);

class ResourceImporterTexture : public ResourceImporter {
GDCLASS(ResourceImporterTexture, ResourceImporter);

static Vector<Ref<EditorTexturePostImportPlugin>> texture_import_plugins;

public:
enum CompressMode {
COMPRESS_LOSSLESS,
Expand Down Expand Up @@ -83,6 +124,11 @@ class ResourceImporterTexture : public ResourceImporter {
static void save_to_ctex_format(Ref<FileAccess> f, const Ref<Image> &p_image, CompressMode p_compress_mode, Image::UsedChannels p_channels, Image::CompressMode p_compress_format, float p_lossy_quality);

static ResourceImporterTexture *get_singleton() { return singleton; }

static void add_post_import_plugin(Ref<EditorTexturePostImportPlugin> p_plugin, bool p_first_priority = false);
static void remove_post_import_plugin(Ref<EditorTexturePostImportPlugin> p_plugin);
static void clean_up_importer_plugins();

virtual String get_importer_name() const override;
virtual String get_visible_name() const override;
virtual void get_recognized_extensions(List<String> *p_extensions) const override;
Expand Down
Loading