Merge pull request #76545 from JoNax97/gradient_color_spaces

Add Linear SRGB and OKLab color spaces to Gradient.
This commit is contained in:
Rémi Verschelde 2023-05-29 10:28:58 +02:00
commit c97201babc
No known key found for this signature in database
GPG key ID: C3336907360768E1
5 changed files with 142 additions and 20 deletions

View file

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

View file

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

View file

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

View file

@ -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++) {

View file

@ -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];
const Point &point0 = points[p0];
const Point &point3 = 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);
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);
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));
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