[gthumb] use a generic GthCurve interface + started the curve editor



commit 0e904ceab63deb14ff9e2750f462c3196384d3e5
Author: Paolo Bacchilega <paobac src gnome org>
Date:   Thu Dec 25 11:22:46 2014 +0100

    use a generic GthCurve interface + started the curve editor

 extensions/file_tools/Makefile.am               |   10 +-
 extensions/file_tools/data/ui/curves-options.ui |    3 +
 extensions/file_tools/gth-curve-editor.c        |  877 +++++++++++++++++++++++
 extensions/file_tools/gth-curve-editor.h        |   70 ++
 extensions/file_tools/gth-curve.c               |  413 +++++++++++
 extensions/file_tools/gth-curve.h               |   96 +++
 extensions/file_tools/gth-file-tool-curves.c    |  289 +-------
 extensions/file_tools/gth-points.c              |   55 ++
 extensions/file_tools/gth-points.h              |   51 ++
 9 files changed, 1607 insertions(+), 257 deletions(-)
---
diff --git a/extensions/file_tools/Makefile.am b/extensions/file_tools/Makefile.am
index bfc3dae..22c8d46 100644
--- a/extensions/file_tools/Makefile.am
+++ b/extensions/file_tools/Makefile.am
@@ -10,6 +10,8 @@ ENUM_TYPES =          \
 HEADER_FILES =                                 \
        cairo-blur.h                    \
        cairo-rotate.h                  \
+       gth-curve.h                     \
+       gth-curve-editor.h              \
        gth-file-tool-adjust-colors.h   \
        gth-file-tool-adjust-contrast.h \
        gth-file-tool-crop.h            \
@@ -30,6 +32,7 @@ HEADER_FILES =                                \
        gth-file-tool-undo.h            \
        gth-image-line-tool.h           \
        gth-image-rotator.h             \
+       gth-points.h                    \
        gth-preview-tool.h              \
        preferences.h
 
@@ -55,10 +58,12 @@ enum-types.c: $(HEADER_FILES)
 libfile_tools_la_SOURCES =             \
        $(ENUM_TYPES)                   \
        $(HEADER_FILES)                 \
-       callbacks.c                     \
-       callbacks.h                     \
        cairo-blur.c                    \
        cairo-rotate.c                  \
+       callbacks.c                     \
+       callbacks.h                     \
+       gth-curve.c                     \
+       gth-curve-editor.c              \
        gth-file-tool-adjust-colors.c   \
        gth-file-tool-adjust-contrast.c \
        gth-file-tool-crop.c            \
@@ -78,6 +83,7 @@ libfile_tools_la_SOURCES =            \
        gth-file-tool-undo.c            \
        gth-image-line-tool.c           \
        gth-image-rotator.c             \
+       gth-points.c                    \
        gth-preview-tool.c              \
        main.c
 
diff --git a/extensions/file_tools/data/ui/curves-options.ui b/extensions/file_tools/data/ui/curves-options.ui
index 66ee39c..63ab110 100644
--- a/extensions/file_tools/data/ui/curves-options.ui
+++ b/extensions/file_tools/data/ui/curves-options.ui
@@ -21,10 +21,13 @@
                 <property name="visible">True</property>
                 <property name="can_focus">False</property>
                 <property name="orientation">vertical</property>
+                <property name="spacing">6</property>
                 <child>
                   <object class="GtkBox" id="curves_box">
+                    <property name="height_request">300</property>
                     <property name="visible">True</property>
                     <property name="can_focus">False</property>
+                    <property name="orientation">vertical</property>
                     <child>
                       <placeholder/>
                     </child>
