[gthumb] curves: completed the curve editor



commit 65cb47a8ccbd805bfe43af7f2bfbf233b69c8b2c
Author: Paolo Bacchilega <paobac src gnome org>
Date:   Sat Dec 27 00:16:26 2014 +0100

    curves: completed the curve editor

 extensions/file_tools/data/ui/curves-options.ui |    6 +-
 extensions/file_tools/gth-curve-editor.c        |  508 +++++++++++++++++++----
 extensions/file_tools/gth-curve-editor.h        |    5 +
 extensions/file_tools/gth-curve.c               |  526 ++++++++++++++++++-----
 extensions/file_tools/gth-curve.h               |   63 +++-
 extensions/file_tools/gth-file-tool-curves.c    |   71 ++--
 extensions/file_tools/gth-points.c              |   88 ++++
 extensions/file_tools/gth-points.h              |   15 +-
 8 files changed, 1040 insertions(+), 242 deletions(-)
---
diff --git a/extensions/file_tools/data/ui/curves-options.ui b/extensions/file_tools/data/ui/curves-options.ui
index 63ab110..8afd626 100644
--- a/extensions/file_tools/data/ui/curves-options.ui
+++ b/extensions/file_tools/data/ui/curves-options.ui
@@ -21,10 +21,10 @@
                 <property name="visible">True</property>
                 <property name="can_focus">False</property>
                 <property name="orientation">vertical</property>
-                <property name="spacing">6</property>
+                <property name="spacing">10</property>
                 <child>
                   <object class="GtkBox" id="curves_box">
-                    <property name="height_request">300</property>
+                    <property name="height_request">350</property>
                     <property name="visible">True</property>
                     <property name="can_focus">False</property>
                     <property name="orientation">vertical</property>
@@ -53,7 +53,7 @@
                   <packing>
                     <property name="expand">False</property>
                     <property name="fill">True</property>
-                    <property name="position">1</property>
+                    <property name="position">2</property>
                   </packing>
                 </child>
               </object>
diff --git a/extensions/file_tools/gth-curve-editor.c b/extensions/file_tools/gth-curve-editor.c
index e74bae4..ce5cabc 100644
--- a/extensions/file_tools/gth-curve-editor.c
+++ b/extensions/file_tools/gth-curve-editor.c
@@ -44,6 +44,13 @@ enum {
 };
 
 
+/* Signals */
+enum {
+        CHANGED,
+        LAST_SIGNAL
+};
+
+
 struct _GthCurveEditorPrivate {
        GthHistogram        *histogram;
        gulong               histogram_changed_event;
@@ -53,11 +60,19 @@ struct _GthCurveEditorPrivate {
        GtkWidget           *linear_histogram_button;
        GtkWidget           *logarithmic_histogram_button;
        GtkWidget           *channel_combo_box;
-       GthPoints            points[GTH_HISTOGRAM_N_CHANNELS];
        GthCurve            *curve[GTH_HISTOGRAM_N_CHANNELS];
+       GthPoint            *active_point;
+       int                  active_point_lower_limit;
+       int                  active_point_upper_limit;
+       GthPoint             cursor;
+       gboolean             dragging;
+       gboolean             paint_position;
 };
 
 
+static guint gth_curve_editor_signals[LAST_SIGNAL] = { 0 };
+
+
 G_DEFINE_TYPE (GthCurveEditor, gth_curve_editor, GTK_TYPE_BOX)
 
 
@@ -170,33 +185,46 @@ gth_curve_editor_class_init (GthCurveEditorClass *klass)
                                                            GTH_TYPE_HISTOGRAM_SCALE,
                                                            GTH_HISTOGRAM_SCALE_LOGARITHMIC,
                                                            G_PARAM_READWRITE));
+
+       /* signals */
+
+       gth_curve_editor_signals[CHANGED] =
+                g_signal_new ("changed",
+                              G_TYPE_FROM_CLASS (klass),
+                              G_SIGNAL_RUN_LAST,
+                              G_STRUCT_OFFSET (GthCurveEditorClass, changed),
+                              NULL, NULL,
+                              g_cclosure_marshal_VOID__VOID,
+                              G_TYPE_NONE,
+                              0);
 }
 
 
 #define convert_to_scale(scale_type, value) (((scale_type) == GTH_HISTOGRAM_SCALE_LOGARITHMIC) ? log (value) 
: (value))
-#define HISTOGRAM_TRANSPARENCY 0.25
+#define HISTOGRAM_TRANSPARENCY 0.20
 
 
 static void
 _cairo_set_source_color_from_channel (cairo_t *cr,
-                                     int      channel)
+                                     int      channel,
+                                     double   alpha)
 {
        switch (channel) {
        case GTH_HISTOGRAM_CHANNEL_VALUE:
        default:
-               cairo_set_source_rgba (cr, 0.8, 0.8, 0.8, HISTOGRAM_TRANSPARENCY);
+               cairo_set_source_rgba (cr, 0.8, 0.8, 0.8, alpha);
                break;
        case GTH_HISTOGRAM_CHANNEL_RED:
-               cairo_set_source_rgba (cr, 0.68, 0.18, 0.19, HISTOGRAM_TRANSPARENCY); /* #af2e31 */
+               cairo_set_source_rgba (cr, 0.68, 0.18, 0.19, alpha); /* #af2e31 */
                break;
        case GTH_HISTOGRAM_CHANNEL_GREEN:
-               cairo_set_source_rgba (cr, 0.33, 0.78, 0.30, HISTOGRAM_TRANSPARENCY); /* #55c74d */
+               cairo_set_source_rgba (cr, 0.33, 0.78, 0.30, alpha); /* #55c74d */
                break;
        case GTH_HISTOGRAM_CHANNEL_BLUE:
-               cairo_set_source_rgba (cr, 0.13, 0.54, 0.8, HISTOGRAM_TRANSPARENCY); /* #238acc */
+               cairo_set_source_rgba (cr, 0.13, 0.54, 0.8, alpha); /* #238acc */
                break;
        case GTH_HISTOGRAM_CHANNEL_ALPHA:
-               cairo_set_source_rgba (cr, 0.5, 0.5, 0.5, HISTOGRAM_TRANSPARENCY);
+               cairo_set_source_rgba (cr, 0.5, 0.5, 0.5, alpha);
                break;
        }
 }
