diff options
-rw-r--r-- | doc/classes/Gradient.xml | 15 | ||||
-rw-r--r-- | editor/plugins/gradient_editor.cpp | 13 | ||||
-rw-r--r-- | editor/plugins/gradient_editor.h | 4 | ||||
-rw-r--r-- | scene/resources/gradient.cpp | 25 | ||||
-rw-r--r-- | scene/resources/gradient.h | 109 |
5 files changed, 144 insertions, 22 deletions
diff --git a/doc/classes/Gradient.xml b/doc/classes/Gradient.xml index aa941f9fd3..f76ccb49ac 100644 --- a/doc/classes/Gradient.xml +++ b/doc/classes/Gradient.xml @@ -80,8 +80,12 @@ <member name="colors" type="PackedColorArray" setter="set_colors" getter="get_colors" default="PackedColorArray(0, 0, 0, 1, 1, 1, 1, 1)"> Gradient's colors returned as a [PackedColorArray]. </member> + <member name="interpolation_color_space" type="int" setter="set_interpolation_color_space" getter="get_interpolation_color_space" enum="Gradient.ColorSpace" default="0"> + The color space used to interpolate between points of the gradient. It does not affect the returned colors, which will always be in sRGB space. See [enum ColorSpace] for available modes. + [b]Note:[/b] This setting has no effect when [member interpolation_mode] is set to [constant GRADIENT_INTERPOLATE_CONSTANT]. + </member> <member name="interpolation_mode" type="int" setter="set_interpolation_mode" getter="get_interpolation_mode" enum="Gradient.InterpolationMode" default="0"> - Defines how the colors between points of the gradient are interpolated. See [enum InterpolationMode] for available modes. + The algorithm used to interpolate between points of the gradient. See [enum InterpolationMode] for available modes. </member> <member name="offsets" type="PackedFloat32Array" setter="set_offsets" getter="get_offsets" default="PackedFloat32Array(0, 1)"> Gradient's offsets returned as a [PackedFloat32Array]. @@ -97,5 +101,14 @@ <constant name="GRADIENT_INTERPOLATE_CUBIC" value="2" enum="InterpolationMode"> Cubic interpolation. </constant> + <constant name="GRADIENT_COLOR_SPACE_SRGB" value="0" enum="ColorSpace"> + sRGB color space. + </constant> + <constant name="GRADIENT_COLOR_SPACE_LINEAR_SRGB" value="1" enum="ColorSpace"> + Linear sRGB color space. + </constant> + <constant name="GRADIENT_COLOR_SPACE_OKLAB" value="2" enum="ColorSpace"> + [url=https://bottosson.github.io/posts/oklab/]Oklab[/url] color space. This color space provides a smooth and uniform-looking transition between colors. + </constant> </constants> </class> diff --git a/editor/plugins/gradient_editor.cpp b/editor/plugins/gradient_editor.cpp index 2eb17b3f13..000db06d48 100644 --- a/editor/plugins/gradient_editor.cpp +++ b/editor/plugins/gradient_editor.cpp @@ -41,6 +41,7 @@ void GradientEditor::set_gradient(const Ref<Gradient> &p_gradient) { gradient->connect("changed", callable_mp(this, &GradientEditor::_gradient_changed)); set_points(gradient->get_points()); set_interpolation_mode(gradient->get_interpolation_mode()); + set_interpolation_color_space(gradient->get_interpolation_color_space()); } void GradientEditor::reverse_gradient() { @@ -93,6 +94,7 @@ void GradientEditor::_gradient_changed() { Vector<Gradient::Point> grad_points = gradient->get_points(); set_points(grad_points); set_interpolation_mode(gradient->get_interpolation_mode()); + set_interpolation_color_space(gradient->get_interpolation_color_space()); queue_redraw(); editing = false; } @@ -104,9 +106,11 @@ void GradientEditor::_ramp_changed() { undo_redo->add_do_method(gradient.ptr(), "set_offsets", get_offsets()); undo_redo->add_do_method(gradient.ptr(), "set_colors", get_colors()); undo_redo->add_do_method(gradient.ptr(), "set_interpolation_mode", get_interpolation_mode()); + undo_redo->add_do_method(gradient.ptr(), "set_interpolation_color_space", get_interpolation_color_space()); undo_redo->add_undo_method(gradient.ptr(), "set_offsets", gradient->get_offsets()); undo_redo->add_undo_method(gradient.ptr(), "set_colors", gradient->get_colors()); undo_redo->add_undo_method(gradient.ptr(), "set_interpolation_mode", gradient->get_interpolation_mode()); + undo_redo->add_undo_method(gradient.ptr(), "set_interpolation_color_space", gradient->get_interpolation_color_space()); undo_redo->commit_action(); editing = false; } @@ -171,6 +175,14 @@ Gradient::InterpolationMode GradientEditor::get_interpolation_mode() { return interpolation_mode; } +void GradientEditor::set_interpolation_color_space(Gradient::ColorSpace p_color_space) { + interpolation_color_space = p_color_space; +} + +Gradient::ColorSpace GradientEditor::get_interpolation_color_space() { + return interpolation_color_space; +} + ColorPicker *GradientEditor::get_picker() { return picker; } @@ -385,6 +397,7 @@ void GradientEditor::_notification(int p_what) { // Draw color ramp. gradient_cache->set_points(points); gradient_cache->set_interpolation_mode(interpolation_mode); + gradient_cache->set_interpolation_color_space(interpolation_color_space); preview_texture->set_gradient(gradient_cache); draw_texture_rect(preview_texture, Rect2(handle_width / 2, 0, total_w, h)); diff --git a/editor/plugins/gradient_editor.h b/editor/plugins/gradient_editor.h index a8757573d5..b78b740f4f 100644 --- a/editor/plugins/gradient_editor.h +++ b/editor/plugins/gradient_editor.h @@ -45,6 +45,7 @@ class GradientEditor : public Control { int grabbed = -1; Vector<Gradient::Point> points; Gradient::InterpolationMode interpolation_mode = Gradient::GRADIENT_INTERPOLATE_LINEAR; + Gradient::ColorSpace interpolation_color_space = Gradient::GRADIENT_COLOR_SPACE_SRGB; bool editing = false; Ref<Gradient> gradient; @@ -84,6 +85,9 @@ public: void set_interpolation_mode(Gradient::InterpolationMode p_interp_mode); Gradient::InterpolationMode get_interpolation_mode(); + void set_interpolation_color_space(Gradient::ColorSpace p_color_space); + Gradient::ColorSpace get_interpolation_color_space(); + ColorPicker *get_picker(); PopupPanel *get_popup(); diff --git a/scene/resources/gradient.cpp b/scene/resources/gradient.cpp index eafc4bec7d..ec4b756a85 100644 --- a/scene/resources/gradient.cpp +++ b/scene/resources/gradient.cpp @@ -69,7 +69,12 @@ void Gradient::_bind_methods() { ClassDB::bind_method(D_METHOD("set_interpolation_mode", "interpolation_mode"), &Gradient::set_interpolation_mode); ClassDB::bind_method(D_METHOD("get_interpolation_mode"), &Gradient::get_interpolation_mode); + ClassDB::bind_method(D_METHOD("set_interpolation_color_space", "interpolation_color_space"), &Gradient::set_interpolation_color_space); + ClassDB::bind_method(D_METHOD("get_interpolation_color_space"), &Gradient::get_interpolation_color_space); + + ADD_GROUP("Interpolation", "interpolation_"); ADD_PROPERTY(PropertyInfo(Variant::INT, "interpolation_mode", PROPERTY_HINT_ENUM, "Linear,Constant,Cubic"), "set_interpolation_mode", "get_interpolation_mode"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "interpolation_color_space", PROPERTY_HINT_ENUM, "sRGB,Linear sRGB,Oklab"), "set_interpolation_color_space", "get_interpolation_color_space"); ADD_GROUP("Raw Data", ""); ADD_PROPERTY(PropertyInfo(Variant::PACKED_FLOAT32_ARRAY, "offsets"), "set_offsets", "get_offsets"); @@ -78,6 +83,16 @@ void Gradient::_bind_methods() { BIND_ENUM_CONSTANT(GRADIENT_INTERPOLATE_LINEAR); BIND_ENUM_CONSTANT(GRADIENT_INTERPOLATE_CONSTANT); BIND_ENUM_CONSTANT(GRADIENT_INTERPOLATE_CUBIC); + + BIND_ENUM_CONSTANT(GRADIENT_COLOR_SPACE_SRGB); + BIND_ENUM_CONSTANT(GRADIENT_COLOR_SPACE_LINEAR_SRGB); + BIND_ENUM_CONSTANT(GRADIENT_COLOR_SPACE_OKLAB); +} + +void Gradient::_validate_property(PropertyInfo &p_property) const { + if (p_property.name == "interpolation_color_space" && interpolation_mode == GRADIENT_INTERPOLATE_CONSTANT) { + p_property.usage = PROPERTY_USAGE_NO_EDITOR; + } } Vector<float> Gradient::get_offsets() const { @@ -101,12 +116,22 @@ Vector<Color> Gradient::get_colors() const { void Gradient::set_interpolation_mode(Gradient::InterpolationMode p_interp_mode) { interpolation_mode = p_interp_mode; emit_signal(CoreStringNames::get_singleton()->changed); + notify_property_list_changed(); } Gradient::InterpolationMode Gradient::get_interpolation_mode() { return interpolation_mode; } +void Gradient::set_interpolation_color_space(Gradient::ColorSpace p_color_space) { + interpolation_color_space = p_color_space; + emit_signal(CoreStringNames::get_singleton()->changed); +} + +Gradient::ColorSpace Gradient::get_interpolation_color_space() { + return interpolation_color_space; +} + void Gradient::set_offsets(const Vector<float> &p_offsets) { points.resize(p_offsets.size()); for (int i = 0; i < points.size(); i++) { diff --git a/scene/resources/gradient.h b/scene/resources/gradient.h index 9e3267f37b..b88399117d 100644 --- a/scene/resources/gradient.h +++ b/scene/resources/gradient.h @@ -33,6 +33,8 @@ #include "core/io/resource.h" +#include "thirdparty/misc/ok_color.h" + class Gradient : public Resource { GDCLASS(Gradient, Resource); OBJ_SAVE_TYPE(Gradient); @@ -44,6 +46,12 @@ public: GRADIENT_INTERPOLATE_CUBIC, }; + enum ColorSpace { + GRADIENT_COLOR_SPACE_SRGB, + GRADIENT_COLOR_SPACE_LINEAR_SRGB, + GRADIENT_COLOR_SPACE_OKLAB, + }; + struct Point { float offset = 0.0; Color color; @@ -56,6 +64,7 @@ private: Vector<Point> points; bool is_sorted = true; InterpolationMode interpolation_mode = GRADIENT_INTERPOLATE_LINEAR; + ColorSpace interpolation_color_space = GRADIENT_COLOR_SPACE_SRGB; _FORCE_INLINE_ void _update_sorting() { if (!is_sorted) { @@ -64,8 +73,55 @@ private: } } + _FORCE_INLINE_ Color transform_color_space(const Color p_color) const { + switch (interpolation_color_space) { + case GRADIENT_COLOR_SPACE_SRGB: + default: + return p_color; + + case GRADIENT_COLOR_SPACE_LINEAR_SRGB: + return p_color.srgb_to_linear(); + + case GRADIENT_COLOR_SPACE_OKLAB: + Color linear_color = p_color.srgb_to_linear(); + ok_color::RGB rgb{}; + rgb.r = linear_color.r; + rgb.g = linear_color.g; + rgb.b = linear_color.b; + + ok_color ok_color; + ok_color::Lab lab_color = ok_color.linear_srgb_to_oklab(rgb); + + // Constructs an RGB color using the Lab values directly. This allows reusing the interpolation code. + return { lab_color.L, lab_color.a, lab_color.b, linear_color.a }; + } + } + + _FORCE_INLINE_ Color inv_transform_color_space(const Color p_color) const { + switch (interpolation_color_space) { + case GRADIENT_COLOR_SPACE_SRGB: + default: + return p_color; + + case GRADIENT_COLOR_SPACE_LINEAR_SRGB: + return p_color.linear_to_srgb(); + + case GRADIENT_COLOR_SPACE_OKLAB: + ok_color::Lab lab{}; + lab.L = p_color.r; + lab.a = p_color.g; + lab.b = p_color.b; + + ok_color new_ok_color; + ok_color::RGB ok_rgb = new_ok_color.oklab_to_linear_srgb(lab); + Color linear{ ok_rgb.r, ok_rgb.g, ok_rgb.b, p_color.a }; + return linear.linear_to_srgb(); + } + } + protected: static void _bind_methods(); + void _validate_property(PropertyInfo &p_property) const; public: Gradient(); @@ -92,6 +148,9 @@ public: void set_interpolation_mode(InterpolationMode p_interp_mode); InterpolationMode get_interpolation_mode(); + void set_interpolation_color_space(Gradient::ColorSpace p_color_space); + ColorSpace get_interpolation_color_space(); + _FORCE_INLINE_ Color get_color_at_offset(float p_offset) { if (points.is_empty()) { return Color(0, 0, 0, 1); @@ -134,16 +193,22 @@ public: if (first < 0) { return points[0].color; } - const Point &pointFirst = points[first]; - const Point &pointSecond = points[second]; + const Point &point1 = points[first]; + const Point &point2 = points[second]; + float weight = (p_offset - point1.offset) / (point2.offset - point1.offset); switch (interpolation_mode) { - case GRADIENT_INTERPOLATE_LINEAR: { - return pointFirst.color.lerp(pointSecond.color, (p_offset - pointFirst.offset) / (pointSecond.offset - pointFirst.offset)); - } break; case GRADIENT_INTERPOLATE_CONSTANT: { - return pointFirst.color; - } break; + return point1.color; + } + case GRADIENT_INTERPOLATE_LINEAR: + default: { // Fallback to linear interpolation. + Color color1 = transform_color_space(point1.color); + Color color2 = transform_color_space(point2.color); + + Color interpolated = color1.lerp(color2, weight); + return inv_transform_color_space(interpolated); + } case GRADIENT_INTERPOLATE_CUBIC: { int p0 = first - 1; int p3 = second + 1; @@ -153,20 +218,21 @@ public: if (p0 < 0) { p0 = first; } - const Point &pointP0 = points[p0]; - const Point &pointP3 = points[p3]; - - float x = (p_offset - pointFirst.offset) / (pointSecond.offset - pointFirst.offset); - float r = Math::cubic_interpolate(pointFirst.color.r, pointSecond.color.r, pointP0.color.r, pointP3.color.r, x); - float g = Math::cubic_interpolate(pointFirst.color.g, pointSecond.color.g, pointP0.color.g, pointP3.color.g, x); - float b = Math::cubic_interpolate(pointFirst.color.b, pointSecond.color.b, pointP0.color.b, pointP3.color.b, x); - float a = Math::cubic_interpolate(pointFirst.color.a, pointSecond.color.a, pointP0.color.a, pointP3.color.a, x); - - return Color(r, g, b, a); - } break; - default: { - // Fallback to linear interpolation. - return pointFirst.color.lerp(pointSecond.color, (p_offset - pointFirst.offset) / (pointSecond.offset - pointFirst.offset)); + const Point &point0 = points[p0]; + const Point &point3 = points[p3]; + + Color color0 = transform_color_space(point0.color); + Color color1 = transform_color_space(point1.color); + Color color2 = transform_color_space(point2.color); + Color color3 = transform_color_space(point3.color); + + Color interpolated; + interpolated[0] = Math::cubic_interpolate(color1[0], color2[0], color0[0], color3[0], weight); + interpolated[1] = Math::cubic_interpolate(color1[1], color2[1], color0[1], color3[1], weight); + interpolated[2] = Math::cubic_interpolate(color1[2], color2[2], color0[2], color3[2], weight); + interpolated[3] = Math::cubic_interpolate(color1[3], color2[3], color0[3], color3[3], weight); + + return inv_transform_color_space(interpolated); } } } @@ -175,5 +241,6 @@ public: }; VARIANT_ENUM_CAST(Gradient::InterpolationMode); +VARIANT_ENUM_CAST(Gradient::ColorSpace); #endif // GRADIENT_H |