diff --git a/extensions/file_tools/gth-curve-editor.c b/extensions/file_tools/gth-curve-editor.c
new file mode 100644
index 0000000..e74bae4
--- /dev/null
+++ b/extensions/file_tools/gth-curve-editor.c
@@ -0,0 +1,877 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ *  GThumb
+ *
+ *  Copyright (C) 2001-2014 The Free Software Foundation, Inc.
+ *
+ *  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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <stdlib.h>
+#include <math.h>
+#include <cairo/cairo.h>
+#include <glib/gi18n.h>
+#include "gth-curve.h"
+#include "gth-curve-editor.h"
+#include "gth-enum-types.h"
+#include "gth-points.h"
+
+
+/* Properties */
+enum {
+        PROP_0,
+        PROP_HISTOGRAM,
+        PROP_CURRENT_CHANNEL,
+        PROP_SCALE_TYPE
+};
+
+enum {
+       CHANNEL_COLUMN_NAME,
+       CHANNEL_COLUMN_SENSITIVE
+};
+
+
+struct _GthCurveEditorPrivate {
+       GthHistogram        *histogram;
+       gulong               histogram_changed_event;
+       GthHistogramScale    scale_type;
+       GthHistogramChannel  current_channel;
+       GtkWidget           *view;
+       GtkWidget           *linear_histogram_button;
+       GtkWidget           *logarithmic_histogram_button;
+       GtkWidget           *channel_combo_box;
+       GthPoints            points[GTH_HISTOGRAM_N_CHANNELS];
+       GthCurve            *curve[GTH_HISTOGRAM_N_CHANNELS];
+};
+
+
+G_DEFINE_TYPE (GthCurveEditor, gth_curve_editor, GTK_TYPE_BOX)
+
+
+static void
+gth_curve_editor_set_property (GObject      *object,
+                              guint         property_id,
+                              const GValue *value,
+                              GParamSpec   *pspec)
+{
+       GthCurveEditor *self;
+
+        self = GTH_CURVE_EDITOR (object);
+
+       switch (property_id) {
+       case PROP_HISTOGRAM:
+               gth_curve_editor_set_histogram (self, g_value_get_object (value));
+               break;
+       case PROP_CURRENT_CHANNEL:
+               gth_curve_editor_set_current_channel (self, g_value_get_enum (value));
+               break;
+       case PROP_SCALE_TYPE:
+               gth_curve_editor_set_scale_type (self, g_value_get_enum (value));
+               break;
+       default:
+               break;
+       }
+}
+
+
+static void
+gth_curve_editor_get_property (GObject    *object,
+                              guint       property_id,
+                              GValue     *value,
+                              GParamSpec *pspec)
+{
+       GthCurveEditor *self;
+
+        self = GTH_CURVE_EDITOR (object);
+
+       switch (property_id) {
+       case PROP_HISTOGRAM:
+               g_value_set_object (value, self->priv->histogram);
+               break;
+       case PROP_CURRENT_CHANNEL:
+               g_value_set_int (value, self->priv->current_channel);
+               break;
+       case PROP_SCALE_TYPE:
+               g_value_set_enum (value, self->priv->scale_type);
+               break;
+       default:
+               G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+               break;
+       }
+}
+
+
+static void
+gth_curve_editor_finalize (GObject *obj)
+{
+       GthCurveEditor *self;
+       int             c;
+
+       self = GTH_CURVE_EDITOR (obj);
+
+       if (self->priv->histogram_changed_event != 0)
+               g_signal_handler_disconnect (self->priv->histogram, self->priv->histogram_changed_event);
+       _g_object_unref (self->priv->histogram);
+
+       for (c = 0; c < GTH_HISTOGRAM_N_CHANNELS; c++)
+               _g_object_unref (self->priv->curve[c]);
+
+       G_OBJECT_CLASS (gth_curve_editor_parent_class)->finalize (obj);
+}
+
+
+static void
+gth_curve_editor_class_init (GthCurveEditorClass *klass)
+{
+       GObjectClass   *object_class;
+
+       g_type_class_add_private (klass, sizeof (GthCurveEditorPrivate));
+
+       object_class = (GObjectClass*) klass;
+       object_class->set_property = gth_curve_editor_set_property;
+       object_class->get_property = gth_curve_editor_get_property;
+       object_class->finalize = gth_curve_editor_finalize;
+
+       /* properties */
+
+       g_object_class_install_property (object_class,
+                                        PROP_HISTOGRAM,
+                                        g_param_spec_object ("histogram",
+                                                             "Histogram",
+                                                             "The histogram to display",
+                                                             GTH_TYPE_HISTOGRAM,
+                                                             G_PARAM_READWRITE));
+       g_object_class_install_property (object_class,
+                                        PROP_CURRENT_CHANNEL,
+                                        g_param_spec_enum ("current-channel",
+                                                           "Channel",
+                                                           "The channel to display",
+                                                           GTH_TYPE_HISTOGRAM_CHANNEL,
+                                                           GTH_HISTOGRAM_CHANNEL_VALUE,
+                                                           G_PARAM_READWRITE));
+       g_object_class_install_property (object_class,
+                                        PROP_SCALE_TYPE,
+                                        g_param_spec_enum ("scale-type",
+                                                           "Scale",
+                                                           "The scale type",
+                                                           GTH_TYPE_HISTOGRAM_SCALE,
+                                                           GTH_HISTOGRAM_SCALE_LOGARITHMIC,
+                                                           G_PARAM_READWRITE));
+}
+
+
+#define convert_to_scale(scale_type, value) (((scale_type) == GTH_HISTOGRAM_SCALE_LOGARITHMIC) ? log (value) 
: (value))
+#define HISTOGRAM_TRANSPARENCY 0.25
+
+
+static void
+_cairo_set_source_color_from_channel (cairo_t *cr,
+                                     int      channel)
+{
+       switch (channel) {
+       case GTH_HISTOGRAM_CHANNEL_VALUE:
+       default:
+               cairo_set_source_rgba (cr, 0.8, 0.8, 0.8, HISTOGRAM_TRANSPARENCY);
+               break;
+       case GTH_HISTOGRAM_CHANNEL_RED:
+               cairo_set_source_rgba (cr, 0.68, 0.18, 0.19, HISTOGRAM_TRANSPARENCY); /* #af2e31 */
+               break;
+       case GTH_HISTOGRAM_CHANNEL_GREEN:
+               cairo_set_source_rgba (cr, 0.33, 0.78, 0.30, HISTOGRAM_TRANSPARENCY); /* #55c74d */
+               break;
+       case GTH_HISTOGRAM_CHANNEL_BLUE:
+               cairo_set_source_rgba (cr, 0.13, 0.54, 0.8, HISTOGRAM_TRANSPARENCY); /* #238acc */
+               break;
+       case GTH_HISTOGRAM_CHANNEL_ALPHA:
+               cairo_set_source_rgba (cr, 0.5, 0.5, 0.5, HISTOGRAM_TRANSPARENCY);
+               break;
+       }
+}
+
+
+static void
+gth_histogram_paint_channel (GthCurveEditor   *self,
+                            GtkStyleContext  *style_context,
+                            cairo_t          *cr,
+                            int               channel,
+                            GtkAllocation    *allocation)
+{
+       double max;
+       double step;
+       int    i;
+
+       if (channel > gth_histogram_get_nchannels (self->priv->histogram))
+               return;
+
+       _cairo_set_source_color_from_channel (cr, channel);
+
+       cairo_save (cr);
+       cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
+
+       max = gth_histogram_get_channel_max (self->priv->histogram, channel);
+       if (max > 0.0)
+               max = convert_to_scale (self->priv->scale_type, max);
+       else
+               max = 1.0;
+
+       step = (double) allocation->width / 256.0;
+       cairo_set_line_width (cr, 0.5);
+       for (i = 0; i <= 255; i++) {
+               double value;
+               int    y;
+
+               value = gth_histogram_get_value (self->priv->histogram, channel, i);
+               y = CLAMP ((int) (allocation->height * convert_to_scale (self->priv->scale_type, value)) / 
max, 0, allocation->height);
+
+               cairo_rectangle (cr,
+                                allocation->x + (i * step) + 0.5,
+                                allocation->y + allocation->height - y + 0.5,
+                                step,
+                                y);
+       }
+       cairo_fill (cr);
+       cairo_restore (cr);
+}
+
+
+static void
+gth_histogram_paint_grid (GthCurveEditor *self,
+                         GtkStyleContext  *style_context,
+                         cairo_t          *cr,
+                         GtkAllocation    *allocation)
+{
+       GdkRGBA color;
+       double  grid_step;
+       int     i;
+
+       cairo_save (cr);
+       gtk_style_context_get_border_color (style_context,
+                                           gtk_widget_get_state_flags (GTK_WIDGET (self)),
+                                           &color);
+       cairo_set_line_width (cr, 0.5);
+
+       grid_step = (double) allocation->width / 8.0;
+       for (i = 0; i <= 8; i++) {
+               int ofs = round (grid_step * i);
+
+               cairo_set_source_rgba (cr, color.red, color.green, color.blue, (i == 4) ? 1.0 : 0.5);
+               cairo_move_to (cr, allocation->x + ofs + 0.5, allocation->y);
+               cairo_line_to (cr, allocation->x + ofs + 0.5, allocation->y + allocation->height);
+               cairo_stroke (cr);
+       }
+
+       grid_step = (double) allocation->height / 8.0;
+       for (i = 0; i <= 8; i++) {
+               int ofs = round (grid_step * i);
+
+               cairo_set_source_rgba (cr, color.red, color.green, color.blue, (i == 4) ? 1.0 : 0.5);
+               cairo_move_to (cr, allocation->x + 0.5, allocation->y + ofs + 0.5);
+               cairo_line_to (cr, allocation->x + allocation->width + 0.5, allocation->y + ofs + 0.5);
+               cairo_stroke (cr);
+       }
+
+       /* diagonal */
+       cairo_set_line_width (cr, 1.0);
+       cairo_set_source_rgba (cr, color.red, color.green, color.blue, 0.5);
+       cairo_move_to (cr, allocation->x + 0.5, allocation->y + allocation->height + 0.5);
+       cairo_line_to (cr, allocation->x + allocation->width+ 0.5, allocation->y + 0.5);
+       cairo_stroke (cr);
+
+       cairo_restore (cr);
+}
+
+
+static void
+gth_histogram_paint_points (GthCurveEditor  *self,
+                           GtkStyleContext *style_context,
+                           cairo_t         *cr,
+                           GthPoints       *points,
+                           GtkAllocation   *allocation)
+{
+       double x_scale, y_scale;
+       int    i;
+
+       cairo_save (cr);
+       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 < points->n; i++) {
+               double x = points->p[i].x;
+               double y = points->p[i].y;
+
+               cairo_arc (cr,
+                          round (allocation->x + (x * x_scale)),
+                          round (allocation->y + allocation->height - (y * y_scale)),
+                          3.5,
+                          0.0,
+                          2 * M_PI);
+               cairo_stroke (cr);
+       }
+
+       cairo_restore (cr);
+}
+
+
+static void
+gth_histogram_paint_curve (GthCurveEditor  *self,
+                          GtkStyleContext *style_context,
+                          cairo_t         *cr,
+                          GthCurve        *spline,
+                          GtkAllocation   *allocation)
+{
+       double x_scale, y_scale;
+       int    i;
+
+       cairo_save (cr);
+       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++) {
+               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));
+
+               if (x == 0)
+                       cairo_move_to (cr, x, y);
+               else
+                       cairo_line_to (cr, x, y);
+
+       }
+       cairo_stroke (cr);
+       cairo_restore (cr);
+}
+
+
+static gboolean
+curve_editor_draw_cb (GtkWidget *widget,
+                     cairo_t   *cr,
+                     gpointer   user_data)
+{
+       GthCurveEditor *self = user_data;
+       GtkAllocation     allocation;
+       GtkStyleContext  *style_context;
+
+       style_context = gtk_widget_get_style_context (widget);
+       gtk_style_context_save (style_context);
+       gtk_style_context_add_class (style_context, GTK_STYLE_CLASS_VIEW);
+       gtk_style_context_add_class (style_context, "histogram");
+
+       gtk_widget_get_allocation (widget, &allocation);
+       gtk_render_background (style_context, cr, 0, 0, allocation.width, allocation.height);
+
+       if ((self->priv->histogram != NULL)
+           && ((int) self->priv->current_channel <= gth_histogram_get_nchannels (self->priv->histogram)))
+       {
+               GtkBorder     padding;
+               GtkAllocation inner_allocation;
+
+               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);
+       }
+
+       gtk_style_context_restore (style_context);
+
+       return TRUE;
+}
+
+
+static gboolean
+curve_editor_scroll_event_cb (GtkWidget      *widget,
+                               GdkEventScroll *event,
+                               gpointer        user_data)
+{
+       GthCurveEditor *self = user_data;
+       int               channel = 0;
+
+       if (self->priv->histogram == NULL)
+               return FALSE;
+
+       if (event->direction == GDK_SCROLL_UP)
+               channel = self->priv->current_channel - 1;
+       else if (event->direction == GDK_SCROLL_DOWN)
+               channel = self->priv->current_channel + 1;
+
+       if (channel <= gth_histogram_get_nchannels (self->priv->histogram))
+               gth_curve_editor_set_current_channel (self, CLAMP (channel, 0, GTH_HISTOGRAM_N_CHANNELS - 1));
+
+       return TRUE;
+}
+
+
+static gboolean
+curve_editor_button_press_event_cb (GtkWidget      *widget,
+                                   GdkEventButton *event,
+                                   gpointer        user_data)
+{
+       GthCurveEditor *self = user_data;
+       GtkAllocation   allocation;
+       int             value;
+
+       gtk_widget_get_allocation (self->priv->view, &allocation);
+       value = CLAMP (event->x / allocation.width * 256 + .5, 0, 255);
+       /* FIXME */
+
+       return TRUE;
+}
+
+
+static gboolean
+curve_editor_button_release_event_cb (GtkWidget      *widget,
+                                       GdkEventButton *event,
+                                       gpointer        user_data)
+{
+       /* FIXME */
+
+       return TRUE;
+}
+
+
+static gboolean
+curve_editor_motion_notify_event_cb (GtkWidget      *widget,
+                                      GdkEventMotion *event,
+                                      gpointer        user_data)
+{
+       /* FIXME */
+
+       return TRUE;
+}
+
+
+static void
+linear_histogram_button_toggled_cb (GtkToggleButton *button,
+                                   gpointer   user_data)
+{
+       GthCurveEditor *self = user_data;
+
+       if (gtk_toggle_button_get_active (button))
+               gth_curve_editor_set_scale_type (GTH_CURVE_EDITOR (self), GTH_HISTOGRAM_SCALE_LINEAR);
+}
+
+
+static void
+logarithmic_histogram_button_toggled_cb (GtkToggleButton *button,
+                                        gpointer         user_data)
+{
+       GthCurveEditor *self = user_data;
+
+       if (gtk_toggle_button_get_active (button))
+               gth_curve_editor_set_scale_type (GTH_CURVE_EDITOR (self), GTH_HISTOGRAM_SCALE_LOGARITHMIC);
+}
+
+
+static void
+channel_combo_box_changed_cb (GtkComboBox *combo_box,
+                             gpointer     user_data)
+{
+       GthCurveEditor *self = user_data;
+       int             n_channel;
+
+       n_channel = gtk_combo_box_get_active (combo_box);
+       if (n_channel < GTH_HISTOGRAM_N_CHANNELS)
+               gth_curve_editor_set_current_channel (GTH_CURVE_EDITOR (self), n_channel);
+}
+
+
+static void
+self_notify_current_channel_cb (GObject    *gobject,
+                               GParamSpec *pspec,
+                               gpointer    user_data)
+{
+       GthCurveEditor *self = user_data;
+
+       gtk_combo_box_set_active (GTK_COMBO_BOX (self->priv->channel_combo_box), self->priv->current_channel);
+}
+
+
+static void
+self_notify_scale_type_cb (GObject    *gobject,
+                          GParamSpec *pspec,
+                          gpointer    user_data)
+{
+       GthCurveEditor *self = user_data;
+
+       gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (self->priv->linear_histogram_button), 
self->priv->scale_type == GTH_HISTOGRAM_SCALE_LINEAR);
+       gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (self->priv->logarithmic_histogram_button), 
self->priv->scale_type == GTH_HISTOGRAM_SCALE_LOGARITHMIC);
+}
+
+
+static void
+gth_curve_editor_init (GthCurveEditor *self)
+{
+       GtkWidget       *topbar_box;
+       GtkWidget       *sub_box;
+       PangoAttrList   *attr_list;
+       GtkWidget       *label;
+       GtkWidget       *view_container;
+       GtkListStore    *channel_model;
+       GtkCellRenderer *renderer;
+       GtkTreeIter      iter;
+       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;
+       }
+
+       gtk_orientable_set_orientation (GTK_ORIENTABLE (self), GTK_ORIENTATION_VERTICAL);
+       gtk_box_set_spacing (GTK_BOX (self), 6);
+       gtk_widget_set_vexpand (GTK_WIDGET (self), FALSE);
+
+       /* topbar */
+
+       topbar_box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
+       gtk_widget_show (topbar_box);
+
+       /* linear / logarithmic buttons */
+
+       sub_box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
+       gtk_widget_show (sub_box);
+       gtk_box_pack_end (GTK_BOX (topbar_box), sub_box, FALSE, FALSE, 0);
+
+       self->priv->linear_histogram_button = gtk_toggle_button_new ();
+       gtk_widget_set_tooltip_text (self->priv->linear_histogram_button, _("Linear scale"));
+       gtk_button_set_relief (GTK_BUTTON (self->priv->linear_histogram_button), GTK_RELIEF_NONE);
+       gtk_container_add (GTK_CONTAINER (self->priv->linear_histogram_button), gtk_image_new_from_icon_name 
("format-linear-symbolic", GTK_ICON_SIZE_MENU));
+       gtk_widget_show_all (self->priv->linear_histogram_button);
+       gtk_box_pack_start (GTK_BOX (sub_box), self->priv->linear_histogram_button, FALSE, FALSE, 0);
+
+       g_signal_connect (self->priv->linear_histogram_button,
+                         "toggled",
+                         G_CALLBACK (linear_histogram_button_toggled_cb),
+                         self);
+
+       self->priv->logarithmic_histogram_button = gtk_toggle_button_new ();
+       gtk_widget_set_tooltip_text (self->priv->logarithmic_histogram_button, _("Logarithmic scale"));
+       gtk_button_set_relief (GTK_BUTTON (self->priv->logarithmic_histogram_button), GTK_RELIEF_NONE);
+       gtk_container_add (GTK_CONTAINER (self->priv->logarithmic_histogram_button), 
gtk_image_new_from_icon_name ("format-logarithmic-symbolic", GTK_ICON_SIZE_MENU));
+       gtk_widget_show_all (self->priv->logarithmic_histogram_button);
+       gtk_box_pack_start (GTK_BOX (sub_box), self->priv->logarithmic_histogram_button, FALSE, FALSE, 0);
+
+       g_signal_connect (self->priv->logarithmic_histogram_button,
+                         "toggled",
+                         G_CALLBACK (logarithmic_histogram_button_toggled_cb),
+                         self);
+
+       /* channel selector */
+
+       sub_box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
+       gtk_widget_show (sub_box);
+       gtk_box_pack_start (GTK_BOX (topbar_box), sub_box, FALSE, FALSE, 0);
+
+       attr_list = pango_attr_list_new ();
+       pango_attr_list_insert (attr_list, pango_attr_size_new (PANGO_SCALE * 8));
+
+       label = gtk_label_new (_("Channel:"));
+       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);
+
+       renderer = gtk_cell_renderer_text_new ();
+       g_object_set (renderer, "attributes", attr_list, NULL);
+       gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (self->priv->channel_combo_box),
+                                   renderer,
+                                   TRUE);
+       gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (self->priv->channel_combo_box),
+                                       renderer,
+                                       "text", CHANNEL_COLUMN_NAME,
+                                       "sensitive", CHANNEL_COLUMN_SENSITIVE,
+                                       NULL);
+
+       gtk_list_store_append (channel_model, &iter);
+       gtk_list_store_set (channel_model, &iter,
+                           CHANNEL_COLUMN_NAME, _("Value"),
+                           CHANNEL_COLUMN_SENSITIVE, TRUE,
+                           -1);
+       gtk_list_store_append (channel_model, &iter);
+       gtk_list_store_set (channel_model, &iter,
+                           CHANNEL_COLUMN_NAME, _("Red"),
+                           CHANNEL_COLUMN_SENSITIVE, TRUE,
+                           -1);
+       gtk_list_store_append (channel_model, &iter);
+       gtk_list_store_set (channel_model, &iter,
+                           CHANNEL_COLUMN_NAME, _("Green"),
+                           CHANNEL_COLUMN_SENSITIVE, TRUE,
+                           -1);
+       gtk_list_store_append (channel_model, &iter);
+       gtk_list_store_set (channel_model, &iter,
+                           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);
+       gtk_box_pack_start (GTK_BOX (sub_box), self->priv->channel_combo_box, FALSE, FALSE, 0);
+
+       g_signal_connect (self->priv->channel_combo_box,
+                         "changed",
+                         G_CALLBACK (channel_combo_box_changed_cb),
+                         self);
+
+       pango_attr_list_unref (attr_list);
+
+       /* histogram view */
+
+       view_container = gtk_scrolled_window_new (NULL, NULL);
+       gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (view_container), GTK_SHADOW_IN);
+       gtk_widget_set_vexpand (view_container, TRUE);
+       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_show (self->priv->view);
+       gtk_container_add (GTK_CONTAINER (view_container), self->priv->view);
+
+       g_signal_connect (self->priv->view,
+                         "draw",
+                         G_CALLBACK (curve_editor_draw_cb),
+                         self);
+       g_signal_connect (self->priv->view,
+                         "scroll-event",
+                         G_CALLBACK (curve_editor_scroll_event_cb),
+                         self);
+       g_signal_connect (self->priv->view,
+                         "button-press-event",
+                         G_CALLBACK (curve_editor_button_press_event_cb),
+                         self);
+       g_signal_connect (self->priv->view,
+                         "button-release-event",
+                         G_CALLBACK (curve_editor_button_release_event_cb),
+                         self);
+       g_signal_connect (self->priv->view,
+                         "motion-notify-event",
+                         G_CALLBACK (curve_editor_motion_notify_event_cb),
+                         self);
+
+       /* pack the widget */
+
+       gtk_box_pack_start (GTK_BOX (self), topbar_box, FALSE, FALSE, 0);
+       gtk_box_pack_start (GTK_BOX (self), view_container, TRUE, TRUE, 0);
+
+       /* update widgets when a property changes */
+
+       g_signal_connect (self,
+                         "notify::current-channel",
+                         G_CALLBACK (self_notify_current_channel_cb),
+                         self);
+       g_signal_connect (self,
+                         "notify::scale-type",
+                         G_CALLBACK (self_notify_scale_type_cb),
+                         self);
+
+       /* default values */
+
+       gth_curve_editor_set_scale_type (self, GTH_HISTOGRAM_SCALE_LINEAR);
+       gth_curve_editor_set_current_channel (self, GTH_HISTOGRAM_CHANNEL_VALUE);
+}
+
+
+GtkWidget *
+gth_curve_editor_new (GthHistogram *histogram)
+{
+       return (GtkWidget *) g_object_new (GTH_TYPE_CURVE_EDITOR, "histogram", histogram, NULL);
+}
+
+
+static void
+update_sensitivity (GthCurveEditor *self)
+{
+       gboolean     has_alpha;
+       GtkTreePath *path;
+       GtkTreeIter  iter;
+
+       /* view */
+
+       if ((self->priv->histogram == NULL)
+           || ((int) self->priv->current_channel > gth_histogram_get_nchannels (self->priv->histogram)))
+       {
+               gtk_widget_set_sensitive (self->priv->view, FALSE);
+       }
+       else
+               gtk_widget_set_sensitive (self->priv->view, TRUE);
+
+       /* channel combobox */
+
+       has_alpha = (self->priv->histogram != NULL) && (gth_histogram_get_nchannels (self->priv->histogram) > 
3);
+       path = gtk_tree_path_new_from_indices (GTH_HISTOGRAM_CHANNEL_ALPHA, -1);
+       if (gtk_tree_model_get_iter (GTK_TREE_MODEL (gtk_combo_box_get_model (GTK_COMBO_BOX 
(self->priv->channel_combo_box))),
+                                    &iter,
+                                    path))
+       {
+               gtk_list_store_set (GTK_LIST_STORE (gtk_combo_box_get_model (GTK_COMBO_BOX 
(self->priv->channel_combo_box))),
+                                   &iter,
+                                   CHANNEL_COLUMN_SENSITIVE, has_alpha,
+                                   -1);
+       }
+
+       gtk_tree_path_free (path);
+}
+
+
+static void
+histogram_changed_cb (GthHistogram *histogram,
+                     gpointer      user_data)
+{
+       GthCurveEditor *self = user_data;
+
+       update_sensitivity (self);
+       gtk_widget_queue_draw (GTK_WIDGET (self));
+}
+
+
+void
+gth_curve_editor_set_histogram (GthCurveEditor *self,
+                                 GthHistogram     *histogram)
+{
+       g_return_if_fail (GTH_IS_CURVE_EDITOR (self));
+
+       if (self->priv->histogram == histogram)
+               return;
+
+       if (self->priv->histogram != NULL) {
+               g_signal_handler_disconnect (self->priv->histogram, self->priv->histogram_changed_event);
+               _g_object_unref (self->priv->histogram);
+               self->priv->histogram_changed_event = 0;
+               self->priv->histogram = NULL;
+       }
+
+       if (histogram != NULL) {
+               self->priv->histogram = g_object_ref (histogram);
+               self->priv->histogram_changed_event = g_signal_connect (self->priv->histogram, "changed", 
G_CALLBACK (histogram_changed_cb), self);
+       }
+
+       g_object_notify (G_OBJECT (self), "histogram");
+
+       update_sensitivity (self);
+}
+
+
+GthHistogram *
+gth_curve_editor_get_histogram (GthCurveEditor *self)
+{
+       g_return_val_if_fail (GTH_IS_CURVE_EDITOR (self), NULL);
+       return self->priv->histogram;
+}
+
+
+void
+gth_curve_editor_set_current_channel (GthCurveEditor *self,
+                                       int               n_channel)
+{
+       g_return_if_fail (GTH_IS_CURVE_EDITOR (self));
+
+       if (n_channel == self->priv->current_channel)
+               return;
+
+       self->priv->current_channel = CLAMP (n_channel, 0, GTH_HISTOGRAM_N_CHANNELS);
+       g_object_notify (G_OBJECT (self), "current-channel");
+
+       gtk_widget_queue_draw (GTK_WIDGET (self));
+}
+
+
+gint
+gth_curve_editor_get_current_channel (GthCurveEditor *self)
+{
+       g_return_val_if_fail (GTH_IS_CURVE_EDITOR (self), 0);
+       return self->priv->current_channel;
+}
+
+
+void
+gth_curve_editor_set_scale_type (GthCurveEditor  *self,
+                                  GthHistogramScale  scale_type)
+{
+       g_return_if_fail (GTH_IS_CURVE_EDITOR (self));
+
+       self->priv->scale_type = scale_type;
+       g_object_notify (G_OBJECT (self), "scale-type");
+
+       gtk_widget_queue_draw (GTK_WIDGET (self));
+}
+
+
+GthHistogramScale
+gth_curve_editor_get_scale_type (GthCurveEditor *self)
+{
+       g_return_val_if_fail (GTH_IS_CURVE_EDITOR (self), 0);
+       return self->priv->scale_type;
+}
+
+
+void
+gth_curve_editor_set_points (GthCurveEditor     *self,
+                            GthPoints           *points)
+{
+       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]);
+       }
+
+       gtk_widget_queue_draw (GTK_WIDGET (self));
+}
+
+
+void
+gth_curve_editor_get_points (GthCurveEditor  *self,
+                            GthPoints       *points)
+{
+       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]);
+       }
+}
diff --git a/extensions/file_tools/gth-curve-editor.h b/extensions/file_tools/gth-curve-editor.h
new file mode 100644
index 0000000..ea0f941
--- /dev/null
+++ b/extensions/file_tools/gth-curve-editor.h
@@ -0,0 +1,70 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ *  GThumb
+ *
+ *  Copyright (C) 2001-2014 The Free Software Foundation, Inc.
+ *
+ *  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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef GTH_CURVE_EDITOR_H
+#define GTH_CURVE_EDITOR_H
+
+#include <glib.h>
+#include <gtk/gtk.h>
+#include <gthumb.h>
+#include "gth-points.h"
+
+G_BEGIN_DECLS
+
+#define GTH_TYPE_CURVE_EDITOR (gth_curve_editor_get_type ())
+#define GTH_CURVE_EDITOR(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTH_TYPE_CURVE_EDITOR, GthCurveEditor))
+#define GTH_CURVE_EDITOR_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTH_TYPE_CURVE_EDITOR, 
GthCurveEditorClass))
+#define GTH_IS_CURVE_EDITOR(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTH_TYPE_CURVE_EDITOR))
+#define GTH_IS_CURVE_EDITOR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTH_TYPE_CURVE_EDITOR))
+#define GTH_CURVE_EDITOR_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTH_TYPE_CURVE_EDITOR, 
GthCurveEditorClass))
+
+typedef struct _GthCurveEditor GthCurveEditor;
+typedef struct _GthCurveEditorClass GthCurveEditorClass;
+typedef struct _GthCurveEditorPrivate GthCurveEditorPrivate;
+
+struct _GthCurveEditor {
+       GtkBox parent_instance;
+       GthCurveEditorPrivate *priv;
+};
+
+struct _GthCurveEditorClass {
+       GtkBoxClass parent_class;
+};
+
+GType              gth_curve_editor_get_type           (void);
+GtkWidget *        gth_curve_editor_new                        (GthHistogram            *histogram);
+void               gth_curve_editor_set_histogram      (GthCurveEditor          *self,
+                                                        GthHistogram            *histogram);
+GthHistogram *     gth_curve_editor_get_histogram      (GthCurveEditor          *self);
+void               gth_curve_editor_set_current_channel        (GthCurveEditor          *self,
+                                                        int                      n_channel);
+int                gth_curve_editor_get_current_channel        (GthCurveEditor          *self);
+void               gth_curve_editor_set_scale_type     (GthCurveEditor          *self,
+                                                        GthHistogramScale        scale);
+GthHistogramScale  gth_curve_editor_get_scale_type     (GthCurveEditor          *self);
+void              gth_curve_editor_set_points          (GthCurveEditor          *self,
+                                                        GthPoints               *points);
+void              gth_curve_editor_get_points          (GthCurveEditor          *self,
+                                                        GthPoints               *points);
+
+G_END_DECLS
+
+#endif /* GTH_CURVE_EDITOR_H */
diff --git a/extensions/file_tools/gth-curve.c b/extensions/file_tools/gth-curve.c
new file mode 100644
index 0000000..fff00e7
--- /dev/null
+++ b/extensions/file_tools/gth-curve.c
@@ -0,0 +1,413 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ *  GThumb
+ *
+ *  Copyright (C) 2014 Free Software Foundation, Inc.
+ *
+ *  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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <math.h>
+#include "gth-curve.h"
+
+
+/* -- GthCurve -- */
+
+
+G_DEFINE_INTERFACE (GthCurve, gth_curve, 0)
+
+
+static void
+gth_curve_default_init (GthCurveInterface *iface)
+{
+       /* void */
+}
+
+
+double
+gth_curve_eval (GthCurve *self,
+               double    x)
+{
+       return GTH_CURVE_GET_INTERFACE (self)->eval (self, x);
+}
+
+
+/* -- Gauss-Jordan linear equation solver (for splines) -- */
+
+
+typedef struct {
+       double **v;
+       int      r;
+       int      c;
+} Matrix;
+
+
+static Matrix *
+GJ_matrix_new (int r, int c)
+{
+       Matrix *m;
+       int     i, j;
+
+       m = g_new (Matrix, 1);
+       m->r = r;
+       m->c = c;
+       m->v = g_new (double *, r);
+       for (i = 0; i < r; i++) {
+               m->v[i] = g_new (double, c);
+               for (j = 0; j < c; j++)
+                       m->v[i][j] = 0.0;
+       }
+
+       return m;
+}
+
+
+static void
+GJ_matrix_free (Matrix *m)
+{
+       int i;
+       for (i = 0; i < m->r; i++)
+               g_free (m->v[i]);
+       g_free (m->v);
+       g_free (m);
+}
+
+
+static void
+GJ_swap_rows (double **m, int k, int l)
+{
+       double *t = m[k];
+       m[k] = m[l];
+       m[l] = t;
+}
+
+
+static gboolean
+GJ_matrix_solve (Matrix *m, double *x)
+{
+       double **A = m->v;
+       int      r = m->r;
+       int      k, i, j;
+
+       for (k = 0; k < r; k++) { // column
+               // pivot for column
+               int    i_max = 0;
+               double vali = 0;
+
+               for (i = k; i < r; i++) {
+                       if ((i == k) || (A[i][k] > vali)) {
+                               i_max = i;
+                               vali = A[i][k];
+                       }
+               }
+               GJ_swap_rows (A, k, i_max);
+
+               if (A[i_max][i] == 0) {
+                       g_print ("matrix is singular!\n");
+                       return TRUE;
+               }
+
+               // for all rows below pivot
+               for (i = k + 1; i < r; i++) {
+                       for (j = k + 1; j < r + 1; j++)
+                               A[i][j] = A[i][j] - A[k][j] * (A[i][k] / A[k][k]);
+                       A[i][k] = 0.0;
+               }
+       }
+
+       for (i = r - 1; i >= 0; i--) { // rows = columns
+               double v = A[i][r] / A[i][i];
+
+               x[i] = v;
+               for (j = i - 1; j >= 0; j--) { // rows
+                       A[j][r] -= A[j][i] * v;
+                       A[j][i] = 0.0;
+               }
+       }
+
+       return FALSE;
+}
+
+
+/* -- 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))
+
+
+static void
+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);
+}
+
+
+static void
+gth_spline_class_init (GthSplineClass *class)
+{
+        GObjectClass *object_class;
+
+        object_class = (GObjectClass*) class;
+        object_class->finalize = gth_spline_finalize;
+}
+
+
+static double
+gth_spline_eval (GthCurve *curve,
+                double    x)
+{
+       GthSpline *spline;
+       GthPoint  *p;
+       double    *k;
+       int        i;
+
+       spline = GTH_SPLINE (curve);
+       p = spline->points.p;
+       k = spline->k;
+
+       if (spline->is_singular)
+               return x;
+
+       for (i = 1; p[i].x < x; i++)
+               /* void */;
+       double t = (x - p[i-1].x) / (p[i].x - p[i-1].x);
+       double a = k[i-1] * (p[i].x - p[i-1].x) - (p[i].y - p[i-1].y);
+       double b = - k[i] * (p[i].x - p[i-1].x) + (p[i].y - p[i-1].y);
+       double y = round ( ((1-t) * p[i-1].y) + (t * p[i].y) + (t * (1-t) * (a * (1-t) + b * t)) );
+
+       return CLAMP (y, 0, 255);
+}
+
+
+void
+gth_spline_gth_curve_interface_init (GthCurveInterface *iface)
+{
+       iface->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)
+{
+       GthSpline *spline;
+       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);
+
+       return GTH_CURVE (spline);
+}
+
+
+/* -- 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))
+
+
+static void
+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 void
+gth_cspline_class_init (GthCSplineClass *class)
+{
+        GObjectClass *object_class;
+
+        object_class = (GObjectClass*) class;
+        object_class->finalize = gth_cspline_finalize;
+}
+
+
+static inline double h00 (double x, double x2, double x3)
+{
+       return 2*x3 - 3*x2 + 1;
+}
+
+
+static inline double h10 (double x, double x2, double x3)
+{
+       return x3 - 2*x2 + x;
+}
+
+
+static inline double h01 (double x, double x2, double x3)
+{
+       return -2*x3 + 3*x2;
+}
+
+
+static inline double h11 (double x, double x2, double x3)
+{
+       return x3 - x2;
+}
+
+
+static double
+gth_cspline_eval (GthCurve *curve,
+                 double    x)
+{
+       GthCSpline *spline;
+       GthPoint   *p;
+       double     *t;
+       int         k;
+       double      d, z, z2, z3;
+       double      y;
+
+       spline = GTH_CSPLINE (curve);
+       p = spline->points.p;
+       t = spline->tangents;
+
+       for (k = 1; p[k].x < x; k++)
+               /* void */;
+       k--;
+
+       d = p[k+1].x - p[k].x;
+       z = (x - p[k].x) / d;
+       z2 = z * z;
+       z3 = z2 * z;
+       y = (h00(z, z2, z3) * p[k].y) + (h10(z, z2, z3) * d * t[k]) + (h01(z, z2, z3) * p[k+1].y) + (h11(z, 
z2, z3) * d * t[k+1]);
+
+       return CLAMP (y, 0, 255);
+}
+
+
+void
+gth_cspline_gth_curve_interface_init (GthCurveInterface *iface)
+{
+       iface->eval = gth_cspline_eval;
+}
+
+
+static void
+gth_cspline_init (GthCSpline *spline)
+{
+       gth_points_init (&spline->points, 0);
+       spline->tangents = NULL;
+}
+
+
+static void
+gth_cspline_setup (GthCSpline *spline)
+{
+       double   *t;
+       GthPoint *p;
+       int       k;
+
+       /* calculate the tangents using the three-point difference */
+
+       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));
+       }
+}
+
+
+GthCurve *
+gth_cspline_new (GthPoints *points)
+{
+       GthCSpline *spline;
+       int         i;
+
+       spline = g_object_new (GTH_TYPE_CSPLINE, NULL);
+       gth_points_copy (points, &spline->points);
+       gth_cspline_setup (spline);
+
+       return GTH_CURVE (spline);
+}
diff --git a/extensions/file_tools/gth-curve.h b/extensions/file_tools/gth-curve.h
new file mode 100644
index 0000000..cd901e4
--- /dev/null
+++ b/extensions/file_tools/gth-curve.h
@@ -0,0 +1,96 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ *  GThumb
+ *
+ *  Copyright (C) 2001-2014 The Free Software Foundation, Inc.
+ *
+ *  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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef GTH_CURVE_H
+#define GTH_CURVE_H
+
+#include <glib.h>
+#include <gtk/gtk.h>
+#include <gthumb.h>
+#include "gth-points.h"
+
+G_BEGIN_DECLS
+
+/* 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_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))
+
+typedef struct _GthCurve GthCurve;
+
+typedef struct {
+       GTypeInterface parent_iface;
+
+       /*< virtual functions >*/
+
+       double  (*eval)         (GthCurve *curve, double x);
+} GthCurveInterface;
+
+GType          gth_curve_get_type              (void);
+double         gth_curve_eval                  (GthCurve       *curve,
+                                                double          x);
+
+/* GthSpline */
+
+#define GTH_TYPE_SPLINE (gth_spline_get_type ())
+#define GTH_SPLINE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTH_TYPE_SPLINE, GthSpline))
+#define GTH_SPLINE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTH_TYPE_SPLINE, GthSplineClass))
+#define GTH_IS_SPLINE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTH_TYPE_SPLINE))
+#define GTH_IS_SPLINE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTH_TYPE_SPLINE))
+#define GTH_SPLINE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTH_TYPE_SPLINE, GthSplineClass))
+
+typedef struct {
+       GObject    parent_instance;
+       GthPoints  points;
+       double    *k;
+       gboolean   is_singular;
+} GthSpline;
+
+typedef GObjectClass GthSplineClass;
+
+GType          gth_spline_get_type             (void);
+GthCurve *     gth_spline_new                  (GthPoints      *points);
+
+/* GthCSpline */
+
+#define GTH_TYPE_CSPLINE (gth_cspline_get_type ())
+#define GTH_CSPLINE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTH_TYPE_CSPLINE, GthCSpline))
+#define GTH_CSPLINE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTH_TYPE_CSPLINE, GthCSplineClass))
+#define GTH_IS_CSPLINE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTH_TYPE_CSPLINE))
+#define GTH_IS_CSPLINE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTH_TYPE_CSPLINE))
+#define GTH_CSPLINE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTH_TYPE_CSPLINE, GthCSplineClass))
+
+typedef struct {
+       GObject    parent_instance;
+       GthPoints  points;
+       double    *tangents;
+} GthCSpline;
+
+typedef GObjectClass GthCSplineClass;
+
+GType          gth_cspline_get_type            (void);
+GthCurve *     gth_cspline_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 b2fc692..6524022 100644
--- a/extensions/file_tools/gth-file-tool-curves.c
+++ b/extensions/file_tools/gth-file-tool-curves.c
@@ -21,7 +21,10 @@
 
 #include <config.h>
 #include <math.h>
