[gimp] app: G-Pen algorithm for GIMP trunk. Now smoothing function works for Ink and Brush tools.



commit 082aa272a4915127144b44a3faf2b2f3754525e1
Author: Alexia Death <alexiadeath gmail com>
Date:   Fri Oct 22 21:47:36 2010 +0900

    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 71f5f13..f87d6c2 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, &current_coords);
+  
+  if (paint_core->smoothing_history) {
+    current_coords = gimp_paint_options_get_smoothed_coords(paint_options, &current_coords, paint_core->smoothing_history);
+    gimp_paint_core_set_current_coords (paint_core, &current_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 96e72dc..247063d 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 6cb185b..dbea24c 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 c545faf..16df3a1 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 a42ac51..e914b46 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 0a0d315..49f1e9e 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 c306a57..8cc0050 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 eb117e0..0c67618 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;
+}
+



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