PIKApp/app/core/pikacurve.c

1297 lines
36 KiB
C

/* PIKA - Photo and Image Kooker Application
* a rebranding of The GNU Image Manipulation Program (created with heckimp)
* A derived work which may be trivial. However, any changes may be (C)2023 by Aldercone Studio
*
* Original copyright, applying to most contents (license remains unchanged):
* 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 "libpikabase/pikabase.h"
#include "libpikamath/pikamath.h"
#include "libpikaconfig/pikaconfig.h"
#include "core-types.h"
#include "pikacurve.h"
#include "pikacurve-load.h"
#include "pikacurve-save.h"
#include "pikaparamspecs.h"
#include "pika-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 pika_curve_config_iface_init (PikaConfigInterface *iface);
static void pika_curve_finalize (GObject *object);
static void pika_curve_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec);
static void pika_curve_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec);
static gint64 pika_curve_get_memsize (PikaObject *object,
gint64 *gui_size);
static void pika_curve_get_preview_size (PikaViewable *viewable,
gint size,
gboolean popup,
gboolean dot_for_dot,
gint *width,
gint *height);
static gboolean pika_curve_get_popup_size (PikaViewable *viewable,
gint width,
gint height,
gboolean dot_for_dot,
gint *popup_width,
gint *popup_height);
static PikaTempBuf * pika_curve_get_new_preview (PikaViewable *viewable,
PikaContext *context,
gint width,
gint height);
static gchar * pika_curve_get_description (PikaViewable *viewable,
gchar **tooltip);
static void pika_curve_dirty (PikaData *data);
static const gchar * pika_curve_get_extension (PikaData *data);
static void pika_curve_data_copy (PikaData *data,
PikaData *src_data);
static gboolean pika_curve_serialize (PikaConfig *config,
PikaConfigWriter *writer,
gpointer data);
static gboolean pika_curve_deserialize (PikaConfig *config,
GScanner *scanner,
gint nest_level,
gpointer data);
static gboolean pika_curve_equal (PikaConfig *a,
PikaConfig *b);
static void _pika_curve_reset (PikaConfig *config);
static gboolean pika_curve_config_copy (PikaConfig *src,
PikaConfig *dest,
GParamFlags flags);
static void pika_curve_calculate (PikaCurve *curve);
static void pika_curve_plot (PikaCurve *curve,
gint p1,
gint p2,
gint p3,
gint p4);
G_DEFINE_TYPE_WITH_CODE (PikaCurve, pika_curve, PIKA_TYPE_DATA,
G_IMPLEMENT_INTERFACE (PIKA_TYPE_CONFIG,
pika_curve_config_iface_init))
#define parent_class pika_curve_parent_class
static void
pika_curve_class_init (PikaCurveClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
PikaObjectClass *pika_object_class = PIKA_OBJECT_CLASS (klass);
PikaViewableClass *viewable_class = PIKA_VIEWABLE_CLASS (klass);
PikaDataClass *data_class = PIKA_DATA_CLASS (klass);
GParamSpec *array_spec;
object_class->finalize = pika_curve_finalize;
object_class->set_property = pika_curve_set_property;
object_class->get_property = pika_curve_get_property;
pika_object_class->get_memsize = pika_curve_get_memsize;
viewable_class->default_icon_name = "FIXME icon name";
viewable_class->get_preview_size = pika_curve_get_preview_size;
viewable_class->get_popup_size = pika_curve_get_popup_size;
viewable_class->get_new_preview = pika_curve_get_new_preview;
viewable_class->get_description = pika_curve_get_description;
data_class->dirty = pika_curve_dirty;
data_class->save = pika_curve_save;
data_class->get_extension = pika_curve_get_extension;
data_class->copy = pika_curve_data_copy;
obj_props[PROP_CURVE_TYPE] =
g_param_spec_enum ("curve-type",
"Curve Type",
"The curve type",
PIKA_TYPE_CURVE_TYPE,
PIKA_CURVE_SMOOTH,
PIKA_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 */
PIKA_CONFIG_PARAM_IGNORE | PIKA_CONFIG_PARAM_FLAGS);
array_spec = g_param_spec_double ("point", NULL, NULL,
-1.0, 1.0, 0.0, PIKA_PARAM_READWRITE);
obj_props[PROP_POINTS] =
pika_param_spec_value_array ("points",
NULL, NULL,
array_spec,
PIKA_CONFIG_PARAM_FLAGS);
array_spec = g_param_spec_enum ("point-type", NULL, NULL,
PIKA_TYPE_CURVE_POINT_TYPE,
PIKA_CURVE_POINT_SMOOTH,
PIKA_PARAM_READWRITE);
obj_props[PROP_POINT_TYPES] =
pika_param_spec_value_array ("point-types",
NULL, NULL,
array_spec,
PIKA_CONFIG_PARAM_FLAGS);
obj_props[PROP_N_SAMPLES] =
g_param_spec_int ("n-samples",
"Number of Samples",
"The number of samples",
256, 256, 256,
PIKA_CONFIG_PARAM_FLAGS);
array_spec = g_param_spec_double ("sample", NULL, NULL,
0.0, 1.0, 0.0, PIKA_PARAM_READWRITE);
obj_props[PROP_SAMPLES] =
pika_param_spec_value_array ("samples",
NULL, NULL,
array_spec,
PIKA_CONFIG_PARAM_FLAGS);
g_object_class_install_properties (object_class, N_PROPS, obj_props);
}
static void
pika_curve_config_iface_init (PikaConfigInterface *iface)
{
iface->serialize = pika_curve_serialize;
iface->deserialize = pika_curve_deserialize;
iface->equal = pika_curve_equal;
iface->reset = _pika_curve_reset;
iface->copy = pika_curve_config_copy;
}
static void
pika_curve_init (PikaCurve *curve)
{
curve->n_points = 0;
curve->points = NULL;
curve->n_samples = 0;
curve->samples = NULL;
curve->identity = FALSE;
}
static void
pika_curve_finalize (GObject *object)
{
PikaCurve *curve = PIKA_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
pika_curve_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec)
{
PikaCurve *curve = PIKA_CURVE (object);
switch (property_id)
{
case PROP_CURVE_TYPE:
pika_curve_set_curve_type (curve, g_value_get_enum (value));
break;
case PROP_N_POINTS:
/* ignored */
break;
case PROP_POINTS:
{
PikaValueArray *array = g_value_get_boxed (value);
PikaCurvePoint *points;
gint length;
gint n_points;
gint i;
if (! array)
{
pika_curve_clear_points (curve);
break;
}
length = pika_value_array_length (array) / 2;
n_points = 0;
points = g_new0 (PikaCurvePoint, length);
for (i = 0; i < length; i++)
{
GValue *x = pika_value_array_index (array, i * 2);
GValue *y = pika_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 = PIKA_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:
{
PikaValueArray *array = g_value_get_boxed (value);
PikaCurvePoint *points;
gint length;
gdouble x = 0.0;
gdouble y = 0.0;
gint i;
if (! array)
{
pika_curve_clear_points (curve);
break;
}
length = pika_value_array_length (array);
points = g_new0 (PikaCurvePoint, length);
for (i = 0; i < length; i++)
{
GValue *type = pika_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:
pika_curve_set_n_samples (curve, g_value_get_int (value));
break;
case PROP_SAMPLES:
{
PikaValueArray *array = g_value_get_boxed (value);
gint length;
gint i;
if (! array)
break;
length = pika_value_array_length (array);
for (i = 0; i < curve->n_samples && i < length; i++)
{
GValue *v = pika_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
pika_curve_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec)
{
PikaCurve *curve = PIKA_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:
{
PikaValueArray *array = pika_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);
pika_value_array_append (array, &v);
g_value_set_double (&v, curve->points[i].y);
pika_value_array_append (array, &v);
}
g_value_unset (&v);
g_value_take_boxed (value, array);
}
break;
case PROP_POINT_TYPES:
{
PikaValueArray *array = pika_value_array_new (curve->n_points);
GValue v = G_VALUE_INIT;
gint i;
g_value_init (&v, PIKA_TYPE_CURVE_POINT_TYPE);
for (i = 0; i < curve->n_points; i++)
{
g_value_set_enum (&v, curve->points[i].type);
pika_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:
{
PikaValueArray *array = pika_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]);
pika_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
pika_curve_get_memsize (PikaObject *object,
gint64 *gui_size)
{
PikaCurve *curve = PIKA_CURVE (object);
gint64 memsize = 0;
memsize += curve->n_points * sizeof (PikaCurvePoint);
memsize += curve->n_samples * sizeof (gdouble);
return memsize + PIKA_OBJECT_CLASS (parent_class)->get_memsize (object,
gui_size);
}
static void
pika_curve_get_preview_size (PikaViewable *viewable,
gint size,
gboolean popup,
gboolean dot_for_dot,
gint *width,
gint *height)
{
*width = size;
*height = size;
}
static gboolean
pika_curve_get_popup_size (PikaViewable *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 PikaTempBuf *
pika_curve_get_new_preview (PikaViewable *viewable,
PikaContext *context,
gint width,
gint height)
{
return NULL;
}
static gchar *
pika_curve_get_description (PikaViewable *viewable,
gchar **tooltip)
{
PikaCurve *curve = PIKA_CURVE (viewable);
return g_strdup_printf ("%s", pika_object_get_name (curve));
}
static void
pika_curve_dirty (PikaData *data)
{
PikaCurve *curve = PIKA_CURVE (data);
curve->identity = FALSE;
pika_curve_calculate (curve);
PIKA_DATA_CLASS (parent_class)->dirty (data);
}
static const gchar *
pika_curve_get_extension (PikaData *data)
{
return PIKA_CURVE_FILE_EXTENSION;
}
static void
pika_curve_data_copy (PikaData *data,
PikaData *src_data)
{
pika_data_freeze (data);
pika_config_copy (PIKA_CONFIG (src_data),
PIKA_CONFIG (data), 0);
pika_data_thaw (data);
}
static gboolean
pika_curve_serialize (PikaConfig *config,
PikaConfigWriter *writer,
gpointer data)
{
return pika_config_serialize_properties (config, writer);
}
static gboolean
pika_curve_deserialize (PikaConfig *config,
GScanner *scanner,
gint nest_level,
gpointer data)
{
gboolean success;
success = pika_config_deserialize_properties (config, scanner, nest_level);
PIKA_CURVE (config)->identity = FALSE;
return success;
}
static gboolean
pika_curve_equal (PikaConfig *a,
PikaConfig *b)
{
PikaCurve *a_curve = PIKA_CURVE (a);
PikaCurve *b_curve = PIKA_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 (PikaCurvePoint) * 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
_pika_curve_reset (PikaConfig *config)
{
pika_curve_reset (PIKA_CURVE (config), TRUE);
}
static gboolean
pika_curve_config_copy (PikaConfig *src,
PikaConfig *dest,
GParamFlags flags)
{
PikaCurve *src_curve = PIKA_CURVE (src);
PikaCurve *dest_curve = PIKA_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;
pika_config_sync (G_OBJECT (src), G_OBJECT (dest), flags);
dest_curve->identity = src_curve->identity;
pika_data_dirty (PIKA_DATA (dest));
return TRUE;
}
/* public functions */
PikaData *
pika_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 (PIKA_TYPE_CURVE,
"name", name,
NULL);
}
PikaData *
pika_curve_get_standard (void)
{
static PikaData *standard_curve = NULL;
if (! standard_curve)
{
standard_curve = pika_curve_new ("Standard");
pika_data_clean (standard_curve);
pika_data_make_internal (standard_curve,
"pika-curve-standard");
g_object_ref (standard_curve);
}
return standard_curve;
}
void
pika_curve_reset (PikaCurve *curve,
gboolean reset_type)
{
gint i;
g_return_if_fail (PIKA_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 (PikaCurvePoint, 2);
curve->points[0].x = 0.0;
curve->points[0].y = 0.0;
curve->points[0].type = PIKA_CURVE_POINT_SMOOTH;
curve->points[1].x = 1.0;
curve->points[1].y = 1.0;
curve->points[1].type = PIKA_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 = PIKA_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));
pika_data_dirty (PIKA_DATA (curve));
}
void
pika_curve_set_curve_type (PikaCurve *curve,
PikaCurveType curve_type)
{
g_return_if_fail (PIKA_IS_CURVE (curve));
if (curve->curve_type != curve_type)
{
pika_data_freeze (PIKA_DATA (curve));
g_object_freeze_notify (G_OBJECT (curve));
curve->curve_type = curve_type;
if (curve_type == PIKA_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 (PikaCurvePoint, 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 = PIKA_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
{
pika_curve_clear_points (curve);
}
g_object_notify_by_pspec (G_OBJECT (curve), obj_props[PROP_CURVE_TYPE]);
g_object_thaw_notify (G_OBJECT (curve));
pika_data_thaw (PIKA_DATA (curve));
}
}
PikaCurveType
pika_curve_get_curve_type (PikaCurve *curve)
{
g_return_val_if_fail (PIKA_IS_CURVE (curve), PIKA_CURVE_SMOOTH);
return curve->curve_type;
}
gint
pika_curve_get_n_points (PikaCurve *curve)
{
g_return_val_if_fail (PIKA_IS_CURVE (curve), 0);
return curve->n_points;
}
void
pika_curve_set_n_samples (PikaCurve *curve,
gint n_samples)
{
g_return_if_fail (PIKA_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 == PIKA_CURVE_FREE)
curve->identity = TRUE;
g_object_thaw_notify (G_OBJECT (curve));
}
}
gint
pika_curve_get_n_samples (PikaCurve *curve)
{
g_return_val_if_fail (PIKA_IS_CURVE (curve), 0);
return curve->n_samples;
}
gint
pika_curve_get_point_at (PikaCurve *curve,
gdouble x)
{
gint closest_point = -1;
gdouble distance = EPSILON;
gint i;
g_return_val_if_fail (PIKA_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
pika_curve_get_closest_point (PikaCurve *curve,
gdouble x,
gdouble y,
gdouble max_distance)
{
gint closest_point = -1;
gdouble distance2 = G_MAXDOUBLE;
gint i;
g_return_val_if_fail (PIKA_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
pika_curve_add_point (PikaCurve *curve,
gdouble x,
gdouble y)
{
PikaCurvePoint *points;
gint point;
g_return_val_if_fail (PIKA_IS_CURVE (curve), -1);
if (curve->curve_type == PIKA_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 (PikaCurvePoint, curve->n_points + 1);
memcpy (points, curve->points,
point * sizeof (PikaCurvePoint));
memcpy (points + point + 1, curve->points + point,
(curve->n_points - point) * sizeof (PikaCurvePoint));
points[point].x = x;
points[point].y = y;
points[point].type = PIKA_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]);
pika_data_dirty (PIKA_DATA (curve));
return point;
}
void
pika_curve_delete_point (PikaCurve *curve,
gint point)
{
PikaCurvePoint *points;
g_return_if_fail (PIKA_IS_CURVE (curve));
g_return_if_fail (point >= 0 && point < curve->n_points);
points = g_new0 (PikaCurvePoint, curve->n_points - 1);
memcpy (points, curve->points,
point * sizeof (PikaCurvePoint));
memcpy (points + point, curve->points + point + 1,
(curve->n_points - point - 1) * sizeof (PikaCurvePoint));
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]);
pika_data_dirty (PIKA_DATA (curve));
}
void
pika_curve_set_point (PikaCurve *curve,
gint point,
gdouble x,
gdouble y)
{
g_return_if_fail (PIKA_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]);
pika_data_dirty (PIKA_DATA (curve));
}
void
pika_curve_move_point (PikaCurve *curve,
gint point,
gdouble y)
{
g_return_if_fail (PIKA_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]);
pika_data_dirty (PIKA_DATA (curve));
}
void
pika_curve_get_point (PikaCurve *curve,
gint point,
gdouble *x,
gdouble *y)
{
g_return_if_fail (PIKA_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
pika_curve_set_point_type (PikaCurve *curve,
gint point,
PikaCurvePointType type)
{
g_return_if_fail (PIKA_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]);
pika_data_dirty (PIKA_DATA (curve));
}
PikaCurvePointType
pika_curve_get_point_type (PikaCurve *curve,
gint point)
{
g_return_val_if_fail (PIKA_IS_CURVE (curve), PIKA_CURVE_POINT_SMOOTH);
g_return_val_if_fail (point >= 0 && point < curve->n_points, PIKA_CURVE_POINT_SMOOTH);
return curve->points[point].type;
}
void
pika_curve_clear_points (PikaCurve *curve)
{
g_return_if_fail (PIKA_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]);
pika_data_dirty (PIKA_DATA (curve));
}
}
void
pika_curve_set_curve (PikaCurve *curve,
gdouble x,
gdouble y)
{
g_return_if_fail (PIKA_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 == PIKA_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]);
pika_data_dirty (PIKA_DATA (curve));
}
/**
* pika_curve_is_identity:
* @curve: a #PikaCurve 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
pika_curve_is_identity (PikaCurve *curve)
{
g_return_val_if_fail (PIKA_IS_CURVE (curve), FALSE);
return curve->identity;
}
void
pika_curve_get_uchar (PikaCurve *curve,
gint n_samples,
guchar *samples)
{
gint i;
g_return_if_fail (PIKA_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
pika_curve_calculate (PikaCurve *curve)
{
gint i;
gint p1, p2, p3, p4;
if (pika_data_is_frozen (PIKA_DATA (curve)))
return;
switch (curve->curve_type)
{
case PIKA_CURVE_SMOOTH:
/* Initialize boundary curve points */
if (curve->n_points > 0)
{
PikaCurvePoint 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 == PIKA_CURVE_POINT_CORNER)
p1 = p2;
if (curve->points[p3].type == PIKA_CURVE_POINT_CORNER)
p4 = p3;
pika_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 PIKA_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
pika_curve_plot (PikaCurve *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);
}
}