+#include "gth-curve-editor.h"
 #include "gth-file-tool-curves.h"
+#include "gth-curve.h"
+#include "gth-points.h"
 #include "gth-preview-tool.h"
 
 
@@ -33,18 +36,6 @@
 G_DEFINE_TYPE (GthFileToolCurves, gth_file_tool_curves, GTH_TYPE_IMAGE_VIEWER_PAGE_TOOL)
 
 
-typedef struct {
-       double x;
-       double y;
-} Point;
-
-
-typedef struct {
-       Point *p;
-       int    n;
-} Points;
-
-
 struct _GthFileToolCurvesPrivate {
        cairo_surface_t    *destination;
        cairo_surface_t    *preview;
@@ -52,237 +43,21 @@ struct _GthFileToolCurvesPrivate {
        GthTask            *image_task;
        guint               apply_event;
        GthImageViewerTool *preview_tool;
+       GthHistogram       *histogram;
        gboolean            view_original;
        gboolean            apply_to_original;
        gboolean            closing;
-       Points              points[GTH_HISTOGRAM_N_CHANNELS];
+       GthPoints           points[GTH_HISTOGRAM_N_CHANNELS];
+       GtkWidget          *curve_editor;
 };
 
 
-/* -- Gauss-Jordan linear equation solver (for splines) -- */
-
-
-typedef struct {
-       double **v;
-       int      r;
-       int      c;
-} Matrix;
-
-
-static Matrix *
-GJ_matrix_new (int r, int c)
-{
-       Matrix *m;
-       int     i, j;
-
-       m = g_new (Matrix, 1);
-       m->r = r;
-       m->c = c;
-       m->v = g_new (double *, r);
-       for (i = 0; i < r; i++) {
-               m->v[i] = g_new (double, c);
-               for (j = 0; j < c; j++)
-                       m->v[i][j] = 0.0;
-       }
-
-       return m;
-}
-
-
-static void
-GJ_matrix_free (Matrix *m)
-{
-       int i;
-       for (i = 0; i < m->r; i++)
-               g_free (m->v[i]);
-       g_free (m->v);
-       g_free (m);
-}
-
-
-static void
-GJ_swap_rows (double **m, int k, int l)
-{
-       double *t = m[k];
-       m[k] = m[l];
-       m[l] = t;
-}
-
-
-static gboolean
-GJ_matrix_solve (Matrix *m, double *x)
-{
-       double **A = m->v;
-       int      r = m->r;
-       int      k, i, j;
-
-       for (k = 0; k < r; k++) { // column
-               // pivot for column
-               int    i_max = 0;
-               double vali = 0;
-
-               for (i = k; i < r; i++) {
-                       if ((i == k) || (A[i][k] > vali)) {
-                               i_max = i;
-                               vali = A[i][k];
-                       }
-               }
-               GJ_swap_rows (A, k, i_max);
-
-               if (A[i_max][i] == 0) {
-                       g_print ("matrix is singular!\n");
-                       return TRUE;
-               }
-
-               // for all rows below pivot
-               for (i = k + 1; i < r; i++) {
-                       for (j = k + 1; j < r + 1; j++)
-                               A[i][j] = A[i][j] - A[k][j] * (A[i][k] / A[k][k]);
-                       A[i][k] = 0.0;
-               }
-       }
-
-       for (i = r - 1; i >= 0; i--) { // rows = columns
-               double v = A[i][r] / A[i][i];
-
-               x[i] = v;
-               for (j = i - 1; j >= 0; j--) { // rows
-                       A[j][r] -= A[j][i] * v;
-                       A[j][i] = 0.0;
-               }
-       }
-
-       return FALSE;
-}
-
-
-/* -- points -- */
-
-
-static void
-points_init (Points *p, int n)
-{
-       p->n = n;
-       p->p = g_new (Point, p->n);
-}
-
-
-static void
-points_dispose (Points *p)
-{
-       if (p->p != NULL)
-               g_free (p->p);
-       points_init (p, 0);
-}
-
-
-static void
-points_copy (Points *source, Points *dest)
-{
-       int i;
-
-       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;
-       }
-}
-
-
-/* -- spline function -- */
-
-
-typedef struct {
-       Points    points;
-       double   *k;
-       gboolean  is_singular;
-} Spline;
-
-
-static Spline *
-spline_new (Points *points)
-{
-       Spline *spline;
-       int     i;
-
-       spline = g_new (Spline, 1);
-       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;
-       spline->is_singular = FALSE;
-
-       return spline;
-}
-
-
-static void
-spline_free (Spline *spline)
-{
-       g_return_if_fail (spline != NULL);
-
-       points_dispose (&spline->points);
-       g_free (spline->k);
-       g_free (spline);
-}
-
-
-static void
-spline_set_natural_ks (Spline *spline)
-{
-       int      n = spline->points.n;
-       Point   *p = spline->points.p;
-       Matrix  *m;
-       double **A;
-       int      i;
-
-       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);
-}
-
-
-static int
-spline_eval (Spline *spline, double x)
-{
-       Point  *p = spline->points.p;
-       double *k = spline->k;
-       int     i;
-
-       for (i = 1; p[i].x < x; i++)
-               /* void */;
-       double t = (x - p[i-1].x) / (p[i].x - p[i-1].x);
-       double a = k[i-1] * (p[i].x - p[i-1].x) - (p[i].y - p[i-1].y);
-       double b = - k[i] * (p[i].x - p[i-1].x) + (p[i].y - p[i-1].y);
-       double y = round ( ((1-t) * p[i-1].y) + (t * p[i].y) + (t * (1-t) * (a * (1-t) + b * t)) );
-
-       return CLAMP (y, 0, 255);
-}
-
-
 /* -- apply_changes -- */
 
 
 typedef struct {
-       long   *value_map[GTH_HISTOGRAM_N_CHANNELS];
-       Spline *spline[GTH_HISTOGRAM_N_CHANNELS];
+       long     *value_map[GTH_HISTOGRAM_N_CHANNELS];
+       GthCurve *curve[GTH_HISTOGRAM_N_CHANNELS];
 } TaskData;
 
 
