1/**************************************************************************/
2/* gradient.h */
3/**************************************************************************/
4/* This file is part of: */
5/* GODOT ENGINE */
6/* https://godotengine.org */
7/**************************************************************************/
8/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
9/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
10/* */
11/* Permission is hereby granted, free of charge, to any person obtaining */
12/* a copy of this software and associated documentation files (the */
13/* "Software"), to deal in the Software without restriction, including */
14/* without limitation the rights to use, copy, modify, merge, publish, */
15/* distribute, sublicense, and/or sell copies of the Software, and to */
16/* permit persons to whom the Software is furnished to do so, subject to */
17/* the following conditions: */
18/* */
19/* The above copyright notice and this permission notice shall be */
20/* included in all copies or substantial portions of the Software. */
21/* */
22/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
23/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
24/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
25/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
26/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
27/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
28/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
29/**************************************************************************/
30
31#ifndef GRADIENT_H
32#define GRADIENT_H
33
34#include "core/io/resource.h"
35
36#include "thirdparty/misc/ok_color.h"
37
38class Gradient : public Resource {
39 GDCLASS(Gradient, Resource);
40 OBJ_SAVE_TYPE(Gradient);
41
42public:
43 enum InterpolationMode {
44 GRADIENT_INTERPOLATE_LINEAR,
45 GRADIENT_INTERPOLATE_CONSTANT,
46 GRADIENT_INTERPOLATE_CUBIC,
47 };
48
49 enum ColorSpace {
50 GRADIENT_COLOR_SPACE_SRGB,
51 GRADIENT_COLOR_SPACE_LINEAR_SRGB,
52 GRADIENT_COLOR_SPACE_OKLAB,
53 };
54
55 struct Point {
56 float offset = 0.0;
57 Color color;
58 bool operator<(const Point &p_ponit) const {
59 return offset < p_ponit.offset;
60 }
61 };
62
63private:
64 Vector<Point> points;
65 bool is_sorted = true;
66 InterpolationMode interpolation_mode = GRADIENT_INTERPOLATE_LINEAR;
67 ColorSpace interpolation_color_space = GRADIENT_COLOR_SPACE_SRGB;
68
69 _FORCE_INLINE_ void _update_sorting() {
70 if (!is_sorted) {
71 points.sort();
72 is_sorted = true;
73 }
74 }
75
76 _FORCE_INLINE_ Color transform_color_space(const Color p_color) const {
77 switch (interpolation_color_space) {
78 case GRADIENT_COLOR_SPACE_SRGB:
79 default:
80 return p_color;
81
82 case GRADIENT_COLOR_SPACE_LINEAR_SRGB:
83 return p_color.srgb_to_linear();
84
85 case GRADIENT_COLOR_SPACE_OKLAB:
86 Color linear_color = p_color.srgb_to_linear();
87 ok_color::RGB rgb{};
88 rgb.r = linear_color.r;
89 rgb.g = linear_color.g;
90 rgb.b = linear_color.b;
91
92 ok_color ok_color;
93 ok_color::Lab lab_color = ok_color.linear_srgb_to_oklab(rgb);
94
95 // Constructs an RGB color using the Lab values directly. This allows reusing the interpolation code.
96 return { lab_color.L, lab_color.a, lab_color.b, linear_color.a };
97 }
98 }
99
100 _FORCE_INLINE_ Color inv_transform_color_space(const Color p_color) const {
101 switch (interpolation_color_space) {
102 case GRADIENT_COLOR_SPACE_SRGB:
103 default:
104 return p_color;
105
106 case GRADIENT_COLOR_SPACE_LINEAR_SRGB:
107 return p_color.linear_to_srgb();
108
109 case GRADIENT_COLOR_SPACE_OKLAB:
110 ok_color::Lab lab{};
111 lab.L = p_color.r;
112 lab.a = p_color.g;
113 lab.b = p_color.b;
114
115 ok_color new_ok_color;
116 ok_color::RGB ok_rgb = new_ok_color.oklab_to_linear_srgb(lab);
117 Color linear{ ok_rgb.r, ok_rgb.g, ok_rgb.b, p_color.a };
118 return linear.linear_to_srgb();
119 }
120 }
121
122protected:
123 static void _bind_methods();
124 void _validate_property(PropertyInfo &p_property) const;
125
126public:
127 Gradient();
128 virtual ~Gradient();
129
130 void add_point(float p_offset, const Color &p_color);
131 void remove_point(int p_index);
132 void set_points(const Vector<Point> &p_points);
133 Vector<Point> &get_points();
134 void reverse();
135
136 void set_offset(int pos, const float offset);
137 float get_offset(int pos);
138
139 void set_color(int pos, const Color &color);
140 Color get_color(int pos);
141
142 void set_offsets(const Vector<float> &p_offsets);
143 Vector<float> get_offsets() const;
144
145 void set_colors(const Vector<Color> &p_colors);
146 Vector<Color> get_colors() const;
147
148 void set_interpolation_mode(InterpolationMode p_interp_mode);
149 InterpolationMode get_interpolation_mode();
150
151 void set_interpolation_color_space(Gradient::ColorSpace p_color_space);
152 ColorSpace get_interpolation_color_space();
153
154 _FORCE_INLINE_ Color get_color_at_offset(float p_offset) {
155 if (points.is_empty()) {
156 return Color(0, 0, 0, 1);
157 }
158
159 _update_sorting();
160
161 // Binary search.
162 int low = 0;
163 int high = points.size() - 1;
164 int middle = 0;
165
166#ifdef DEBUG_ENABLED
167 if (low > high) {
168 ERR_PRINT("low > high, this may be a bug");
169 }
170#endif
171
172 while (low <= high) {
173 middle = (low + high) / 2;
174 const Point &point = points[middle];
175 if (point.offset > p_offset) {
176 high = middle - 1; //search low end of array
177 } else if (point.offset < p_offset) {
178 low = middle + 1; //search high end of array
179 } else {
180 return point.color;
181 }
182 }
183
184 // Return sampled value.
185 if (points[middle].offset > p_offset) {
186 middle--;
187 }
188 int first = middle;
189 int second = middle + 1;
190 if (second >= points.size()) {
191 return points[points.size() - 1].color;
192 }
193 if (first < 0) {
194 return points[0].color;
195 }
196 const Point &point1 = points[first];
197 const Point &point2 = points[second];
198 float weight = (p_offset - point1.offset) / (point2.offset - point1.offset);
199
200 switch (interpolation_mode) {
201 case GRADIENT_INTERPOLATE_CONSTANT: {
202 return point1.color;
203 }
204 case GRADIENT_INTERPOLATE_LINEAR:
205 default: { // Fallback to linear interpolation.
206 Color color1 = transform_color_space(point1.color);
207 Color color2 = transform_color_space(point2.color);
208
209 Color interpolated = color1.lerp(color2, weight);
210 return inv_transform_color_space(interpolated);
211 }
212 case GRADIENT_INTERPOLATE_CUBIC: {
213 int p0 = first - 1;
214 int p3 = second + 1;
215 if (p3 >= points.size()) {
216 p3 = second;
217 }
218 if (p0 < 0) {
219 p0 = first;
220 }
221 const Point &point0 = points[p0];
222 const Point &point3 = points[p3];
223
224 Color color0 = transform_color_space(point0.color);
225 Color color1 = transform_color_space(point1.color);
226 Color color2 = transform_color_space(point2.color);
227 Color color3 = transform_color_space(point3.color);
228
229 Color interpolated;
230 interpolated[0] = Math::cubic_interpolate(color1[0], color2[0], color0[0], color3[0], weight);
231 interpolated[1] = Math::cubic_interpolate(color1[1], color2[1], color0[1], color3[1], weight);
232 interpolated[2] = Math::cubic_interpolate(color1[2], color2[2], color0[2], color3[2], weight);
233 interpolated[3] = Math::cubic_interpolate(color1[3], color2[3], color0[3], color3[3], weight);
234
235 return inv_transform_color_space(interpolated);
236 }
237 }
238 }
239
240 int get_point_count() const;
241};
242
243VARIANT_ENUM_CAST(Gradient::InterpolationMode);
244VARIANT_ENUM_CAST(Gradient::ColorSpace);
245
246#endif // GRADIENT_H
247