gimp/app/core/gimpcurve.c

889 lines
24 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 2 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, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#include "config.h"
#include <stdlib.h>
#include <string.h>
#include <glib-object.h>
#include "libgimpmath/gimpmath.h"
#include "core-types.h"
#include "gimpcurve.h"
#include "gimpcurve-load.h"
#include "gimpcurve-save.h"
#include "gimp-intl.h"
enum
{
PROP_0,
PROP_CURVE_TYPE,
PROP_N_POINTS,
PROP_POINTS,
PROP_N_SAMPLES,
PROP_SAMPLES
};
/* local function prototypes */
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 TempBuf * 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 gchar * gimp_curve_get_extension (GimpData *data);
static GimpData * gimp_curve_duplicate (GimpData *data);
static void gimp_curve_set_n_points (GimpCurve *curve,
gint n_points);
static void gimp_curve_set_n_samples (GimpCurve *curve,
gint n_samples);
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 (GimpCurve, gimp_curve, GIMP_TYPE_DATA)
#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);
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_stock_id = "FIXME";
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->duplicate = gimp_curve_duplicate;
g_object_class_install_property (object_class, PROP_CURVE_TYPE,
g_param_spec_enum ("curve-type", NULL, NULL,
GIMP_TYPE_CURVE_TYPE,
GIMP_CURVE_SMOOTH,
GIMP_PARAM_READWRITE |
G_PARAM_CONSTRUCT));
g_object_class_install_property (object_class, PROP_N_POINTS,
g_param_spec_int ("n-points", NULL, NULL,
17, 17, 17,
GIMP_PARAM_READWRITE |
G_PARAM_CONSTRUCT_ONLY));
g_object_class_install_property (object_class, PROP_POINTS,
g_param_spec_boolean ("points", NULL, NULL,
FALSE,
GIMP_PARAM_READWRITE |
G_PARAM_CONSTRUCT));
g_object_class_install_property (object_class, PROP_N_SAMPLES,
g_param_spec_int ("n-samples", NULL, NULL,
256, 256, 256,
GIMP_PARAM_READWRITE |
G_PARAM_CONSTRUCT_ONLY));
g_object_class_install_property (object_class, PROP_SAMPLES,
g_param_spec_boolean ("samples", NULL, NULL,
FALSE,
GIMP_PARAM_READWRITE |
G_PARAM_CONSTRUCT));
}
static void
gimp_curve_init (GimpCurve *curve)
{
}
static void
gimp_curve_finalize (GObject *object)
{
GimpCurve *curve = GIMP_CURVE (object);
if (curve->points)
{
g_free (curve->points);
curve->points = NULL;
}
if (curve->samples)
{
g_free (curve->samples);
curve->samples = NULL;
}
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:
gimp_curve_set_n_points (curve, g_value_get_int (value));
break;
case PROP_POINTS:
break;
case PROP_N_SAMPLES:
gimp_curve_set_n_samples (curve, g_value_get_int (value));
break;
case PROP_SAMPLES:
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:
break;
case PROP_N_SAMPLES:
g_value_set_int (value, curve->n_samples);
break;
case PROP_SAMPLES:
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 (GimpVector2);
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)
{
GimpCurve *curve = GIMP_CURVE (viewable);
*popup_width = width * 2;
*popup_height = height * 2;
return TRUE;
}
static TempBuf *
gimp_curve_get_new_preview (GimpViewable *viewable,
GimpContext *context,
gint width,
gint height)
{
GimpCurve *curve = GIMP_CURVE (viewable);
return NULL;
}
static gchar *
gimp_curve_get_description (GimpViewable *viewable,
gchar **tooltip)
{
GimpCurve *curve = GIMP_CURVE (viewable);
return g_strdup_printf ("%s", GIMP_OBJECT (curve)->name);
}
static void
gimp_curve_dirty (GimpData *data)
{
gimp_curve_calculate (GIMP_CURVE (data));
GIMP_DATA_CLASS (parent_class)->dirty (data);
}
static gchar *
gimp_curve_get_extension (GimpData *data)
{
return GIMP_CURVE_FILE_EXTENSION;
}
static GimpData *
gimp_curve_duplicate (GimpData *data)
{
GimpCurve *curve = GIMP_CURVE (data);
GimpCurve *new;
new = g_object_new (GIMP_TYPE_CURVE,
"curve-type", curve->curve_type,
NULL);
return GIMP_DATA (new);
}
/* 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");
standard_curve->dirty = FALSE;
gimp_data_make_internal (standard_curve);
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");
curve->points[0].x = 0.0;
curve->points[0].y = 0.0;
for (i = 1; i < curve->n_points - 1; i++)
{
curve->points[i].x = -1.0;
curve->points[i].y = -1.0;
}
curve->points[curve->n_points - 1].x = 1.0;
curve->points[curve->n_points - 1].y = 1.0;
g_object_notify (G_OBJECT (curve), "points");
if (reset_type)
{
curve->curve_type = GIMP_CURVE_SMOOTH;
g_object_notify (G_OBJECT (curve), "curve-type");
}
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)
{
g_object_freeze_notify (G_OBJECT (curve));
curve->curve_type = curve_type;
if (curve_type == GIMP_CURVE_SMOOTH)
{
gint n_points;
gint i;
for (i = 0; i < curve->n_points; i++)
{
curve->points[i].x = -1;
curve->points[i].y = -1;
}
/* pick some points from the curve and make them control
* points
*/
n_points = CLAMP (9, curve->n_points / 2, curve->n_points);
for (i = 0; i < n_points; i++)
{
gint sample = i * (curve->n_samples - 1) / (n_points - 1);
gint point = i * (curve->n_points - 1) / (n_points - 1);
curve->points[point].x = ((gdouble) sample /
(gdouble) (curve->n_samples - 1));
curve->points[point].y = curve->samples[sample];
}
g_object_notify (G_OBJECT (curve), "points");
}
g_object_notify (G_OBJECT (curve), "curve-type");
g_object_thaw_notify (G_OBJECT (curve));
gimp_data_dirty (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;
}
static void
gimp_curve_set_n_points (GimpCurve *curve,
gint n_points)
{
g_return_if_fail (GIMP_IS_CURVE (curve));
if (n_points != curve->n_points)
{
gint i;
g_object_freeze_notify (G_OBJECT (curve));
curve->n_points = n_points;
g_object_notify (G_OBJECT (curve), "n-points");
curve->points = g_renew (GimpVector2, curve->points, curve->n_points);
curve->points[0].x = 0.0;
curve->points[0].y = 0.0;
for (i = 1; i < curve->n_points - 1; i++)
{
curve->points[i].x = -1.0;
curve->points[i].y = -1.0;
}
curve->points[curve->n_points - 1].x = 1.0;
curve->points[curve->n_points - 1].y = 1.0;
g_object_notify (G_OBJECT (curve), "points");
g_object_thaw_notify (G_OBJECT (curve));
}
}
gint
gimp_curve_get_n_points (GimpCurve *curve)
{
g_return_val_if_fail (GIMP_IS_CURVE (curve), 0);
return curve->n_points;
}
static void
gimp_curve_set_n_samples (GimpCurve *curve,
gint n_samples)
{
g_return_if_fail (GIMP_IS_CURVE (curve));
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");
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_closest_point (GimpCurve *curve,
gdouble x)
{
gint closest_point = 0;
gdouble distance = G_MAXDOUBLE;
gint i;
g_return_val_if_fail (GIMP_IS_CURVE (curve), 0);
for (i = 0; i < curve->n_points; i++)
{
if (curve->points[i].x >= 0.0 &&
fabs (x - curve->points[i].x) < distance)
{
distance = fabs (x - curve->points[i].x);
closest_point = i;
}
}
if (distance > (1.0 / (curve->n_points * 2.0)))
closest_point = ROUND (x * (gdouble) (curve->n_points - 1));
return closest_point;
}
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);
g_return_if_fail (x == -1.0 || (x >= 0 && x <= 1.0));
g_return_if_fail (y == -1.0 || (y >= 0 && y <= 1.0));
if (curve->curve_type == GIMP_CURVE_FREE)
return;
g_object_freeze_notify (G_OBJECT (curve));
curve->points[point].x = x;
curve->points[point].y = y;
g_object_notify (G_OBJECT (curve), "points");
g_object_thaw_notify (G_OBJECT (curve));
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);
g_return_if_fail (y >= 0 && y <= 1.0);
if (curve->curve_type == GIMP_CURVE_FREE)
return;
g_object_freeze_notify (G_OBJECT (curve));
curve->points[point].y = y;
g_object_notify (G_OBJECT (curve), "points");
g_object_thaw_notify (G_OBJECT (curve));
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 (curve->curve_type == GIMP_CURVE_FREE)
return;
if (x) *x = curve->points[point].x;
if (y) *y = curve->points[point].y;
}
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;
g_object_freeze_notify (G_OBJECT (curve));
curve->samples[ROUND (x * (gdouble) (curve->n_samples - 1))] = y;
g_object_notify (G_OBJECT (curve), "samples");
g_object_thaw_notify (G_OBJECT (curve));
gimp_data_dirty (GIMP_DATA (curve));
}
gdouble
gimp_curve_map (GimpCurve *curve,
gdouble x)
{
gdouble value;
g_return_val_if_fail (GIMP_IS_CURVE (curve), 0.0);
if (x < 0.0)
{
value = curve->samples[0];
}
else if (x >= 1.0)
{
value = curve->samples[curve->n_samples - 1];
}
else /* interpolate the curve */
{
gint index = floor (x * (gdouble) (curve->n_samples - 1));
gdouble f = x * (gdouble) (curve->n_samples - 1) - index;
value = ((1.0 - f) * curve->samples[index ] +
f * curve->samples[index + 1]);
}
return value;
}
void
gimp_curve_get_uchar (GimpCurve *curve,
gint n_samples,
guchar *samples)
{
gint i;
g_return_if_fail (GIMP_IS_CURVE (curve));
#ifdef __GNUC__
#warning: FIXME: support n_samples != curve->n_samples
#endif
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 *points;
gint num_pts;
gint p1, p2, p3, p4;
if (GIMP_DATA (curve)->freeze_count > 0)
return;
points = g_newa (gint, curve->n_points);
switch (curve->curve_type)
{
case GIMP_CURVE_SMOOTH:
/* cycle through the curves */
num_pts = 0;
for (i = 0; i < curve->n_points; i++)
if (curve->points[i].x >= 0.0)
points[num_pts++] = i;
/* Initialize boundary curve points */
if (num_pts != 0)
{
GimpVector2 point;
gint boundary;
point = curve->points[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[points[num_pts - 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 < num_pts - 1; i++)
{
p1 = points[MAX (i - 1, 0)];
p2 = points[i];
p3 = points[i + 1];
p4 = points[MIN (i + 2, num_pts - 1)];
gimp_curve_plot (curve, p1, p2, p3, p4);
}
/* ensure that the control points are used exactly */
for (i = 0; i < num_pts; i++)
{
gdouble x = curve->points[points[i]].x;
gdouble y = curve->points[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 = 1/3*x0 + 2/3*x3 and x2 = 2/3*x0 + 1/3*x3
* this ensures that the x values increase linearily with the
* parameter t and enables us to skip the calculation of the x
* values altogehter - just calculate y(t) evenly spaced.
*/
dx = x3 - x0;
dy = y3 - y0;
g_return_if_fail (dx > 0);
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 homogenously distributed values for t, since x(t) increases linearily.
*/
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 = ROUND (x0 * (gdouble) (curve->n_samples - 1)) + i;
curve->samples[index] = CLAMP (y, 0.0, 1.0);
}
}