@@ -293,22 +68,10 @@ curves_setup (TaskData        *task_data,
        long **value_map;
        int    c, v;
 
-       for (c = 0; c < GTH_HISTOGRAM_N_CHANNELS; c++)
-               spline_set_natural_ks (task_data->spline[c]);
-
        for (c = 0; c < GTH_HISTOGRAM_N_CHANNELS; c++) {
                task_data->value_map[c] = g_new (long, 256);
-               if (task_data->spline[c]->is_singular) {
-                       for (v = 0; v <= 255; v++)
-                               task_data->value_map[c][v] = v;
-               }
-               else {
-                       for (v = 0; v <= 255; v++) {
-                               task_data->value_map[c][v] = spline_eval (task_data->spline[c], v);
-                               /*if (c == 0) FIXME
-                                       g_print ("%d -> %ld\n", v, task_data->value_map[c][v]);*/
-                       }
-               }
+               for (v = 0; v <= 255; v++)
+                       task_data->value_map[c][v] = gth_curve_eval (task_data->curve[c], v);
        }
 }
 
@@ -453,7 +216,7 @@ image_task_completed_cb (GthTask  *task,
 
 
 static TaskData *
-task_data_new (Points *points)
+task_data_new (GthPoints *points)
 {
        TaskData *task_data;
        int       c, n;
@@ -461,7 +224,7 @@ task_data_new (Points *points)
        task_data = g_new (TaskData, 1);
        for (c = 0; c < GTH_HISTOGRAM_N_CHANNELS; c++) {
                task_data->value_map[c] = NULL;
-               task_data->spline[c] = spline_new (&points[c]);
+               task_data->curve[c] = gth_cspline_new (&points[c]);
        }
 
        return task_data;
@@ -478,7 +241,7 @@ task_data_destroy (gpointer user_data)
                return;
 
        for (c = 0; c < GTH_HISTOGRAM_N_CHANNELS; c++)
-               spline_free (task_data->spline[c]);
+               g_object_unref (task_data->curve[c]);
        for (c = 0; c < GTH_HISTOGRAM_N_CHANNELS; c++)
                g_free (task_data->value_map[c]);
        g_free (task_data);
@@ -545,7 +308,7 @@ reset_button_clicked_cb (GtkButton *button,
        int                c;
 
        for (c = 0; c < GTH_HISTOGRAM_N_CHANNELS; c++) {
-               points_init (&self->priv->points[c], 3);
+               gth_points_init (&self->priv->points[c], 3);
 
                self->priv->points[c].p[0].x = 0;
                self->priv->points[c].p[0].y = 0;
@@ -556,7 +319,9 @@ reset_button_clicked_cb (GtkButton *button,
                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 */
        apply_changes (self);
 }
 
@@ -617,6 +382,11 @@ gth_file_tool_curves_get_options (GthFileTool *base)
        options = _gtk_builder_get_widget (self->priv->builder, "options");
        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 (GET_WIDGET ("preview_checkbutton"),
                          "toggled",
                          G_CALLBACK (preview_checkbutton_toggled_cb),
@@ -625,6 +395,7 @@ gth_file_tool_curves_get_options (GthFileTool *base)
        self->priv->preview_tool = gth_preview_tool_new ();
        gth_preview_tool_set_image (GTH_PREVIEW_TOOL (self->priv->preview_tool), self->priv->preview);
        gth_image_viewer_set_tool (GTH_IMAGE_VIEWER (viewer), self->priv->preview_tool);
+       gth_histogram_calculate_for_image (self->priv->histogram, self->priv->preview);
        apply_changes (self);
 
        return options;
@@ -723,19 +494,26 @@ gth_file_tool_curves_init (GthFileToolCurves *self)
        self->priv->builder = NULL;
        self->priv->image_task = NULL;
        self->priv->view_original = FALSE;
+       self->priv->histogram = gth_histogram_new ();
 
        for (c = 0; c < GTH_HISTOGRAM_N_CHANNELS; c++) {
-               /* points_init (&self->priv->points[c], 0); FIXME */
-               points_init (&self->priv->points[c], 4);
+               /* 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].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[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;
@@ -759,6 +537,7 @@ gth_file_tool_curves_finalize (GObject *object)
        cairo_surface_destroy (self->priv->preview);
        cairo_surface_destroy (self->priv->destination);
        _g_object_unref (self->priv->builder);
+       _g_object_unref (self->priv->histogram);
 
        G_OBJECT_CLASS (gth_file_tool_curves_parent_class)->finalize (object);
 }
diff --git a/extensions/file_tools/gth-points.c b/extensions/file_tools/gth-points.c
new file mode 100644
index 0000000..eef3dc6
--- /dev/null
+++ b/extensions/file_tools/gth-points.c
@@ -0,0 +1,55 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ *  GThumb
+ *
+ *  Copyright (C) 2014 Free Software Foundation, Inc.
+ *
+ *  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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include "gth-points.h"
+
+
+void
+gth_points_init (GthPoints *p,
+                int        n)
+{
+       p->n = n;
+       p->p = g_new (GthPoint, p->n);
+}
+
+
+void
+gth_points_dispose (GthPoints *p)
+{
+       if (p->p != NULL)
+               g_free (p->p);
+       gth_points_init (p, 0);
+}
+
+
+void
+gth_points_copy (GthPoints *source,
+                GthPoints *dest)
+{
+       int i;
+
+       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;
+       }
+}
diff --git a/extensions/file_tools/gth-points.h b/extensions/file_tools/gth-points.h
new file mode 100644
index 0000000..25f3641
--- /dev/null
+++ b/extensions/file_tools/gth-points.h
@@ -0,0 +1,51 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ *  GThumb
+ *
+ *  Copyright (C) 2001-2014 The Free Software Foundation, Inc.
+ *
+ *  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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef GTH_POINT_H
+#define GTH_POINT_H
+
+#include <glib.h>
+#include <gtk/gtk.h>
+#include <gthumb.h>
+
+G_BEGIN_DECLS
+
+typedef struct {
+       double x;
+       double y;
+} GthPoint;
+
+
+typedef struct {
+       GthPoint *p;
+       int       n;
+} GthPoints;
+
+
+void   gth_points_init         (GthPoints *p,
+                                int        n);
+void   gth_points_dispose      (GthPoints *p);
+void   gth_points_copy         (GthPoints *source,
+                                GthPoints *dest);
+
+G_END_DECLS
+
+#endif /* GTH_POINT_H */


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