From 71d20ea670c46adf8be45ff563c541ad7f60d5a8 Mon Sep 17 00:00:00 2001
From: FireForge <67974470+fire-forge@users.noreply.github.com>
Date: Thu, 12 May 2022 12:51:56 -0500
Subject: [PATCH] Add interpolation modes to Gradient - Linear, Constant, and
Cubic interpolation modes - Partial backport of #53321
---
doc/classes/Gradient.xml | 12 +++++
editor/plugins/gradient_editor_plugin.cpp | 5 ++
scene/gui/gradient_edit.cpp | 57 +++++++----------------
scene/gui/gradient_edit.h | 7 +++
scene/resources/gradient.cpp | 19 ++++++++
scene/resources/gradient.h | 51 +++++++++++++++++++-
6 files changed, 110 insertions(+), 41 deletions(-)
diff --git a/doc/classes/Gradient.xml b/doc/classes/Gradient.xml
index ec952c44a..2ff464655 100644
--- a/doc/classes/Gradient.xml
+++ b/doc/classes/Gradient.xml
@@ -72,10 +72,22 @@
Gradient's colors returned as a [PoolColorArray].
+
+ Defines how the colors between points of the gradient are interpolated. See [enum InterpolationMode] for available modes.
+
Gradient's offsets returned as a [PoolRealArray].
+
+ Linear interpolation.
+
+
+ Constant interpolation, color changes abruptly at each point and stays uniform between. This might cause visible aliasing when used for a gradient texture in some cases.
+
+
+ Cubic interpolation.
+
diff --git a/editor/plugins/gradient_editor_plugin.cpp b/editor/plugins/gradient_editor_plugin.cpp
index 546cb7fcd..b4ede7c8d 100644
--- a/editor/plugins/gradient_editor_plugin.cpp
+++ b/editor/plugins/gradient_editor_plugin.cpp
@@ -49,6 +49,8 @@ void GradientEditor::_gradient_changed() {
editing = true;
Vector points = gradient->get_points();
set_points(points);
+ set_interpolation_mode(gradient->get_interpolation_mode());
+ update();
editing = false;
}
@@ -58,8 +60,10 @@ void GradientEditor::_ramp_changed() {
undo_redo->create_action(TTR("Gradient Edited"), UndoRedo::MERGE_ENDS);
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_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->commit_action();
editing = false;
}
@@ -74,6 +78,7 @@ void GradientEditor::set_gradient(const Ref &p_gradient) {
connect("ramp_changed", this, "_ramp_changed");
gradient->connect("changed", this, "_gradient_changed");
set_points(gradient->get_points());
+ set_interpolation_mode(gradient->get_interpolation_mode());
}
GradientEditor::GradientEditor() {
diff --git a/scene/gui/gradient_edit.cpp b/scene/gui/gradient_edit.cpp
index fd9438d17..27f8deca8 100644
--- a/scene/gui/gradient_edit.cpp
+++ b/scene/gui/gradient_edit.cpp
@@ -55,6 +55,11 @@ GradientEdit::GradientEdit() {
add_child(popup);
+ gradient_cache.instance();
+ preview_texture.instance();
+
+ preview_texture->set_width(1024);
+
checker = Ref(memnew(ImageTexture));
Ref img = memnew(Image(checker_bg_png));
checker->create_from_image(img, ImageTexture::FLAG_REPEAT);
@@ -318,46 +323,10 @@ void GradientEdit::_notification(int p_what) {
_draw_checker(0, 0, total_w, h);
//Draw color ramp
- Gradient::Point prev;
- prev.offset = 0;
- if (points.size() == 0) {
- prev.color = Color(0, 0, 0); //Draw black rectangle if we have no points
- } else {
- prev.color = points[0].color; //Extend color of first point to the beginning.
- }
-
- for (int i = -1; i < points.size(); i++) {
- Gradient::Point next;
- //If there is no next point
- if (i + 1 == points.size()) {
- if (points.size() == 0) {
- next.color = Color(0, 0, 0); //Draw black rectangle if we have no points
- } else {
- next.color = points[i].color; //Extend color of last point to the end.
- }
- next.offset = 1;
- } else {
- next = points[i + 1];
- }
-
- if (prev.offset == next.offset) {
- prev = next;
- continue;
- }
-
- Vector points;
- Vector colors;
- points.push_back(Vector2(prev.offset * total_w, h));
- points.push_back(Vector2(prev.offset * total_w, 0));
- points.push_back(Vector2(next.offset * total_w, 0));
- points.push_back(Vector2(next.offset * total_w, h));
- colors.push_back(prev.color);
- colors.push_back(prev.color);
- colors.push_back(next.color);
- colors.push_back(next.color);
- draw_primitive(points, colors, Vector());
- prev = next;
- }
+ gradient_cache->set_points(points);
+ gradient_cache->set_interpolation_mode(interpolation_mode);
+ preview_texture->set_gradient(gradient_cache);
+ draw_texture_rect(preview_texture, Rect2(0, 0, total_w, h));
//Draw point markers
for (int i = 0; i < points.size(); i++) {
@@ -485,6 +454,14 @@ Vector &GradientEdit::get_points() {
return points;
}
+void GradientEdit::set_interpolation_mode(Gradient::InterpolationMode p_interp_mode) {
+ interpolation_mode = p_interp_mode;
+}
+
+Gradient::InterpolationMode GradientEdit::get_interpolation_mode() {
+ return interpolation_mode;
+}
+
void GradientEdit::_bind_methods() {
ClassDB::bind_method(D_METHOD("_gui_input"), &GradientEdit::_gui_input);
ClassDB::bind_method(D_METHOD("_color_changed"), &GradientEdit::_color_changed);
diff --git a/scene/gui/gradient_edit.h b/scene/gui/gradient_edit.h
index 9d5762f66..26f884993 100644
--- a/scene/gui/gradient_edit.h
+++ b/scene/gui/gradient_edit.h
@@ -49,6 +49,10 @@ class GradientEdit : public Control {
bool grabbing;
int grabbed;
Vector points;
+ Gradient::InterpolationMode interpolation_mode = Gradient::GRADIENT_INTERPOLATE_LINEAR;
+
+ Ref gradient_cache;
+ Ref preview_texture;
void _draw_checker(int x, int y, int w, int h);
void _color_changed(const Color &p_color);
@@ -66,6 +70,9 @@ public:
Vector get_colors() const;
void set_points(Vector &p_points);
Vector &get_points();
+ void set_interpolation_mode(Gradient::InterpolationMode p_interp_mode);
+ Gradient::InterpolationMode get_interpolation_mode();
+
virtual Size2 get_minimum_size() const;
GradientEdit();
diff --git a/scene/resources/gradient.cpp b/scene/resources/gradient.cpp
index ffeb5f168..66c28b6e8 100644
--- a/scene/resources/gradient.cpp
+++ b/scene/resources/gradient.cpp
@@ -71,8 +71,18 @@ void Gradient::_bind_methods() {
ClassDB::bind_method(D_METHOD(COLOR_RAMP_SET_COLORS, "colors"), &Gradient::set_colors);
ClassDB::bind_method(D_METHOD(COLOR_RAMP_GET_COLORS), &Gradient::get_colors);
+ 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);
+
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "interpolation_mode", PROPERTY_HINT_ENUM, "Linear,Constant,Cubic"), "set_interpolation_mode", "get_interpolation_mode");
+
+ ADD_GROUP("Raw Data", "");
ADD_PROPERTY(PropertyInfo(Variant::POOL_REAL_ARRAY, "offsets"), COLOR_RAMP_SET_OFFSETS, COLOR_RAMP_GET_OFFSETS);
ADD_PROPERTY(PropertyInfo(Variant::POOL_COLOR_ARRAY, "colors"), COLOR_RAMP_SET_COLORS, COLOR_RAMP_GET_COLORS);
+
+ BIND_ENUM_CONSTANT(GRADIENT_INTERPOLATE_LINEAR);
+ BIND_ENUM_CONSTANT(GRADIENT_INTERPOLATE_CONSTANT);
+ BIND_ENUM_CONSTANT(GRADIENT_INTERPOLATE_CUBIC);
}
Vector Gradient::get_offsets() const {
@@ -93,6 +103,15 @@ Vector Gradient::get_colors() const {
return colors;
}
+void Gradient::set_interpolation_mode(Gradient::InterpolationMode p_interp_mode) {
+ interpolation_mode = p_interp_mode;
+ emit_signal(CoreStringNames::get_singleton()->changed);
+}
+
+Gradient::InterpolationMode Gradient::get_interpolation_mode() {
+ return interpolation_mode;
+}
+
void Gradient::set_offsets(const Vector &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 a1f1295ac..ab9ca143d 100644
--- a/scene/resources/gradient.h
+++ b/scene/resources/gradient.h
@@ -37,6 +37,12 @@ class Gradient : public Resource {
OBJ_SAVE_TYPE(Gradient);
public:
+ enum InterpolationMode {
+ GRADIENT_INTERPOLATE_LINEAR,
+ GRADIENT_INTERPOLATE_CONSTANT,
+ GRADIENT_INTERPOLATE_CUBIC,
+ };
+
struct Point {
float offset;
Color color;
@@ -48,6 +54,8 @@ public:
private:
Vector points;
bool is_sorted;
+ InterpolationMode interpolation_mode = GRADIENT_INTERPOLATE_LINEAR;
+
_FORCE_INLINE_ void _update_sorting() {
if (!is_sorted) {
points.sort();
@@ -80,6 +88,13 @@ public:
void set_colors(const Vector &p_colors);
Vector get_colors() const;
+ void set_interpolation_mode(InterpolationMode p_interp_mode);
+ InterpolationMode get_interpolation_mode();
+
+ _FORCE_INLINE_ float cubic_interpolate(float p0, float p1, float p2, float p3, float x) {
+ return p1 + 0.5 * x * (p2 - p0 + x * (2.0 * p0 - 5.0 * p1 + 4.0 * p2 - p3 + x * (3.0 * (p1 - p2) + p3 - p0)));
+ }
+
_FORCE_INLINE_ Color get_color_at_offset(float p_offset) {
if (points.empty()) {
return Color(0, 0, 0, 1);
@@ -123,10 +138,44 @@ public:
}
const Point &pointFirst = points[first];
const Point &pointSecond = points[second];
- return pointFirst.color.linear_interpolate(pointSecond.color, (p_offset - pointFirst.offset) / (pointSecond.offset - pointFirst.offset));
+
+ switch (interpolation_mode) {
+ case GRADIENT_INTERPOLATE_LINEAR: {
+ return pointFirst.color.linear_interpolate(pointSecond.color, (p_offset - pointFirst.offset) / (pointSecond.offset - pointFirst.offset));
+ } break;
+ case GRADIENT_INTERPOLATE_CONSTANT: {
+ return pointFirst.color;
+ } break;
+ case GRADIENT_INTERPOLATE_CUBIC: {
+ int p0 = first - 1;
+ int p3 = second + 1;
+ if (p3 >= points.size()) {
+ p3 = second;
+ }
+ 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 = cubic_interpolate(pointP0.color.r, pointFirst.color.r, pointSecond.color.r, pointP3.color.r, x);
+ float g = cubic_interpolate(pointP0.color.g, pointFirst.color.g, pointSecond.color.g, pointP3.color.g, x);
+ float b = cubic_interpolate(pointP0.color.b, pointFirst.color.b, pointSecond.color.b, pointP3.color.b, x);
+ float a = cubic_interpolate(pointP0.color.a, pointFirst.color.a, pointSecond.color.a, pointP3.color.a, x);
+
+ return Color(r, g, b, a);
+ } break;
+ default: {
+ // Fallback to linear interpolation.
+ return pointFirst.color.linear_interpolate(pointSecond.color, (p_offset - pointFirst.offset) / (pointSecond.offset - pointFirst.offset));
+ }
+ }
}
int get_points_count() const;
};
+VARIANT_ENUM_CAST(Gradient::InterpolationMode);
+
#endif // GRADIENT_H