diff options
Diffstat (limited to 'app/core/gimpcurve.c')
-rw-r--r-- | app/core/gimpcurve.c | 1289 |
1 files changed, 1289 insertions, 0 deletions
diff --git a/app/core/gimpcurve.c b/app/core/gimpcurve.c new file mode 100644 index 0000000..bde20b2 --- /dev/null +++ b/app/core/gimpcurve.c @@ -0,0 +1,1289 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <stdlib.h> +#include <string.h> /* memcmp */ + +#include <gdk-pixbuf/gdk-pixbuf.h> +#include <gegl.h> + +#include "libgimpbase/gimpbase.h" +#include "libgimpmath/gimpmath.h" +#include "libgimpconfig/gimpconfig.h" + +#include "core-types.h" + +#include "gimpcurve.h" +#include "gimpcurve-load.h" +#include "gimpcurve-save.h" +#include "gimpparamspecs.h" + +#include "gimp-intl.h" + + +#define EPSILON 1e-6 + + +enum +{ + PROP_0, + PROP_CURVE_TYPE, + PROP_N_POINTS, + PROP_POINTS, + PROP_POINT_TYPES, + PROP_N_SAMPLES, + PROP_SAMPLES +}; + + +/* local function prototypes */ + +static void gimp_curve_config_iface_init (GimpConfigInterface *iface); + +static void gimp_curve_finalize (GObject *object); +static void gimp_curve_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_curve_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); + +static gint64 gimp_curve_get_memsize (GimpObject *object, + gint64 *gui_size); + +static void gimp_curve_get_preview_size (GimpViewable *viewable, + gint size, + gboolean popup, + gboolean dot_for_dot, + gint *width, + gint *height); +static gboolean gimp_curve_get_popup_size (GimpViewable *viewable, + gint width, + gint height, + gboolean dot_for_dot, + gint *popup_width, + gint *popup_height); +static GimpTempBuf * gimp_curve_get_new_preview (GimpViewable *viewable, + GimpContext *context, + gint width, + gint height); +static gchar * gimp_curve_get_description (GimpViewable *viewable, + gchar **tooltip); + +static void gimp_curve_dirty (GimpData *data); +static const gchar * gimp_curve_get_extension (GimpData *data); +static void gimp_curve_data_copy (GimpData *data, + GimpData *src_data); + +static gboolean gimp_curve_serialize (GimpConfig *config, + GimpConfigWriter *writer, + gpointer data); +static gboolean gimp_curve_deserialize (GimpConfig *config, + GScanner *scanner, + gint nest_level, + gpointer data); +static gboolean gimp_curve_equal (GimpConfig *a, + GimpConfig *b); +static void _gimp_curve_reset (GimpConfig *config); +static gboolean gimp_curve_config_copy (GimpConfig *src, + GimpConfig *dest, + GParamFlags flags); + +static void gimp_curve_calculate (GimpCurve *curve); +static void gimp_curve_plot (GimpCurve *curve, + gint p1, + gint p2, + gint p3, + gint p4); + + +G_DEFINE_TYPE_WITH_CODE (GimpCurve, gimp_curve, GIMP_TYPE_DATA, + G_IMPLEMENT_INTERFACE (GIMP_TYPE_CONFIG, + gimp_curve_config_iface_init)) + +#define parent_class gimp_curve_parent_class + + +static void +gimp_curve_class_init (GimpCurveClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GimpObjectClass *gimp_object_class = GIMP_OBJECT_CLASS (klass); + GimpViewableClass *viewable_class = GIMP_VIEWABLE_CLASS (klass); + GimpDataClass *data_class = GIMP_DATA_CLASS (klass); + GParamSpec *array_spec; + + object_class->finalize = gimp_curve_finalize; + object_class->set_property = gimp_curve_set_property; + object_class->get_property = gimp_curve_get_property; + + gimp_object_class->get_memsize = gimp_curve_get_memsize; + + viewable_class->default_icon_name = "FIXME icon name"; + viewable_class->get_preview_size = gimp_curve_get_preview_size; + viewable_class->get_popup_size = gimp_curve_get_popup_size; + viewable_class->get_new_preview = gimp_curve_get_new_preview; + viewable_class->get_description = gimp_curve_get_description; + + data_class->dirty = gimp_curve_dirty; + data_class->save = gimp_curve_save; + data_class->get_extension = gimp_curve_get_extension; + data_class->copy = gimp_curve_data_copy; + + GIMP_CONFIG_PROP_ENUM (object_class, PROP_CURVE_TYPE, + "curve-type", + "Curve Type", + "The curve type", + GIMP_TYPE_CURVE_TYPE, + GIMP_CURVE_SMOOTH, 0); + + GIMP_CONFIG_PROP_INT (object_class, PROP_N_POINTS, + "n-points", + "Number of Points", + "The number of points", + 0, G_MAXINT, 0, + /* for backward compatibility */ + GIMP_CONFIG_PARAM_IGNORE); + + array_spec = g_param_spec_double ("point", NULL, NULL, + -1.0, 1.0, 0.0, GIMP_PARAM_READWRITE); + g_object_class_install_property (object_class, PROP_POINTS, + gimp_param_spec_value_array ("points", + NULL, NULL, + array_spec, + GIMP_PARAM_STATIC_STRINGS | + GIMP_CONFIG_PARAM_FLAGS)); + + array_spec = g_param_spec_enum ("point-type", NULL, NULL, + GIMP_TYPE_CURVE_POINT_TYPE, + GIMP_CURVE_POINT_SMOOTH, + GIMP_PARAM_READWRITE); + g_object_class_install_property (object_class, PROP_POINT_TYPES, + gimp_param_spec_value_array ("point-types", + NULL, NULL, + array_spec, + GIMP_PARAM_STATIC_STRINGS | + GIMP_CONFIG_PARAM_FLAGS)); + + GIMP_CONFIG_PROP_INT (object_class, PROP_N_SAMPLES, + "n-samples", + "Number of Samples", + "The number of samples", + 256, 256, 256, 0); + + array_spec = g_param_spec_double ("sample", NULL, NULL, + 0.0, 1.0, 0.0, GIMP_PARAM_READWRITE); + g_object_class_install_property (object_class, PROP_SAMPLES, + gimp_param_spec_value_array ("samples", + NULL, NULL, + array_spec, + GIMP_PARAM_STATIC_STRINGS | + GIMP_CONFIG_PARAM_FLAGS)); +} + +static void +gimp_curve_config_iface_init (GimpConfigInterface *iface) +{ + iface->serialize = gimp_curve_serialize; + iface->deserialize = gimp_curve_deserialize; + iface->equal = gimp_curve_equal; + iface->reset = _gimp_curve_reset; + iface->copy = gimp_curve_config_copy; +} + +static void +gimp_curve_init (GimpCurve *curve) +{ + curve->n_points = 0; + curve->points = NULL; + curve->n_samples = 0; + curve->samples = NULL; + curve->identity = FALSE; +} + +static void +gimp_curve_finalize (GObject *object) +{ + GimpCurve *curve = GIMP_CURVE (object); + + g_clear_pointer (&curve->points, g_free); + curve->n_points = 0; + + g_clear_pointer (&curve->samples, g_free); + curve->n_samples = 0; + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gimp_curve_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpCurve *curve = GIMP_CURVE (object); + + switch (property_id) + { + case PROP_CURVE_TYPE: + gimp_curve_set_curve_type (curve, g_value_get_enum (value)); + break; + + case PROP_N_POINTS: + /* ignored */ + break; + + case PROP_POINTS: + { + GimpValueArray *array = g_value_get_boxed (value); + GimpCurvePoint *points; + gint length; + gint n_points; + gint i; + + if (! array) + { + gimp_curve_clear_points (curve); + + break; + } + + length = gimp_value_array_length (array) / 2; + + n_points = 0; + points = g_new0 (GimpCurvePoint, length); + + for (i = 0; i < length; i++) + { + GValue *x = gimp_value_array_index (array, i * 2); + GValue *y = gimp_value_array_index (array, i * 2 + 1); + + /* for backward compatibility */ + if (g_value_get_double (x) < 0.0) + continue; + + points[n_points].x = CLAMP (g_value_get_double (x), 0.0, 1.0); + points[n_points].y = CLAMP (g_value_get_double (y), 0.0, 1.0); + + if (n_points > 0) + { + points[n_points].x = MAX (points[n_points].x, + points[n_points - 1].x); + } + + if (n_points < curve->n_points) + points[n_points].type = curve->points[n_points].type; + else + points[n_points].type = GIMP_CURVE_POINT_SMOOTH; + + n_points++; + } + + g_free (curve->points); + + curve->n_points = n_points; + curve->points = points; + + g_object_notify (object, "n-points"); + g_object_notify (object, "point-types"); + } + break; + + case PROP_POINT_TYPES: + { + GimpValueArray *array = g_value_get_boxed (value); + GimpCurvePoint *points; + gint length; + gdouble x = 0.0; + gdouble y = 0.0; + gint i; + + if (! array) + { + gimp_curve_clear_points (curve); + + break; + } + + length = gimp_value_array_length (array); + + points = g_new0 (GimpCurvePoint, length); + + for (i = 0; i < length; i++) + { + GValue *type = gimp_value_array_index (array, i); + + points[i].type = g_value_get_enum (type); + + if (i < curve->n_points) + { + x = curve->points[i].x; + y = curve->points[i].y; + } + + points[i].x = x; + points[i].y = y; + } + + g_free (curve->points); + + curve->n_points = length; + curve->points = points; + + g_object_notify (object, "n-points"); + g_object_notify (object, "points"); + } + break; + + case PROP_N_SAMPLES: + gimp_curve_set_n_samples (curve, g_value_get_int (value)); + break; + + case PROP_SAMPLES: + { + GimpValueArray *array = g_value_get_boxed (value); + gint length; + gint i; + + if (! array) + break; + + length = gimp_value_array_length (array); + + for (i = 0; i < curve->n_samples && i < length; i++) + { + GValue *v = gimp_value_array_index (array, i); + + curve->samples[i] = CLAMP (g_value_get_double (v), 0.0, 1.0); + } + } + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_curve_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpCurve *curve = GIMP_CURVE (object); + + switch (property_id) + { + case PROP_CURVE_TYPE: + g_value_set_enum (value, curve->curve_type); + break; + + case PROP_N_POINTS: + g_value_set_int (value, curve->n_points); + break; + + case PROP_POINTS: + { + GimpValueArray *array = gimp_value_array_new (curve->n_points * 2); + GValue v = G_VALUE_INIT; + gint i; + + g_value_init (&v, G_TYPE_DOUBLE); + + for (i = 0; i < curve->n_points; i++) + { + g_value_set_double (&v, curve->points[i].x); + gimp_value_array_append (array, &v); + + g_value_set_double (&v, curve->points[i].y); + gimp_value_array_append (array, &v); + } + + g_value_unset (&v); + + g_value_take_boxed (value, array); + } + break; + + case PROP_POINT_TYPES: + { + GimpValueArray *array = gimp_value_array_new (curve->n_points); + GValue v = G_VALUE_INIT; + gint i; + + g_value_init (&v, GIMP_TYPE_CURVE_POINT_TYPE); + + for (i = 0; i < curve->n_points; i++) + { + g_value_set_enum (&v, curve->points[i].type); + gimp_value_array_append (array, &v); + } + + g_value_unset (&v); + + g_value_take_boxed (value, array); + } + break; + + case PROP_N_SAMPLES: + g_value_set_int (value, curve->n_samples); + break; + + case PROP_SAMPLES: + { + GimpValueArray *array = gimp_value_array_new (curve->n_samples); + GValue v = G_VALUE_INIT; + gint i; + + g_value_init (&v, G_TYPE_DOUBLE); + + for (i = 0; i < curve->n_samples; i++) + { + g_value_set_double (&v, curve->samples[i]); + gimp_value_array_append (array, &v); + } + + g_value_unset (&v); + + g_value_take_boxed (value, array); + } + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static gint64 +gimp_curve_get_memsize (GimpObject *object, + gint64 *gui_size) +{ + GimpCurve *curve = GIMP_CURVE (object); + gint64 memsize = 0; + + memsize += curve->n_points * sizeof (GimpCurvePoint); + memsize += curve->n_samples * sizeof (gdouble); + + return memsize + GIMP_OBJECT_CLASS (parent_class)->get_memsize (object, + gui_size); +} + +static void +gimp_curve_get_preview_size (GimpViewable *viewable, + gint size, + gboolean popup, + gboolean dot_for_dot, + gint *width, + gint *height) +{ + *width = size; + *height = size; +} + +static gboolean +gimp_curve_get_popup_size (GimpViewable *viewable, + gint width, + gint height, + gboolean dot_for_dot, + gint *popup_width, + gint *popup_height) +{ + *popup_width = width * 2; + *popup_height = height * 2; + + return TRUE; +} + +static GimpTempBuf * +gimp_curve_get_new_preview (GimpViewable *viewable, + GimpContext *context, + gint width, + gint height) +{ + return NULL; +} + +static gchar * +gimp_curve_get_description (GimpViewable *viewable, + gchar **tooltip) +{ + GimpCurve *curve = GIMP_CURVE (viewable); + + return g_strdup_printf ("%s", gimp_object_get_name (curve)); +} + +static void +gimp_curve_dirty (GimpData *data) +{ + GimpCurve *curve = GIMP_CURVE (data); + + curve->identity = FALSE; + + gimp_curve_calculate (curve); + + GIMP_DATA_CLASS (parent_class)->dirty (data); +} + +static const gchar * +gimp_curve_get_extension (GimpData *data) +{ + return GIMP_CURVE_FILE_EXTENSION; +} + +static void +gimp_curve_data_copy (GimpData *data, + GimpData *src_data) +{ + gimp_data_freeze (data); + + gimp_config_copy (GIMP_CONFIG (src_data), + GIMP_CONFIG (data), 0); + + gimp_data_thaw (data); +} + +static gboolean +gimp_curve_serialize (GimpConfig *config, + GimpConfigWriter *writer, + gpointer data) +{ + return gimp_config_serialize_properties (config, writer); +} + +static gboolean +gimp_curve_deserialize (GimpConfig *config, + GScanner *scanner, + gint nest_level, + gpointer data) +{ + gboolean success; + + success = gimp_config_deserialize_properties (config, scanner, nest_level); + + GIMP_CURVE (config)->identity = FALSE; + + return success; +} + +static gboolean +gimp_curve_equal (GimpConfig *a, + GimpConfig *b) +{ + GimpCurve *a_curve = GIMP_CURVE (a); + GimpCurve *b_curve = GIMP_CURVE (b); + + if (a_curve->curve_type != b_curve->curve_type) + return FALSE; + + if (a_curve->n_points != b_curve->n_points || + memcmp (a_curve->points, b_curve->points, + sizeof (GimpCurvePoint) * a_curve->n_points)) + { + return FALSE; + } + + if (a_curve->n_samples != b_curve->n_samples || + memcmp (a_curve->samples, b_curve->samples, + sizeof (gdouble) * a_curve->n_samples)) + { + return FALSE; + } + + return TRUE; +} + +static void +_gimp_curve_reset (GimpConfig *config) +{ + gimp_curve_reset (GIMP_CURVE (config), TRUE); +} + +static gboolean +gimp_curve_config_copy (GimpConfig *src, + GimpConfig *dest, + GParamFlags flags) +{ + GimpCurve *src_curve = GIMP_CURVE (src); + GimpCurve *dest_curve = GIMP_CURVE (dest); + + /* make sure the curve type is copied *before* the points, so that we don't + * overwrite the copied points when changing the type + */ + dest_curve->curve_type = src_curve->curve_type; + + gimp_config_sync (G_OBJECT (src), G_OBJECT (dest), flags); + + dest_curve->identity = src_curve->identity; + + gimp_data_dirty (GIMP_DATA (dest)); + + return TRUE; +} + + +/* public functions */ + +GimpData * +gimp_curve_new (const gchar *name) +{ + g_return_val_if_fail (name != NULL, NULL); + g_return_val_if_fail (*name != '\0', NULL); + + return g_object_new (GIMP_TYPE_CURVE, + "name", name, + NULL); +} + +GimpData * +gimp_curve_get_standard (void) +{ + static GimpData *standard_curve = NULL; + + if (! standard_curve) + { + standard_curve = gimp_curve_new ("Standard"); + + gimp_data_clean (standard_curve); + gimp_data_make_internal (standard_curve, + "gimp-curve-standard"); + + g_object_ref (standard_curve); + } + + return standard_curve; +} + +void +gimp_curve_reset (GimpCurve *curve, + gboolean reset_type) +{ + gint i; + + g_return_if_fail (GIMP_IS_CURVE (curve)); + + g_object_freeze_notify (G_OBJECT (curve)); + + for (i = 0; i < curve->n_samples; i++) + curve->samples[i] = (gdouble) i / (gdouble) (curve->n_samples - 1); + + g_object_notify (G_OBJECT (curve), "samples"); + + g_free (curve->points); + + curve->n_points = 2; + curve->points = g_new0 (GimpCurvePoint, 2); + + curve->points[0].x = 0.0; + curve->points[0].y = 0.0; + curve->points[0].type = GIMP_CURVE_POINT_SMOOTH; + + curve->points[1].x = 1.0; + curve->points[1].y = 1.0; + curve->points[1].type = GIMP_CURVE_POINT_SMOOTH; + + g_object_notify (G_OBJECT (curve), "n-points"); + g_object_notify (G_OBJECT (curve), "points"); + g_object_notify (G_OBJECT (curve), "point-types"); + + if (reset_type) + { + curve->curve_type = GIMP_CURVE_SMOOTH; + g_object_notify (G_OBJECT (curve), "curve-type"); + } + + curve->identity = TRUE; + + g_object_thaw_notify (G_OBJECT (curve)); + + gimp_data_dirty (GIMP_DATA (curve)); +} + +void +gimp_curve_set_curve_type (GimpCurve *curve, + GimpCurveType curve_type) +{ + g_return_if_fail (GIMP_IS_CURVE (curve)); + + if (curve->curve_type != curve_type) + { + gimp_data_freeze (GIMP_DATA (curve)); + + g_object_freeze_notify (G_OBJECT (curve)); + + curve->curve_type = curve_type; + + if (curve_type == GIMP_CURVE_SMOOTH) + { + gint i; + + g_free (curve->points); + + /* pick some points from the curve and make them control + * points + */ + curve->n_points = 9; + curve->points = g_new0 (GimpCurvePoint, 9); + + for (i = 0; i < curve->n_points; i++) + { + gint sample = i * (curve->n_samples - 1) / (curve->n_points - 1); + + curve->points[i].x = (gdouble) sample / + (gdouble) (curve->n_samples - 1); + curve->points[i].y = curve->samples[sample]; + curve->points[i].type = GIMP_CURVE_POINT_SMOOTH; + } + + g_object_notify (G_OBJECT (curve), "n-points"); + g_object_notify (G_OBJECT (curve), "points"); + g_object_notify (G_OBJECT (curve), "point-types"); + } + else + { + gimp_curve_clear_points (curve); + } + + g_object_notify (G_OBJECT (curve), "curve-type"); + + g_object_thaw_notify (G_OBJECT (curve)); + + gimp_data_thaw (GIMP_DATA (curve)); + } +} + +GimpCurveType +gimp_curve_get_curve_type (GimpCurve *curve) +{ + g_return_val_if_fail (GIMP_IS_CURVE (curve), GIMP_CURVE_SMOOTH); + + return curve->curve_type; +} + +gint +gimp_curve_get_n_points (GimpCurve *curve) +{ + g_return_val_if_fail (GIMP_IS_CURVE (curve), 0); + + return curve->n_points; +} + +void +gimp_curve_set_n_samples (GimpCurve *curve, + gint n_samples) +{ + g_return_if_fail (GIMP_IS_CURVE (curve)); + g_return_if_fail (n_samples >= 256); + g_return_if_fail (n_samples <= 4096); + + if (n_samples != curve->n_samples) + { + gint i; + + g_object_freeze_notify (G_OBJECT (curve)); + + curve->n_samples = n_samples; + g_object_notify (G_OBJECT (curve), "n-samples"); + + curve->samples = g_renew (gdouble, curve->samples, curve->n_samples); + + for (i = 0; i < curve->n_samples; i++) + curve->samples[i] = (gdouble) i / (gdouble) (curve->n_samples - 1); + + g_object_notify (G_OBJECT (curve), "samples"); + + if (curve->curve_type == GIMP_CURVE_FREE) + curve->identity = TRUE; + + g_object_thaw_notify (G_OBJECT (curve)); + } +} + +gint +gimp_curve_get_n_samples (GimpCurve *curve) +{ + g_return_val_if_fail (GIMP_IS_CURVE (curve), 0); + + return curve->n_samples; +} + +gint +gimp_curve_get_point_at (GimpCurve *curve, + gdouble x) +{ + gint closest_point = -1; + gdouble distance = EPSILON; + gint i; + + g_return_val_if_fail (GIMP_IS_CURVE (curve), -1); + + for (i = 0; i < curve->n_points; i++) + { + gdouble point_distance; + + point_distance = fabs (x - curve->points[i].x); + + if (point_distance <= distance) + { + closest_point = i; + distance = point_distance; + } + } + + return closest_point; +} + +gint +gimp_curve_get_closest_point (GimpCurve *curve, + gdouble x, + gdouble y, + gdouble max_distance) +{ + gint closest_point = -1; + gdouble distance2 = G_MAXDOUBLE; + gint i; + + g_return_val_if_fail (GIMP_IS_CURVE (curve), -1); + + if (max_distance >= 0.0) + distance2 = SQR (max_distance); + + for (i = curve->n_points - 1; i >= 0; i--) + { + gdouble point_distance2; + + point_distance2 = SQR (x - curve->points[i].x) + + SQR (y - curve->points[i].y); + + if (point_distance2 <= distance2) + { + closest_point = i; + distance2 = point_distance2; + } + } + + return closest_point; +} + +gint +gimp_curve_add_point (GimpCurve *curve, + gdouble x, + gdouble y) +{ + GimpCurvePoint *points; + gint point; + + g_return_val_if_fail (GIMP_IS_CURVE (curve), -1); + + if (curve->curve_type == GIMP_CURVE_FREE) + return -1; + + x = CLAMP (x, 0.0, 1.0); + y = CLAMP (y, 0.0, 1.0); + + for (point = 0; point < curve->n_points; point++) + { + if (curve->points[point].x > x) + break; + } + + points = g_new0 (GimpCurvePoint, curve->n_points + 1); + + memcpy (points, curve->points, + point * sizeof (GimpCurvePoint)); + memcpy (points + point + 1, curve->points + point, + (curve->n_points - point) * sizeof (GimpCurvePoint)); + + points[point].x = x; + points[point].y = y; + points[point].type = GIMP_CURVE_POINT_SMOOTH; + + g_free (curve->points); + + curve->n_points++; + curve->points = points; + + g_object_notify (G_OBJECT (curve), "n-points"); + g_object_notify (G_OBJECT (curve), "points"); + g_object_notify (G_OBJECT (curve), "point-types"); + + gimp_data_dirty (GIMP_DATA (curve)); + + return point; +} + +void +gimp_curve_delete_point (GimpCurve *curve, + gint point) +{ + GimpCurvePoint *points; + + g_return_if_fail (GIMP_IS_CURVE (curve)); + g_return_if_fail (point >= 0 && point < curve->n_points); + + points = g_new0 (GimpCurvePoint, curve->n_points - 1); + + memcpy (points, curve->points, + point * sizeof (GimpCurvePoint)); + memcpy (points + point, curve->points + point + 1, + (curve->n_points - point - 1) * sizeof (GimpCurvePoint)); + + g_free (curve->points); + + curve->n_points--; + curve->points = points; + + g_object_notify (G_OBJECT (curve), "n-points"); + g_object_notify (G_OBJECT (curve), "points"); + g_object_notify (G_OBJECT (curve), "point-types"); + + gimp_data_dirty (GIMP_DATA (curve)); +} + +void +gimp_curve_set_point (GimpCurve *curve, + gint point, + gdouble x, + gdouble y) +{ + g_return_if_fail (GIMP_IS_CURVE (curve)); + g_return_if_fail (point >= 0 && point < curve->n_points); + + curve->points[point].x = CLAMP (x, 0.0, 1.0); + curve->points[point].y = CLAMP (y, 0.0, 1.0); + + if (point > 0) + curve->points[point].x = MAX (x, curve->points[point - 1].x); + + if (point < curve->n_points - 1) + curve->points[point].x = MIN (x, curve->points[point + 1].x); + + g_object_notify (G_OBJECT (curve), "points"); + + gimp_data_dirty (GIMP_DATA (curve)); +} + +void +gimp_curve_move_point (GimpCurve *curve, + gint point, + gdouble y) +{ + g_return_if_fail (GIMP_IS_CURVE (curve)); + g_return_if_fail (point >= 0 && point < curve->n_points); + + curve->points[point].y = CLAMP (y, 0.0, 1.0); + + g_object_notify (G_OBJECT (curve), "points"); + + gimp_data_dirty (GIMP_DATA (curve)); +} + +void +gimp_curve_get_point (GimpCurve *curve, + gint point, + gdouble *x, + gdouble *y) +{ + g_return_if_fail (GIMP_IS_CURVE (curve)); + g_return_if_fail (point >= 0 && point < curve->n_points); + + if (x) *x = curve->points[point].x; + if (y) *y = curve->points[point].y; +} + +void +gimp_curve_set_point_type (GimpCurve *curve, + gint point, + GimpCurvePointType type) +{ + g_return_if_fail (GIMP_IS_CURVE (curve)); + g_return_if_fail (point >= 0 && point < curve->n_points); + + curve->points[point].type = type; + + g_object_notify (G_OBJECT (curve), "point-types"); + + gimp_data_dirty (GIMP_DATA (curve)); +} + +GimpCurvePointType +gimp_curve_get_point_type (GimpCurve *curve, + gint point) +{ + g_return_val_if_fail (GIMP_IS_CURVE (curve), GIMP_CURVE_POINT_SMOOTH); + g_return_val_if_fail (point >= 0 && point < curve->n_points, GIMP_CURVE_POINT_SMOOTH); + + return curve->points[point].type; +} + +void +gimp_curve_clear_points (GimpCurve *curve) +{ + g_return_if_fail (GIMP_IS_CURVE (curve)); + + if (curve->points) + { + g_clear_pointer (&curve->points, g_free); + curve->n_points = 0; + + g_object_notify (G_OBJECT (curve), "n-points"); + g_object_notify (G_OBJECT (curve), "points"); + g_object_notify (G_OBJECT (curve), "point-types"); + + gimp_data_dirty (GIMP_DATA (curve)); + } +} + +void +gimp_curve_set_curve (GimpCurve *curve, + gdouble x, + gdouble y) +{ + g_return_if_fail (GIMP_IS_CURVE (curve)); + g_return_if_fail (x >= 0 && x <= 1.0); + g_return_if_fail (y >= 0 && y <= 1.0); + + if (curve->curve_type == GIMP_CURVE_SMOOTH) + return; + + curve->samples[ROUND (x * (gdouble) (curve->n_samples - 1))] = y; + + g_object_notify (G_OBJECT (curve), "samples"); + + gimp_data_dirty (GIMP_DATA (curve)); +} + +/** + * gimp_curve_is_identity: + * @curve: a #GimpCurve object + * + * If this function returns %TRUE, then the curve maps each value to + * itself. If it returns %FALSE, then this assumption can not be made. + * + * Return value: %TRUE if the curve is an identity mapping, %FALSE otherwise. + **/ +gboolean +gimp_curve_is_identity (GimpCurve *curve) +{ + g_return_val_if_fail (GIMP_IS_CURVE (curve), FALSE); + + return curve->identity; +} + +void +gimp_curve_get_uchar (GimpCurve *curve, + gint n_samples, + guchar *samples) +{ + gint i; + + g_return_if_fail (GIMP_IS_CURVE (curve)); + /* FIXME: support n_samples != curve->n_samples */ + g_return_if_fail (n_samples == curve->n_samples); + g_return_if_fail (samples != NULL); + + for (i = 0; i < curve->n_samples; i++) + samples[i] = curve->samples[i] * 255.999; +} + + +/* private functions */ + +static void +gimp_curve_calculate (GimpCurve *curve) +{ + gint i; + gint p1, p2, p3, p4; + + if (gimp_data_is_frozen (GIMP_DATA (curve))) + return; + + switch (curve->curve_type) + { + case GIMP_CURVE_SMOOTH: + /* Initialize boundary curve points */ + if (curve->n_points > 0) + { + GimpCurvePoint point; + gint boundary; + + point = curve->points[0]; + boundary = ROUND (point.x * (gdouble) (curve->n_samples - 1)); + + for (i = 0; i < boundary; i++) + curve->samples[i] = point.y; + + point = curve->points[curve->n_points - 1]; + boundary = ROUND (point.x * (gdouble) (curve->n_samples - 1)); + + for (i = boundary; i < curve->n_samples; i++) + curve->samples[i] = point.y; + } + + for (i = 0; i < curve->n_points - 1; i++) + { + p1 = MAX (i - 1, 0); + p2 = i; + p3 = i + 1; + p4 = MIN (i + 2, curve->n_points - 1); + + if (curve->points[p2].type == GIMP_CURVE_POINT_CORNER) + p1 = p2; + + if (curve->points[p3].type == GIMP_CURVE_POINT_CORNER) + p4 = p3; + + gimp_curve_plot (curve, p1, p2, p3, p4); + } + + /* ensure that the control points are used exactly */ + for (i = 0; i < curve->n_points; i++) + { + gdouble x = curve->points[i].x; + gdouble y = curve->points[i].y; + + curve->samples[ROUND (x * (gdouble) (curve->n_samples - 1))] = y; + } + + g_object_notify (G_OBJECT (curve), "samples"); + break; + + case GIMP_CURVE_FREE: + break; + } +} + +/* + * This function calculates the curve values between the control points + * p2 and p3, taking the potentially existing neighbors p1 and p4 into + * account. + * + * This function uses a cubic bezier curve for the individual segments and + * calculates the necessary intermediate control points depending on the + * neighbor curve control points. + */ +static void +gimp_curve_plot (GimpCurve *curve, + gint p1, + gint p2, + gint p3, + gint p4) +{ + gint i; + gdouble x0, x3; + gdouble y0, y1, y2, y3; + gdouble dx, dy; + gdouble slope; + + /* the outer control points for the bezier curve. */ + x0 = curve->points[p2].x; + y0 = curve->points[p2].y; + x3 = curve->points[p3].x; + y3 = curve->points[p3].y; + + /* + * the x values of the inner control points are fixed at + * x1 = 2/3*x0 + 1/3*x3 and x2 = 1/3*x0 + 2/3*x3 + * this ensures that the x values increase linearly with the + * parameter t and enables us to skip the calculation of the x + * values altogether - just calculate y(t) evenly spaced. + */ + + dx = x3 - x0; + dy = y3 - y0; + + if (dx <= EPSILON) + { + gint index; + + index = ROUND (x0 * (gdouble) (curve->n_samples - 1)); + + curve->samples[index] = y3; + + return; + } + + if (p1 == p2 && p3 == p4) + { + /* No information about the neighbors, + * calculate y1 and y2 to get a straight line + */ + y1 = y0 + dy / 3.0; + y2 = y0 + dy * 2.0 / 3.0; + } + else if (p1 == p2 && p3 != p4) + { + /* only the right neighbor is available. Make the tangent at the + * right endpoint parallel to the line between the left endpoint + * and the right neighbor. Then point the tangent at the left towards + * the control handle of the right tangent, to ensure that the curve + * does not have an inflection point. + */ + slope = (curve->points[p4].y - y0) / (curve->points[p4].x - x0); + + y2 = y3 - slope * dx / 3.0; + y1 = y0 + (y2 - y0) / 2.0; + } + else if (p1 != p2 && p3 == p4) + { + /* see previous case */ + slope = (y3 - curve->points[p1].y) / (x3 - curve->points[p1].x); + + y1 = y0 + slope * dx / 3.0; + y2 = y3 + (y1 - y3) / 2.0; + } + else /* (p1 != p2 && p3 != p4) */ + { + /* Both neighbors are available. Make the tangents at the endpoints + * parallel to the line between the opposite endpoint and the adjacent + * neighbor. + */ + slope = (y3 - curve->points[p1].y) / (x3 - curve->points[p1].x); + + y1 = y0 + slope * dx / 3.0; + + slope = (curve->points[p4].y - y0) / (curve->points[p4].x - x0); + + y2 = y3 - slope * dx / 3.0; + } + + /* + * finally calculate the y(t) values for the given bezier values. We can + * use homogeneously distributed values for t, since x(t) increases linearly. + */ + for (i = 0; i <= ROUND (dx * (gdouble) (curve->n_samples - 1)); i++) + { + gdouble y, t; + gint index; + + t = i / dx / (gdouble) (curve->n_samples - 1); + y = y0 * (1-t) * (1-t) * (1-t) + + 3 * y1 * (1-t) * (1-t) * t + + 3 * y2 * (1-t) * t * t + + y3 * t * t * t; + + index = i + ROUND (x0 * (gdouble) (curve->n_samples - 1)); + + if (index < curve->n_samples) + curve->samples[index] = CLAMP (y, 0.0, 1.0); + } +} |