[gthumb] use a generic GthCurve interface + started the curve editor
- From: Paolo Bacchilega <paobac src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gthumb] use a generic GthCurve interface + started the curve editor
- Date: Sun, 25 Jan 2015 12:05:22 +0000 (UTC)
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]