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; +} +