1
/* GIMP - The GNU Image Manipulation Program
2
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
4
* This program is free software; you can redistribute it and/or modify
5
* it under the terms of the GNU General Public License as published by
6
* the Free Software Foundation; either version 2 of the License, or
7
* (at your option) any later version.
9
* This program is distributed in the hope that it will be useful,
10
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
* GNU General Public License for more details.
14
* You should have received a copy of the GNU General Public License
15
* along with this program; if not, write to the Free Software
16
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
23
#include <glib-object.h>
25
#include "libgimpmath/gimpmath.h"
26
#include "libgimpconfig/gimpconfig.h"
28
#include "core-types.h"
30
#include "gimpcurve.h"
31
#include "gimpcurve-load.h"
32
#include "gimpcurve-save.h"
34
#include "gimp-intl.h"
48
/* local function prototypes */
50
static void gimp_curve_config_iface_init (GimpConfigInterface *iface);
52
static void gimp_curve_finalize (GObject *object);
53
static void gimp_curve_set_property (GObject *object,
57
static void gimp_curve_get_property (GObject *object,
62
static gint64 gimp_curve_get_memsize (GimpObject *object,
65
static void gimp_curve_get_preview_size (GimpViewable *viewable,
71
static gboolean gimp_curve_get_popup_size (GimpViewable *viewable,
77
static TempBuf * gimp_curve_get_new_preview (GimpViewable *viewable,
81
static gchar * gimp_curve_get_description (GimpViewable *viewable,
84
static void gimp_curve_dirty (GimpData *data);
85
static const gchar * gimp_curve_get_extension (GimpData *data);
86
static GimpData * gimp_curve_duplicate (GimpData *data);
88
static gboolean gimp_curve_serialize (GimpConfig *config,
89
GimpConfigWriter *writer,
91
static gboolean gimp_curve_deserialize (GimpConfig *config,
95
static gboolean gimp_curve_equal (GimpConfig *a,
97
static void _gimp_curve_reset (GimpConfig *config);
98
static gboolean gimp_curve_copy (GimpConfig *src,
102
static void gimp_curve_set_n_points (GimpCurve *curve,
104
static void gimp_curve_set_n_samples (GimpCurve *curve,
107
static void gimp_curve_calculate (GimpCurve *curve);
108
static void gimp_curve_plot (GimpCurve *curve,
115
G_DEFINE_TYPE_WITH_CODE (GimpCurve, gimp_curve, GIMP_TYPE_DATA,
116
G_IMPLEMENT_INTERFACE (GIMP_TYPE_CONFIG,
117
gimp_curve_config_iface_init))
119
#define parent_class gimp_curve_parent_class
123
gimp_curve_class_init (GimpCurveClass *klass)
125
GObjectClass *object_class = G_OBJECT_CLASS (klass);
126
GimpObjectClass *gimp_object_class = GIMP_OBJECT_CLASS (klass);
127
GimpViewableClass *viewable_class = GIMP_VIEWABLE_CLASS (klass);
128
GimpDataClass *data_class = GIMP_DATA_CLASS (klass);
129
GParamSpec *array_spec;
131
object_class->finalize = gimp_curve_finalize;
132
object_class->set_property = gimp_curve_set_property;
133
object_class->get_property = gimp_curve_get_property;
135
gimp_object_class->get_memsize = gimp_curve_get_memsize;
137
viewable_class->default_stock_id = "FIXME";
138
viewable_class->get_preview_size = gimp_curve_get_preview_size;
139
viewable_class->get_popup_size = gimp_curve_get_popup_size;
140
viewable_class->get_new_preview = gimp_curve_get_new_preview;
141
viewable_class->get_description = gimp_curve_get_description;
143
data_class->dirty = gimp_curve_dirty;
144
data_class->save = gimp_curve_save;
145
data_class->get_extension = gimp_curve_get_extension;
146
data_class->duplicate = gimp_curve_duplicate;
148
GIMP_CONFIG_INSTALL_PROP_ENUM (object_class, PROP_CURVE_TYPE,
151
GIMP_TYPE_CURVE_TYPE,
152
GIMP_CURVE_SMOOTH, 0);
154
GIMP_CONFIG_INSTALL_PROP_INT (object_class, PROP_N_POINTS,
156
"The number of points",
159
array_spec = g_param_spec_double ("point", NULL, NULL,
160
-1.0, 1.0, 0.0, GIMP_PARAM_READWRITE);
161
g_object_class_install_property (object_class, PROP_POINTS,
162
g_param_spec_value_array ("points",
165
GIMP_PARAM_STATIC_STRINGS |
166
GIMP_CONFIG_PARAM_FLAGS));
168
GIMP_CONFIG_INSTALL_PROP_INT (object_class, PROP_N_SAMPLES,
170
"The number of samples",
173
array_spec = g_param_spec_double ("sample", NULL, NULL,
174
0.0, 1.0, 0.0, GIMP_PARAM_READWRITE);
175
g_object_class_install_property (object_class, PROP_SAMPLES,
176
g_param_spec_value_array ("samples",
179
GIMP_PARAM_STATIC_STRINGS |
180
GIMP_CONFIG_PARAM_FLAGS));
184
gimp_curve_config_iface_init (GimpConfigInterface *iface)
186
iface->serialize = gimp_curve_serialize;
187
iface->deserialize = gimp_curve_deserialize;
188
iface->equal = gimp_curve_equal;
189
iface->reset = _gimp_curve_reset;
190
iface->copy = gimp_curve_copy;
194
gimp_curve_init (GimpCurve *curve)
197
curve->points = NULL;
198
curve->n_samples = 0;
199
curve->samples = NULL;
200
curve->identity = FALSE;
204
gimp_curve_finalize (GObject *object)
206
GimpCurve *curve = GIMP_CURVE (object);
210
g_free (curve->points);
211
curve->points = NULL;
216
g_free (curve->samples);
217
curve->samples = NULL;
220
G_OBJECT_CLASS (parent_class)->finalize (object);
224
gimp_curve_set_property (GObject *object,
229
GimpCurve *curve = GIMP_CURVE (object);
233
case PROP_CURVE_TYPE:
234
gimp_curve_set_curve_type (curve, g_value_get_enum (value));
238
gimp_curve_set_n_points (curve, g_value_get_int (value));
243
GValueArray *array = g_value_get_boxed (value);
249
for (i = 0; i < curve->n_points && i * 2 < array->n_values; i++)
251
GValue *x = g_value_array_get_nth (array, i * 2);
252
GValue *y = g_value_array_get_nth (array, i * 2 + 1);
254
curve->points[i].x = g_value_get_double (x);
255
curve->points[i].y = g_value_get_double (y);
261
gimp_curve_set_n_samples (curve, g_value_get_int (value));
266
GValueArray *array = g_value_get_boxed (value);
272
for (i = 0; i < curve->n_samples && i < array->n_values; i++)
274
GValue *v = g_value_array_get_nth (array, i);
276
curve->samples[i] = g_value_get_double (v);
282
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
288
gimp_curve_get_property (GObject *object,
293
GimpCurve *curve = GIMP_CURVE (object);
297
case PROP_CURVE_TYPE:
298
g_value_set_enum (value, curve->curve_type);
302
g_value_set_int (value, curve->n_points);
307
GValueArray *array = g_value_array_new (curve->n_points * 2);
311
g_value_init (&v, G_TYPE_DOUBLE);
313
for (i = 0; i < curve->n_points; i++)
315
g_value_set_double (&v, curve->points[i].x);
316
g_value_array_append (array, &v);
318
g_value_set_double (&v, curve->points[i].y);
319
g_value_array_append (array, &v);
324
g_value_take_boxed (value, array);
329
g_value_set_int (value, curve->n_samples);
334
GValueArray *array = g_value_array_new (curve->n_samples);
338
g_value_init (&v, G_TYPE_DOUBLE);
340
for (i = 0; i < curve->n_samples; i++)
342
g_value_set_double (&v, curve->samples[i]);
343
g_value_array_append (array, &v);
348
g_value_take_boxed (value, array);
353
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
359
gimp_curve_get_memsize (GimpObject *object,
362
GimpCurve *curve = GIMP_CURVE (object);
365
memsize += curve->n_points * sizeof (GimpVector2);
366
memsize += curve->n_samples * sizeof (gdouble);
368
return memsize + GIMP_OBJECT_CLASS (parent_class)->get_memsize (object,
373
gimp_curve_get_preview_size (GimpViewable *viewable,
376
gboolean dot_for_dot,
385
gimp_curve_get_popup_size (GimpViewable *viewable,
388
gboolean dot_for_dot,
392
*popup_width = width * 2;
393
*popup_height = height * 2;
399
gimp_curve_get_new_preview (GimpViewable *viewable,
400
GimpContext *context,
408
gimp_curve_get_description (GimpViewable *viewable,
411
GimpCurve *curve = GIMP_CURVE (viewable);
413
return g_strdup_printf ("%s", GIMP_OBJECT (curve)->name);
417
gimp_curve_dirty (GimpData *data)
419
GimpCurve *curve = GIMP_CURVE (data);
421
curve->identity = FALSE;
423
gimp_curve_calculate (curve);
425
GIMP_DATA_CLASS (parent_class)->dirty (data);
429
gimp_curve_get_extension (GimpData *data)
431
return GIMP_CURVE_FILE_EXTENSION;
435
gimp_curve_duplicate (GimpData *data)
437
GimpCurve *curve = GIMP_CURVE (data);
440
new = g_object_new (GIMP_TYPE_CURVE,
441
"curve-type", curve->curve_type,
444
return GIMP_DATA (new);
448
gimp_curve_serialize (GimpConfig *config,
449
GimpConfigWriter *writer,
452
return gimp_config_serialize_properties (config, writer);
456
gimp_curve_deserialize (GimpConfig *config,
463
success = gimp_config_deserialize_properties (config, scanner, nest_level);
465
GIMP_CURVE (config)->identity = FALSE;
471
gimp_curve_equal (GimpConfig *a,
474
GimpCurve *a_curve = GIMP_CURVE (a);
475
GimpCurve *b_curve = GIMP_CURVE (b);
477
if (a_curve->curve_type != b_curve->curve_type)
480
if (memcmp (a_curve->points, b_curve->points,
481
sizeof (GimpVector2) * b_curve->n_points) ||
482
memcmp (a_curve->samples, b_curve->samples,
483
sizeof (gdouble) * b_curve->n_samples))
490
_gimp_curve_reset (GimpConfig *config)
492
gimp_curve_reset (GIMP_CURVE (config), TRUE);
496
gimp_curve_copy (GimpConfig *src,
500
GimpCurve *src_curve = GIMP_CURVE (src);
501
GimpCurve *dest_curve = GIMP_CURVE (dest);
503
gimp_config_sync (G_OBJECT (src), G_OBJECT (dest), flags);
505
dest_curve->identity = src_curve->identity;
507
gimp_data_dirty (GIMP_DATA (dest));
513
/* public functions */
516
gimp_curve_new (const gchar *name)
518
g_return_val_if_fail (name != NULL, NULL);
519
g_return_val_if_fail (*name != '\0', NULL);
521
return g_object_new (GIMP_TYPE_CURVE,
527
gimp_curve_get_standard (void)
529
static GimpData *standard_curve = NULL;
531
if (! standard_curve)
533
standard_curve = gimp_curve_new ("Standard");
535
standard_curve->dirty = FALSE;
536
gimp_data_make_internal (standard_curve);
538
g_object_ref (standard_curve);
541
return standard_curve;
545
gimp_curve_reset (GimpCurve *curve,
550
g_return_if_fail (GIMP_IS_CURVE (curve));
552
g_object_freeze_notify (G_OBJECT (curve));
554
for (i = 0; i < curve->n_samples; i++)
555
curve->samples[i] = (gdouble) i / (gdouble) (curve->n_samples - 1);
557
g_object_notify (G_OBJECT (curve), "samples");
559
curve->points[0].x = 0.0;
560
curve->points[0].y = 0.0;
562
for (i = 1; i < curve->n_points - 1; i++)
564
curve->points[i].x = -1.0;
565
curve->points[i].y = -1.0;
568
curve->points[curve->n_points - 1].x = 1.0;
569
curve->points[curve->n_points - 1].y = 1.0;
571
g_object_notify (G_OBJECT (curve), "points");
575
curve->curve_type = GIMP_CURVE_SMOOTH;
576
g_object_notify (G_OBJECT (curve), "curve-type");
579
curve->identity = TRUE;
581
g_object_thaw_notify (G_OBJECT (curve));
583
gimp_data_dirty (GIMP_DATA (curve));
587
gimp_curve_set_curve_type (GimpCurve *curve,
588
GimpCurveType curve_type)
590
g_return_if_fail (GIMP_IS_CURVE (curve));
592
if (curve->curve_type != curve_type)
594
g_object_freeze_notify (G_OBJECT (curve));
596
curve->curve_type = curve_type;
598
if (curve_type == GIMP_CURVE_SMOOTH)
603
for (i = 0; i < curve->n_points; i++)
605
curve->points[i].x = -1;
606
curve->points[i].y = -1;
609
/* pick some points from the curve and make them control
612
n_points = CLAMP (9, curve->n_points / 2, curve->n_points);
614
for (i = 0; i < n_points; i++)
616
gint sample = i * (curve->n_samples - 1) / (n_points - 1);
617
gint point = i * (curve->n_points - 1) / (n_points - 1);
619
curve->points[point].x = ((gdouble) sample /
620
(gdouble) (curve->n_samples - 1));
621
curve->points[point].y = curve->samples[sample];
624
g_object_notify (G_OBJECT (curve), "points");
627
g_object_notify (G_OBJECT (curve), "curve-type");
629
g_object_thaw_notify (G_OBJECT (curve));
631
gimp_data_dirty (GIMP_DATA (curve));
636
gimp_curve_get_curve_type (GimpCurve *curve)
638
g_return_val_if_fail (GIMP_IS_CURVE (curve), GIMP_CURVE_SMOOTH);
640
return curve->curve_type;
644
gimp_curve_set_n_points (GimpCurve *curve,
647
g_return_if_fail (GIMP_IS_CURVE (curve));
649
if (n_points != curve->n_points)
653
g_object_freeze_notify (G_OBJECT (curve));
655
curve->n_points = n_points;
656
g_object_notify (G_OBJECT (curve), "n-points");
658
curve->points = g_renew (GimpVector2, curve->points, curve->n_points);
660
curve->points[0].x = 0.0;
661
curve->points[0].y = 0.0;
663
for (i = 1; i < curve->n_points - 1; i++)
665
curve->points[i].x = -1.0;
666
curve->points[i].y = -1.0;
669
curve->points[curve->n_points - 1].x = 1.0;
670
curve->points[curve->n_points - 1].y = 1.0;
672
g_object_notify (G_OBJECT (curve), "points");
674
if (curve->curve_type == GIMP_CURVE_SMOOTH)
675
curve->identity = TRUE;
677
g_object_thaw_notify (G_OBJECT (curve));
682
gimp_curve_get_n_points (GimpCurve *curve)
684
g_return_val_if_fail (GIMP_IS_CURVE (curve), 0);
686
return curve->n_points;
690
gimp_curve_set_n_samples (GimpCurve *curve,
693
g_return_if_fail (GIMP_IS_CURVE (curve));
695
if (n_samples != curve->n_samples)
699
g_object_freeze_notify (G_OBJECT (curve));
701
curve->n_samples = n_samples;
702
g_object_notify (G_OBJECT (curve), "n-samples");
704
curve->samples = g_renew (gdouble, curve->samples, curve->n_samples);
706
for (i = 0; i < curve->n_samples; i++)
707
curve->samples[i] = (gdouble) i / (gdouble) (curve->n_samples - 1);
709
g_object_notify (G_OBJECT (curve), "samples");
711
if (curve->curve_type == GIMP_CURVE_FREE)
712
curve->identity = TRUE;
714
g_object_thaw_notify (G_OBJECT (curve));
719
gimp_curve_get_n_samples (GimpCurve *curve)
721
g_return_val_if_fail (GIMP_IS_CURVE (curve), 0);
723
return curve->n_samples;
727
gimp_curve_get_closest_point (GimpCurve *curve,
730
gint closest_point = 0;
731
gdouble distance = G_MAXDOUBLE;
734
g_return_val_if_fail (GIMP_IS_CURVE (curve), 0);
736
for (i = 0; i < curve->n_points; i++)
738
if (curve->points[i].x >= 0.0 &&
739
fabs (x - curve->points[i].x) < distance)
741
distance = fabs (x - curve->points[i].x);
746
if (distance > (1.0 / (curve->n_points * 2.0)))
747
closest_point = ROUND (x * (gdouble) (curve->n_points - 1));
749
return closest_point;
753
gimp_curve_set_point (GimpCurve *curve,
758
g_return_if_fail (GIMP_IS_CURVE (curve));
759
g_return_if_fail (point >= 0 && point < curve->n_points);
760
g_return_if_fail (x == -1.0 || (x >= 0 && x <= 1.0));
761
g_return_if_fail (y == -1.0 || (y >= 0 && y <= 1.0));
763
if (curve->curve_type == GIMP_CURVE_FREE)
766
g_object_freeze_notify (G_OBJECT (curve));
768
curve->points[point].x = x;
769
curve->points[point].y = y;
771
g_object_notify (G_OBJECT (curve), "points");
773
g_object_thaw_notify (G_OBJECT (curve));
775
gimp_data_dirty (GIMP_DATA (curve));
779
gimp_curve_move_point (GimpCurve *curve,
783
g_return_if_fail (GIMP_IS_CURVE (curve));
784
g_return_if_fail (point >= 0 && point < curve->n_points);
785
g_return_if_fail (y >= 0 && y <= 1.0);
787
if (curve->curve_type == GIMP_CURVE_FREE)
790
g_object_freeze_notify (G_OBJECT (curve));
792
curve->points[point].y = y;
794
g_object_notify (G_OBJECT (curve), "points");
796
g_object_thaw_notify (G_OBJECT (curve));
798
gimp_data_dirty (GIMP_DATA (curve));
802
gimp_curve_get_point (GimpCurve *curve,
807
g_return_if_fail (GIMP_IS_CURVE (curve));
808
g_return_if_fail (point >= 0 && point < curve->n_points);
810
if (curve->curve_type == GIMP_CURVE_FREE)
813
if (x) *x = curve->points[point].x;
814
if (y) *y = curve->points[point].y;
818
gimp_curve_set_curve (GimpCurve *curve,
822
g_return_if_fail (GIMP_IS_CURVE (curve));
823
g_return_if_fail (x >= 0 && x <= 1.0);
824
g_return_if_fail (y >= 0 && y <= 1.0);
826
if (curve->curve_type == GIMP_CURVE_SMOOTH)
829
g_object_freeze_notify (G_OBJECT (curve));
831
curve->samples[ROUND (x * (gdouble) (curve->n_samples - 1))] = y;
833
g_object_notify (G_OBJECT (curve), "samples");
835
g_object_thaw_notify (G_OBJECT (curve));
837
gimp_data_dirty (GIMP_DATA (curve));
841
* gimp_curve_is_identity:
842
* @curve: a #GimpCurve object
844
* If this function returns %TRUE, then the curve maps each value to
845
* itself. If it returns %FALSE, then this assumption can not be made.
847
* Return value: %TRUE if the curve is an identity mapping, %FALSE otherwise.
850
gimp_curve_is_identity (GimpCurve *curve)
852
g_return_val_if_fail (GIMP_IS_CURVE (curve), FALSE);
854
return curve->identity;
858
gimp_curve_get_uchar (GimpCurve *curve,
864
g_return_if_fail (GIMP_IS_CURVE (curve));
866
#warning: FIXME: support n_samples != curve->n_samples
868
g_return_if_fail (n_samples == curve->n_samples);
869
g_return_if_fail (samples != NULL);
871
for (i = 0; i < curve->n_samples; i++)
872
samples[i] = curve->samples[i] * 255.999;
876
/* private functions */
879
gimp_curve_calculate (GimpCurve *curve)
886
if (GIMP_DATA (curve)->freeze_count > 0)
889
points = g_newa (gint, curve->n_points);
891
switch (curve->curve_type)
893
case GIMP_CURVE_SMOOTH:
894
/* cycle through the curves */
896
for (i = 0; i < curve->n_points; i++)
897
if (curve->points[i].x >= 0.0)
898
points[num_pts++] = i;
900
/* Initialize boundary curve points */
906
point = curve->points[points[0]];
907
boundary = ROUND (point.x * (gdouble) (curve->n_samples - 1));
909
for (i = 0; i < boundary; i++)
910
curve->samples[i] = point.y;
912
point = curve->points[points[num_pts - 1]];
913
boundary = ROUND (point.x * (gdouble) (curve->n_samples - 1));
915
for (i = boundary; i < curve->n_samples; i++)
916
curve->samples[i] = point.y;
919
for (i = 0; i < num_pts - 1; i++)
921
p1 = points[MAX (i - 1, 0)];
924
p4 = points[MIN (i + 2, num_pts - 1)];
926
gimp_curve_plot (curve, p1, p2, p3, p4);
929
/* ensure that the control points are used exactly */
930
for (i = 0; i < num_pts; i++)
932
gdouble x = curve->points[points[i]].x;
933
gdouble y = curve->points[points[i]].y;
935
curve->samples[ROUND (x * (gdouble) (curve->n_samples - 1))] = y;
938
g_object_notify (G_OBJECT (curve), "samples");
941
case GIMP_CURVE_FREE:
947
* This function calculates the curve values between the control points
948
* p2 and p3, taking the potentially existing neighbors p1 and p4 into
951
* This function uses a cubic bezier curve for the individual segments and
952
* calculates the necessary intermediate control points depending on the
953
* neighbor curve control points.
956
gimp_curve_plot (GimpCurve *curve,
964
gdouble y0, y1, y2, y3;
968
/* the outer control points for the bezier curve. */
969
x0 = curve->points[p2].x;
970
y0 = curve->points[p2].y;
971
x3 = curve->points[p3].x;
972
y3 = curve->points[p3].y;
975
* the x values of the inner control points are fixed at
976
* x1 = 2/3*x0 + 1/3*x3 and x2 = 1/3*x0 + 2/3*x3
977
* this ensures that the x values increase linearily with the
978
* parameter t and enables us to skip the calculation of the x
979
* values altogehter - just calculate y(t) evenly spaced.
985
g_return_if_fail (dx > 0);
987
if (p1 == p2 && p3 == p4)
989
/* No information about the neighbors,
990
* calculate y1 and y2 to get a straight line
993
y2 = y0 + dy * 2.0 / 3.0;
995
else if (p1 == p2 && p3 != p4)
997
/* only the right neighbor is available. Make the tangent at the
998
* right endpoint parallel to the line between the left endpoint
999
* and the right neighbor. Then point the tangent at the left towards
1000
* the control handle of the right tangent, to ensure that the curve
1001
* does not have an inflection point.
1003
slope = (curve->points[p4].y - y0) / (curve->points[p4].x - x0);
1005
y2 = y3 - slope * dx / 3.0;
1006
y1 = y0 + (y2 - y0) / 2.0;
1008
else if (p1 != p2 && p3 == p4)
1010
/* see previous case */
1011
slope = (y3 - curve->points[p1].y) / (x3 - curve->points[p1].x);
1013
y1 = y0 + slope * dx / 3.0;
1014
y2 = y3 + (y1 - y3) / 2.0;
1016
else /* (p1 != p2 && p3 != p4) */
1018
/* Both neighbors are available. Make the tangents at the endpoints
1019
* parallel to the line between the opposite endpoint and the adjacent
1022
slope = (y3 - curve->points[p1].y) / (x3 - curve->points[p1].x);
1024
y1 = y0 + slope * dx / 3.0;
1026
slope = (curve->points[p4].y - y0) / (curve->points[p4].x - x0);
1028
y2 = y3 - slope * dx / 3.0;
1032
* finally calculate the y(t) values for the given bezier values. We can
1033
* use homogenously distributed values for t, since x(t) increases linearily.
1035
for (i = 0; i <= ROUND (dx * (gdouble) (curve->n_samples - 1)); i++)
1040
t = i / dx / (gdouble) (curve->n_samples - 1);
1041
y = y0 * (1-t) * (1-t) * (1-t) +
1042
3 * y1 * (1-t) * (1-t) * t +
1043
3 * y2 * (1-t) * t * t +
1046
index = i + ROUND (x0 * (gdouble) (curve->n_samples - 1));
1048
if (index < curve->n_samples)
1049
curve->samples[index] = CLAMP (y, 0.0, 1.0);