Merge pull request #100157 from Zylann/texture_preview_channel_selector

Add color channel filter to editor texture previews
This commit is contained in:
Rémi Verschelde 2025-01-14 12:08:54 +01:00
commit f07e3ed551
13 changed files with 582 additions and 38 deletions

View file

@ -1135,7 +1135,7 @@ static void _overlay(const uint8_t *__restrict p_src, uint8_t *__restrict p_dst,
}
bool Image::is_size_po2() const {
return uint32_t(width) == next_power_of_2(width) && uint32_t(height) == next_power_of_2(height);
return is_power_of_2(width) && is_power_of_2(height);
}
void Image::resize_to_po2(bool p_square, Interpolation p_interpolation) {
@ -3953,6 +3953,97 @@ String Image::get_format_name(Format p_format) {
return format_names[p_format];
}
uint32_t Image::get_format_component_mask(Format p_format) {
const uint32_t r = 1;
const uint32_t rg = 3;
const uint32_t rgb = 7;
const uint32_t rgba = 15;
switch (p_format) {
case FORMAT_L8:
return rgb;
case FORMAT_LA8:
return rgba;
case FORMAT_R8:
return r;
case FORMAT_RG8:
return rg;
case FORMAT_RGB8:
return rgb;
case FORMAT_RGBA8:
return rgba;
case FORMAT_RGBA4444:
return rgba;
case FORMAT_RGB565:
return rgb;
case FORMAT_RF:
return r;
case FORMAT_RGF:
return rg;
case FORMAT_RGBF:
return rgb;
case FORMAT_RGBAF:
return rgba;
case FORMAT_RH:
return r;
case FORMAT_RGH:
return rg;
case FORMAT_RGBH:
return rgb;
case FORMAT_RGBAH:
return rgba;
case FORMAT_RGBE9995:
return rgba;
case FORMAT_DXT1:
return rgb;
case FORMAT_DXT3:
return rgb;
case FORMAT_DXT5:
return rgba;
case FORMAT_RGTC_R:
return r;
case FORMAT_RGTC_RG:
return rg;
case FORMAT_BPTC_RGBA:
return rgba;
case FORMAT_BPTC_RGBF:
return rgb;
case FORMAT_BPTC_RGBFU:
return rgb;
case FORMAT_ETC:
return rgb;
case FORMAT_ETC2_R11:
return r;
case FORMAT_ETC2_R11S:
return r;
case FORMAT_ETC2_RG11:
return rg;
case FORMAT_ETC2_RG11S:
return rg;
case FORMAT_ETC2_RGB8:
return rgb;
case FORMAT_ETC2_RGBA8:
return rgba;
case FORMAT_ETC2_RGB8A1:
return rgba;
case FORMAT_ETC2_RA_AS_RG:
return rgba;
case FORMAT_DXT5_RA_AS_RG:
return rgba;
case FORMAT_ASTC_4x4:
return rgba;
case FORMAT_ASTC_4x4_HDR:
return rgba;
case FORMAT_ASTC_8x8:
return rgba;
case FORMAT_ASTC_8x8_HDR:
return rgba;
default:
ERR_PRINT("Unhandled format.");
return rgba;
}
}
Error Image::load_png_from_buffer(const Vector<uint8_t> &p_array) {
return _load_from_buffer(p_array, _png_mem_loader_func);
}

View file

@ -395,6 +395,7 @@ public:
Ref<Image> get_region(const Rect2i &p_area) const;
static String get_format_name(Format p_format);
static uint32_t get_format_component_mask(Format p_format);
Error load_png_from_buffer(const Vector<uint8_t> &p_array);
Error load_jpg_from_buffer(const Vector<uint8_t> &p_array);

View file

@ -64,10 +64,6 @@ SafeNumeric<uint64_t> Memory::max_usage;
SafeNumeric<uint64_t> Memory::alloc_count;
inline bool is_power_of_2(size_t x) {
return x && ((x & (x - 1U)) == 0U);
}
void *Memory::alloc_aligned_static(size_t p_bytes, size_t p_alignment) {
DEV_ASSERT(is_power_of_2(p_alignment));

View file

@ -135,6 +135,12 @@ constexpr auto CLAMP(const T m_a, const T2 m_min, const T3 m_max) {
/* Functions to handle powers of 2 and shifting. */
// Returns `true` if a positive integer is a power of 2, `false` otherwise.
template <typename T>
inline bool is_power_of_2(const T x) {
return x && ((x & (x - 1)) == 0);
}
// Function to find the next power of 2 to an integer.
static _FORCE_INLINE_ unsigned int next_power_of_2(unsigned int x) {
if (x == 0) {

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="none" stroke="#000" stroke-linejoin="round" stroke-opacity=".8" stroke-width="2" d="m8 1 7 3v8l-7 3-7-3V4Z"/><path fill-opacity=".8" d="M2 5h12v6l-12-.008Z" paint-order="stroke fill markers"/><path fill="#c2c2c2" d="m8 7.5-7 3V12l7 3 7-3v-1.5z"/><path fill="#d6d6d6" d="m8 15-7-3v-1.5l7 3z"/><path fill="#f9f9f9" d="m1 10.5 7 3 7-3-7-3z"/><path fill="#c2c2c2" d="m8 4.25-7 3v1.5l7 3 7-3v-1.5z"/><path fill="#d6d6d6" d="m8 11.75-7-3v-1.5l7 3z"/><path fill="#f9f9f9" d="m1 7.25 7 3 7-3-7-3z"/><path fill="#c2c2c2" d="M8 1 1 4v1.5l7 3 7-3V4Z"/><path fill="#f9f9f9" d="m1 4 7 3 7-3-7-3z"/><path fill="#d6d6d6" d="m8 8.5-7-3V4l7 3z"/></svg>

After

Width:  |  Height:  |  Size: 710 B

View file

@ -0,0 +1,146 @@
/**************************************************************************/
/* color_channel_selector.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#include "color_channel_selector.h"
#include "editor/themes/editor_scale.h"
#include "scene/gui/box_container.h"
#include "scene/gui/button.h"
#include "scene/gui/panel_container.h"
#include "scene/resources/style_box_flat.h"
ColorChannelSelector::ColorChannelSelector() {
toggle_button = memnew(Button);
toggle_button->set_flat(true);
toggle_button->set_toggle_mode(true);
toggle_button->connect(SceneStringName(toggled), callable_mp(this, &ColorChannelSelector::on_toggled));
toggle_button->add_theme_style_override("focus", memnew(StyleBoxEmpty));
add_child(toggle_button);
panel = memnew(PanelContainer);
panel->hide();
HBoxContainer *container = memnew(HBoxContainer);
container->add_theme_constant_override("separation", 0);
create_button(0, "R", container);
create_button(1, "G", container);
create_button(2, "B", container);
create_button(3, "A", container);
// Use a bit of transparency to be less distracting.
set_modulate(Color(1, 1, 1, 0.7));
panel->add_child(container);
add_child(panel);
}
void ColorChannelSelector::_notification(int p_what) {
if (p_what == NOTIFICATION_THEME_CHANGED) {
// PanelContainer's background is invisible in the editor. We need a background.
// And we need this in turn because buttons don't look good without background (for example, hover is transparent).
Ref<StyleBox> bg_style = get_theme_stylebox(SceneStringName(panel), "TabContainer");
ERR_FAIL_COND(bg_style.is_null());
bg_style = bg_style->duplicate();
// The default content margin makes the widget become a bit too large. It should be like mini-toolbar.
const float editor_scale = EditorScale::get_scale();
bg_style->set_content_margin(SIDE_LEFT, 1.0f * editor_scale);
bg_style->set_content_margin(SIDE_RIGHT, 1.0f * editor_scale);
bg_style->set_content_margin(SIDE_TOP, 1.0f * editor_scale);
bg_style->set_content_margin(SIDE_BOTTOM, 1.0f * editor_scale);
panel->add_theme_style_override(SceneStringName(panel), bg_style);
Ref<Texture2D> icon = get_editor_theme_icon(SNAME("TexturePreviewChannels"));
toggle_button->set_button_icon(icon);
}
}
void ColorChannelSelector::set_available_channels_mask(uint32_t p_mask) {
for (unsigned int i = 0; i < CHANNEL_COUNT; ++i) {
const bool available = (p_mask & (1u << i)) != 0;
Button *button = channel_buttons[i];
button->set_visible(available);
}
}
void ColorChannelSelector::on_channel_button_toggled(bool p_unused_pressed) {
emit_signal("selected_channels_changed");
}
uint32_t ColorChannelSelector::get_selected_channels_mask() const {
uint32_t mask = 0;
for (unsigned int i = 0; i < CHANNEL_COUNT; ++i) {
Button *button = channel_buttons[i];
if (button->is_visible() && channel_buttons[i]->is_pressed()) {
mask |= (1 << i);
}
}
return mask;
}
// Helper
Vector4 ColorChannelSelector::get_selected_channel_factors() const {
Vector4 channel_factors;
const uint32_t mask = get_selected_channels_mask();
for (unsigned int i = 0; i < 4; ++i) {
if ((mask & (1 << i)) != 0) {
channel_factors[i] = 1;
}
}
return channel_factors;
}
void ColorChannelSelector::create_button(unsigned int p_channel_index, const String &p_text, Control *p_parent) {
ERR_FAIL_COND(p_channel_index >= CHANNEL_COUNT);
ERR_FAIL_COND(channel_buttons[p_channel_index] != nullptr);
Button *button = memnew(Button);
button->set_text(p_text);
button->set_toggle_mode(true);
button->set_pressed(true);
// Don't show focus, it stands out too much and remains visible which can be confusing.
button->add_theme_style_override("focus", memnew(StyleBoxEmpty));
// Make it look similar to toolbar buttons.
button->set_theme_type_variation(SceneStringName(FlatButton));
button->connect(SceneStringName(toggled), callable_mp(this, &ColorChannelSelector::on_channel_button_toggled));
p_parent->add_child(button);
channel_buttons[p_channel_index] = button;
}
void ColorChannelSelector::on_toggled(bool p_pressed) {
panel->set_visible(p_pressed);
}
void ColorChannelSelector::_bind_methods() {
ADD_SIGNAL(MethodInfo("selected_channels_changed"));
}

View file

@ -0,0 +1,65 @@
/**************************************************************************/
/* color_channel_selector.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef COLOR_CHANNEL_SELECTOR_H
#define COLOR_CHANNEL_SELECTOR_H
#include "scene/gui/box_container.h"
class PanelContainer;
class Button;
class ColorChannelSelector : public HBoxContainer {
GDCLASS(ColorChannelSelector, HBoxContainer);
static const unsigned int CHANNEL_COUNT = 4;
public:
ColorChannelSelector();
void set_available_channels_mask(uint32_t p_mask);
uint32_t get_selected_channels_mask() const;
Vector4 get_selected_channel_factors() const;
private:
void _notification(int p_what);
void on_channel_button_toggled(bool p_unused_pressed);
void create_button(unsigned int p_channel_index, const String &p_text, Control *p_parent);
void on_toggled(bool p_pressed);
static void _bind_methods();
Button *channel_buttons[CHANNEL_COUNT] = {};
PanelContainer *panel = nullptr;
Button *toggle_button = nullptr;
};
#endif // COLOR_CHANNEL_SELECTOR_H

View file

@ -31,6 +31,7 @@
#include "texture_3d_editor_plugin.h"
#include "editor/editor_string_names.h"
#include "editor/plugins/color_channel_selector.h"
#include "editor/themes/editor_scale.h"
#include "scene/gui/label.h"
@ -44,8 +45,29 @@ constexpr const char *texture_3d_shader = R"(
uniform sampler3D tex;
uniform float layer;
uniform vec4 u_channel_factors = vec4(1.0);
vec4 filter_preview_colors(vec4 input_color, vec4 factors) {
// Filter RGB.
vec4 output_color = input_color * vec4(factors.rgb, input_color.a);
// Remove transparency when alpha is not enabled.
output_color.a = mix(1.0, output_color.a, factors.a);
// Switch to opaque grayscale when visualizing only one channel.
float csum = factors.r + factors.g + factors.b + factors.a;
float single = clamp(2.0 - csum, 0.0, 1.0);
for (int i = 0; i < 4; i++) {
float c = input_color[i];
output_color = mix(output_color, vec4(c, c, c, 1.0), factors[i] * single);
}
return output_color;
}
void fragment() {
COLOR = textureLod(tex, vec3(UV, layer), 0.0);
COLOR = filter_preview_colors(COLOR, u_channel_factors);
}
)";
@ -94,6 +116,8 @@ void Texture3DEditor::_update_material(bool p_texture_changed) {
if (p_texture_changed) {
material->set_shader_parameter("tex", texture->get_rid());
}
material->set_shader_parameter("u_channel_factors", channel_selector->get_selected_channel_factors());
}
void Texture3DEditor::_make_shaders() {
@ -138,30 +162,44 @@ void Texture3DEditor::_update_gui() {
layer->set_max(texture->get_depth() - 1);
const String format = Image::get_format_name(texture->get_format());
const Image::Format format = texture->get_format();
const String format_name = Image::get_format_name(format);
if (texture->has_mipmaps()) {
const int mip_count = Image::get_image_required_mipmaps(texture->get_width(), texture->get_height(), texture->get_format());
const int memory = Image::get_image_data_size(texture->get_width(), texture->get_height(), texture->get_format(), true) * texture->get_depth();
const int mip_count = Image::get_image_required_mipmaps(texture->get_width(), texture->get_height(), format);
const int memory = Image::get_image_data_size(texture->get_width(), texture->get_height(), format, true) * texture->get_depth();
info->set_text(vformat(String::utf8("%d×%d×%d %s\n") + TTR("%s Mipmaps") + "\n" + TTR("Memory: %s"),
texture->get_width(),
texture->get_height(),
texture->get_depth(),
format,
format_name,
mip_count,
String::humanize_size(memory)));
} else {
const int memory = Image::get_image_data_size(texture->get_width(), texture->get_height(), texture->get_format(), false) * texture->get_depth();
const int memory = Image::get_image_data_size(texture->get_width(), texture->get_height(), format, false) * texture->get_depth();
info->set_text(vformat(String::utf8("%d×%d×%d %s\n") + TTR("No Mipmaps") + "\n" + TTR("Memory: %s"),
texture->get_width(),
texture->get_height(),
texture->get_depth(),
format,
format_name,
String::humanize_size(memory)));
}
const uint32_t components_mask = Image::get_format_component_mask(format);
if (is_power_of_2(components_mask)) {
// Only one channel available, no point in showing a channel selector.
channel_selector->hide();
} else {
channel_selector->show();
channel_selector->set_available_channels_mask(components_mask);
}
}
void Texture3DEditor::on_selected_channels_changed() {
_update_material(false);
}
void Texture3DEditor::edit(Ref<Texture3D> p_texture) {
@ -215,6 +253,11 @@ Texture3DEditor::Texture3DEditor() {
add_child(layer);
channel_selector = memnew(ColorChannelSelector);
channel_selector->connect("selected_channels_changed", callable_mp(this, &Texture3DEditor::on_selected_channels_changed));
channel_selector->set_anchors_preset(Control::PRESET_TOP_LEFT);
add_child(channel_selector);
info = memnew(Label);
info->add_theme_color_override(SceneStringName(font_color), Color(1, 1, 1));
info->add_theme_color_override("font_shadow_color", Color(0, 0, 0));

View file

@ -37,6 +37,8 @@
#include "scene/resources/shader.h"
#include "scene/resources/texture.h"
class ColorChannelSelector;
class Texture3DEditor : public Control {
GDCLASS(Texture3DEditor, Control);
@ -49,6 +51,8 @@ class Texture3DEditor : public Control {
Control *texture_rect = nullptr;
ColorChannelSelector *channel_selector = nullptr;
bool setting = false;
void _make_shaders();
@ -67,6 +71,8 @@ class Texture3DEditor : public Control {
void _update_material(bool p_texture_changed);
void _update_gui();
void on_selected_channels_changed();
protected:
void _notification(int p_what);

View file

@ -31,6 +31,7 @@
#include "texture_editor_plugin.h"
#include "editor/editor_string_names.h"
#include "editor/plugins/color_channel_selector.h"
#include "editor/themes/editor_scale.h"
#include "scene/gui/aspect_ratio_container.h"
#include "scene/gui/color_rect.h"
@ -41,6 +42,36 @@
#include "scene/resources/compressed_texture.h"
#include "scene/resources/image_texture.h"
#include "scene/resources/portable_compressed_texture.h"
#include "scene/resources/style_box_flat.h"
constexpr const char *texture_2d_shader = R"(
shader_type canvas_item;
render_mode blend_mix;
uniform vec4 u_channel_factors = vec4(1.0);
vec4 filter_preview_colors(vec4 input_color, vec4 factors) {
// Filter RGB.
vec4 output_color = input_color * vec4(factors.rgb, input_color.a);
// Remove transparency when alpha is not enabled.
output_color.a = mix(1.0, output_color.a, factors.a);
// Switch to opaque grayscale when visualizing only one channel.
float csum = factors.r + factors.g + factors.b + factors.a;
float single = clamp(2.0 - csum, 0.0, 1.0);
for (int i = 0; i < 4; i++) {
float c = input_color[i];
output_color = mix(output_color, vec4(c, c, c, 1.0), factors[i] * single);
}
return output_color;
}
void fragment() {
COLOR = filter_preview_colors(texture(TEXTURE, UV), u_channel_factors);
}
)";
TextureRect *TexturePreview::get_texture_display() {
return texture_display;
@ -72,8 +103,8 @@ void TexturePreview::_notification(int p_what) {
void TexturePreview::_draw_outline() {
const float outline_width = Math::round(EDSCALE);
const Rect2 outline_rect = Rect2(Vector2(), texture_display->get_size()).grow(outline_width * 0.5);
texture_display->draw_rect(outline_rect, cached_outline_color, false, outline_width);
const Rect2 outline_rect = Rect2(Vector2(), outline_overlay->get_size()).grow(outline_width * 0.5);
outline_overlay->draw_rect(outline_rect, cached_outline_color, false, outline_width);
}
void TexturePreview::_update_texture_display_ratio() {
@ -82,25 +113,49 @@ void TexturePreview::_update_texture_display_ratio() {
}
}
void TexturePreview::_update_metadata_label_text() {
const Ref<Texture2D> texture = texture_display->get_texture();
String format;
if (Object::cast_to<ImageTexture>(*texture)) {
format = Image::get_format_name(Object::cast_to<ImageTexture>(*texture)->get_format());
} else if (Object::cast_to<CompressedTexture2D>(*texture)) {
format = Image::get_format_name(Object::cast_to<CompressedTexture2D>(*texture)->get_format());
} else {
format = texture->get_class();
static Image::Format get_texture_2d_format(const Ref<Texture2D> &p_texture) {
const Ref<ImageTexture> image_texture = p_texture;
if (image_texture.is_valid()) {
return image_texture->get_format();
}
const Ref<Image> image = texture->get_image();
const Ref<CompressedTexture2D> compressed_texture = p_texture;
if (compressed_texture.is_valid()) {
return compressed_texture->get_format();
}
// AtlasTexture?
// Unknown
return Image::FORMAT_MAX;
}
static int get_texture_mipmaps_count(const Ref<Texture2D> &p_texture) {
ERR_FAIL_COND_V(p_texture.is_null(), -1);
// We are having to download the image only to get its mipmaps count. It would be nice if we didn't have to.
Ref<Image> image = p_texture->get_image();
if (image.is_valid()) {
const int mipmaps = image->get_mipmap_count();
return image->get_mipmap_count();
}
return -1;
}
void TexturePreview::_update_metadata_label_text() {
const Ref<Texture2D> texture = texture_display->get_texture();
ERR_FAIL_COND(texture.is_null());
const Image::Format format = get_texture_2d_format(texture.ptr());
const String format_name = format != Image::FORMAT_MAX ? Image::get_format_name(format) : texture->get_class();
const Vector2i resolution = texture->get_size();
const int mipmaps = get_texture_mipmaps_count(texture);
if (format != Image::FORMAT_MAX) {
// Avoid signed integer overflow that could occur with huge texture sizes by casting everything to uint64_t.
uint64_t memory = uint64_t(image->get_width()) * uint64_t(image->get_height()) * uint64_t(Image::get_format_pixel_size(image->get_format()));
uint64_t memory = uint64_t(resolution.x) * uint64_t(resolution.y) * uint64_t(Image::get_format_pixel_size(format));
// Handle VRAM-compressed formats that are stored with 4 bpp.
memory >>= Image::get_format_pixel_rshift(image->get_format());
memory >>= Image::get_format_pixel_rshift(format);
float mipmaps_multiplier = 1.0;
float mipmap_increase = 0.25;
@ -117,7 +172,7 @@ void TexturePreview::_update_metadata_label_text() {
vformat(String::utf8("%d×%d %s\n") + TTR("%s Mipmaps") + "\n" + TTR("Memory: %s"),
texture->get_width(),
texture->get_height(),
format,
format_name,
mipmaps,
String::humanize_size(memory)));
} else {
@ -127,7 +182,7 @@ void TexturePreview::_update_metadata_label_text() {
vformat(String::utf8("%d×%d %s\n") + TTR("No Mipmaps") + "\n" + TTR("Memory: %s"),
texture->get_width(),
texture->get_height(),
format,
format_name,
String::humanize_size(memory)));
}
} else {
@ -135,10 +190,14 @@ void TexturePreview::_update_metadata_label_text() {
vformat(String::utf8("%d×%d %s"),
texture->get_width(),
texture->get_height(),
format));
format_name));
}
}
void TexturePreview::on_selected_channels_changed() {
material->set_shader_parameter("u_channel_factors", channel_selector->get_selected_channel_factors());
}
TexturePreview::TexturePreview(Ref<Texture2D> p_texture, bool p_show_metadata) {
set_custom_minimum_size(Size2(0.0, 256.0) * EDSCALE);
@ -163,19 +222,48 @@ TexturePreview::TexturePreview(Ref<Texture2D> p_texture, bool p_show_metadata) {
checkerboard->set_texture_repeat(CanvasItem::TEXTURE_REPEAT_ENABLED);
centering_container->add_child(checkerboard);
{
Ref<Shader> shader;
shader.instantiate();
shader->set_code(texture_2d_shader);
material.instantiate();
material->set_shader(shader);
material->set_shader_parameter("u_channel_factors", Vector4(1, 1, 1, 1));
}
texture_display = memnew(TextureRect);
texture_display->set_texture_filter(TEXTURE_FILTER_NEAREST_WITH_MIPMAPS);
texture_display->set_texture(p_texture);
texture_display->set_expand_mode(TextureRect::EXPAND_IGNORE_SIZE);
texture_display->set_material(material);
centering_container->add_child(texture_display);
texture_display->connect(SceneStringName(draw), callable_mp(this, &TexturePreview::_draw_outline));
// Creating a separate control so it is not affected by the filtering shader.
outline_overlay = memnew(Control);
centering_container->add_child(outline_overlay);
outline_overlay->connect(SceneStringName(draw), callable_mp(this, &TexturePreview::_draw_outline));
if (p_texture.is_valid()) {
_update_texture_display_ratio();
p_texture->connect_changed(callable_mp(this, &TexturePreview::_update_texture_display_ratio));
}
// Null can be passed by `Camera3DPreview` (which immediately after sets a texture anyways).
const Image::Format format = p_texture.is_valid() ? get_texture_2d_format(p_texture.ptr()) : Image::FORMAT_MAX;
const uint32_t components_mask = format != Image::FORMAT_MAX ? Image::get_format_component_mask(format) : 0xf;
// Add color channel selector at the bottom left if more than 1 channel is available.
if (p_show_metadata && !is_power_of_2(components_mask)) {
channel_selector = memnew(ColorChannelSelector);
channel_selector->connect("selected_channels_changed", callable_mp(this, &TexturePreview::on_selected_channels_changed));
channel_selector->set_h_size_flags(Control::SIZE_SHRINK_BEGIN);
channel_selector->set_v_size_flags(Control::SIZE_SHRINK_BEGIN);
channel_selector->set_available_channels_mask(components_mask);
add_child(channel_selector);
}
if (p_show_metadata) {
metadata_label = memnew(Label);

View file

@ -39,6 +39,8 @@
class AspectRatioContainer;
class ColorRect;
class TextureRect;
class ShaderMaterial;
class ColorChannelSelector;
class TexturePreview : public MarginContainer {
GDCLASS(TexturePreview, MarginContainer);
@ -47,10 +49,14 @@ private:
TextureRect *texture_display = nullptr;
MarginContainer *margin_container = nullptr;
Control *outline_overlay = nullptr;
AspectRatioContainer *centering_container = nullptr;
ColorRect *bg_rect = nullptr;
TextureRect *checkerboard = nullptr;
Label *metadata_label = nullptr;
Ref<ShaderMaterial> material;
ColorChannelSelector *channel_selector = nullptr;
Color cached_outline_color;
@ -61,6 +67,8 @@ protected:
void _notification(int p_what);
void _update_texture_display_ratio();
void on_selected_channels_changed();
public:
TextureRect *get_texture_display();
TexturePreview(Ref<Texture2D> p_texture, bool p_show_metadata);

View file

@ -31,6 +31,7 @@
#include "texture_layered_editor_plugin.h"
#include "editor/editor_string_names.h"
#include "editor/plugins/color_channel_selector.h"
#include "editor/themes/editor_scale.h"
#include "scene/gui/label.h"
@ -43,9 +44,29 @@ constexpr const char *array_2d_shader = R"(
uniform sampler2DArray tex;
uniform float layer;
uniform vec4 u_channel_factors = vec4(1.0);
vec4 filter_preview_colors(vec4 input_color, vec4 factors) {
// Filter RGB.
vec4 output_color = input_color * vec4(factors.rgb, input_color.a);
// Remove transparency when alpha is not enabled.
output_color.a = mix(1.0, output_color.a, factors.a);
// Switch to opaque grayscale when visualizing only one channel.
float csum = factors.r + factors.g + factors.b + factors.a;
float single = clamp(2.0 - csum, 0.0, 1.0);
for (int i = 0; i < 4; i++) {
float c = input_color[i];
output_color = mix(output_color, vec4(c, c, c, 1.0), factors[i] * single);
}
return output_color;
}
void fragment() {
COLOR = textureLod(tex, vec3(UV, layer), 0.0);
COLOR = filter_preview_colors(COLOR, u_channel_factors);
}
)";
@ -58,9 +79,30 @@ constexpr const char *cubemap_shader = R"(
uniform vec3 normal;
uniform mat3 rot;
uniform vec4 u_channel_factors = vec4(1.0);
vec4 filter_preview_colors(vec4 input_color, vec4 factors) {
// Filter RGB.
vec4 output_color = input_color * vec4(factors.rgb, input_color.a);
// Remove transparency when alpha is not enabled.
output_color.a = mix(1.0, output_color.a, factors.a);
// Switch to opaque grayscale when visualizing only one channel.
float csum = factors.r + factors.g + factors.b + factors.a;
float single = clamp(2.0 - csum, 0.0, 1.0);
for (int i = 0; i < 4; i++) {
float c = input_color[i];
output_color = mix(output_color, vec4(c, c, c, 1.0), factors[i] * single);
}
return output_color;
}
void fragment() {
vec3 n = rot * normalize(vec3(normal.xy * (UV * 2.0 - 1.0), normal.z));
COLOR = textureLod(tex, n, 0.0);
COLOR = filter_preview_colors(COLOR, u_channel_factors);
}
)";
@ -73,9 +115,30 @@ constexpr const char *cubemap_array_shader = R"(
uniform mat3 rot;
uniform float layer;
uniform vec4 u_channel_factors = vec4(1.0);
vec4 filter_preview_colors(vec4 input_color, vec4 factors) {
// Filter RGB.
vec4 output_color = input_color * vec4(factors.rgb, input_color.a);
// Remove transparency when alpha is not enabled.
output_color.a = mix(1.0, output_color.a, factors.a);
// Switch to opaque grayscale when visualizing only one channel.
float csum = factors.r + factors.g + factors.b + factors.a;
float single = clamp(2.0 - csum, 0.0, 1.0);
for (int i = 0; i < 4; i++) {
float c = input_color[i];
output_color = mix(output_color, vec4(c, c, c, 1.0), factors[i] * single);
}
return output_color;
}
void fragment() {
vec3 n = rot * normalize(vec3(normal.xy * (UV * 2.0 - 1.0), normal.z));
COLOR = textureLod(tex, vec4(n, layer), 0.0);
COLOR = filter_preview_colors(COLOR, u_channel_factors);
}
)";
@ -102,7 +165,8 @@ void TextureLayeredEditor::_update_gui() {
_texture_rect_update_area();
const String format = Image::get_format_name(texture->get_format());
const Image::Format format = texture->get_format();
const String format_name = Image::get_format_name(format);
String texture_info;
switch (texture->get_layered_type()) {
@ -113,7 +177,7 @@ void TextureLayeredEditor::_update_gui() {
texture->get_width(),
texture->get_height(),
texture->get_layers(),
format);
format_name);
} break;
case TextureLayered::LAYERED_TYPE_CUBEMAP: {
@ -122,7 +186,7 @@ void TextureLayeredEditor::_update_gui() {
texture_info = vformat(String::utf8("%d×%d %s\n"),
texture->get_width(),
texture->get_height(),
format);
format_name);
} break;
case TextureLayered::LAYERED_TYPE_CUBEMAP_ARRAY: {
@ -132,7 +196,7 @@ void TextureLayeredEditor::_update_gui() {
texture->get_width(),
texture->get_height(),
texture->get_layers() / 6,
format);
format_name);
} break;
@ -141,21 +205,30 @@ void TextureLayeredEditor::_update_gui() {
}
if (texture->has_mipmaps()) {
const int mip_count = Image::get_image_required_mipmaps(texture->get_width(), texture->get_height(), texture->get_format());
const int memory = Image::get_image_data_size(texture->get_width(), texture->get_height(), texture->get_format(), true) * texture->get_layers();
const int mip_count = Image::get_image_required_mipmaps(texture->get_width(), texture->get_height(), format);
const int memory = Image::get_image_data_size(texture->get_width(), texture->get_height(), format, true) * texture->get_layers();
texture_info += vformat(TTR("%s Mipmaps") + "\n" + TTR("Memory: %s"),
mip_count,
String::humanize_size(memory));
} else {
const int memory = Image::get_image_data_size(texture->get_width(), texture->get_height(), texture->get_format(), false) * texture->get_layers();
const int memory = Image::get_image_data_size(texture->get_width(), texture->get_height(), format, false) * texture->get_layers();
texture_info += vformat(TTR("No Mipmaps") + "\n" + TTR("Memory: %s"),
String::humanize_size(memory));
}
info->set_text(texture_info);
const uint32_t components_mask = Image::get_format_component_mask(format);
if (is_power_of_2(components_mask)) {
// Only one channel available, no point in showing a channel selector.
channel_selector->hide();
} else {
channel_selector->show();
channel_selector->set_available_channels_mask(components_mask);
}
}
void TextureLayeredEditor::_notification(int p_what) {
@ -212,6 +285,15 @@ void TextureLayeredEditor::_update_material(bool p_texture_changed) {
if (p_texture_changed) {
materials[texture->get_layered_type()]->set_shader_parameter("tex", texture->get_rid());
}
const Vector4 channel_factors = channel_selector->get_selected_channel_factors();
for (unsigned int i = 0; i < 3; ++i) {
materials[i]->set_shader_parameter("u_channel_factors", channel_factors);
}
}
void TextureLayeredEditor::on_selected_channels_changed() {
_update_material(false);
}
void TextureLayeredEditor::_make_shaders() {
@ -309,6 +391,11 @@ TextureLayeredEditor::TextureLayeredEditor() {
add_child(layer);
channel_selector = memnew(ColorChannelSelector);
channel_selector->connect("selected_channels_changed", callable_mp(this, &TextureLayeredEditor::on_selected_channels_changed));
channel_selector->set_anchors_and_offsets_preset(Control::PRESET_TOP_LEFT);
add_child(channel_selector);
info = memnew(Label);
info->add_theme_color_override(SceneStringName(font_color), Color(1, 1, 1));
info->add_theme_color_override("font_shadow_color", Color(0, 0, 0));

View file

@ -37,6 +37,8 @@
#include "scene/resources/shader.h"
#include "scene/resources/texture.h"
class ColorChannelSelector;
class TextureLayeredEditor : public Control {
GDCLASS(TextureLayeredEditor, Control);
@ -53,6 +55,8 @@ class TextureLayeredEditor : public Control {
bool setting = false;
ColorChannelSelector *channel_selector = nullptr;
void _make_shaders();
void _update_material(bool p_texture_changed);
@ -69,6 +73,8 @@ class TextureLayeredEditor : public Control {
void _update_gui();
void on_selected_channels_changed();
protected:
void _notification(int p_what);
virtual void gui_input(const Ref<InputEvent> &p_event) override;