@@ -216,7 +244,7 @@ gth_histogram_paint_channel (GthCurveEditor   *self,
        if (channel > gth_histogram_get_nchannels (self->priv->histogram))
                return;
 
-       _cairo_set_source_color_from_channel (cr, channel);
+       _cairo_set_source_color_from_channel (cr, channel, HISTOGRAM_TRANSPARENCY);
 
        cairo_save (cr);
        cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
@@ -247,11 +275,14 @@ gth_histogram_paint_channel (GthCurveEditor   *self,
 }
 
 
+#define GRID_LINES 8
+
+
 static void
-gth_histogram_paint_grid (GthCurveEditor *self,
-                         GtkStyleContext  *style_context,
-                         cairo_t          *cr,
-                         GtkAllocation    *allocation)
+gth_histogram_paint_grid (GthCurveEditor  *self,
+                         GtkStyleContext *style_context,
+                         cairo_t         *cr,
+                         GtkAllocation   *allocation)
 {
        GdkRGBA color;
        double  grid_step;
@@ -263,8 +294,8 @@ gth_histogram_paint_grid (GthCurveEditor *self,
                                            &color);
        cairo_set_line_width (cr, 0.5);
 
-       grid_step = (double) allocation->width / 8.0;
-       for (i = 0; i <= 8; i++) {
+       grid_step = (double) allocation->width / GRID_LINES;
+       for (i = 0; i <= GRID_LINES; i++) {
                int ofs = round (grid_step * i);
 
                cairo_set_source_rgba (cr, color.red, color.green, color.blue, (i == 4) ? 1.0 : 0.5);
@@ -273,8 +304,8 @@ gth_histogram_paint_grid (GthCurveEditor *self,
                cairo_stroke (cr);
        }
 
-       grid_step = (double) allocation->height / 8.0;
-       for (i = 0; i <= 8; i++) {
+       grid_step = (double) allocation->height / GRID_LINES;
+       for (i = 0; i <= GRID_LINES; i++) {
                int ofs = round (grid_step * i);
 
                cairo_set_source_rgba (cr, color.red, color.green, color.blue, (i == 4) ? 1.0 : 0.5);
@@ -320,6 +351,8 @@ gth_histogram_paint_points (GthCurveEditor  *self,
                           3.5,
                           0.0,
                           2 * M_PI);
+               if (&points->p[i] == self->priv->active_point)
+                       cairo_fill_preserve (cr);
                cairo_stroke (cr);
        }
 
@@ -335,41 +368,95 @@ gth_histogram_paint_curve (GthCurveEditor  *self,
                           GtkAllocation   *allocation)
 {
        double x_scale, y_scale;
-       int    i;
+       double i;
 
        cairo_save (cr);
+       cairo_set_antialias (cr, CAIRO_ANTIALIAS_DEFAULT);
        cairo_set_line_width (cr, 1.0);
-       cairo_set_source_rgba (cr, 1.0, 1.0, 1.0, 1.0);
 
        x_scale = (double) allocation->width / 255;
        y_scale = (double) allocation->height / 255;
-       for (i = 0; i < 255; i++) {
+       for (i = 0; i <= 256; i += 1) {
                int    j;
                double x, y;
 
                j = gth_curve_eval (spline, i);
-               x = round (allocation->x + (i * x_scale));
-               y = round (allocation->y + allocation->height - (j * y_scale));
+               x = allocation->x + (i * x_scale);
+               y = allocation->y + allocation->height - (j * y_scale);
 
-               if (x == 0)
+               if (i == 0)
                        cairo_move_to (cr, x, y);
                else
                        cairo_line_to (cr, x, y);
-
        }
        cairo_stroke (cr);
        cairo_restore (cr);
 }
 
 
+static void
+gth_histogram_paint_point_position (GthCurveEditor  *self,
+                                   GtkStyleContext *style_context,
+                                   cairo_t         *cr,
+                                   GthPoint        *point,
+                                   GtkAllocation   *allocation)
+{
+       char                 *text;
+       cairo_text_extents_t  extents;
+       int                   top = 9, left = 9, padding = 3;
+
+       if (point->x < 0 || point->y < 0)
+               return;
+
+       cairo_save (cr);
+
+       /* Translators: the first number is converted to the second number */
+       text = g_strdup_printf (_("%d → %d"), (int) point->x, (int) point->y);
+       cairo_text_extents (cr, text, &extents);
+
+       cairo_set_source_rgba (cr, 1.0, 1.0, 1.0, 0.5);
+       cairo_rectangle (cr, left - padding, top - padding, extents.width + 2*padding, extents.height + 
2*padding);
+       cairo_fill (cr);
+
+       cairo_set_source_rgba (cr, 0.0, 0.0, 0.0, 1.0);
+       cairo_move_to (cr, left - extents.x_bearing, top - extents.y_bearing);
+       cairo_show_text (cr, text);
+       g_free (text);
+
+       cairo_restore (cr);
+}
+
+
+static void
+gth_curve_editor_get_graph_area (GthCurveEditor *self,
+                                GtkAllocation  *area)
+{
+       GtkAllocation allocation;
+       GtkBorder     padding;
+
+       gtk_widget_get_allocation (GTK_WIDGET (self->priv->view), &allocation);
+
+       padding.left = 5;
+       padding.right = 5;
+       padding.top = 5;
+       padding.bottom = 5;
+
+       area->x = padding.left;
+       area->y = padding.top;
+       area->width = allocation.width - (padding.right + padding.left) - 1;
+       area->height = allocation.height - (padding.top + padding.bottom) - 1;
+
+}
+
+
 static gboolean
 curve_editor_draw_cb (GtkWidget *widget,
                      cairo_t   *cr,
                      gpointer   user_data)
 {
-       GthCurveEditor *self = user_data;
-       GtkAllocation     allocation;
-       GtkStyleContext  *style_context;
+       GthCurveEditor  *self = user_data;
+       GtkAllocation    allocation;
+       GtkStyleContext *style_context;
 
        style_context = gtk_widget_get_style_context (widget);
        gtk_style_context_save (style_context);
@@ -382,27 +469,48 @@ curve_editor_draw_cb (GtkWidget *widget,
        if ((self->priv->histogram != NULL)
            && ((int) self->priv->current_channel <= gth_histogram_get_nchannels (self->priv->histogram)))
        {
-               GtkBorder     padding;
-               GtkAllocation inner_allocation;
+               GtkAllocation  area;
+               int            c;
 
+               cairo_save (cr);
                cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
-
-               gtk_style_context_get_padding (style_context, gtk_widget_get_state_flags (widget), &padding);
-
-               padding.left = 5;
-               padding.right = 5;
-               padding.top = 5;
-               padding.bottom = 5;
-
-               inner_allocation.x = padding.left;
-               inner_allocation.y = padding.top;
-               inner_allocation.width = allocation.width - (padding.right + padding.left) - 1;
-               inner_allocation.height = allocation.height - (padding.top + padding.bottom) - 1;
-
-               gth_histogram_paint_channel (self, style_context, cr, self->priv->current_channel, 
&inner_allocation);
-               gth_histogram_paint_grid (self, style_context, cr, &inner_allocation);
-               gth_histogram_paint_points (self, style_context, cr, 
&self->priv->points[self->priv->current_channel], &inner_allocation);
-               gth_histogram_paint_curve (self, style_context, cr, 
self->priv->curve[self->priv->current_channel], &inner_allocation);
+               gth_curve_editor_get_graph_area (self, &area);
+               gth_histogram_paint_channel (self, style_context, cr, self->priv->current_channel, &area);
+               gth_histogram_paint_grid (self, style_context, cr, &area);
+
+               cairo_save (cr);
+               for (c = GTH_HISTOGRAM_CHANNEL_VALUE; c <= GTH_HISTOGRAM_CHANNEL_BLUE; c++) {
+                       GthCurve  *curve;
+                       GthPoints *points;
+                       GthPoint  *p;
+
+                       if (c == self->priv->current_channel)
+                               continue;
+
+                       curve = self->priv->curve[c];
+                       points = gth_curve_get_points (curve);
+                       p = points->p;
+
+                       /* do not paint unchanged curves */
+                       if ((points->n == 2) && (p[0].x == 0 && p[0].y == 0) && (p[1].x == 255 && p[1].y == 
255))
+                               continue;
+
+                       _cairo_set_source_color_from_channel (cr, c, 0.25);
+                       gth_histogram_paint_curve (self, style_context, cr, curve, &area);
+               }
+               _cairo_set_source_color_from_channel (cr, self->priv->current_channel, 1.0);
+               gth_histogram_paint_curve (self, style_context, cr, 
self->priv->curve[self->priv->current_channel], &area);
+               cairo_restore (cr);
+
+               gth_histogram_paint_points (self, style_context, cr, gth_curve_get_points 
(self->priv->curve[self->priv->current_channel]), &area);
+
+               if (self->priv->paint_position) {
+                       if (self->priv->active_point != NULL)
+                               gth_histogram_paint_point_position (self, style_context, cr, 
self->priv->active_point, &area);
+                       else
+                               gth_histogram_paint_point_position (self, style_context, cr, 
&self->priv->cursor, &area);
+               }
+               cairo_restore (cr);
        }
 
        gtk_style_context_restore (style_context);
@@ -413,8 +521,8 @@ curve_editor_draw_cb (GtkWidget *widget,
 
 static gboolean
 curve_editor_scroll_event_cb (GtkWidget      *widget,
-                               GdkEventScroll *event,
-                               gpointer        user_data)
+                             GdkEventScroll *event,
+                             gpointer        user_data)
 {
        GthCurveEditor *self = user_data;
        int               channel = 0;
@@ -434,18 +542,135 @@ curve_editor_scroll_event_cb (GtkWidget      *widget,
 }
 
 
+static void
+gth_curve_editor_get_point_from_event (GthCurveEditor *self,
+                                      GthPoint       *p,
+                                      double          x,
+                                      double          y)
+{
+       GtkAllocation area;
+
+       gth_curve_editor_get_graph_area (self, &area);
+       p->x = x - area.x;
+       p->y = area.height - (y - area.y);
+
+       p->x *= 255.0 / area.width;
+       p->y *= 255.0 / area.height;
+
+       p->x = round (p->x);
+       p->y = round (p->y);
+}
+
+
+#define POINT_ATTRACTION_THRESHOLD 10
+
+
+static void
+gth_curve_editor_get_nearest_point (GthCurveEditor  *self,
+                                   GthPoint        *p,
+                                   int             *n)
+{
+       double     min = 0;
+       GthPoints *points;
+       int        i;
+
+       *n = -1;
+       points = gth_curve_get_points (self->priv->curve[self->priv->current_channel]);
+       for (i = 0; i < points->n; i++) {
+               GthPoint *q = &points->p[i];
+               double    d;
+
+               d = sqrt (SQR (q->x - p->x) + SQR (q->y - p->y));
+               if ((d < POINT_ATTRACTION_THRESHOLD) && ((*n == -1) || (d < min))) {
+                       min = d;
+                       *n = i;
+               }
+       }
+}
+
+
+static void
+gth_curve_editor_set_active_point (GthCurveEditor *self,
+                                  int             n)
+{
+       GthCurve  *curve;
+       GthPoints *points;
+
+       curve = self->priv->curve[self->priv->current_channel];
+       points = gth_curve_get_points (curve);
+       if (n >= points->n)
+               n = -1;
+
+       if (n >= 0) {
+               self->priv->active_point = points->p + n;
+               self->priv->active_point_lower_limit = (n > 0) ? points->p[n-1].x + 1 : 0;
+               self->priv->active_point_upper_limit = (n < points->n-1) ? points->p[n+1].x - 1 : 255;
+       }
+       else
+               self->priv->active_point = NULL;
+}
+
+
+static void
+gth_curve_editor_changed (GthCurveEditor *self)
+{
+       g_signal_emit (self, gth_curve_editor_signals[CHANGED], 0);
+}
+
+
 static gboolean
 curve_editor_button_press_event_cb (GtkWidget      *widget,
                                    GdkEventButton *event,
                                    gpointer        user_data)
 {
        GthCurveEditor *self = user_data;
-       GtkAllocation   allocation;
-       int             value;
+       GthPoint        p;
+       int             n_active_point;
+
+       gth_curve_editor_get_point_from_event (self, &p, event->x, event->y);
+       gth_curve_editor_get_nearest_point (self, &p, &n_active_point);
+
+       if (event->button == 1) {
+               if (n_active_point < 0) {
+                       GthCurve  *curve;
+                       GthPoints *points;
 
-       gtk_widget_get_allocation (self->priv->view, &allocation);
-       value = CLAMP (event->x / allocation.width * 256 + .5, 0, 255);
-       /* FIXME */
+                       curve = self->priv->curve[self->priv->current_channel];
+                       points = gth_curve_get_points (curve);
+                       n_active_point = gth_points_add_point (points, p.x, p.y);
+                       gth_curve_setup (curve);
+                       gth_curve_editor_changed (self);
+               }
+
+               if (n_active_point >= 0) {
+                       GdkCursor *cursor;
+
+                       self->priv->dragging = TRUE;
+
+                       cursor = gdk_cursor_new_for_display (gtk_widget_get_display (self->priv->view), 
GDK_BLANK_CURSOR);
+                       gdk_window_set_cursor (gtk_widget_get_window (self->priv->view), cursor);
+                       g_object_unref (cursor);
+               }
+       }
+       else if (event->button == 3) {
+               if (n_active_point >= 0) {
+                       GthCurve  *curve;
+                       GthPoints *points;
+
+                       curve = self->priv->curve[self->priv->current_channel];
+                       points = gth_curve_get_points (curve);
+                       if (points->n > 2) {
+                               gth_points_delete_point (points, n_active_point);
+                               n_active_point = -1;
+                               gth_curve_setup (curve);
+                               gth_curve_editor_changed (self);
+                       }
+               }
+       }
+
+       gth_curve_editor_set_active_point (self, n_active_point);
+
+       gtk_widget_queue_draw (self->priv->view);
 
        return TRUE;
 }
@@ -453,10 +678,20 @@ curve_editor_button_press_event_cb (GtkWidget      *widget,
 
 static gboolean
 curve_editor_button_release_event_cb (GtkWidget      *widget,
-                                       GdkEventButton *event,
-                                       gpointer        user_data)
+                                     GdkEventButton *event,
+                                     gpointer        user_data)
 {
-       /* FIXME */
+       GthCurveEditor *self = user_data;
+
+       if (self->priv->dragging) {
+               GdkCursor *cursor;
+
+               cursor = gdk_cursor_new_for_display (gtk_widget_get_display (self->priv->view), 
GDK_CROSSHAIR);
+               gdk_window_set_cursor (gtk_widget_get_window (self->priv->view), cursor);
+               g_object_unref (cursor);
+       }
+
+       self->priv->dragging = FALSE;
 
        return TRUE;
 }
@@ -464,15 +699,64 @@ curve_editor_button_release_event_cb (GtkWidget      *widget,
 
 static gboolean
 curve_editor_motion_notify_event_cb (GtkWidget      *widget,
-                                      GdkEventMotion *event,
-                                      gpointer        user_data)
+                                    GdkEventMotion *event,
+                                    gpointer        user_data)
 {
-       /* FIXME */
+       GthCurveEditor *self = user_data;
+       GthPoint        p;
+
+       gth_curve_editor_get_point_from_event (self, &p, event->x, event->y);
+       self->priv->cursor.x = (p.x >= 0 && p.x <= 255) ? p.x : -1;
+       self->priv->cursor.y = (p.y >= 0 && p.y <= 255) ? p.y : -1;
+
+       if (self->priv->dragging) {
+               g_return_val_if_fail (self->priv->active_point != NULL, TRUE);
+               self->priv->active_point->x = CLAMP (p.x, self->priv->active_point_lower_limit, 
self->priv->active_point_upper_limit);
+               self->priv->active_point->y = CLAMP (p.y, 0, 255);
+               gth_curve_setup (self->priv->curve[self->priv->current_channel]);
+               gth_curve_editor_changed (self);
+       }
+       else {
+               int n_active_point;
+
+               gth_curve_editor_get_nearest_point (self, &p, &n_active_point);
+               gth_curve_editor_set_active_point (self, n_active_point);
+       }
+
+       self->priv->paint_position = TRUE;
+       gtk_widget_queue_draw (self->priv->view);
 
        return TRUE;
 }
 
 
+static gboolean
+curve_editor_leave_notify_event_cb (GtkWidget *widget,
+                                   GdkEvent  *event,
+                                   gpointer   user_data)
+{
+       GthCurveEditor *self = user_data;
+
+       self->priv->paint_position = FALSE;
+       gtk_widget_queue_draw (self->priv->view);
+
+       return FALSE;
+}
+
+
+static void
+curve_editor_realize_cb (GtkWidget *widget,
+                        gpointer   user_data)
+{
+       GthCurveEditor *self = user_data;
+       GdkCursor      *cursor;
+
+       cursor = gdk_cursor_new_for_display (gtk_widget_get_display (self->priv->view), GDK_CROSSHAIR);
+       gdk_window_set_cursor (gtk_widget_get_window (self->priv->view), cursor);
+       g_object_unref (cursor);
+}
+
+
 static void
 linear_histogram_button_toggled_cb (GtkToggleButton *button,
                                    gpointer   user_data)
@@ -509,6 +793,38 @@ channel_combo_box_changed_cb (GtkComboBox *combo_box,
 
 
 static void
+gth_curve_editor_reset_channel (GthCurveEditor      *self,
+                               GthHistogramChannel  c)
+{
+       GthCurve  *curve;
+       GthPoints *points;
+
+       curve = self->priv->curve[c];
+       points = gth_curve_get_points (curve);
+       gth_points_dispose (points);
+
+       gth_points_init (points, 2);
+       points->p[0].x = 0;
+       points->p[0].y = 0;
+       points->p[1].x = 255;
+       points->p[1].y = 255;
+       gth_curve_setup (curve);
+}
+
+
+static void
+reset_current_channel_button_clicked_cb (GtkButton *button,
+                                        gpointer   user_data)
+{
+       GthCurveEditor *self = user_data;
+
+       gth_curve_editor_reset_channel (self, self->priv->current_channel);
+       gth_curve_editor_changed (self);
+       gtk_widget_queue_draw (self->priv->view);
+}
+
+
+static void
 self_notify_current_channel_cb (GObject    *gobject,
                                GParamSpec *pspec,
                                gpointer    user_data)
@@ -542,15 +858,17 @@ gth_curve_editor_init (GthCurveEditor *self)
        GtkListStore    *channel_model;
        GtkCellRenderer *renderer;
        GtkTreeIter      iter;
+       GtkWidget       *button;
        int              c;
 
        self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, GTH_TYPE_CURVE_EDITOR, GthCurveEditorPrivate);
        self->priv->histogram = NULL;
        self->priv->current_channel = GTH_HISTOGRAM_CHANNEL_VALUE;
        self->priv->scale_type = GTH_HISTOGRAM_SCALE_LINEAR;
+
        for (c = 0; c < GTH_HISTOGRAM_N_CHANNELS; c++) {
-               gth_points_init (&self->priv->points[c], 0);
-               self->priv->curve[c] = NULL;
+               self->priv->curve[c] = gth_bezier_new (NULL);
+               gth_curve_editor_reset_channel (self, c);
        }
 
        gtk_orientable_set_orientation (GTK_ORIENTABLE (self), GTK_ORIENTATION_VERTICAL);
@@ -605,6 +923,7 @@ gth_curve_editor_init (GthCurveEditor *self)
        gtk_label_set_attributes (GTK_LABEL (label), attr_list);
        gtk_widget_show (label);
        gtk_box_pack_start (GTK_BOX (sub_box), label, FALSE, FALSE, 0);
+
        channel_model = gtk_list_store_new (2, G_TYPE_STRING, G_TYPE_BOOLEAN);
        self->priv->channel_combo_box = gtk_combo_box_new_with_model (GTK_TREE_MODEL (channel_model));
        g_object_unref (channel_model);
@@ -640,11 +959,6 @@ gth_curve_editor_init (GthCurveEditor *self)
                            CHANNEL_COLUMN_NAME, _("Blue"),
                            CHANNEL_COLUMN_SENSITIVE, TRUE,
                            -1);
-       gtk_list_store_append (channel_model, &iter);
-       gtk_list_store_set (channel_model, &iter,
-                           CHANNEL_COLUMN_NAME, _("Alpha"),
-                           CHANNEL_COLUMN_SENSITIVE, FALSE,
-                           -1);
 
        gtk_combo_box_set_active (GTK_COMBO_BOX (self->priv->channel_combo_box), self->priv->current_channel);
        gtk_widget_show (self->priv->channel_combo_box);
@@ -657,6 +971,18 @@ gth_curve_editor_init (GthCurveEditor *self)
 
        pango_attr_list_unref (attr_list);
 
+       /* reset channel button */
+
+       button = gtk_button_new ();
+       gtk_container_add (GTK_CONTAINER (button), gtk_image_new_from_icon_name ("edit-undo-symbolic", 
GTK_ICON_SIZE_BUTTON));
+       gtk_widget_show_all (button);
+       gtk_box_pack_start (GTK_BOX (sub_box), button, FALSE, FALSE, 0);
+
+       g_signal_connect (button,
+                         "clicked",
+                         G_CALLBACK (reset_current_channel_button_clicked_cb),
+                         self);
+
        /* histogram view */
 
        view_container = gtk_scrolled_window_new (NULL, NULL);
@@ -665,7 +991,14 @@ gth_curve_editor_init (GthCurveEditor *self)
        gtk_widget_show (view_container);
 
        self->priv->view = gtk_drawing_area_new ();
-       gtk_widget_add_events (self->priv->view, GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | 
GDK_POINTER_MOTION_MASK | GDK_STRUCTURE_MASK);
+       gtk_widget_add_events (self->priv->view,
+                              (GDK_BUTTON_PRESS_MASK
+                               | GDK_BUTTON_RELEASE_MASK
+                               | GDK_POINTER_MOTION_MASK
+                               | GDK_POINTER_MOTION_HINT_MASK
+                               | GDK_ENTER_NOTIFY_MASK
+                               | GDK_LEAVE_NOTIFY_MASK
+                               | GDK_STRUCTURE_MASK));
        gtk_widget_show (self->priv->view);
        gtk_container_add (GTK_CONTAINER (view_container), self->priv->view);
 
@@ -689,6 +1022,14 @@ gth_curve_editor_init (GthCurveEditor *self)
                          "motion-notify-event",
                          G_CALLBACK (curve_editor_motion_notify_event_cb),
                          self);
+       g_signal_connect (self->priv->view,
+                         "leave-notify-event",
+                         G_CALLBACK (curve_editor_leave_notify_event_cb),
+                         self);
+       g_signal_connect (self->priv->view,
+                         "realize",
+                         G_CALLBACK (curve_editor_realize_cb),
+                         self);
 
        /* pack the widget */
 
@@ -708,6 +1049,12 @@ gth_curve_editor_init (GthCurveEditor *self)
 
        /* default values */
 
+       self->priv->active_point = NULL;
+       self->priv->cursor.x = -1;
+       self->priv->cursor.y = -1;
+       self->priv->dragging = FALSE;
+       self->priv->paint_position = FALSE;
+
        gth_curve_editor_set_scale_type (self, GTH_HISTOGRAM_SCALE_LINEAR);
        gth_curve_editor_set_current_channel (self, GTH_HISTOGRAM_CHANNEL_VALUE);
 }
@@ -852,14 +1199,9 @@ gth_curve_editor_set_points (GthCurveEditor        *self,
 {
        int c;
 
-       for (c = 0; c < GTH_HISTOGRAM_N_CHANNELS; c++) {
-               gth_points_dispose (&self->priv->points[c]);
-               gth_points_copy (&points[c], &self->priv->points[c]);
-
-               _g_object_unref (self->priv->curve[c]);
-               self->priv->curve[c] = gth_cspline_new (&self->priv->points[c]);
-       }
-
+       for (c = 0; c < GTH_HISTOGRAM_N_CHANNELS; c++)
+               gth_curve_set_points (self->priv->curve[c], points + c);
+       gth_curve_editor_changed (self);
        gtk_widget_queue_draw (GTK_WIDGET (self));
 }
 
@@ -871,7 +1213,19 @@ gth_curve_editor_get_points (GthCurveEditor  *self,
        int c;
 
        for (c = 0; c < GTH_HISTOGRAM_N_CHANNELS; c++) {
-               gth_points_dispose (&points[c]);
-               gth_points_copy (&self->priv->points[c], &points[c]);
+               gth_points_dispose (points + c);
+               gth_points_copy (gth_curve_get_points (self->priv->curve[c]), &points[c]);
        }
 }
+
+
+void
+gth_curve_editor_reset (GthCurveEditor *self)
+{
+       int c;
+
+       for (c = GTH_HISTOGRAM_CHANNEL_VALUE; c <= GTH_HISTOGRAM_CHANNEL_BLUE; c++)
+               gth_curve_editor_reset_channel (self, c);
+       gth_curve_editor_changed (self);
+       gtk_widget_queue_draw (self->priv->view);
+}
diff --git a/extensions/file_tools/gth-curve-editor.h b/extensions/file_tools/gth-curve-editor.h
index ea0f941..7324f54 100644
--- a/extensions/file_tools/gth-curve-editor.h
+++ b/extensions/file_tools/gth-curve-editor.h
@@ -47,6 +47,10 @@ struct _GthCurveEditor {
 
 struct _GthCurveEditorClass {
        GtkBoxClass parent_class;
+
+       /*< signals >*/
+
+       void    (*changed)      (GthCurveEditor *self);
 };
 
 GType              gth_curve_editor_get_type           (void);
@@ -64,6 +68,7 @@ void             gth_curve_editor_set_points          (GthCurveEditor          *self,
                                                         GthPoints               *points);
 void              gth_curve_editor_get_points          (GthCurveEditor          *self,
                                                         GthPoints               *points);
+void              gth_curve_editor_reset               (GthCurveEditor          *self);
 
 G_END_DECLS
 
diff --git a/extensions/file_tools/gth-curve.c b/extensions/file_tools/gth-curve.c
index fff00e7..0789b27 100644
--- a/extensions/file_tools/gth-curve.c
+++ b/extensions/file_tools/gth-curve.c
@@ -27,21 +27,88 @@
 /* -- GthCurve -- */
 
 
-G_DEFINE_INTERFACE (GthCurve, gth_curve, 0)
+G_DEFINE_TYPE (GthCurve, gth_curve, G_TYPE_OBJECT)
+
+
+void
+gth_curve_set_points (GthCurve *curve,
+                     GthPoints *points)
+{
+       gth_points_copy (points, gth_curve_get_points (curve));
+       gth_curve_setup (curve);
+}
 
 
 static void
-gth_curve_default_init (GthCurveInterface *iface)
+gth_curve_finalize (GObject *object)
+{
+       GthCurve *spline;
+
+       spline = GTH_CURVE (object);
+       gth_points_dispose (&spline->points);
+
+       G_OBJECT_CLASS (gth_curve_parent_class)->finalize (object);
+}
+
+
+static void
+gth_curve_setup_base (GthCurve *self)
 {
        /* void */
 }
 
 
+static double
+gth_curve_eval_base (GthCurve *self,
+                    double    x)
+{
+       return x;
+}
+
+
+static void
+gth_curve_class_init (GthCurveClass *class)
+{
+        GObjectClass *object_class;
+
+        object_class = (GObjectClass*) class;
+        object_class->finalize = gth_curve_finalize;
+
+        class->setup = gth_curve_setup_base;
+        class->eval = gth_curve_eval_base;
+}
+
+
+static void
+gth_curve_init (GthCurve *self)
+{
+       gth_points_init (&self->points, 0);
+}
+
+
+GthPoints *
+gth_curve_get_points (GthCurve *curve)
+{
+       return &curve->points;
+}
+
+
+void
+gth_curve_setup (GthCurve *self)
+{
+       return GTH_CURVE_GET_CLASS (self)->setup (self);
+}
+
+
 double
 gth_curve_eval (GthCurve *self,
                double    x)
 {
-       return GTH_CURVE_GET_INTERFACE (self)->eval (self, x);
+       if (self->points.n > 0) {
+               x = MAX (x, self->points.p[0].x);
+               x = MIN (x, self->points.p[self->points.n - 1].x);
+       }
+       return GTH_CURVE_GET_CLASS (self)->eval (self, x);
 }
 
 
@@ -145,14 +212,7 @@ GJ_matrix_solve (Matrix *m, double *x)
 /* -- GthSpline 
(http://en.wikipedia.org/wiki/Spline_interpolation#Algorithm_to_find_the_interpolating_cubic_spline) -- */
 
 
-static void gth_spline_gth_curve_interface_init (GthCurveInterface *iface);
-
-
-G_DEFINE_TYPE_WITH_CODE (GthSpline,
-                         gth_spline,
-                         G_TYPE_OBJECT,
-                         G_IMPLEMENT_INTERFACE (GTH_TYPE_CURVE,
-                                               gth_spline_gth_curve_interface_init))
+G_DEFINE_TYPE (GthSpline, gth_spline, GTH_TYPE_CURVE)
 
 
 static void
@@ -161,8 +221,6 @@ gth_spline_finalize (GObject *object)
        GthSpline *spline;
 
        spline = GTH_SPLINE (object);
-
-       gth_points_dispose (&spline->points);
        g_free (spline->k);
 
        G_OBJECT_CLASS (gth_spline_parent_class)->finalize (object);
@@ -170,12 +228,45 @@ gth_spline_finalize (GObject *object)
 
 
 static void
-gth_spline_class_init (GthSplineClass *class)
+gth_spline_setup (GthCurve *curve)
 {
-        GObjectClass *object_class;
+       GthSpline  *spline;
+       GthPoints  *points;
+       int         n;
+       GthPoint   *p;
+       Matrix     *m;
+       double    **A;
+       int         i;
 
-        object_class = (GObjectClass*) class;
-        object_class->finalize = gth_spline_finalize;
+       spline = GTH_SPLINE (curve);
+       points = gth_curve_get_points (GTH_CURVE (spline));
+       n = points->n;
+       p = points->p;
+
+       spline->k = g_new (double, n + 1);
+       for (i = 0; i < n + 1; i++)
+               spline->k[i] = 1.0;
+
+       m = GJ_matrix_new (n+1, n+2);
+       A = m->v;
+       for (i = 1; i < n; i++) {
+               A[i][i-1] = 1.0 / (p[i].x - p[i-1].x);
+               A[i][i  ] = 2.0 * (1.0 / (p[i].x - p[i-1].x) + 1.0 / (p[i+1].x - p[i].x));
+               A[i][i+1] = 1.0 / (p[i+1].x - p[i].x);
+               A[i][n+1] = 3.0 * ( (p[i].y - p[i-1].y) / ((p[i].x - p[i-1].x) * (p[i].x - p[i-1].x)) + 
(p[i+1].y - p[i].y) / ((p[i+1].x - p[i].x) * (p[i+1].x - p[i].x)) );
+       }
+
+        A[0][0  ] = 2.0 / (p[1].x - p[0].x);
+        A[0][1  ] = 1.0 / (p[1].x - p[0].x);
+        A[0][n+1] = 3.0 * (p[1].y - p[0].y) / ((p[1].x - p[0].x) * (p[1].x - p[0].x));
+
+        A[n][n-1] = 1.0 / (p[n].x - p[n-1].x);
+        A[n][n  ] = 2.0 / (p[n].x - p[n-1].x);
+        A[n][n+1] = 3.0 * (p[n].y - p[n-1].y) / ((p[n].x - p[n-1].x) * (p[n].x - p[n-1].x));
+
+        spline->is_singular = GJ_matrix_solve (m, spline->k);
+
+       GJ_matrix_free (m);
 }
 
 
@@ -184,12 +275,14 @@ gth_spline_eval (GthCurve *curve,
                 double    x)
 {
        GthSpline *spline;
+       GthPoints *points;
        GthPoint  *p;
        double    *k;
        int        i;
 
        spline = GTH_SPLINE (curve);
-       p = spline->points.p;
+       points = gth_curve_get_points (GTH_CURVE (spline));
+       p = points->p;
        k = spline->k;
 
        if (spline->is_singular)
@@ -206,56 +299,29 @@ gth_spline_eval (GthCurve *curve,
 }
 
 
-void
-gth_spline_gth_curve_interface_init (GthCurveInterface *iface)
+static void
+gth_spline_class_init (GthSplineClass *class)
 {
-       iface->eval = gth_spline_eval;
+        GObjectClass  *object_class;
+        GthCurveClass *curve_class;
+
+        object_class = (GObjectClass*) class;
+        object_class->finalize = gth_spline_finalize;
+
+        curve_class = (GthCurveClass*) class;
+        curve_class->setup = gth_spline_setup;
+        curve_class->eval = gth_spline_eval;
 }
 
 
 static void
 gth_spline_init (GthSpline *spline)
 {
-       gth_points_init (&spline->points, 0);
        spline->k = NULL;
        spline->is_singular = FALSE;
 }
 
 
-static void
-gth_spline_setup (GthSpline *spline)
-{
-       int        n;
-       GthPoint  *p;
-       Matrix    *m;
-       double   **A;
-       int        i;
-
-       n = spline->points.n;
-       p = spline->points.p;
-       m = GJ_matrix_new (n+1, n+2);
-       A = m->v;
-       for (i = 1; i < n; i++) {
-               A[i][i-1] = 1.0 / (p[i].x - p[i-1].x);
-               A[i][i  ] = 2.0 * (1.0 / (p[i].x - p[i-1].x) + 1.0 / (p[i+1].x - p[i].x));
-               A[i][i+1] = 1.0 / (p[i+1].x - p[i].x);
-               A[i][n+1] = 3.0 * ( (p[i].y - p[i-1].y) / ((p[i].x - p[i-1].x) * (p[i].x - p[i-1].x)) + 
(p[i+1].y - p[i].y) / ((p[i+1].x - p[i].x) * (p[i+1].x - p[i].x)) );
-       }
-
-        A[0][0  ] = 2.0 / (p[1].x - p[0].x);
-        A[0][1  ] = 1.0 / (p[1].x - p[0].x);
-        A[0][n+1] = 3.0 * (p[1].y - p[0].y) / ((p[1].x - p[0].x) * (p[1].x - p[0].x));
-
-        A[n][n-1] = 1.0 / (p[n].x - p[n-1].x);
-        A[n][n  ] = 2.0 / (p[n].x - p[n-1].x);
-        A[n][n+1] = 3.0 * (p[n].y - p[n-1].y) / ((p[n].x - p[n-1].x) * (p[n].x - p[n-1].x));
-
-        spline->is_singular = GJ_matrix_solve (m, spline->k);
-
-       GJ_matrix_free (m);
-}
-
-
 GthCurve *
 gth_spline_new (GthPoints *points)
 {
@@ -263,11 +329,7 @@ gth_spline_new (GthPoints *points)
        int        i;
 
        spline = g_object_new (GTH_TYPE_SPLINE, NULL);
-       gth_points_copy (points, &spline->points);
-       spline->k = g_new (double, points->n + 1);
-       for (i = 0; i < points->n + 1; i++)
-               spline->k[i] = 1.0;
-       gth_spline_setup (spline);
+       gth_curve_set_points (GTH_CURVE (spline), points);
 
        return GTH_CURVE (spline);
 }
@@ -276,14 +338,7 @@ gth_spline_new (GthPoints *points)
 /* -- GthCSpline (https://en.wikipedia.org/wiki/Cubic_Hermite_spline) -- */
 
 
-static void gth_cspline_gth_curve_interface_init (GthCurveInterface *iface);
-
-
-G_DEFINE_TYPE_WITH_CODE (GthCSpline,
-                         gth_cspline,
-                         G_TYPE_OBJECT,
-                         G_IMPLEMENT_INTERFACE (GTH_TYPE_CURVE,
-                                               gth_cspline_gth_curve_interface_init))
+G_DEFINE_TYPE (GthCSpline, gth_cspline, GTH_TYPE_CURVE)
 
 
 static void
@@ -292,21 +347,126 @@ gth_cspline_finalize (GObject *object)
        GthCSpline *spline;
 
        spline = GTH_CSPLINE (object);
-
-       gth_points_dispose (&spline->points);
        g_free (spline->tangents);
 
        G_OBJECT_CLASS (gth_cspline_parent_class)->finalize (object);
 }
 
 
+static int number_sign (double x) {
+       return (x < 0) ? -1 : (x > 0) ? 1 : 0;
+}
+
+
 static void
-gth_cspline_class_init (GthCSplineClass *class)
+gth_cspline_setup (GthCurve *curve)
 {
-        GObjectClass *object_class;
+       GthCSpline *spline;
+       GthPoints  *points;
+       int         n;
+       GthPoint   *p;
+       double     *t;
+       int         k;
 
-        object_class = (GObjectClass*) class;
-        object_class->finalize = gth_cspline_finalize;
+       spline = GTH_CSPLINE (curve);
+       points = gth_curve_get_points (GTH_CURVE (spline));
+       n = points->n;
+       p = points->p;
+
+       t = spline->tangents = g_new (double, n);
+
+#if 0
+       /* Finite difference */
+
+       for (k = 0; k < n; k++) {
+               t[k] = 0;
+               if (k > 0)
+                       t[k] += (p[k].y - p[k-1].y) / (2 * (p[k].x - p[k-1].x));
+               if (k < n - 1)
+                       t[k] += (p[k+1].y - p[k].y) / (2 * (p[k+1].x - p[k].x));
+       }
+#endif
+
+#if 1
+       /* Catmull–Rom spline */
+
+       for (k = 0; k < n; k++) {
+               t[k] = 0;
+               if (k == 0) {
+                       t[k] = (p[k+1].y - p[k].y) / (p[k+1].x - p[k].x);
+               }
+               else if (k == n - 1) {
+                       t[k] = (p[k].y - p[k-1].y) / (p[k].x - p[k-1].x);
+               }
+               else
+                       t[k] = (p[k+1].y - p[k-1].y) / (p[k+1].x - p[k-1].x);
+       }
+#endif
+
+#if 0
+
+       /* Monotonic spline (Fritsch–Carlson method) 
(https://en.wikipedia.org/wiki/Monotone_cubic_interpolation) */
+
+       double *delta = g_new (double, n);
+       double *alfa = g_new (double, n);
+       double *beta = g_new (double, n);
+
+       for (k = 0; k < n - 1; k++)
+               delta[k] = (p[k+1].y - p[k].y) / (p[k+1].x - p[k].x);
+
+       t[0] = delta[0];
+       for (k = 1; k < n - 1; k++) {
+               if (number_sign (delta[k-1]) == number_sign (delta[k]))
+                       t[k] = (delta[k-1] + delta[k]) / 2.0;
+               else
+                       t[k] = 0;
+       }
+       t[n-1] = delta[n-2];
+
+       for (k = 0; k < n - 1; k++) {
+               if (delta[k] == 0) {
+                       t[k] = 0;
+                       t[k+1] = 0;
+               }
+       }
+
+       for (k = 0; k < n - 1; k++) {
+               if (delta[k] == 0)
+                       continue;
+               alfa[k] = t[k] / delta[k];
+               beta[k] = t[k+1] / delta[k];
+
+               if (alfa[k] > 3) {
+                       t[k] = 3 * delta[k];
+                       alfa[k] = 3;
+               }
+
+               if (beta[k] > 3) {
+                       t[k+1] = 3 * delta[k];
+                       beta[k] = 3;
+               }
+       }
+
+       for (k = 0; k < n - 1; k++) {
+               if (delta[k] == 0)
+                       continue;
+
+               if ((alfa[k] < 0) && (beta[k-1] < 0))
+                       t[k] = 0;
+
+               double v = SQR (alfa[k]) + SQR (beta[k]);
+               if (v > 9) {
+                       double tau = 3.0 / sqrt (v);
+                       t[k] = tau * alfa[k] * delta[k];
+                       t[k+1] = tau * beta[k] * delta[k];
+               }
+       }
+
+       g_free (beta);
+       g_free (alfa);
+       g_free (delta);
+
+#endif
 }
 
 
@@ -339,6 +499,7 @@ gth_cspline_eval (GthCurve *curve,
                  double    x)
 {
        GthCSpline *spline;
+       GthPoints  *points;
        GthPoint   *p;
        double     *t;
        int         k;
@@ -346,7 +507,8 @@ gth_cspline_eval (GthCurve *curve,
        double      y;
 
        spline = GTH_CSPLINE (curve);
-       p = spline->points.p;
+       points = gth_curve_get_points (GTH_CURVE (spline));
+       p = points->p;
        t = spline->tangents;
 
        for (k = 1; p[k].x < x; k++)
@@ -363,51 +525,217 @@ gth_cspline_eval (GthCurve *curve,
 }
 
 
-void
-gth_cspline_gth_curve_interface_init (GthCurveInterface *iface)
+static void
+gth_cspline_class_init (GthCSplineClass *class)
 {
-       iface->eval = gth_cspline_eval;
+        GObjectClass  *object_class;
+        GthCurveClass *curve_class;
+
+        object_class = (GObjectClass*) class;
+        object_class->finalize = gth_cspline_finalize;
+
+        curve_class = (GthCurveClass*) class;
+        curve_class->setup = gth_cspline_setup;
+        curve_class->eval = gth_cspline_eval;
 }
 
 
 static void
 gth_cspline_init (GthCSpline *spline)
 {
-       gth_points_init (&spline->points, 0);
        spline->tangents = NULL;
 }
 
 
+GthCurve *
+gth_cspline_new (GthPoints *points)
+{
+       GthCSpline *spline;
+       int         i;
+
+       spline = g_object_new (GTH_TYPE_CSPLINE, NULL);
+       gth_curve_set_points (GTH_CURVE (spline), points);
+
+       return GTH_CURVE (spline);
+}
+
+
+/* -- GthBezier -- */
+
+
+G_DEFINE_TYPE (GthBezier, gth_bezier, GTH_TYPE_CURVE)
+
+
+/* ---- */
+
+
 static void
-gth_cspline_setup (GthCSpline *spline)
+gth_bezier_finalize (GObject *object)
 {
-       double   *t;
-       GthPoint *p;
-       int       k;
+       GthBezier *spline;
 
-       /* calculate the tangents using the three-point difference */
+       spline = GTH_BEZIER (object);
+       g_free (spline->k);
 
-       t = spline->tangents = g_new (double, spline->points.n);
-       p = spline->points.p;
-       for (k = 0; k < spline->points.n; k++) {
-               t[k] = 0;
-               if (k > 0)
-                       t[k] += (p[k].y - p[k-1].y) / (2 * (p[k].x - p[k-1].x));
-               if (k < spline->points.n - 1)
-                       t[k] += (p[k+1].y - p[k].y) / (2 * (p[k+1].x - p[k].x));
+       G_OBJECT_CLASS (gth_bezier_parent_class)->finalize (object);
+}
+
+
+/*
+ * calculate the control points coordinates (only the y) relative to
+ * the interval [p1, p2]
+ *
+ * a: the point to the left of p1
+ * b: the point to the right of p2
+ * k: (output) the y values of the 4 points in the [p1, p2] interval
+ *
+ **/
+
+static void
+bezier_set_control_points (GthPoint *a,
+                          GthPoint *p1,
+                          GthPoint *p2,
+                          GthPoint *b,
+                          double   *k)
+{
+       double slope;
+       double c1_y = 0;
+       double c2_y = 0;
+
+       /*
+        * the x coordinates of the control points is fixed to:
+        *
+        * c1_x = 2/3 * p1->x + 1/3 * p2->x;
+        * c2_x = 1/3 * p1->x + 2/3 * p2->x;
+        *
+        * for a more complete explanation see here: 
https://git.gnome.org/browse/gimp/tree/app/core/gimpcurve.c?h=gimp-2-8#n976
+        *
+        **/
+
+       if ((a != NULL) && (b != NULL)) {
+               slope = (p2->y - a->y) / (p2->x - a->x);
+               c1_y = p1->y + slope * (p2->x - p1->x) / 3.0;
+
+               slope = (b->y - p1->y) / (b->x - p1->x);
+               c2_y = p2->y - slope * (p2->x - p1->x) / 3.0;
+       }
+       else if ((a == NULL) && (b != NULL)) {
+               slope = (b->y - p1->y) / (b->x - p1->x);
+               c2_y = p2->y - slope * (p2->x - p1->x) / 3.0;
+               c1_y = p1->y + (c2_y - p1->y) / 2.0;
+       }
+       else if ((a != NULL) && (b == NULL)) {
+               slope = (p2->y - a->y) / (p2->x - a->x);
+               c1_y = p1->y + slope * (p2->x - p1->x) / 3.0;
+               c2_y = p2->y + (c1_y - p2->y) / 2.0;
        }
+       else { /* if ((a == NULL) && (b == NULL)) */
+               c1_y = p1->y + (p2->y - p1->y) / 3.0;
+               c2_y = p1->y + (p2->y - p1->y) * 2.0 / 3.0;
+       }
+
+       k[0] = p1->y;
+       k[1] = c1_y;
+       k[2] = c2_y;
+       k[3] = p2->y;
 }
 
 
-GthCurve *
-gth_cspline_new (GthPoints *points)
+static void
+gth_bezier_setup (GthCurve *curve)
 {
-       GthCSpline *spline;
+       GthBezier  *spline;
+       GthPoints  *points;
+       int         n;
+       GthPoint   *p;
        int         i;
 
-       spline = g_object_new (GTH_TYPE_CSPLINE, NULL);
-       gth_points_copy (points, &spline->points);
-       gth_cspline_setup (spline);
+       spline = GTH_BEZIER (curve);
+       points = gth_curve_get_points (GTH_CURVE (spline));
+       n = points->n;
+       p = points->p;
+
+       spline->linear = (n <= 1);
+       if (spline->linear)
+               return;
+
+       spline->k = g_new (double, 4 * (n - 1)); /* 4 points for each interval */
+       for (i = 0; i < n - 1; i++) {
+               double   *k = spline->k + (i*4);
+               GthPoint *a;
+               GthPoint *b;
+
+               a = (i == 0) ? NULL: p + (i-1);
+               b = (i == n-2) ? NULL : p + (i+2);
+               bezier_set_control_points (a, p + i, p + (i+1), b, k);
+       }
+}
+
+
+static double
+gth_bezier_eval (GthCurve *curve,
+                double    x)
+{
+       GthBezier *spline;
+       GthPoints *points;
+       GthPoint  *p;
+       double    *k;
+       int        i;
+
+       spline = GTH_BEZIER (curve);
+
+       if (spline->linear)
+               return x;
+
+       points = gth_curve_get_points (GTH_CURVE (spline));
+       p = points->p;
+
+       for (i = 1; p[i].x < x; i++)
+               /* void */;
+       i--;
+       k = spline->k + (i*4); /* k: the 4 bezier points of the interval i */
+
+       double t = (x - p[i].x) / (p[i+1].x - p[i].x);
+       double t2 = t*t;
+       double s = 1.0 - t;
+       double s2 = s * s;
+       double y = round (s2*s*k[0] + 3*s2*t*k[1] + 3*s*t2*k[2] + t2*t*k[3]);
+
+       return CLAMP (y, 0, 255);
+}
+
+
+static void
+gth_bezier_class_init (GthBezierClass *class)
+{
+        GObjectClass  *object_class;
+        GthCurveClass *curve_class;
+
+        object_class = (GObjectClass*) class;
+        object_class->finalize = gth_bezier_finalize;
+
+        curve_class = (GthCurveClass*) class;
+        curve_class->setup = gth_bezier_setup;
+        curve_class->eval = gth_bezier_eval;
+}
+
+
+static void
+gth_bezier_init (GthBezier *spline)
+{
+       spline->k = NULL;
+       spline->linear = TRUE;
+}
+
+
+GthCurve *
+gth_bezier_new (GthPoints *points)
+{
+       GthBezier *spline;
+       int        i;
+
+       spline = g_object_new (GTH_TYPE_BEZIER, NULL);
+       gth_curve_set_points (GTH_CURVE (spline), points);
 
        return GTH_CURVE (spline);
 }
diff --git a/extensions/file_tools/gth-curve.h b/extensions/file_tools/gth-curve.h
index cd901e4..8b90d27 100644
--- a/extensions/file_tools/gth-curve.h
+++ b/extensions/file_tools/gth-curve.h
@@ -29,28 +29,42 @@
 
 G_BEGIN_DECLS
 
-/* GthCurve */
+
+/* -- GthCurve -- */
+
 
 #define GTH_TYPE_CURVE (gth_curve_get_type ())
 #define GTH_CURVE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTH_TYPE_CURVE, GthCurve))
+#define GTH_CURVE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTH_TYPE_CURVE, GthCurveClass))
 #define GTH_IS_CURVE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTH_TYPE_CURVE))
-#define GTH_CURVE_GET_INTERFACE(obj) (G_TYPE_INSTANCE_GET_INTERFACE ((obj), GTH_TYPE_CURVE, 
GthCurveInterface))
+#define GTH_IS_CURVE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTH_TYPE_CURVE))
+#define GTH_CURVE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTH_TYPE_CURVE, GthCurveClass))
 
-typedef struct _GthCurve GthCurve;
+typedef struct {
+       GObject    parent_instance;
+       GthPoints  points;
+} GthCurve;
 
 typedef struct {
-       GTypeInterface parent_iface;
+       GObjectClass parent_class;
 
        /*< virtual functions >*/
 
+       void    (*setup)        (GthCurve *curve);
        double  (*eval)         (GthCurve *curve, double x);
-} GthCurveInterface;
+} GthCurveClass;
 
 GType          gth_curve_get_type              (void);
+void           gth_curve_set_points            (GthCurve       *curve,
+                                                GthPoints      *points);
+GthPoints *    gth_curve_get_points            (GthCurve       *curve);
+void           gth_curve_setup                 (GthCurve       *self);
 double         gth_curve_eval                  (GthCurve       *curve,
                                                 double          x);
 
-/* GthSpline */
+
+/* -- GthSpline -- */
+
 
 #define GTH_TYPE_SPLINE (gth_spline_get_type ())
 #define GTH_SPLINE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTH_TYPE_SPLINE, GthSpline))
@@ -60,18 +74,19 @@ double              gth_curve_eval                  (GthCurve       *curve,
 #define GTH_SPLINE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTH_TYPE_SPLINE, GthSplineClass))
 
 typedef struct {
-       GObject    parent_instance;
-       GthPoints  points;
+       GthCurve   parent_instance;
        double    *k;
        gboolean   is_singular;
 } GthSpline;
 
-typedef GObjectClass GthSplineClass;
+typedef GthCurveClass GthSplineClass;
 
 GType          gth_spline_get_type             (void);
 GthCurve *     gth_spline_new                  (GthPoints      *points);
 
-/* GthCSpline */
+
+/* -- GthCSpline -- */
+
 
 #define GTH_TYPE_CSPLINE (gth_cspline_get_type ())
 #define GTH_CSPLINE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTH_TYPE_CSPLINE, GthCSpline))
@@ -81,16 +96,38 @@ GthCurve *  gth_spline_new                  (GthPoints      *points);
 #define GTH_CSPLINE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTH_TYPE_CSPLINE, GthCSplineClass))
 
 typedef struct {
-       GObject    parent_instance;
-       GthPoints  points;
+       GthCurve   parent_instance;
        double    *tangents;
 } GthCSpline;
 
-typedef GObjectClass GthCSplineClass;
+typedef GthCurveClass GthCSplineClass;
 
 GType          gth_cspline_get_type            (void);
 GthCurve *     gth_cspline_new                 (GthPoints      *points);
 
+
+/* -- GthBezier -- */
+
+
+#define GTH_TYPE_BEZIER (gth_bezier_get_type ())
+#define GTH_BEZIER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTH_TYPE_BEZIER, GthBezier))
+#define GTH_BEZIER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTH_TYPE_BEZIER, GthBezierClass))
+#define GTH_IS_BEZIER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTH_TYPE_BEZIER))
+#define GTH_IS_BEZIER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTH_TYPE_BEZIER))
+#define GTH_BEZIER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTH_TYPE_BEZIER, GthBezierClass))
+
+typedef struct {
+       GthCurve   parent_instance;
+       double    *k;
+       gboolean   linear;
+} GthBezier;
+
+typedef GthCurveClass GthBezierClass;
+
+GType          gth_bezier_get_type             (void);
+GthCurve *     gth_bezier_new                  (GthPoints      *points);
+
+
 G_END_DECLS
 
 #endif /* GTH_CURVE_H */
diff --git a/extensions/file_tools/gth-file-tool-curves.c b/extensions/file_tools/gth-file-tool-curves.c
index 6524022..6b5bd86 100644
--- a/extensions/file_tools/gth-file-tool-curves.c
+++ b/extensions/file_tools/gth-file-tool-curves.c
@@ -47,7 +47,6 @@ struct _GthFileToolCurvesPrivate {
        gboolean            view_original;
        gboolean            apply_to_original;
        gboolean            closing;
-       GthPoints           points[GTH_HISTOGRAM_N_CHANNELS];
        GtkWidget          *curve_editor;
 };
 
@@ -68,10 +67,14 @@ curves_setup (TaskData        *task_data,
        long **value_map;
        int    c, v;
 
-       for (c = 0; c < GTH_HISTOGRAM_N_CHANNELS; c++) {
+       for (c = GTH_HISTOGRAM_CHANNEL_VALUE; c <= GTH_HISTOGRAM_CHANNEL_BLUE; c++) {
                task_data->value_map[c] = g_new (long, 256);
-               for (v = 0; v <= 255; v++)
-                       task_data->value_map[c][v] = gth_curve_eval (task_data->curve[c], v);
+               for (v = 0; v <= 255; v++) {
+                       double u = gth_curve_eval (task_data->curve[c], v);
+                       if (c > GTH_HISTOGRAM_CHANNEL_VALUE)
+                               u = task_data->value_map[GTH_HISTOGRAM_CHANNEL_VALUE][(int)u];
+                       task_data->value_map[c][v] = u;
+               }
        }
 }
 
@@ -224,7 +227,7 @@ task_data_new (GthPoints *points)
        task_data = g_new (TaskData, 1);
        for (c = 0; c < GTH_HISTOGRAM_N_CHANNELS; c++) {
                task_data->value_map[c] = NULL;
-               task_data->curve[c] = gth_cspline_new (&points[c]);
+               task_data->curve[c] = gth_bezier_new (&points[c]);
        }
 
        return task_data;
@@ -253,6 +256,8 @@ apply_cb (gpointer user_data)
 {
        GthFileToolCurves *self = user_data;
        GtkWidget         *window;
+       GthPoints          points[GTH_HISTOGRAM_N_CHANNELS];
+       int                c;
        TaskData          *task_data;
 
        if (self->priv->apply_event != 0) {
@@ -267,7 +272,10 @@ apply_cb (gpointer user_data)
 
        window = gth_file_tool_get_window (GTH_FILE_TOOL (self));
 
-       task_data = task_data_new (self->priv->points);
+       for (c = 0; c <= GTH_HISTOGRAM_N_CHANNELS; c++)
+               gth_points_init (points + c, 0);
+       gth_curve_editor_get_points (GTH_CURVE_EDITOR (self->priv->curve_editor), points);
+       task_data = task_data_new (points);
        self->priv->image_task =  gth_image_task_new (_("Applying changes"),
                                                      NULL,
                                                      curves_exec,
@@ -305,23 +313,15 @@ reset_button_clicked_cb (GtkButton *button,
                         gpointer   user_data)
 {
        GthFileToolCurves *self = user_data;
-       int                c;
-
-       for (c = 0; c < GTH_HISTOGRAM_N_CHANNELS; c++) {
-               gth_points_init (&self->priv->points[c], 3);
-
-               self->priv->points[c].p[0].x = 0;
-               self->priv->points[c].p[0].y = 0;
-
-               self->priv->points[c].p[1].x = 128;
-               self->priv->points[c].p[1].y = 128;
+       gth_curve_editor_reset (GTH_CURVE_EDITOR (self->priv->curve_editor));
+}
 
-               self->priv->points[c].p[2].x = 255;
-               self->priv->points[c].p[2].y = 255;
-       }
-       gth_curve_editor_set_points (GTH_CURVE_EDITOR (self->priv->curve_editor), self->priv->points);
 
-       /* FIXME: remove after adding the 'changed' signal to GthCurveEditor */
+static void
+curve_editor_changed_cb (GthCurveEditor *curve_editor,
+                        gpointer        user_data)
+{
+       GthFileToolCurves *self = user_data;
        apply_changes (self);
 }
 
@@ -383,10 +383,14 @@ gth_file_tool_curves_get_options (GthFileTool *base)
        gtk_widget_show (options);
 
        self->priv->curve_editor = gth_curve_editor_new (self->priv->histogram);
-       gth_curve_editor_set_points (GTH_CURVE_EDITOR (self->priv->curve_editor), self->priv->points);
        gtk_widget_show (self->priv->curve_editor);
        gtk_box_pack_start (GTK_BOX (GET_WIDGET ("curves_box")), self->priv->curve_editor, TRUE, TRUE, 0);
 
+       g_signal_connect (self->priv->curve_editor,
+                         "changed",
+                         G_CALLBACK (curve_editor_changed_cb),
+                         self);
+
        g_signal_connect (GET_WIDGET ("preview_checkbutton"),
                          "toggled",
                          G_CALLBACK (preview_checkbutton_toggled_cb),
@@ -496,29 +500,6 @@ gth_file_tool_curves_init (GthFileToolCurves *self)
        self->priv->view_original = FALSE;
        self->priv->histogram = gth_histogram_new ();
 
-       for (c = 0; c < GTH_HISTOGRAM_N_CHANNELS; c++) {
-               /* gth_points_init (&self->priv->points[c], 0); FIXME */
-               gth_points_init (&self->priv->points[c], 4);
-
-               self->priv->points[c].p[0].x = 0;
-               self->priv->points[c].p[0].y = 0;
-
-               /*self->priv->points[c].p[1].x = 100;
-               self->priv->points[c].p[1].y = 54;
-
-               self->priv->points[c].p[2].x = 161;
-               self->priv->points[c].p[2].y = 190;*/
-
-               self->priv->points[c].p[1].x = 127;
-               self->priv->points[c].p[1].y = 95;
-
-               self->priv->points[c].p[2].x = 183;
-               self->priv->points[c].p[2].y = 216;
-
-               self->priv->points[c].p[3].x = 255;
-               self->priv->points[c].p[3].y = 255;
-       }
-
        gth_file_tool_construct (GTH_FILE_TOOL (self), "curves-symbolic", _("Curves"), 
GTH_TOOLBOX_SECTION_COLORS);
        gtk_widget_set_tooltip_text (GTK_WIDGET (self), _("Adjust color curves"));
 }
diff --git a/extensions/file_tools/gth-points.c b/extensions/file_tools/gth-points.c
index eef3dc6..8d4c620 100644
--- a/extensions/file_tools/gth-points.c
+++ b/extensions/file_tools/gth-points.c
@@ -47,9 +47,97 @@ gth_points_copy (GthPoints *source,
 {
        int i;
 
+       if (source == NULL) {
+               gth_points_init (dest, 0);
+               return;
+       }
+
        gth_points_init (dest, source->n);
        for (i = 0; i < source->n; i++) {
                dest->p[i].x = source->p[i].x;
                dest->p[i].y = source->p[i].y;
        }
 }
+
+
+int
+gth_points_add_point (GthPoints        *points,
+                     double     x,
+                     double     y)
+{
+       GthPoint *old_p;
+       int       old_n;
+       gboolean  add_point;
+       int       i, old_i;
+       int       new_point_pos = -1;
+
+       old_p = points->p;
+       old_n = points->n;
+
+       add_point = TRUE;
+       for (old_i = 0; old_i < old_n; old_i++) {
+               if (old_p[old_i].x == x) {
+                       old_p[old_i].y = y;
+                       add_point = FALSE;
+                       new_point_pos = old_i;
+                       break;
+               }
+       }
+
+       if (add_point) {
+               points->n = old_n + 1;
+               points->p = g_new (GthPoint, points->n);
+
+               for (i = 0, old_i = 0; (i < points->n) && (old_i < old_n); /* void */) {
+                       if (old_p[old_i].x < x) {
+                               points->p[i].x = old_p[old_i].x;
+                               points->p[i].y = old_p[old_i].y;
+                               i++;
+                               old_i++;
+                       }
+                       else
+                               break;
+               }
+
+               points->p[i].x = x;
+               points->p[i].y = y;
+               new_point_pos = i;
+               i++;
+
+               while (old_i < old_n) {
+                       points->p[i].x = old_p[old_i].x;
+                       points->p[i].y = old_p[old_i].y;
+                       i++;
+                       old_i++;
+               }
+
+               g_free (old_p);
+       }
+
+       return new_point_pos;
+}
+
+
+void
+gth_points_delete_point        (GthPoints *points,
+                        int        n_point)
+{
+       GthPoint *old_p;
+       int       old_n, old_i;
+       int       i;
+
+       old_p = points->p;
+       old_n = points->n;
+
+       points->n = old_n - 1;
+       points->p = g_new (GthPoint, points->n);
+       for (old_i = 0, i = 0; old_i < old_n; old_i++) {
+               if (old_i != n_point) {
+                       points->p[i].x = old_p[old_i].x;
+                       points->p[i].y = old_p[old_i].y;
+                       i++;
+               }
+       }
+
+       g_free (old_p);
+}
diff --git a/extensions/file_tools/gth-points.h b/extensions/file_tools/gth-points.h
index 25f3641..4d59a03 100644
--- a/extensions/file_tools/gth-points.h
+++ b/extensions/file_tools/gth-points.h
@@ -40,11 +40,16 @@ typedef struct {
 } GthPoints;
 
 
-void   gth_points_init         (GthPoints *p,
-                                int        n);
-void   gth_points_dispose      (GthPoints *p);
-void   gth_points_copy         (GthPoints *source,
-                                GthPoints *dest);
+void           gth_points_init         (GthPoints      *points,
+                                        int             n);
+void           gth_points_dispose      (GthPoints      *points);
+void           gth_points_copy         (GthPoints      *source,
+                                        GthPoints      *dest);
+int            gth_points_add_point    (GthPoints      *points,
+                                        double          x,
+                                        double          y);
+void            gth_points_delete_point        (GthPoints      *points,
+                                        int             n_point);
 
 G_END_DECLS
 


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]