From 082aa272a4915127144b44a3faf2b2f3754525e1 Mon Sep 17 00:00:00 2001 From: Alexia Death Date: Fri, 22 Oct 2010 21:47:36 +0900 Subject: [PATCH] app: G-Pen algorithm for GIMP trunk. Now smoothing function works for Ink and Brush tools. Rebased/fixed to go on top of current master. Next commit will add cleanup. Had to change author tag because gnome is not accepting random stuff in email fields. Original author is tarai, from gimp painter project in sourceforge. --- app/paint/gimpbrushcore.c | 7 +- app/paint/gimpink.c | 28 +++-- app/paint/gimpink.h | 6 +- app/paint/gimppaintcore.c | 7 ++ app/paint/gimppaintcore.h | 3 + app/paint/gimppaintoptions.c | 178 +++++++++++++++++++++++++++++-- app/paint/gimppaintoptions.h | 32 ++++++ app/tools/gimppaintoptions-gui.c | 50 +++++++-- 8 files changed, 282 insertions(+), 29 deletions(-) diff --git a/app/paint/gimpbrushcore.c b/app/paint/gimpbrushcore.c index 71f5f13cce..f87d6c21f3 100644 --- a/app/paint/gimpbrushcore.c +++ b/app/paint/gimpbrushcore.c @@ -398,7 +398,7 @@ gimp_brush_core_post_paint (GimpPaintCore *paint_core, guint32 time) { GimpBrushCore *core = GIMP_BRUSH_CORE (paint_core); - + if (paint_state == GIMP_PAINT_STATE_MOTION) { core->brush = core->main_brush; @@ -520,6 +520,11 @@ gimp_brush_core_interpolate (GimpPaintCore *paint_core, gimp_paint_core_get_last_coords (paint_core, &last_coords); gimp_paint_core_get_current_coords (paint_core, ¤t_coords); + + if (paint_core->smoothing_history) { + current_coords = gimp_paint_options_get_smoothed_coords(paint_options, ¤t_coords, paint_core->smoothing_history); + gimp_paint_core_set_current_coords (paint_core, ¤t_coords); + } /*Zero sized brushes are unfit for interpolate, * so we just let paint core fail onits own diff --git a/app/paint/gimpink.c b/app/paint/gimpink.c index 96e72dc906..247063db58 100644 --- a/app/paint/gimpink.c +++ b/app/paint/gimpink.c @@ -152,6 +152,7 @@ gimp_ink_paint (GimpPaintCore *paint_core, case GIMP_PAINT_STATE_INIT: gimp_paint_core_get_last_coords (paint_core, &last_coords); + ink->queue = gimp_circular_queue_new(sizeof(GimpCoords), paint_options->smoothing_options->smoothing_history); if (coords->x == last_coords.x && coords->y == last_coords.y) @@ -186,6 +187,8 @@ gimp_ink_paint (GimpPaintCore *paint_core, break; case GIMP_PAINT_STATE_FINISH: + gimp_circular_queue_free(ink->queue); + ink->queue = NULL; break; } } @@ -254,17 +257,20 @@ gimp_ink_motion (GimpPaintCore *paint_core, TempBuf *area; guchar col[MAX_CHANNELS]; PixelRegion blob_maskPR; + GimpCoords modified_coords; image = gimp_item_get_image (GIMP_ITEM (drawable)); + + modified_coords = gimp_paint_options_get_smoothed_coords(paint_options, coords, ink->queue); if (! ink->last_blob) { ink->last_blob = ink_pen_ellipse (options, - coords->x, - coords->y, - coords->pressure, - coords->xtilt, - coords->ytilt, + modified_coords.x, + modified_coords.y, + modified_coords.pressure, + modified_coords.xtilt, + modified_coords.ytilt, 100); if (ink->start_blob) @@ -277,12 +283,12 @@ gimp_ink_motion (GimpPaintCore *paint_core, else { GimpBlob *blob = ink_pen_ellipse (options, - coords->x, - coords->y, - coords->pressure, - coords->xtilt, - coords->ytilt, - coords->velocity * 100); + modified_coords.x, + modified_coords.y, + modified_coords.pressure, + modified_coords.xtilt, + modified_coords.ytilt, + modified_coords.velocity * 100); blob_union = gimp_blob_convex_union (ink->last_blob, blob); diff --git a/app/paint/gimpink.h b/app/paint/gimpink.h index 6cb185b6af..dbea24c8e7 100644 --- a/app/paint/gimpink.h +++ b/app/paint/gimpink.h @@ -21,6 +21,7 @@ #include "gimppaintcore.h" #include "gimpink-blob.h" +#include "gimppaintoptions.h" #define GIMP_TYPE_INK (gimp_ink_get_type ()) @@ -39,8 +40,9 @@ struct _GimpInk GimpBlob *start_blob; /* starting blob (for undo) */ - GimpBlob *cur_blob; /* current blob */ - GimpBlob *last_blob; /* blob for last cursor position */ + GimpBlob *cur_blob; /* current blob */ + GimpBlob *last_blob; /* blob for last cursor position */ + GimpCircularQueue *queue; }; struct _GimpInkClass diff --git a/app/paint/gimppaintcore.c b/app/paint/gimppaintcore.c index c545faf1d1..16df3a1d2c 100644 --- a/app/paint/gimppaintcore.c +++ b/app/paint/gimppaintcore.c @@ -351,6 +351,8 @@ gimp_paint_core_start (GimpPaintCore *core, g_return_val_if_fail (error == NULL || *error == NULL, FALSE); item = GIMP_ITEM (drawable); + + core->smoothing_history = gimp_circular_queue_new (sizeof(GimpCoords), paint_options->smoothing_options->smoothing_history); core->cur_coords = *coords; @@ -418,6 +420,11 @@ gimp_paint_core_finish (GimpPaintCore *core, g_return_if_fail (GIMP_IS_PAINT_CORE (core)); g_return_if_fail (GIMP_IS_DRAWABLE (drawable)); g_return_if_fail (gimp_item_is_attached (GIMP_ITEM (drawable))); + + if (core->smoothing_history) { + gimp_circular_queue_free (core->smoothing_history); + core->smoothing_history = NULL; + } image = gimp_item_get_image (GIMP_ITEM (drawable)); diff --git a/app/paint/gimppaintcore.h b/app/paint/gimppaintcore.h index a42ac516f7..e914b46182 100644 --- a/app/paint/gimppaintcore.h +++ b/app/paint/gimppaintcore.h @@ -21,6 +21,7 @@ #include "libgimpmath/gimpvector.h" #include "core/gimpobject.h" +#include "gimppaintoptions.h" /* GimpCircularQueue */ #define GIMP_TYPE_PAINT_CORE (gimp_paint_core_get_type ()) @@ -63,6 +64,8 @@ struct _GimpPaintCore TempBuf *orig_buf; /* the unmodified drawable pixels */ TempBuf *orig_proj_buf; /* the unmodified projection pixels */ TempBuf *canvas_buf; /* the buffer to paint pixels to */ + + GimpCircularQueue *smoothing_history; }; struct _GimpPaintCoreClass diff --git a/app/paint/gimppaintoptions.c b/app/paint/gimppaintoptions.c index 0a0d315073..49f1e9e7cb 100644 --- a/app/paint/gimppaintoptions.c +++ b/app/paint/gimppaintoptions.c @@ -36,6 +36,54 @@ #include "gimp-intl.h" +GimpCircularQueue* gimp_circular_queue_new(guint element_size, guint queue_size) +{ + GimpCircularQueue* queue = g_new0(GimpCircularQueue, 1); + queue->data = g_malloc0(element_size * queue_size); + if (queue->data) { + queue->element_size = element_size; + queue->queue_size = queue_size; + } + queue->start = 0; + queue->end = 0; + return queue; +} + +void gimp_circular_queue_free(GimpCircularQueue* queue) +{ + if (queue) { + if (queue->data) + g_free(queue->data); + g_free(queue); + } +} + +void gimp_circular_queue_enqueue_data(GimpCircularQueue* queue, gpointer data) +{ + guint index = queue->end % queue->queue_size; + + if (index > queue->start && index == queue->start % queue->queue_size) + queue->start ++; + + index *= queue->element_size; + g_memmove((guchar*)queue->data + index, data, queue->element_size); + queue->end ++; +} + +gpointer gimp_circular_queue_get_nth_offset(GimpCircularQueue* queue, guint index) +{ + index = (queue->start + index) % queue->queue_size; + index *= queue->element_size; + return (guchar*)queue->data + index; +} + +gpointer gimp_circular_queue_get_last_offset(GimpCircularQueue* queue) +{ + gint index = (queue->end - 1) % queue->queue_size; + index *= queue->element_size; + return (guchar*)queue->data + index; +} + #define DEFAULT_BRUSH_SIZE 20.0 #define DEFAULT_BRUSH_ASPECT_RATIO 1.0 @@ -62,6 +110,8 @@ #define DYNAMIC_MAX_VALUE 1.0 #define DYNAMIC_MIN_VALUE 0.0 +#define DEFAULT_SMOOTHING_HISTORY 20 +#define DEFAULT_SMOOTHING_FACTOR 50 enum { @@ -95,7 +145,11 @@ enum PROP_PATTERN_VIEW_TYPE, PROP_PATTERN_VIEW_SIZE, PROP_GRADIENT_VIEW_TYPE, - PROP_GRADIENT_VIEW_SIZE + PROP_GRADIENT_VIEW_SIZE, + + PROP_USE_SMOOTHING, + PROP_SMOOTHING_HISTORY, + PROP_SMOOTHING_FACTOR }; @@ -243,6 +297,22 @@ gimp_paint_options_class_init (GimpPaintOptionsClass *klass) GIMP_VIEWABLE_MAX_BUTTON_SIZE, GIMP_VIEW_SIZE_LARGE, GIMP_PARAM_STATIC_STRINGS); + + GIMP_CONFIG_INSTALL_PROP_INT (object_class, PROP_SMOOTHING_HISTORY, + "smoothing-history", NULL, + 1, + 100, + DEFAULT_SMOOTHING_HISTORY, + GIMP_PARAM_STATIC_STRINGS); + + GIMP_CONFIG_INSTALL_PROP_BOOLEAN (object_class, PROP_USE_SMOOTHING, + "use-smoothing", NULL, + FALSE, + GIMP_PARAM_STATIC_STRINGS); + GIMP_CONFIG_INSTALL_PROP_DOUBLE (object_class, PROP_SMOOTHING_FACTOR, + "smoothing-factor", NULL, + 0.0, 1000.0, DEFAULT_SMOOTHING_FACTOR, + GIMP_PARAM_STATIC_STRINGS); } static void @@ -253,6 +323,7 @@ gimp_paint_options_init (GimpPaintOptions *options) options->jitter_options = g_slice_new0 (GimpJitterOptions); options->fade_options = g_slice_new0 (GimpFadeOptions); options->gradient_options = g_slice_new0 (GimpGradientOptions); + options->smoothing_options = g_slice_new0 (GimpSmoothingOptions); } static void @@ -277,6 +348,7 @@ gimp_paint_options_finalize (GObject *object) g_slice_free (GimpJitterOptions, options->jitter_options); g_slice_free (GimpFadeOptions, options->fade_options); g_slice_free (GimpGradientOptions, options->gradient_options); + g_slice_free (GimpSmoothingOptions, options->smoothing_options); G_OBJECT_CLASS (parent_class)->finalize (object); } @@ -287,10 +359,11 @@ gimp_paint_options_set_property (GObject *object, const GValue *value, GParamSpec *pspec) { - GimpPaintOptions *options = GIMP_PAINT_OPTIONS (object); - GimpJitterOptions *jitter_options = options->jitter_options; - GimpFadeOptions *fade_options = options->fade_options; - GimpGradientOptions *gradient_options = options->gradient_options; + GimpPaintOptions *options = GIMP_PAINT_OPTIONS (object); + GimpFadeOptions *fade_options = options->fade_options; + GimpJitterOptions *jitter_options = options->jitter_options; + GimpGradientOptions *gradient_options = options->gradient_options; + GimpSmoothingOptions *smoothing_options= options->smoothing_options; switch (property_id) { @@ -382,6 +455,18 @@ gimp_paint_options_set_property (GObject *object, options->gradient_view_size = g_value_get_int (value); break; + case PROP_USE_SMOOTHING: + smoothing_options->use_smoothing = g_value_get_boolean (value); + break; + + case PROP_SMOOTHING_HISTORY: + smoothing_options->smoothing_history = g_value_get_int (value); + break; + + case PROP_SMOOTHING_FACTOR: + smoothing_options->smoothing_factor = g_value_get_double (value); + break; + default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; @@ -394,10 +479,11 @@ gimp_paint_options_get_property (GObject *object, GValue *value, GParamSpec *pspec) { - GimpPaintOptions *options = GIMP_PAINT_OPTIONS (object); - GimpJitterOptions *jitter_options = options->jitter_options; - GimpFadeOptions *fade_options = options->fade_options; - GimpGradientOptions *gradient_options = options->gradient_options; + GimpPaintOptions *options = GIMP_PAINT_OPTIONS (object); + GimpFadeOptions *fade_options = options->fade_options; + GimpJitterOptions *jitter_options = options->jitter_options; + GimpGradientOptions *gradient_options = options->gradient_options; + GimpSmoothingOptions *smoothing_options= options->smoothing_options; switch (property_id) { @@ -488,6 +574,18 @@ gimp_paint_options_get_property (GObject *object, case PROP_GRADIENT_VIEW_SIZE: g_value_set_int (value, options->gradient_view_size); break; + + case PROP_USE_SMOOTHING: + g_value_set_boolean(value, smoothing_options->use_smoothing); + break; + + case PROP_SMOOTHING_HISTORY: + g_value_set_int (value, smoothing_options->smoothing_history); + break; + + case PROP_SMOOTHING_FACTOR: + g_value_set_double (value, smoothing_options->smoothing_factor); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); @@ -651,3 +749,65 @@ gimp_paint_options_get_brush_mode (GimpPaintOptions *paint_options) return GIMP_BRUSH_SOFT; } + +GimpCoords gimp_paint_options_get_smoothed_coords (GimpPaintOptions *paint_options, + const GimpCoords *original_coords, + GimpCircularQueue *history) +{ + gdouble PI = 4 * atan(1); + GimpSmoothingOptions *smoothing_options = paint_options->smoothing_options; + if (smoothing_options->use_smoothing && smoothing_options->smoothing_history > 0) { + int i; + GimpCoords result = *original_coords; + guint length; + gdouble gaussian_weight = 0.0; + gdouble gaussian_weight2 = SQR (smoothing_options->smoothing_factor); + gdouble velocity_sum = 0.0; + gint min_index; + gdouble scale_sum = 0.0; + + result.x = result.y = 0.0; + gimp_circular_queue_enqueue(history, *original_coords); + length = gimp_circular_queue_length(history); + + min_index = length - MIN(length, smoothing_options->smoothing_history); + + if (gaussian_weight2 != 0.0) + gaussian_weight = 1 / (sqrt (2 * PI) * smoothing_options->smoothing_factor); + +// g_print("IN:%f-%f\n", original_coords->x, original_coords->y); + + for (i = (int)(length - 1); i >= min_index; i--) { + gdouble rate = 0; + GimpCoords* next_coords = &gimp_circular_queue_index(history, GimpCoords, i); +// g_print("%d: %f-%f\n", i, next_coords->x, next_coords->y); + + if (gaussian_weight2 != 0) + { + /* We use gaussian function with velocity as a window function */ + velocity_sum += next_coords->velocity * 100; + rate = gaussian_weight * exp (-velocity_sum*velocity_sum / (2 * gaussian_weight2)); + /* If i == 0 && rate == 0.0, resulting value becomes zero. + * To avoid this, we treat this as a special case. + */ + if (i == 0 && rate == 0.0) + rate = 1.0; + } + else + { + rate = (i == 0) ? 1.0 : 0.0; + } + scale_sum += rate; + result.x += rate * next_coords->x; + result.y += rate * next_coords->y; + } + if (scale_sum != 0.0) + { + result.x /= scale_sum; + result.y /= scale_sum; + } +// g_print("OUT:%f-%f\n", result.x, result.y); + return result; + } + return *original_coords; +} diff --git a/app/paint/gimppaintoptions.h b/app/paint/gimppaintoptions.h index c306a575fb..8cc00506d0 100644 --- a/app/paint/gimppaintoptions.h +++ b/app/paint/gimppaintoptions.h @@ -29,10 +29,30 @@ GIMP_CONTEXT_BRUSH_MASK | \ GIMP_CONTEXT_DYNAMICS_MASK +typedef struct _GimpCircularQueue GimpCircularQueue; +struct _GimpCircularQueue +{ + guint element_size; + guint queue_size; + guint start; + guint end; + gpointer data; +}; +GimpCircularQueue* gimp_circular_queue_new(guint element_size, guint queue_size); +void gimp_circular_queue_free(GimpCircularQueue* queue); +void gimp_circular_queue_enqueue_data(GimpCircularQueue* queue, gpointer data); +gpointer gimp_circular_queue_get_nth_offset(GimpCircularQueue* queue, guint index); +gpointer gimp_circular_queue_get_last_offset(GimpCircularQueue* queue); +#define gimp_circular_queue_length(q) ((q)->end - (q)->start) +#define gimp_circular_queue_enqueue(q, a) gimp_circular_queue_enqueue_data(q, (void*)(&(a))) +#define gimp_circular_queue_index(q, type, i) (*(type*)gimp_circular_queue_get_nth_offset(q, i)) +#define gimp_circular_queue_last(q, type) (*(type*)gimp_circular_queue_get_last_offset(q)) + typedef struct _GimpJitterOptions GimpJitterOptions; typedef struct _GimpFadeOptions GimpFadeOptions; typedef struct _GimpGradientOptions GimpGradientOptions; +typedef struct _GimpSmoothingOptions GimpSmoothingOptions; struct _GimpJitterOptions { @@ -54,6 +74,13 @@ struct _GimpGradientOptions GimpRepeatMode gradient_repeat; }; +struct _GimpSmoothingOptions +{ + gboolean use_smoothing; + gint smoothing_history; + gdouble smoothing_factor; +}; + #define GIMP_TYPE_PAINT_OPTIONS (gimp_paint_options_get_type ()) #define GIMP_PAINT_OPTIONS(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_PAINT_OPTIONS, GimpPaintOptions)) @@ -85,6 +112,7 @@ struct _GimpPaintOptions gboolean dynamics_expanded; GimpFadeOptions *fade_options; GimpGradientOptions *gradient_options; + GimpSmoothingOptions *smoothing_options; GimpViewType brush_view_type; GimpViewSize brush_view_size; @@ -119,6 +147,10 @@ gboolean gimp_paint_options_get_gradient_color (GimpPaintOptions *paint_options, gdouble pixel_dist, GimpRGB *color); +GimpCoords gimp_paint_options_get_smoothed_coords (GimpPaintOptions *paint_options, + const GimpCoords *original_coords, + GimpCircularQueue *history); + GimpBrushApplicationMode gimp_paint_options_get_brush_mode (GimpPaintOptions *paint_options); diff --git a/app/tools/gimppaintoptions-gui.c b/app/tools/gimppaintoptions-gui.c index eb117e0751..0c67618064 100644 --- a/app/tools/gimppaintoptions-gui.c +++ b/app/tools/gimppaintoptions-gui.c @@ -50,16 +50,16 @@ #include "gimp-intl.h" - - +static void gimp_paint_options_gui_reset_size (GtkWidget *button, + GimpPaintOptions *paint_options); static GtkWidget * dynamics_options_gui (GimpPaintOptions *paint_options, GType tool_type); -static GtkWidget * jitter_options_gui (GimpPaintOptions *paint_options, - GType tool_type); -static void gimp_paint_options_gui_reset_size (GtkWidget *button, - GimpPaintOptions *paint_options); +static GtkWidget * jitter_options_gui (GimpPaintOptions *paint_options, + GType tool_type); +static GtkWidget * smoothing_options_gui (GimpPaintOptions *paint_options, + GType tool_type); /* public functions */ @@ -171,6 +171,11 @@ gimp_paint_options_gui (GimpToolOptions *tool_options) gtk_widget_show (frame); } + frame = smoothing_options_gui (options, tool_type); + gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, FALSE, 0); + gtk_widget_show (frame); + + /* the "incremental" toggle */ if (tool_type == GIMP_TYPE_PENCIL_TOOL || tool_type == GIMP_TYPE_PAINTBRUSH_TOOL || @@ -326,3 +331,36 @@ gimp_paint_options_gui_reset_size (GtkWidget *button, NULL); } } + +static GtkWidget * +smoothing_options_gui (GimpPaintOptions *paint_options, + GType tool_type) +{ + GObject *config = G_OBJECT (paint_options); + GtkWidget *frame; + GtkWidget *table; + GtkObject *factor; + + table = gtk_table_new (2, 3, FALSE); + gtk_table_set_col_spacings (GTK_TABLE (table), 2); + + frame = gimp_prop_expanding_frame_new (config, "use-smoothing", + _("Apply Smoothing"), + table, NULL); + + gimp_prop_scale_entry_new (config, "smoothing-history", + GTK_TABLE (table), 0, 0, + _("Quality:"), + 1, 10, 1, + FALSE, 0, 100); + + factor = gimp_prop_scale_entry_new (config, "smoothing-factor", + GTK_TABLE (table), 0, 1, + _("Factor:"), + 1, 10, 1, + FALSE, 0, 100); + gimp_scale_entry_set_logarithmic (factor, TRUE); + + return frame; +} +