1 /* GIMP - The GNU Image Manipulation Program
2 * Copyright (C) 1995 Spencer Kimball and Peter Mattis
3 *
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 3 of the License, or
7 * (at your option) any later version.
8 *
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.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <https://www.gnu.org/licenses/>.
16 */
17
18 #include "config.h"
19
20 #include <stdlib.h>
21 #include <string.h> /* memcmp */
22
23 #include <gdk-pixbuf/gdk-pixbuf.h>
24 #include <gegl.h>
25
26 #include "libgimpbase/gimpbase.h"
27 #include "libgimpmath/gimpmath.h"
28 #include "libgimpconfig/gimpconfig.h"
29
30 #include "core-types.h"
31
32 #include "gimpcurve.h"
33 #include "gimpcurve-load.h"
34 #include "gimpcurve-save.h"
35 #include "gimpparamspecs.h"
36
37 #include "gimp-intl.h"
38
39
40 #define EPSILON 1e-6
41
42
43 enum
44 {
45 PROP_0,
46 PROP_CURVE_TYPE,
47 PROP_N_POINTS,
48 PROP_POINTS,
49 PROP_POINT_TYPES,
50 PROP_N_SAMPLES,
51 PROP_SAMPLES
52 };
53
54
55 /* local function prototypes */
56
57 static void gimp_curve_config_iface_init (GimpConfigInterface *iface);
58
59 static void gimp_curve_finalize (GObject *object);
60 static void gimp_curve_set_property (GObject *object,
61 guint property_id,
62 const GValue *value,
63 GParamSpec *pspec);
64 static void gimp_curve_get_property (GObject *object,
65 guint property_id,
66 GValue *value,
67 GParamSpec *pspec);
68
69 static gint64 gimp_curve_get_memsize (GimpObject *object,
70 gint64 *gui_size);
71
72 static void gimp_curve_get_preview_size (GimpViewable *viewable,
73 gint size,
74 gboolean popup,
75 gboolean dot_for_dot,
76 gint *width,
77 gint *height);
78 static gboolean gimp_curve_get_popup_size (GimpViewable *viewable,
79 gint width,
80 gint height,
81 gboolean dot_for_dot,
82 gint *popup_width,
83 gint *popup_height);
84 static GimpTempBuf * gimp_curve_get_new_preview (GimpViewable *viewable,
85 GimpContext *context,
86 gint width,
87 gint height);
88 static gchar * gimp_curve_get_description (GimpViewable *viewable,
89 gchar **tooltip);
90
91 static void gimp_curve_dirty (GimpData *data);
92 static const gchar * gimp_curve_get_extension (GimpData *data);
93 static void gimp_curve_data_copy (GimpData *data,
94 GimpData *src_data);
95
96 static gboolean gimp_curve_serialize (GimpConfig *config,
97 GimpConfigWriter *writer,
98 gpointer data);
99 static gboolean gimp_curve_deserialize (GimpConfig *config,
100 GScanner *scanner,
101 gint nest_level,
102 gpointer data);
103 static gboolean gimp_curve_equal (GimpConfig *a,
104 GimpConfig *b);
105 static void _gimp_curve_reset (GimpConfig *config);
106 static gboolean gimp_curve_config_copy (GimpConfig *src,
107 GimpConfig *dest,
108 GParamFlags flags);
109
110 static void gimp_curve_calculate (GimpCurve *curve);
111 static void gimp_curve_plot (GimpCurve *curve,
112 gint p1,
113 gint p2,
114 gint p3,
115 gint p4);
116
117
G_DEFINE_TYPE_WITH_CODE(GimpCurve,gimp_curve,GIMP_TYPE_DATA,G_IMPLEMENT_INTERFACE (GIMP_TYPE_CONFIG,gimp_curve_config_iface_init))118 G_DEFINE_TYPE_WITH_CODE (GimpCurve, gimp_curve, GIMP_TYPE_DATA,
119 G_IMPLEMENT_INTERFACE (GIMP_TYPE_CONFIG,
120 gimp_curve_config_iface_init))
121
122 #define parent_class gimp_curve_parent_class
123
124
125 static void
126 gimp_curve_class_init (GimpCurveClass *klass)
127 {
128 GObjectClass *object_class = G_OBJECT_CLASS (klass);
129 GimpObjectClass *gimp_object_class = GIMP_OBJECT_CLASS (klass);
130 GimpViewableClass *viewable_class = GIMP_VIEWABLE_CLASS (klass);
131 GimpDataClass *data_class = GIMP_DATA_CLASS (klass);
132 GParamSpec *array_spec;
133
134 object_class->finalize = gimp_curve_finalize;
135 object_class->set_property = gimp_curve_set_property;
136 object_class->get_property = gimp_curve_get_property;
137
138 gimp_object_class->get_memsize = gimp_curve_get_memsize;
139
140 viewable_class->default_icon_name = "FIXME icon name";
141 viewable_class->get_preview_size = gimp_curve_get_preview_size;
142 viewable_class->get_popup_size = gimp_curve_get_popup_size;
143 viewable_class->get_new_preview = gimp_curve_get_new_preview;
144 viewable_class->get_description = gimp_curve_get_description;
145
146 data_class->dirty = gimp_curve_dirty;
147 data_class->save = gimp_curve_save;
148 data_class->get_extension = gimp_curve_get_extension;
149 data_class->copy = gimp_curve_data_copy;
150
151 GIMP_CONFIG_PROP_ENUM (object_class, PROP_CURVE_TYPE,
152 "curve-type",
153 "Curve Type",
154 "The curve type",
155 GIMP_TYPE_CURVE_TYPE,
156 GIMP_CURVE_SMOOTH, 0);
157
158 GIMP_CONFIG_PROP_INT (object_class, PROP_N_POINTS,
159 "n-points",
160 "Number of Points",
161 "The number of points",
162 0, G_MAXINT, 0,
163 /* for backward compatibility */
164 GIMP_CONFIG_PARAM_IGNORE);
165
166 array_spec = g_param_spec_double ("point", NULL, NULL,
167 -1.0, 1.0, 0.0, GIMP_PARAM_READWRITE);
168 g_object_class_install_property (object_class, PROP_POINTS,
169 gimp_param_spec_value_array ("points",
170 NULL, NULL,
171 array_spec,
172 GIMP_PARAM_STATIC_STRINGS |
173 GIMP_CONFIG_PARAM_FLAGS));
174
175 array_spec = g_param_spec_enum ("point-type", NULL, NULL,
176 GIMP_TYPE_CURVE_POINT_TYPE,
177 GIMP_CURVE_POINT_SMOOTH,
178 GIMP_PARAM_READWRITE);
179 g_object_class_install_property (object_class, PROP_POINT_TYPES,
180 gimp_param_spec_value_array ("point-types",
181 NULL, NULL,
182 array_spec,
183 GIMP_PARAM_STATIC_STRINGS |
184 GIMP_CONFIG_PARAM_FLAGS));
185
186 GIMP_CONFIG_PROP_INT (object_class, PROP_N_SAMPLES,
187 "n-samples",
188 "Number of Samples",
189 "The number of samples",
190 256, 256, 256, 0);
191
192 array_spec = g_param_spec_double ("sample", NULL, NULL,
193 0.0, 1.0, 0.0, GIMP_PARAM_READWRITE);
194 g_object_class_install_property (object_class, PROP_SAMPLES,
195 gimp_param_spec_value_array ("samples",
196 NULL, NULL,
197 array_spec,
198 GIMP_PARAM_STATIC_STRINGS |
199 GIMP_CONFIG_PARAM_FLAGS));
200 }
201
202 static void
gimp_curve_config_iface_init(GimpConfigInterface * iface)203 gimp_curve_config_iface_init (GimpConfigInterface *iface)
204 {
205 iface->serialize = gimp_curve_serialize;
206 iface->deserialize = gimp_curve_deserialize;
207 iface->equal = gimp_curve_equal;
208 iface->reset = _gimp_curve_reset;
209 iface->copy = gimp_curve_config_copy;
210 }
211
212 static void
gimp_curve_init(GimpCurve * curve)213 gimp_curve_init (GimpCurve *curve)
214 {
215 curve->n_points = 0;
216 curve->points = NULL;
217 curve->n_samples = 0;
218 curve->samples = NULL;
219 curve->identity = FALSE;
220 }
221
222 static void
gimp_curve_finalize(GObject * object)223 gimp_curve_finalize (GObject *object)
224 {
225 GimpCurve *curve = GIMP_CURVE (object);
226
227 g_clear_pointer (&curve->points, g_free);
228 curve->n_points = 0;
229
230 g_clear_pointer (&curve->samples, g_free);
231 curve->n_samples = 0;
232
233 G_OBJECT_CLASS (parent_class)->finalize (object);
234 }
235
236 static void
gimp_curve_set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * pspec)237 gimp_curve_set_property (GObject *object,
238 guint property_id,
239 const GValue *value,
240 GParamSpec *pspec)
241 {
242 GimpCurve *curve = GIMP_CURVE (object);
243
244 switch (property_id)
245 {
246 case PROP_CURVE_TYPE:
247 gimp_curve_set_curve_type (curve, g_value_get_enum (value));
248 break;
249
250 case PROP_N_POINTS:
251 /* ignored */
252 break;
253
254 case PROP_POINTS:
255 {
256 GimpValueArray *array = g_value_get_boxed (value);
257 GimpCurvePoint *points;
258 gint length;
259 gint n_points;
260 gint i;
261
262 if (! array)
263 {
264 gimp_curve_clear_points (curve);
265
266 break;
267 }
268
269 length = gimp_value_array_length (array) / 2;
270
271 n_points = 0;
272 points = g_new0 (GimpCurvePoint, length);
273
274 for (i = 0; i < length; i++)
275 {
276 GValue *x = gimp_value_array_index (array, i * 2);
277 GValue *y = gimp_value_array_index (array, i * 2 + 1);
278
279 /* for backward compatibility */
280 if (g_value_get_double (x) < 0.0)
281 continue;
282
283 points[n_points].x = CLAMP (g_value_get_double (x), 0.0, 1.0);
284 points[n_points].y = CLAMP (g_value_get_double (y), 0.0, 1.0);
285
286 if (n_points > 0)
287 {
288 points[n_points].x = MAX (points[n_points].x,
289 points[n_points - 1].x);
290 }
291
292 if (n_points < curve->n_points)
293 points[n_points].type = curve->points[n_points].type;
294 else
295 points[n_points].type = GIMP_CURVE_POINT_SMOOTH;
296
297 n_points++;
298 }
299
300 g_free (curve->points);
301
302 curve->n_points = n_points;
303 curve->points = points;
304
305 g_object_notify (object, "n-points");
306 g_object_notify (object, "point-types");
307 }
308 break;
309
310 case PROP_POINT_TYPES:
311 {
312 GimpValueArray *array = g_value_get_boxed (value);
313 GimpCurvePoint *points;
314 gint length;
315 gdouble x = 0.0;
316 gdouble y = 0.0;
317 gint i;
318
319 if (! array)
320 {
321 gimp_curve_clear_points (curve);
322
323 break;
324 }
325
326 length = gimp_value_array_length (array);
327
328 points = g_new0 (GimpCurvePoint, length);
329
330 for (i = 0; i < length; i++)
331 {
332 GValue *type = gimp_value_array_index (array, i);
333
334 points[i].type = g_value_get_enum (type);
335
336 if (i < curve->n_points)
337 {
338 x = curve->points[i].x;
339 y = curve->points[i].y;
340 }
341
342 points[i].x = x;
343 points[i].y = y;
344 }
345
346 g_free (curve->points);
347
348 curve->n_points = length;
349 curve->points = points;
350
351 g_object_notify (object, "n-points");
352 g_object_notify (object, "points");
353 }
354 break;
355
356 case PROP_N_SAMPLES:
357 gimp_curve_set_n_samples (curve, g_value_get_int (value));
358 break;
359
360 case PROP_SAMPLES:
361 {
362 GimpValueArray *array = g_value_get_boxed (value);
363 gint length;
364 gint i;
365
366 if (! array)
367 break;
368
369 length = gimp_value_array_length (array);
370
371 for (i = 0; i < curve->n_samples && i < length; i++)
372 {
373 GValue *v = gimp_value_array_index (array, i);
374
375 curve->samples[i] = CLAMP (g_value_get_double (v), 0.0, 1.0);
376 }
377 }
378 break;
379
380 default:
381 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
382 break;
383 }
384 }
385
386 static void
gimp_curve_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)387 gimp_curve_get_property (GObject *object,
388 guint property_id,
389 GValue *value,
390 GParamSpec *pspec)
391 {
392 GimpCurve *curve = GIMP_CURVE (object);
393
394 switch (property_id)
395 {
396 case PROP_CURVE_TYPE:
397 g_value_set_enum (value, curve->curve_type);
398 break;
399
400 case PROP_N_POINTS:
401 g_value_set_int (value, curve->n_points);
402 break;
403
404 case PROP_POINTS:
405 {
406 GimpValueArray *array = gimp_value_array_new (curve->n_points * 2);
407 GValue v = G_VALUE_INIT;
408 gint i;
409
410 g_value_init (&v, G_TYPE_DOUBLE);
411
412 for (i = 0; i < curve->n_points; i++)
413 {
414 g_value_set_double (&v, curve->points[i].x);
415 gimp_value_array_append (array, &v);
416
417 g_value_set_double (&v, curve->points[i].y);
418 gimp_value_array_append (array, &v);
419 }
420
421 g_value_unset (&v);
422
423 g_value_take_boxed (value, array);
424 }
425 break;
426
427 case PROP_POINT_TYPES:
428 {
429 GimpValueArray *array = gimp_value_array_new (curve->n_points);
430 GValue v = G_VALUE_INIT;
431 gint i;
432
433 g_value_init (&v, GIMP_TYPE_CURVE_POINT_TYPE);
434
435 for (i = 0; i < curve->n_points; i++)
436 {
437 g_value_set_enum (&v, curve->points[i].type);
438 gimp_value_array_append (array, &v);
439 }
440
441 g_value_unset (&v);
442
443 g_value_take_boxed (value, array);
444 }
445 break;
446
447 case PROP_N_SAMPLES:
448 g_value_set_int (value, curve->n_samples);
449 break;
450
451 case PROP_SAMPLES:
452 {
453 GimpValueArray *array = gimp_value_array_new (curve->n_samples);
454 GValue v = G_VALUE_INIT;
455 gint i;
456
457 g_value_init (&v, G_TYPE_DOUBLE);
458
459 for (i = 0; i < curve->n_samples; i++)
460 {
461 g_value_set_double (&v, curve->samples[i]);
462 gimp_value_array_append (array, &v);
463 }
464
465 g_value_unset (&v);
466
467 g_value_take_boxed (value, array);
468 }
469 break;
470
471 default:
472 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
473 break;
474 }
475 }
476
477 static gint64
gimp_curve_get_memsize(GimpObject * object,gint64 * gui_size)478 gimp_curve_get_memsize (GimpObject *object,
479 gint64 *gui_size)
480 {
481 GimpCurve *curve = GIMP_CURVE (object);
482 gint64 memsize = 0;
483
484 memsize += curve->n_points * sizeof (GimpCurvePoint);
485 memsize += curve->n_samples * sizeof (gdouble);
486
487 return memsize + GIMP_OBJECT_CLASS (parent_class)->get_memsize (object,
488 gui_size);
489 }
490
491 static void
gimp_curve_get_preview_size(GimpViewable * viewable,gint size,gboolean popup,gboolean dot_for_dot,gint * width,gint * height)492 gimp_curve_get_preview_size (GimpViewable *viewable,
493 gint size,
494 gboolean popup,
495 gboolean dot_for_dot,
496 gint *width,
497 gint *height)
498 {
499 *width = size;
500 *height = size;
501 }
502
503 static gboolean
gimp_curve_get_popup_size(GimpViewable * viewable,gint width,gint height,gboolean dot_for_dot,gint * popup_width,gint * popup_height)504 gimp_curve_get_popup_size (GimpViewable *viewable,
505 gint width,
506 gint height,
507 gboolean dot_for_dot,
508 gint *popup_width,
509 gint *popup_height)
510 {
511 *popup_width = width * 2;
512 *popup_height = height * 2;
513
514 return TRUE;
515 }
516
517 static GimpTempBuf *
gimp_curve_get_new_preview(GimpViewable * viewable,GimpContext * context,gint width,gint height)518 gimp_curve_get_new_preview (GimpViewable *viewable,
519 GimpContext *context,
520 gint width,
521 gint height)
522 {
523 return NULL;
524 }
525
526 static gchar *
gimp_curve_get_description(GimpViewable * viewable,gchar ** tooltip)527 gimp_curve_get_description (GimpViewable *viewable,
528 gchar **tooltip)
529 {
530 GimpCurve *curve = GIMP_CURVE (viewable);
531
532 return g_strdup_printf ("%s", gimp_object_get_name (curve));
533 }
534
535 static void
gimp_curve_dirty(GimpData * data)536 gimp_curve_dirty (GimpData *data)
537 {
538 GimpCurve *curve = GIMP_CURVE (data);
539
540 curve->identity = FALSE;
541
542 gimp_curve_calculate (curve);
543
544 GIMP_DATA_CLASS (parent_class)->dirty (data);
545 }
546
547 static const gchar *
gimp_curve_get_extension(GimpData * data)548 gimp_curve_get_extension (GimpData *data)
549 {
550 return GIMP_CURVE_FILE_EXTENSION;
551 }
552
553 static void
gimp_curve_data_copy(GimpData * data,GimpData * src_data)554 gimp_curve_data_copy (GimpData *data,
555 GimpData *src_data)
556 {
557 gimp_data_freeze (data);
558
559 gimp_config_copy (GIMP_CONFIG (src_data),
560 GIMP_CONFIG (data), 0);
561
562 gimp_data_thaw (data);
563 }
564
565 static gboolean
gimp_curve_serialize(GimpConfig * config,GimpConfigWriter * writer,gpointer data)566 gimp_curve_serialize (GimpConfig *config,
567 GimpConfigWriter *writer,
568 gpointer data)
569 {
570 return gimp_config_serialize_properties (config, writer);
571 }
572
573 static gboolean
gimp_curve_deserialize(GimpConfig * config,GScanner * scanner,gint nest_level,gpointer data)574 gimp_curve_deserialize (GimpConfig *config,
575 GScanner *scanner,
576 gint nest_level,
577 gpointer data)
578 {
579 gboolean success;
580
581 success = gimp_config_deserialize_properties (config, scanner, nest_level);
582
583 GIMP_CURVE (config)->identity = FALSE;
584
585 return success;
586 }
587
588 static gboolean
gimp_curve_equal(GimpConfig * a,GimpConfig * b)589 gimp_curve_equal (GimpConfig *a,
590 GimpConfig *b)
591 {
592 GimpCurve *a_curve = GIMP_CURVE (a);
593 GimpCurve *b_curve = GIMP_CURVE (b);
594
595 if (a_curve->curve_type != b_curve->curve_type)
596 return FALSE;
597
598 if (a_curve->n_points != b_curve->n_points ||
599 memcmp (a_curve->points, b_curve->points,
600 sizeof (GimpCurvePoint) * a_curve->n_points))
601 {
602 return FALSE;
603 }
604
605 if (a_curve->n_samples != b_curve->n_samples ||
606 memcmp (a_curve->samples, b_curve->samples,
607 sizeof (gdouble) * a_curve->n_samples))
608 {
609 return FALSE;
610 }
611
612 return TRUE;
613 }
614
615 static void
_gimp_curve_reset(GimpConfig * config)616 _gimp_curve_reset (GimpConfig *config)
617 {
618 gimp_curve_reset (GIMP_CURVE (config), TRUE);
619 }
620
621 static gboolean
gimp_curve_config_copy(GimpConfig * src,GimpConfig * dest,GParamFlags flags)622 gimp_curve_config_copy (GimpConfig *src,
623 GimpConfig *dest,
624 GParamFlags flags)
625 {
626 GimpCurve *src_curve = GIMP_CURVE (src);
627 GimpCurve *dest_curve = GIMP_CURVE (dest);
628
629 /* make sure the curve type is copied *before* the points, so that we don't
630 * overwrite the copied points when changing the type
631 */
632 dest_curve->curve_type = src_curve->curve_type;
633
634 gimp_config_sync (G_OBJECT (src), G_OBJECT (dest), flags);
635
636 dest_curve->identity = src_curve->identity;
637
638 gimp_data_dirty (GIMP_DATA (dest));
639
640 return TRUE;
641 }
642
643
644 /* public functions */
645
646 GimpData *
gimp_curve_new(const gchar * name)647 gimp_curve_new (const gchar *name)
648 {
649 g_return_val_if_fail (name != NULL, NULL);
650 g_return_val_if_fail (*name != '\0', NULL);
651
652 return g_object_new (GIMP_TYPE_CURVE,
653 "name", name,
654 NULL);
655 }
656
657 GimpData *
gimp_curve_get_standard(void)658 gimp_curve_get_standard (void)
659 {
660 static GimpData *standard_curve = NULL;
661
662 if (! standard_curve)
663 {
664 standard_curve = gimp_curve_new ("Standard");
665
666 gimp_data_clean (standard_curve);
667 gimp_data_make_internal (standard_curve,
668 "gimp-curve-standard");
669
670 g_object_ref (standard_curve);
671 }
672
673 return standard_curve;
674 }
675
676 void
gimp_curve_reset(GimpCurve * curve,gboolean reset_type)677 gimp_curve_reset (GimpCurve *curve,
678 gboolean reset_type)
679 {
680 gint i;
681
682 g_return_if_fail (GIMP_IS_CURVE (curve));
683
684 g_object_freeze_notify (G_OBJECT (curve));
685
686 for (i = 0; i < curve->n_samples; i++)
687 curve->samples[i] = (gdouble) i / (gdouble) (curve->n_samples - 1);
688
689 g_object_notify (G_OBJECT (curve), "samples");
690
691 g_free (curve->points);
692
693 curve->n_points = 2;
694 curve->points = g_new0 (GimpCurvePoint, 2);
695
696 curve->points[0].x = 0.0;
697 curve->points[0].y = 0.0;
698 curve->points[0].type = GIMP_CURVE_POINT_SMOOTH;
699
700 curve->points[1].x = 1.0;
701 curve->points[1].y = 1.0;
702 curve->points[1].type = GIMP_CURVE_POINT_SMOOTH;
703
704 g_object_notify (G_OBJECT (curve), "n-points");
705 g_object_notify (G_OBJECT (curve), "points");
706 g_object_notify (G_OBJECT (curve), "point-types");
707
708 if (reset_type)
709 {
710 curve->curve_type = GIMP_CURVE_SMOOTH;
711 g_object_notify (G_OBJECT (curve), "curve-type");
712 }
713
714 curve->identity = TRUE;
715
716 g_object_thaw_notify (G_OBJECT (curve));
717
718 gimp_data_dirty (GIMP_DATA (curve));
719 }
720
721 void
gimp_curve_set_curve_type(GimpCurve * curve,GimpCurveType curve_type)722 gimp_curve_set_curve_type (GimpCurve *curve,
723 GimpCurveType curve_type)
724 {
725 g_return_if_fail (GIMP_IS_CURVE (curve));
726
727 if (curve->curve_type != curve_type)
728 {
729 gimp_data_freeze (GIMP_DATA (curve));
730
731 g_object_freeze_notify (G_OBJECT (curve));
732
733 curve->curve_type = curve_type;
734
735 if (curve_type == GIMP_CURVE_SMOOTH)
736 {
737 gint i;
738
739 g_free (curve->points);
740
741 /* pick some points from the curve and make them control
742 * points
743 */
744 curve->n_points = 9;
745 curve->points = g_new0 (GimpCurvePoint, 9);
746
747 for (i = 0; i < curve->n_points; i++)
748 {
749 gint sample = i * (curve->n_samples - 1) / (curve->n_points - 1);
750
751 curve->points[i].x = (gdouble) sample /
752 (gdouble) (curve->n_samples - 1);
753 curve->points[i].y = curve->samples[sample];
754 curve->points[i].type = GIMP_CURVE_POINT_SMOOTH;
755 }
756
757 g_object_notify (G_OBJECT (curve), "n-points");
758 g_object_notify (G_OBJECT (curve), "points");
759 g_object_notify (G_OBJECT (curve), "point-types");
760 }
761 else
762 {
763 gimp_curve_clear_points (curve);
764 }
765
766 g_object_notify (G_OBJECT (curve), "curve-type");
767
768 g_object_thaw_notify (G_OBJECT (curve));
769
770 gimp_data_thaw (GIMP_DATA (curve));
771 }
772 }
773
774 GimpCurveType
gimp_curve_get_curve_type(GimpCurve * curve)775 gimp_curve_get_curve_type (GimpCurve *curve)
776 {
777 g_return_val_if_fail (GIMP_IS_CURVE (curve), GIMP_CURVE_SMOOTH);
778
779 return curve->curve_type;
780 }
781
782 gint
gimp_curve_get_n_points(GimpCurve * curve)783 gimp_curve_get_n_points (GimpCurve *curve)
784 {
785 g_return_val_if_fail (GIMP_IS_CURVE (curve), 0);
786
787 return curve->n_points;
788 }
789
790 void
gimp_curve_set_n_samples(GimpCurve * curve,gint n_samples)791 gimp_curve_set_n_samples (GimpCurve *curve,
792 gint n_samples)
793 {
794 g_return_if_fail (GIMP_IS_CURVE (curve));
795 g_return_if_fail (n_samples >= 256);
796 g_return_if_fail (n_samples <= 4096);
797
798 if (n_samples != curve->n_samples)
799 {
800 gint i;
801
802 g_object_freeze_notify (G_OBJECT (curve));
803
804 curve->n_samples = n_samples;
805 g_object_notify (G_OBJECT (curve), "n-samples");
806
807 curve->samples = g_renew (gdouble, curve->samples, curve->n_samples);
808
809 for (i = 0; i < curve->n_samples; i++)
810 curve->samples[i] = (gdouble) i / (gdouble) (curve->n_samples - 1);
811
812 g_object_notify (G_OBJECT (curve), "samples");
813
814 if (curve->curve_type == GIMP_CURVE_FREE)
815 curve->identity = TRUE;
816
817 g_object_thaw_notify (G_OBJECT (curve));
818 }
819 }
820
821 gint
gimp_curve_get_n_samples(GimpCurve * curve)822 gimp_curve_get_n_samples (GimpCurve *curve)
823 {
824 g_return_val_if_fail (GIMP_IS_CURVE (curve), 0);
825
826 return curve->n_samples;
827 }
828
829 gint
gimp_curve_get_point_at(GimpCurve * curve,gdouble x)830 gimp_curve_get_point_at (GimpCurve *curve,
831 gdouble x)
832 {
833 gint closest_point = -1;
834 gdouble distance = EPSILON;
835 gint i;
836
837 g_return_val_if_fail (GIMP_IS_CURVE (curve), -1);
838
839 for (i = 0; i < curve->n_points; i++)
840 {
841 gdouble point_distance;
842
843 point_distance = fabs (x - curve->points[i].x);
844
845 if (point_distance <= distance)
846 {
847 closest_point = i;
848 distance = point_distance;
849 }
850 }
851
852 return closest_point;
853 }
854
855 gint
gimp_curve_get_closest_point(GimpCurve * curve,gdouble x,gdouble y,gdouble max_distance)856 gimp_curve_get_closest_point (GimpCurve *curve,
857 gdouble x,
858 gdouble y,
859 gdouble max_distance)
860 {
861 gint closest_point = -1;
862 gdouble distance2 = G_MAXDOUBLE;
863 gint i;
864
865 g_return_val_if_fail (GIMP_IS_CURVE (curve), -1);
866
867 if (max_distance >= 0.0)
868 distance2 = SQR (max_distance);
869
870 for (i = curve->n_points - 1; i >= 0; i--)
871 {
872 gdouble point_distance2;
873
874 point_distance2 = SQR (x - curve->points[i].x) +
875 SQR (y - curve->points[i].y);
876
877 if (point_distance2 <= distance2)
878 {
879 closest_point = i;
880 distance2 = point_distance2;
881 }
882 }
883
884 return closest_point;
885 }
886
887 gint
gimp_curve_add_point(GimpCurve * curve,gdouble x,gdouble y)888 gimp_curve_add_point (GimpCurve *curve,
889 gdouble x,
890 gdouble y)
891 {
892 GimpCurvePoint *points;
893 gint point;
894
895 g_return_val_if_fail (GIMP_IS_CURVE (curve), -1);
896
897 if (curve->curve_type == GIMP_CURVE_FREE)
898 return -1;
899
900 x = CLAMP (x, 0.0, 1.0);
901 y = CLAMP (y, 0.0, 1.0);
902
903 for (point = 0; point < curve->n_points; point++)
904 {
905 if (curve->points[point].x > x)
906 break;
907 }
908
909 points = g_new0 (GimpCurvePoint, curve->n_points + 1);
910
911 memcpy (points, curve->points,
912 point * sizeof (GimpCurvePoint));
913 memcpy (points + point + 1, curve->points + point,
914 (curve->n_points - point) * sizeof (GimpCurvePoint));
915
916 points[point].x = x;
917 points[point].y = y;
918 points[point].type = GIMP_CURVE_POINT_SMOOTH;
919
920 g_free (curve->points);
921
922 curve->n_points++;
923 curve->points = points;
924
925 g_object_notify (G_OBJECT (curve), "n-points");
926 g_object_notify (G_OBJECT (curve), "points");
927 g_object_notify (G_OBJECT (curve), "point-types");
928
929 gimp_data_dirty (GIMP_DATA (curve));
930
931 return point;
932 }
933
934 void
gimp_curve_delete_point(GimpCurve * curve,gint point)935 gimp_curve_delete_point (GimpCurve *curve,
936 gint point)
937 {
938 GimpCurvePoint *points;
939
940 g_return_if_fail (GIMP_IS_CURVE (curve));
941 g_return_if_fail (point >= 0 && point < curve->n_points);
942
943 points = g_new0 (GimpCurvePoint, curve->n_points - 1);
944
945 memcpy (points, curve->points,
946 point * sizeof (GimpCurvePoint));
947 memcpy (points + point, curve->points + point + 1,
948 (curve->n_points - point - 1) * sizeof (GimpCurvePoint));
949
950 g_free (curve->points);
951
952 curve->n_points--;
953 curve->points = points;
954
955 g_object_notify (G_OBJECT (curve), "n-points");
956 g_object_notify (G_OBJECT (curve), "points");
957 g_object_notify (G_OBJECT (curve), "point-types");
958
959 gimp_data_dirty (GIMP_DATA (curve));
960 }
961
962 void
gimp_curve_set_point(GimpCurve * curve,gint point,gdouble x,gdouble y)963 gimp_curve_set_point (GimpCurve *curve,
964 gint point,
965 gdouble x,
966 gdouble y)
967 {
968 g_return_if_fail (GIMP_IS_CURVE (curve));
969 g_return_if_fail (point >= 0 && point < curve->n_points);
970
971 curve->points[point].x = CLAMP (x, 0.0, 1.0);
972 curve->points[point].y = CLAMP (y, 0.0, 1.0);
973
974 if (point > 0)
975 curve->points[point].x = MAX (x, curve->points[point - 1].x);
976
977 if (point < curve->n_points - 1)
978 curve->points[point].x = MIN (x, curve->points[point + 1].x);
979
980 g_object_notify (G_OBJECT (curve), "points");
981
982 gimp_data_dirty (GIMP_DATA (curve));
983 }
984
985 void
gimp_curve_move_point(GimpCurve * curve,gint point,gdouble y)986 gimp_curve_move_point (GimpCurve *curve,
987 gint point,
988 gdouble y)
989 {
990 g_return_if_fail (GIMP_IS_CURVE (curve));
991 g_return_if_fail (point >= 0 && point < curve->n_points);
992
993 curve->points[point].y = CLAMP (y, 0.0, 1.0);
994
995 g_object_notify (G_OBJECT (curve), "points");
996
997 gimp_data_dirty (GIMP_DATA (curve));
998 }
999
1000 void
gimp_curve_get_point(GimpCurve * curve,gint point,gdouble * x,gdouble * y)1001 gimp_curve_get_point (GimpCurve *curve,
1002 gint point,
1003 gdouble *x,
1004 gdouble *y)
1005 {
1006 g_return_if_fail (GIMP_IS_CURVE (curve));
1007 g_return_if_fail (point >= 0 && point < curve->n_points);
1008
1009 if (x) *x = curve->points[point].x;
1010 if (y) *y = curve->points[point].y;
1011 }
1012
1013 void
gimp_curve_set_point_type(GimpCurve * curve,gint point,GimpCurvePointType type)1014 gimp_curve_set_point_type (GimpCurve *curve,
1015 gint point,
1016 GimpCurvePointType type)
1017 {
1018 g_return_if_fail (GIMP_IS_CURVE (curve));
1019 g_return_if_fail (point >= 0 && point < curve->n_points);
1020
1021 curve->points[point].type = type;
1022
1023 g_object_notify (G_OBJECT (curve), "point-types");
1024
1025 gimp_data_dirty (GIMP_DATA (curve));
1026 }
1027
1028 GimpCurvePointType
gimp_curve_get_point_type(GimpCurve * curve,gint point)1029 gimp_curve_get_point_type (GimpCurve *curve,
1030 gint point)
1031 {
1032 g_return_val_if_fail (GIMP_IS_CURVE (curve), GIMP_CURVE_POINT_SMOOTH);
1033 g_return_val_if_fail (point >= 0 && point < curve->n_points, GIMP_CURVE_POINT_SMOOTH);
1034
1035 return curve->points[point].type;
1036 }
1037
1038 void
gimp_curve_clear_points(GimpCurve * curve)1039 gimp_curve_clear_points (GimpCurve *curve)
1040 {
1041 g_return_if_fail (GIMP_IS_CURVE (curve));
1042
1043 if (curve->points)
1044 {
1045 g_clear_pointer (&curve->points, g_free);
1046 curve->n_points = 0;
1047
1048 g_object_notify (G_OBJECT (curve), "n-points");
1049 g_object_notify (G_OBJECT (curve), "points");
1050 g_object_notify (G_OBJECT (curve), "point-types");
1051
1052 gimp_data_dirty (GIMP_DATA (curve));
1053 }
1054 }
1055
1056 void
gimp_curve_set_curve(GimpCurve * curve,gdouble x,gdouble y)1057 gimp_curve_set_curve (GimpCurve *curve,
1058 gdouble x,
1059 gdouble y)
1060 {
1061 g_return_if_fail (GIMP_IS_CURVE (curve));
1062 g_return_if_fail (x >= 0 && x <= 1.0);
1063 g_return_if_fail (y >= 0 && y <= 1.0);
1064
1065 if (curve->curve_type == GIMP_CURVE_SMOOTH)
1066 return;
1067
1068 curve->samples[ROUND (x * (gdouble) (curve->n_samples - 1))] = y;
1069
1070 g_object_notify (G_OBJECT (curve), "samples");
1071
1072 gimp_data_dirty (GIMP_DATA (curve));
1073 }
1074
1075 /**
1076 * gimp_curve_is_identity:
1077 * @curve: a #GimpCurve object
1078 *
1079 * If this function returns %TRUE, then the curve maps each value to
1080 * itself. If it returns %FALSE, then this assumption can not be made.
1081 *
1082 * Return value: %TRUE if the curve is an identity mapping, %FALSE otherwise.
1083 **/
1084 gboolean
gimp_curve_is_identity(GimpCurve * curve)1085 gimp_curve_is_identity (GimpCurve *curve)
1086 {
1087 g_return_val_if_fail (GIMP_IS_CURVE (curve), FALSE);
1088
1089 return curve->identity;
1090 }
1091
1092 void
gimp_curve_get_uchar(GimpCurve * curve,gint n_samples,guchar * samples)1093 gimp_curve_get_uchar (GimpCurve *curve,
1094 gint n_samples,
1095 guchar *samples)
1096 {
1097 gint i;
1098
1099 g_return_if_fail (GIMP_IS_CURVE (curve));
1100 /* FIXME: support n_samples != curve->n_samples */
1101 g_return_if_fail (n_samples == curve->n_samples);
1102 g_return_if_fail (samples != NULL);
1103
1104 for (i = 0; i < curve->n_samples; i++)
1105 samples[i] = curve->samples[i] * 255.999;
1106 }
1107
1108
1109 /* private functions */
1110
1111 static void
gimp_curve_calculate(GimpCurve * curve)1112 gimp_curve_calculate (GimpCurve *curve)
1113 {
1114 gint i;
1115 gint p1, p2, p3, p4;
1116
1117 if (gimp_data_is_frozen (GIMP_DATA (curve)))
1118 return;
1119
1120 switch (curve->curve_type)
1121 {
1122 case GIMP_CURVE_SMOOTH:
1123 /* Initialize boundary curve points */
1124 if (curve->n_points > 0)
1125 {
1126 GimpCurvePoint point;
1127 gint boundary;
1128
1129 point = curve->points[0];
1130 boundary = ROUND (point.x * (gdouble) (curve->n_samples - 1));
1131
1132 for (i = 0; i < boundary; i++)
1133 curve->samples[i] = point.y;
1134
1135 point = curve->points[curve->n_points - 1];
1136 boundary = ROUND (point.x * (gdouble) (curve->n_samples - 1));
1137
1138 for (i = boundary; i < curve->n_samples; i++)
1139 curve->samples[i] = point.y;
1140 }
1141
1142 for (i = 0; i < curve->n_points - 1; i++)
1143 {
1144 p1 = MAX (i - 1, 0);
1145 p2 = i;
1146 p3 = i + 1;
1147 p4 = MIN (i + 2, curve->n_points - 1);
1148
1149 if (curve->points[p2].type == GIMP_CURVE_POINT_CORNER)
1150 p1 = p2;
1151
1152 if (curve->points[p3].type == GIMP_CURVE_POINT_CORNER)
1153 p4 = p3;
1154
1155 gimp_curve_plot (curve, p1, p2, p3, p4);
1156 }
1157
1158 /* ensure that the control points are used exactly */
1159 for (i = 0; i < curve->n_points; i++)
1160 {
1161 gdouble x = curve->points[i].x;
1162 gdouble y = curve->points[i].y;
1163
1164 curve->samples[ROUND (x * (gdouble) (curve->n_samples - 1))] = y;
1165 }
1166
1167 g_object_notify (G_OBJECT (curve), "samples");
1168 break;
1169
1170 case GIMP_CURVE_FREE:
1171 break;
1172 }
1173 }
1174
1175 /*
1176 * This function calculates the curve values between the control points
1177 * p2 and p3, taking the potentially existing neighbors p1 and p4 into
1178 * account.
1179 *
1180 * This function uses a cubic bezier curve for the individual segments and
1181 * calculates the necessary intermediate control points depending on the
1182 * neighbor curve control points.
1183 */
1184 static void
gimp_curve_plot(GimpCurve * curve,gint p1,gint p2,gint p3,gint p4)1185 gimp_curve_plot (GimpCurve *curve,
1186 gint p1,
1187 gint p2,
1188 gint p3,
1189 gint p4)
1190 {
1191 gint i;
1192 gdouble x0, x3;
1193 gdouble y0, y1, y2, y3;
1194 gdouble dx, dy;
1195 gdouble slope;
1196
1197 /* the outer control points for the bezier curve. */
1198 x0 = curve->points[p2].x;
1199 y0 = curve->points[p2].y;
1200 x3 = curve->points[p3].x;
1201 y3 = curve->points[p3].y;
1202
1203 /*
1204 * the x values of the inner control points are fixed at
1205 * x1 = 2/3*x0 + 1/3*x3 and x2 = 1/3*x0 + 2/3*x3
1206 * this ensures that the x values increase linearly with the
1207 * parameter t and enables us to skip the calculation of the x
1208 * values altogether - just calculate y(t) evenly spaced.
1209 */
1210
1211 dx = x3 - x0;
1212 dy = y3 - y0;
1213
1214 if (dx <= EPSILON)
1215 {
1216 gint index;
1217
1218 index = ROUND (x0 * (gdouble) (curve->n_samples - 1));
1219
1220 curve->samples[index] = y3;
1221
1222 return;
1223 }
1224
1225 if (p1 == p2 && p3 == p4)
1226 {
1227 /* No information about the neighbors,
1228 * calculate y1 and y2 to get a straight line
1229 */
1230 y1 = y0 + dy / 3.0;
1231 y2 = y0 + dy * 2.0 / 3.0;
1232 }
1233 else if (p1 == p2 && p3 != p4)
1234 {
1235 /* only the right neighbor is available. Make the tangent at the
1236 * right endpoint parallel to the line between the left endpoint
1237 * and the right neighbor. Then point the tangent at the left towards
1238 * the control handle of the right tangent, to ensure that the curve
1239 * does not have an inflection point.
1240 */
1241 slope = (curve->points[p4].y - y0) / (curve->points[p4].x - x0);
1242
1243 y2 = y3 - slope * dx / 3.0;
1244 y1 = y0 + (y2 - y0) / 2.0;
1245 }
1246 else if (p1 != p2 && p3 == p4)
1247 {
1248 /* see previous case */
1249 slope = (y3 - curve->points[p1].y) / (x3 - curve->points[p1].x);
1250
1251 y1 = y0 + slope * dx / 3.0;
1252 y2 = y3 + (y1 - y3) / 2.0;
1253 }
1254 else /* (p1 != p2 && p3 != p4) */
1255 {
1256 /* Both neighbors are available. Make the tangents at the endpoints
1257 * parallel to the line between the opposite endpoint and the adjacent
1258 * neighbor.
1259 */
1260 slope = (y3 - curve->points[p1].y) / (x3 - curve->points[p1].x);
1261
1262 y1 = y0 + slope * dx / 3.0;
1263
1264 slope = (curve->points[p4].y - y0) / (curve->points[p4].x - x0);
1265
1266 y2 = y3 - slope * dx / 3.0;
1267 }
1268
1269 /*
1270 * finally calculate the y(t) values for the given bezier values. We can
1271 * use homogeneously distributed values for t, since x(t) increases linearly.
1272 */
1273 for (i = 0; i <= ROUND (dx * (gdouble) (curve->n_samples - 1)); i++)
1274 {
1275 gdouble y, t;
1276 gint index;
1277
1278 t = i / dx / (gdouble) (curve->n_samples - 1);
1279 y = y0 * (1-t) * (1-t) * (1-t) +
1280 3 * y1 * (1-t) * (1-t) * t +
1281 3 * y2 * (1-t) * t * t +
1282 y3 * t * t * t;
1283
1284 index = i + ROUND (x0 * (gdouble) (curve->n_samples - 1));
1285
1286 if (index < curve->n_samples)
1287 curve->samples[index] = CLAMP (y, 0.0, 1.0);
1288 }
1289 }
1290