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