gimp/app/core/gimpcurve.c
Jehan 3038c751bc app, pdb: use the real theme background color for brushes with GIMP_VIEW_BG_USE_STYLE.
Previous code was using the correct background color from the theme, but
the foreground color was always either white or black (depending on GUI
config color scheme). Instead, just use the foreground color from theme.

Since core/ doesn't have access to GTK, hence the theme, we had to
update GimpViewable's get_preview() and get_pixbuf() abstract methods to
have a color argument for recoloring previews (when relevant, which for
most types of viewables is not).
2025-05-22 21:14:20 +00:00

1294 lines
36 KiB
C

/* 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,
N_PROPS
};
static GParamSpec *obj_props[N_PROPS] = { NULL, };
/* 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,
GeglColor *color);
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;
obj_props[PROP_CURVE_TYPE] =
g_param_spec_enum ("curve-type",
"Curve Type",
"The curve type",
GIMP_TYPE_CURVE_TYPE,
GIMP_CURVE_SMOOTH,
GIMP_CONFIG_PARAM_FLAGS);
obj_props[PROP_N_POINTS] =
g_param_spec_int ("n-points",
"Number of Points",
"The number of points",
0, G_MAXINT, 0,
/* for backward compatibility */
GIMP_CONFIG_PARAM_IGNORE | GIMP_CONFIG_PARAM_FLAGS);
array_spec = g_param_spec_double ("point", NULL, NULL,
-1.0, 1.0, 0.0, GIMP_PARAM_READWRITE);
obj_props[PROP_POINTS] =
gimp_param_spec_value_array ("points",
NULL, NULL,
array_spec,
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);
obj_props[PROP_POINT_TYPES] =
gimp_param_spec_value_array ("point-types",
NULL, NULL,
array_spec,
GIMP_CONFIG_PARAM_FLAGS);
obj_props[PROP_N_SAMPLES] =
g_param_spec_int ("n-samples",
"Number of Samples",
"The number of samples",
256, 256, 256,
GIMP_CONFIG_PARAM_FLAGS);
array_spec = g_param_spec_double ("sample", NULL, NULL,
0.0, 1.0, 0.0, GIMP_PARAM_READWRITE);
obj_props[PROP_SAMPLES] =
gimp_param_spec_value_array ("samples",
NULL, NULL,
array_spec,
GIMP_CONFIG_PARAM_FLAGS);
g_object_class_install_properties (object_class, N_PROPS, obj_props);
}
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_by_pspec (object, obj_props[PROP_N_POINTS]);
g_object_notify_by_pspec (object, obj_props[PROP_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_by_pspec (object, obj_props[PROP_N_POINTS]);
g_object_notify_by_pspec (object, obj_props[PROP_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,
GeglColor *color G_GNUC_UNUSED)
{
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_by_pspec (G_OBJECT (curve), obj_props[PROP_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_by_pspec (G_OBJECT (curve), obj_props[PROP_N_POINTS]);
g_object_notify_by_pspec (G_OBJECT (curve), obj_props[PROP_POINTS]);
g_object_notify_by_pspec (G_OBJECT (curve), obj_props[PROP_POINT_TYPES]);
if (reset_type)
{
curve->curve_type = GIMP_CURVE_SMOOTH;
g_object_notify_by_pspec (G_OBJECT (curve), obj_props[PROP_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_by_pspec (G_OBJECT (curve), obj_props[PROP_N_POINTS]);
g_object_notify_by_pspec (G_OBJECT (curve), obj_props[PROP_POINTS]);
g_object_notify_by_pspec (G_OBJECT (curve), obj_props[PROP_POINT_TYPES]);
}
else
{
gimp_curve_clear_points (curve);
}
g_object_notify_by_pspec (G_OBJECT (curve), obj_props[PROP_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_by_pspec (G_OBJECT (curve), obj_props[PROP_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_by_pspec (G_OBJECT (curve), obj_props[PROP_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_by_pspec (G_OBJECT (curve), obj_props[PROP_N_POINTS]);
g_object_notify_by_pspec (G_OBJECT (curve), obj_props[PROP_POINTS]);
g_object_notify_by_pspec (G_OBJECT (curve), obj_props[PROP_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_by_pspec (G_OBJECT (curve), obj_props[PROP_N_POINTS]);
g_object_notify_by_pspec (G_OBJECT (curve), obj_props[PROP_POINTS]);
g_object_notify_by_pspec (G_OBJECT (curve), obj_props[PROP_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_by_pspec (G_OBJECT (curve), obj_props[PROP_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_by_pspec (G_OBJECT (curve), obj_props[PROP_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_by_pspec (G_OBJECT (curve), obj_props[PROP_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_by_pspec (G_OBJECT (curve), obj_props[PROP_N_POINTS]);
g_object_notify_by_pspec (G_OBJECT (curve), obj_props[PROP_POINTS]);
g_object_notify_by_pspec (G_OBJECT (curve), obj_props[PROP_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_by_pspec (G_OBJECT (curve), obj_props[PROP_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.
*
* Returns: %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_by_pspec (G_OBJECT (curve), obj_props[PROP_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);
}
}