[gimp/multi-stroke: 1/31] Bug 648776 - mirror symmetries.



commit c1a55202fde1a07bb6e46981868d38a7d81c79a4
Author: Jehan <jehan girinstud io>
Date:   Mon Apr 29 10:53:36 2013 +0900

    Bug 648776 - mirror symmetries.
    
    You can now set any paint tool to mirror any stroke relatively
    horizontal/vertical axis or a central point (any combination of these 3
    symmetries).
    This has been implemented as a new multi-stroke core, where every stroke
    is actually handled as a multi-stroke (of course by default, the multi-stroke
    is of size 1).
    Current version has to be activated in the playground.

 app/config/gimpguiconfig.c              |   14 +
 app/config/gimpguiconfig.h              |    1 +
 app/config/gimprc-blurbs.h              |    3 +
 app/core/Makefile.am                    |    2 +
 app/core/core-types.h                   |    1 +
 app/core/gimpbrush-boundary.c           |    2 +-
 app/core/gimpbrush.c                    |   64 +++-
 app/core/gimpbrush.h                    |    2 +
 app/core/gimpbrushcache.c               |    4 +
 app/core/gimpbrushcache.h               |    3 +
 app/core/gimpcontext.c                  |   19 +
 app/core/gimpimage-private.h            |    4 +
 app/core/gimpimage.c                    |  180 ++++++++-
 app/core/gimpimage.h                    |   14 +
 app/core/gimpmirrorguide.c              |  131 ++++++
 app/core/gimpmirrorguide.h              |   60 +++
 app/dialogs/preferences-dialog.c        |    3 +
 app/display/display-enums.c             |   31 ++
 app/display/display-enums.h             |   10 +
 app/display/gimpcanvas-style.c          |   45 ++-
 app/display/gimpcanvas-style.h          |    1 +
 app/display/gimpcanvasguide.c           |   19 +-
 app/display/gimpcanvasguide.h           |    2 +-
 app/display/gimpdisplayshell-handlers.c |    4 +-
 app/paint/Makefile.am                   |    6 +
 app/paint/gimpairbrush.c                |   47 ++-
 app/paint/gimpairbrush.h                |    2 +
 app/paint/gimpbrushcore.c               |   32 +-
 app/paint/gimpbrushcore.h               |    8 +-
 app/paint/gimpclone.c                   |   26 +-
 app/paint/gimpconvolve.c                |  152 ++++---
 app/paint/gimpdodgeburn.c               |  119 +++---
 app/paint/gimperaser.c                  |   79 ++--
 app/paint/gimpheal.c                    |   27 +-
 app/paint/gimpink.c                     |  251 +++++++----
 app/paint/gimpink.h                     |    6 +-
 app/paint/gimpinkundo.c                 |   32 +-
 app/paint/gimpinkundo.h                 |    2 +-
 app/paint/gimpmirror.c                  |  727 +++++++++++++++++++++++++++++++
 app/paint/gimpmirror.h                  |   62 +++
 app/paint/gimpmultistroke-info.c        |   59 +++
 app/paint/gimpmultistroke-info.h        |   29 ++
 app/paint/gimpmultistroke.c             |  374 ++++++++++++++++
 app/paint/gimpmultistroke.h             |   94 ++++
 app/paint/gimppaintbrush.c              |  181 +++++----
 app/paint/gimppaintbrush.h              |    2 +-
 app/paint/gimppaintcore-loops.c         |   25 +-
 app/paint/gimppaintcore-loops.h         |    2 +-
 app/paint/gimppaintcore.c               |   26 +-
 app/paint/gimppaintcore.h               |    2 +-
 app/paint/gimppaintoptions.c            |   72 +++-
 app/paint/gimppaintoptions.h            |    4 +
 app/paint/gimpperspectiveclone.c        |   73 ++--
 app/paint/gimpsmudge.c                  |  345 +++++++++-------
 app/paint/gimpsmudge.h                  |    2 +-
 app/paint/gimpsourcecore.c              |  186 +++++----
 app/paint/gimpsourcecore.h              |    3 +-
 app/paint/paint-types.h                 |    3 +
 app/tools/gimppaintoptions-gui.c        |  231 ++++++++++-
 app/tools/tool_manager.c                |    9 +
 app/xcf/xcf-load.c                      |  127 ++++++
 app/xcf/xcf-private.h                   |    3 +-
 app/xcf/xcf-save.c                      |  175 ++++++++
 devel-docs/xcf.txt                      |   18 +
 64 files changed, 3552 insertions(+), 690 deletions(-)
---
diff --git a/app/config/gimpguiconfig.c b/app/config/gimpguiconfig.c
index 833cb0f..8b5c6fa 100644
--- a/app/config/gimpguiconfig.c
+++ b/app/config/gimpguiconfig.c
@@ -82,6 +82,7 @@ enum
 
   PROP_PLAYGROUND_NPD_TOOL,
   PROP_PLAYGROUND_HANDLE_TRANSFORM_TOOL,
+  PROP_PLAYGROUND_MULTI_STROKE,
   PROP_PLAYGROUND_MYBRUSH_TOOL,
   PROP_PLAYGROUND_SEAMLESS_CLONE_TOOL,
 
@@ -291,6 +292,13 @@ gimp_gui_config_class_init (GimpGuiConfigClass *klass)
                                     GIMP_PARAM_STATIC_STRINGS |
                                     GIMP_CONFIG_PARAM_RESTART);
   GIMP_CONFIG_INSTALL_PROP_BOOLEAN (object_class,
+                                    PROP_PLAYGROUND_MULTI_STROKE,
+                                    "playground-multi-stroke",
+                                    PLAYGROUND_MULTI_STROKE_BLURB,
+                                    FALSE,
+                                    GIMP_PARAM_STATIC_STRINGS |
+                                    GIMP_CONFIG_PARAM_RESTART);
+  GIMP_CONFIG_INSTALL_PROP_BOOLEAN (object_class,
                                     PROP_PLAYGROUND_MYBRUSH_TOOL,
                                     "playground-mybrush-tool",
                                     PLAYGROUND_MYBRUSH_TOOL_BLURB,
@@ -515,6 +523,9 @@ gimp_gui_config_set_property (GObject      *object,
     case PROP_PLAYGROUND_HANDLE_TRANSFORM_TOOL:
       gui_config->playground_handle_transform_tool = g_value_get_boolean (value);
       break;
+    case PROP_PLAYGROUND_MULTI_STROKE:
+      gui_config->playground_multi_stroke = g_value_get_boolean (value);
+      break;
     case PROP_PLAYGROUND_MYBRUSH_TOOL:
       gui_config->playground_mybrush_tool = g_value_get_boolean (value);
       break;
@@ -662,6 +673,9 @@ gimp_gui_config_get_property (GObject    *object,
     case PROP_PLAYGROUND_HANDLE_TRANSFORM_TOOL:
       g_value_set_boolean (value, gui_config->playground_handle_transform_tool);
       break;
+    case PROP_PLAYGROUND_MULTI_STROKE:
+      g_value_set_boolean (value, gui_config->playground_multi_stroke);
+      break;
     case PROP_PLAYGROUND_MYBRUSH_TOOL:
       g_value_set_boolean (value, gui_config->playground_mybrush_tool);
       break;
diff --git a/app/config/gimpguiconfig.h b/app/config/gimpguiconfig.h
index 2452f3e..8b3a80d 100644
--- a/app/config/gimpguiconfig.h
+++ b/app/config/gimpguiconfig.h
@@ -76,6 +76,7 @@ struct _GimpGuiConfig
   /* experimental playground */
   gboolean             playground_npd_tool;
   gboolean             playground_handle_transform_tool;
+  gboolean             playground_multi_stroke;
   gboolean             playground_mybrush_tool;
   gboolean             playground_seamless_clone_tool;
 
diff --git a/app/config/gimprc-blurbs.h b/app/config/gimprc-blurbs.h
index 70e216a..a71acac 100644
--- a/app/config/gimprc-blurbs.h
+++ b/app/config/gimprc-blurbs.h
@@ -388,6 +388,9 @@ _("Enable the N-Point Deformation tool.")
 #define PLAYGROUND_HANDLE_TRANSFORM_TOOL_BLURB \
 _("Enable the Handle Transform tool.")
 
+#define PLAYGROUND_MULTI_STROKE_BLURB \
+_("Enable the Multi Stroke on painting.")
+
 #define PLAYGROUND_MYBRUSH_TOOL_BLURB \
 _("Enable the MyPaint Brush tool.")
 
diff --git a/app/core/Makefile.am b/app/core/Makefile.am
index 3c6a233..f4836a3 100644
--- a/app/core/Makefile.am
+++ b/app/core/Makefile.am
@@ -197,6 +197,8 @@ libappcore_a_sources = \
        gimpgrouplayer.h                        \
        gimpgrouplayerundo.c                    \
        gimpgrouplayerundo.h                    \
+       gimpmirrorguide.c                       \
+       gimpmirrorguide.h                       \
        gimpguide.c                             \
        gimpguide.h                             \
        gimpguideundo.c                         \
diff --git a/app/core/core-types.h b/app/core/core-types.h
index 018857c..7fe6afb 100644
--- a/app/core/core-types.h
+++ b/app/core/core-types.h
@@ -179,6 +179,7 @@ typedef struct _GimpUndoAccumulator   GimpUndoAccumulator;
 typedef struct _GimpBuffer          GimpBuffer;
 typedef struct _GimpEnvironTable    GimpEnvironTable;
 typedef struct _GimpGuide           GimpGuide;
+typedef struct _GimpMirrorGuide     GimpMirrorGuide;
 typedef struct _GimpHistogram       GimpHistogram;
 typedef struct _GimpIdTable         GimpIdTable;
 typedef struct _GimpImageMap        GimpImageMap;
diff --git a/app/core/gimpbrush-boundary.c b/app/core/gimpbrush-boundary.c
index c1573a5..a9efbec 100644
--- a/app/core/gimpbrush-boundary.c
+++ b/app/core/gimpbrush-boundary.c
@@ -39,7 +39,7 @@ gimp_brush_transform_boundary_exact (GimpBrush *brush,
 {
   const GimpTempBuf *mask;
 
-  mask = gimp_brush_transform_mask (brush,
+  mask = gimp_brush_transform_mask (brush, NULL,
                                     scale, aspect_ratio, angle, hardness);
 
   if (mask)
diff --git a/app/core/gimpbrush.c b/app/core/gimpbrush.c
index e57f0b4..c8d987c 100644
--- a/app/core/gimpbrush.c
+++ b/app/core/gimpbrush.c
@@ -313,12 +313,12 @@ gimp_brush_get_new_preview (GimpViewable *viewable,
             {
                GimpBrushGenerated *gen_brush = GIMP_BRUSH_GENERATED (brush);
 
-               mask_buf = gimp_brush_transform_mask (brush, scale,
+               mask_buf = gimp_brush_transform_mask (brush, NULL, scale,
                                                      0.0, 0.0,
                                                      gimp_brush_generated_get_hardness (gen_brush));
             }
           else
-            mask_buf = gimp_brush_transform_mask (brush, scale,
+            mask_buf = gimp_brush_transform_mask (brush, NULL, scale,
                                                   0.0, 0.0, 1.0);
 
           if (! mask_buf)
@@ -332,7 +332,7 @@ gimp_brush_get_new_preview (GimpViewable *viewable,
             }
 
           if (pixmap_buf)
-            pixmap_buf = gimp_brush_transform_pixmap (brush, scale,
+            pixmap_buf = gimp_brush_transform_pixmap (brush, NULL, scale,
                                                       0.0, 0.0, 1.0);
 
           mask_width  = gimp_temp_buf_get_width  (mask_buf);
@@ -611,6 +611,7 @@ gimp_brush_transform_size (GimpBrush     *brush,
 
 const GimpTempBuf *
 gimp_brush_transform_mask (GimpBrush *brush,
+                           GeglNode  *op,
                            gdouble    scale,
                            gdouble    aspect_ratio,
                            gdouble    angle,
@@ -628,7 +629,7 @@ gimp_brush_transform_mask (GimpBrush *brush,
                              &width, &height);
 
   mask = gimp_brush_cache_get (brush->priv->mask_cache,
-                               width, height,
+                               op, width, height,
                                scale, aspect_ratio, angle, hardness);
 
   if (! mask)
@@ -649,9 +650,31 @@ gimp_brush_transform_mask (GimpBrush *brush,
                                                                hardness);
         }
 
+      if (op)
+        {
+          GeglNode    *graph, *source, *target;
+          GeglBuffer  *buffer = gimp_temp_buf_create_buffer ((GimpTempBuf *) mask);
+
+          graph    = gegl_node_new ();
+          source   = gegl_node_new_child (graph,
+                                          "operation", "gegl:buffer-source",
+                                          "buffer", buffer,
+                                          NULL);
+          gegl_node_add_child (graph, op);
+          target  = gegl_node_new_child (graph,
+                                         "operation", "gegl:write-buffer",
+                                         "buffer", buffer,
+                                         NULL);
+
+          gegl_node_link_many (source, op, target, NULL);
+          gegl_node_process (target);
+
+          g_object_unref (graph);
+          g_object_unref (buffer);
+        }
       gimp_brush_cache_add (brush->priv->mask_cache,
                             (gpointer) mask,
-                            width, height,
+                            op, width, height,
                             scale, aspect_ratio, angle, hardness);
     }
 
@@ -660,6 +683,7 @@ gimp_brush_transform_mask (GimpBrush *brush,
 
 const GimpTempBuf *
 gimp_brush_transform_pixmap (GimpBrush *brush,
+                             GeglNode  *op,
                              gdouble    scale,
                              gdouble    aspect_ratio,
                              gdouble    angle,
@@ -678,7 +702,7 @@ gimp_brush_transform_pixmap (GimpBrush *brush,
                              &width, &height);
 
   pixmap = gimp_brush_cache_get (brush->priv->pixmap_cache,
-                                 width, height,
+                                 op, width, height,
                                  scale, aspect_ratio, angle, hardness);
 
   if (! pixmap)
@@ -699,9 +723,31 @@ gimp_brush_transform_pixmap (GimpBrush *brush,
                                                                    hardness);
         }
 
+      if (op)
+        {
+          GeglNode    *graph, *source, *target;
+          GeglBuffer  *buffer = gimp_temp_buf_create_buffer ((GimpTempBuf *) pixmap);
+
+          graph    = gegl_node_new ();
+          source   = gegl_node_new_child (graph,
+                                          "operation", "gegl:buffer-source",
+                                          "buffer", buffer,
+                                          NULL);
+          gegl_node_add_child (graph, op);
+          target  = gegl_node_new_child (graph,
+                                         "operation", "gegl:write-buffer",
+                                         "buffer", buffer,
+                                         NULL);
+
+          gegl_node_link_many (source, op, target, NULL);
+          gegl_node_process (target);
+
+          g_object_unref (graph);
+          g_object_unref (buffer);
+        }
       gimp_brush_cache_add (brush->priv->pixmap_cache,
                             (gpointer) pixmap,
-                            width, height,
+                            op, width, height,
                             scale, aspect_ratio, angle, hardness);
     }
 
@@ -729,7 +775,7 @@ gimp_brush_transform_boundary (GimpBrush *brush,
                              width, height);
 
   boundary = gimp_brush_cache_get (brush->priv->boundary_cache,
-                                   *width, *height,
+                                   NULL, *width, *height,
                                    scale, aspect_ratio, angle, hardness);
 
   if (! boundary)
@@ -751,7 +797,7 @@ gimp_brush_transform_boundary (GimpBrush *brush,
       if (boundary)
         gimp_brush_cache_add (brush->priv->boundary_cache,
                               (gpointer) boundary,
-                              *width, *height,
+                              NULL, *width, *height,
                               scale, aspect_ratio, angle, hardness);
     }
 
diff --git a/app/core/gimpbrush.h b/app/core/gimpbrush.h
index 53e2762..42390d1 100644
--- a/app/core/gimpbrush.h
+++ b/app/core/gimpbrush.h
@@ -109,11 +109,13 @@ void                   gimp_brush_transform_size     (GimpBrush        *brush,
                                                       gint             *width,
                                                       gint             *height);
 const GimpTempBuf    * gimp_brush_transform_mask     (GimpBrush        *brush,
+                                                      GeglNode         *op,
                                                       gdouble           scale,
                                                       gdouble           aspect_ratio,
                                                       gdouble           angle,
                                                       gdouble           hardness);
 const GimpTempBuf    * gimp_brush_transform_pixmap   (GimpBrush        *brush,
+                                                      GeglNode         *op,
                                                       gdouble           scale,
                                                       gdouble           aspect_ratio,
                                                       gdouble           angle,
diff --git a/app/core/gimpbrushcache.c b/app/core/gimpbrushcache.c
index bbaf348..38c0aba 100644
--- a/app/core/gimpbrushcache.c
+++ b/app/core/gimpbrushcache.c
@@ -176,6 +176,7 @@ gimp_brush_cache_clear (GimpBrushCache *cache)
 
 gconstpointer
 gimp_brush_cache_get (GimpBrushCache *cache,
+                      GeglNode       *op,
                       gint            width,
                       gint            height,
                       gdouble         scale,
@@ -186,6 +187,7 @@ gimp_brush_cache_get (GimpBrushCache *cache,
   g_return_val_if_fail (GIMP_IS_BRUSH_CACHE (cache), NULL);
 
   if (cache->last_data                         &&
+      cache->last_op           == op           &&
       cache->last_width        == width        &&
       cache->last_height       == height       &&
       cache->last_scale        == scale        &&
@@ -208,6 +210,7 @@ gimp_brush_cache_get (GimpBrushCache *cache,
 void
 gimp_brush_cache_add (GimpBrushCache *cache,
                       gpointer        data,
+                      GeglNode       *op,
                       gint            width,
                       gint            height,
                       gdouble         scale,
@@ -225,6 +228,7 @@ gimp_brush_cache_add (GimpBrushCache *cache,
     cache->data_destroy (cache->last_data);
 
   cache->last_data         = data;
+  cache->last_op           = op;
   cache->last_width        = width;
   cache->last_height       = height;
   cache->last_scale        = scale;
diff --git a/app/core/gimpbrushcache.h b/app/core/gimpbrushcache.h
index 5c879cc..c529388 100644
--- a/app/core/gimpbrushcache.h
+++ b/app/core/gimpbrushcache.h
@@ -42,6 +42,7 @@ struct _GimpBrushCache
   GDestroyNotify  data_destroy;
 
   gpointer        last_data;
+  GeglNode       *last_op;
   gint            last_width;
   gint            last_height;
   gdouble         last_scale;
@@ -68,6 +69,7 @@ GimpBrushCache * gimp_brush_cache_new      (GDestroyNotify  data_destory,
 void             gimp_brush_cache_clear    (GimpBrushCache *cache);
 
 gconstpointer    gimp_brush_cache_get      (GimpBrushCache *cache,
+                                            GeglNode       *op,
                                             gint            width,
                                             gint            height,
                                             gdouble         scale,
@@ -76,6 +78,7 @@ gconstpointer    gimp_brush_cache_get      (GimpBrushCache *cache,
                                             gdouble         hardness);
 void             gimp_brush_cache_add      (GimpBrushCache *cache,
                                             gpointer        data,
+                                            GeglNode       *op,
                                             gint            width,
                                             gint            height,
                                             gdouble         scale,
diff --git a/app/core/gimpcontext.c b/app/core/gimpcontext.c
index cb1f0af..414d7f7 100644
--- a/app/core/gimpcontext.c
+++ b/app/core/gimpcontext.c
@@ -51,6 +51,9 @@
 #include "gimptoolinfo.h"
 #include "gimptoolpreset.h"
 
+#include "paint/gimpmultistroke.h"
+#include "paint/gimppaintoptions.h"
+
 #include "text/gimpfont.h"
 
 #include "gimp-intl.h"
@@ -1914,6 +1917,22 @@ gimp_context_real_set_image (GimpContext *context,
 
   context->image = image;
 
+  if (context->tool_info &&
+      GIMP_IS_PAINT_OPTIONS (context->tool_info->tool_options))
+    {
+      GimpPaintOptions *paint_options;
+      GimpMultiStroke  *mstroke = NULL;
+
+      paint_options = GIMP_PAINT_OPTIONS (context->tool_info->tool_options);
+
+      if (image)
+        mstroke = gimp_image_get_selected_multi_stroke (image);
+
+      g_object_set (paint_options, "multi-stroke",
+                    mstroke ? mstroke->type : G_TYPE_NONE,
+                    NULL);
+    }
+
   g_object_notify (G_OBJECT (context), "image");
   gimp_context_image_changed (context);
 }
diff --git a/app/core/gimpimage-private.h b/app/core/gimpimage-private.h
index f2a9dfa..3ed271d 100644
--- a/app/core/gimpimage-private.h
+++ b/app/core/gimpimage-private.h
@@ -87,6 +87,10 @@ struct _GimpImagePrivate
   GeglNode          *graph;                 /*  GEGL projection graph        */
   GeglNode          *visible_mask;          /*  component visibility node    */
 
+  GList             *transformations;       /* Multi-Stroke transformations  */
+  GimpMultiStroke   *selected_transform;    /* Selected transformation       */
+  GimpMultiStroke   *single_stroke;         /* The base "Single" stroke      */
+
   GList             *guides;                /*  guides                       */
   GimpGrid          *grid;                  /*  grid                         */
   GList             *sample_points;         /*  color sample points          */
diff --git a/app/core/gimpimage.c b/app/core/gimpimage.c
index 087c939..8368795 100644
--- a/app/core/gimpimage.c
+++ b/app/core/gimpimage.c
@@ -36,6 +36,9 @@
 
 #include "gegl/gimp-babl.h"
 
+#include "paint/gimpmultistroke.h"
+#include "paint/gimpmultistroke-info.h"
+
 #include "gimp.h"
 #include "gimp-memsize.h"
 #include "gimp-parasites.h"
@@ -697,6 +700,11 @@ gimp_image_init (GimpImage *image)
 
   private->projection          = gimp_projection_new (GIMP_PROJECTABLE (image));
 
+  private->transformations     = NULL;
+  private->selected_transform  = NULL;
+  private->single_stroke       = gimp_multi_stroke_new (GIMP_TYPE_MULTI_STROKE,
+                                                        image);
+
   private->guides              = NULL;
   private->grid                = NULL;
   private->sample_points       = NULL;
@@ -1051,6 +1059,18 @@ gimp_image_finalize (GObject *object)
       private->guides = NULL;
     }
 
+  if (private->transformations)
+    {
+      g_list_free_full (private->transformations, g_object_unref);
+      private->transformations = NULL;
+    }
+
+  if (private->single_stroke)
+    {
+      g_object_unref (private->single_stroke);
+      private->single_stroke = NULL;
+    }
+
   if (private->grid)
     {
       g_object_unref (private->grid);
@@ -2281,8 +2301,11 @@ gimp_image_get_xcf_version (GimpImage    *image,
                             gint         *gimp_version,
                             const gchar **version_string)
 {
-  GList *list;
-  gint   version = 0;  /* default to oldest */
+  GimpImagePrivate *private;
+  GList            *list;
+  gint              version = 0;  /* default to oldest */
+
+  private = GIMP_IMAGE_GET_PRIVATE (image);
 
   /* need version 1 for colormaps */
   if (gimp_image_get_colormap (image))
@@ -2330,6 +2353,12 @@ gimp_image_get_xcf_version (GimpImage    *image,
   if (zlib_compression)
     version = MAX (8, version);
 
+  /* need version 9 for Multi-Stroke */
+  if (private->transformations)
+    {
+      version = MAX (9, version);
+    }
+
   switch (version)
     {
     case 0:
@@ -4568,3 +4597,150 @@ gimp_image_invalidate_previews (GimpImage *image)
   gimp_item_stack_invalidate_previews (layers);
   gimp_item_stack_invalidate_previews (channels);
 }
+
+/**
+ * gimp_image_add_multi_stroke:
+ * @image:   the #GimpImage
+ * @mstroke: the #GimpMultiStroke
+ *
+ * Add a multi-stroke transformation to @image and make it the
+ * selected transformation.
+ **/
+void
+gimp_image_add_multi_stroke (GimpImage       *image,
+                             GimpMultiStroke *mstroke)
+{
+  GimpImagePrivate *private;
+
+  g_return_if_fail (GIMP_IS_IMAGE (image));
+  g_return_if_fail (GIMP_IS_MULTI_STROKE (mstroke));
+
+  private = GIMP_IMAGE_GET_PRIVATE (image);
+
+  private->transformations = g_list_prepend (private->transformations,
+                                             g_object_ref (mstroke));
+  private->selected_transform = mstroke;
+}
+
+/**
+ * gimp_image_remove_multi_stroke:
+ * @image:   the #GimpImage
+ * @mstroke: the #GimpMultiStroke
+ *
+ * Remove @mstroke from the list of transformations of @image.
+ * If it was the selected transformation, unselect it first.
+ **/
+void
+gimp_image_remove_multi_stroke (GimpImage       *image,
+                                GimpMultiStroke *mstroke)
+{
+  GimpImagePrivate *private;
+
+  g_return_if_fail (GIMP_IS_MULTI_STROKE (mstroke));
+  g_return_if_fail (GIMP_IS_IMAGE (image));
+
+  private = GIMP_IMAGE_GET_PRIVATE (image);
+
+  if (private->selected_transform == mstroke)
+    private->selected_transform = NULL;
+  private->transformations = g_list_remove (private->transformations,
+                                            mstroke);
+  g_object_unref (mstroke);
+}
+
+/**
+ * gimp_image_get_multi_stroke:
+ * @image: the #GimpImage
+ *
+ * Returns a list of #GimpMultiStroke set on @image.
+ * The returned list belongs to @image and should not be freed.
+ **/
+GList *
+gimp_image_get_multi_strokes (GimpImage *image)
+{
+  GimpImagePrivate *private;
+
+  g_return_val_if_fail (GIMP_IS_IMAGE (image), FALSE);
+
+  private = GIMP_IMAGE_GET_PRIVATE (image);
+
+  return private->transformations;
+}
+
+/**
+ * gimp_image_select_multi_stroke:
+ * @image: the #GimpImage
+ * @type:  the #GType of the multi-stroke
+ *
+ * Select the multi-stroke of type @type.
+ * Using the GType allows to select a transformation without
+ * knowing whether one of the same @type was already created.
+ *
+ * Returns TRUE on success, FALSE if no such multi-stroke was found.
+ **/
+gboolean
+gimp_image_select_multi_stroke (GimpImage *image,
+                                GType      type)
+{
+  GimpImagePrivate *private;
+  GList *iter;
+
+  g_return_val_if_fail (GIMP_IS_IMAGE (image), FALSE);
+
+  private = GIMP_IMAGE_GET_PRIVATE (image);
+
+  if (type == G_TYPE_NONE)
+    {
+      private->selected_transform = NULL;
+      return TRUE;
+    }
+  else
+    {
+      for (iter = private->transformations; iter; iter = g_list_next (iter))
+        {
+          GimpMultiStroke *mstroke = iter->data;
+          if (g_type_is_a (mstroke->type, type))
+            {
+              private->selected_transform = iter->data;
+              return TRUE;
+            }
+        }
+    }
+  return FALSE;
+}
+
+/**
+ * gimp_image_get_selected_multi_stroke:
+ * @image: the #GimpImage
+ *
+ * Returns the #GimpMultiStroke transformation selected on @image.
+ **/
+GimpMultiStroke *
+gimp_image_get_selected_multi_stroke (GimpImage *image)
+{
+  GimpImagePrivate *private;
+
+  g_return_val_if_fail (GIMP_IS_IMAGE (image), FALSE);
+
+  private = GIMP_IMAGE_GET_PRIVATE (image);
+
+  return private->selected_transform;
+}
+
+/**
+ * gimp_image_get_single_stroke:
+ * @image: the #GimpImage
+ *
+ * Returns the basic "single stroke" #GimpMultiStroke.
+ **/
+GimpMultiStroke *
+gimp_image_get_single_stroke (GimpImage *image)
+{
+  GimpImagePrivate *private;
+
+  g_return_val_if_fail (GIMP_IS_IMAGE (image), FALSE);
+
+  private = GIMP_IMAGE_GET_PRIVATE (image);
+
+  return private->single_stroke;
+}
diff --git a/app/core/gimpimage.h b/app/core/gimpimage.h
index 28d7554..c11a8af 100644
--- a/app/core/gimpimage.h
+++ b/app/core/gimpimage.h
@@ -435,5 +435,19 @@ gboolean    gimp_image_coords_in_active_pickable (GimpImage          *image,
 
 void            gimp_image_invalidate_previews   (GimpImage          *image);
 
+/* Painting Transformations */
+
+void            gimp_image_add_multi_stroke      (GimpImage       *image,
+                                                  GimpMultiStroke *mstroke);
+void            gimp_image_remove_multi_stroke   (GimpImage       *image,
+                                                  GimpMultiStroke *mstroke);
+GList         * gimp_image_get_multi_strokes     (GimpImage       *image);
+
+gboolean        gimp_image_select_multi_stroke   (GimpImage       *image,
+                                                  GType            type);
+GimpMultiStroke *
+            gimp_image_get_selected_multi_stroke (GimpImage       *image);
+GimpMultiStroke * gimp_image_get_single_stroke   (GimpImage       *image);
+
 
 #endif /* __GIMP_IMAGE_H__ */
diff --git a/app/core/gimpmirrorguide.c b/app/core/gimpmirrorguide.c
new file mode 100644
index 0000000..5f19b6d
--- /dev/null
+++ b/app/core/gimpmirrorguide.c
@@ -0,0 +1,131 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * GimpMirrorGuide
+ * Copyright (C) 2015 Jehan <jehan gimp org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gegl.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+
+#include "core-types.h"
+
+#include "core/gimptoolinfo.h"
+
+#include "paint/gimppaintoptions.h"
+
+#include "gimp.h"
+#include "gimpmirrorguide.h"
+
+enum
+{
+  PROP_0,
+  PROP_GIMP
+};
+
+static void   gimp_mirror_guide_finalize     (GObject      *object);
+static void   gimp_mirror_guide_get_property (GObject      *object,
+                                              guint         property_id,
+                                              GValue       *value,
+                                              GParamSpec   *pspec);
+static void   gimp_mirror_guide_set_property (GObject      *object,
+                                              guint         property_id,
+                                              const GValue *value,
+                                              GParamSpec   *pspec);
+
+G_DEFINE_TYPE (GimpMirrorGuide, gimp_mirror_guide, GIMP_TYPE_GUIDE)
+
+#define parent_class gimp_mirror_guide_parent_class
+
+static void
+gimp_mirror_guide_class_init (GimpMirrorGuideClass *klass)
+{
+  GObjectClass *object_class  = G_OBJECT_CLASS (klass);
+
+  object_class->get_property  = gimp_mirror_guide_get_property;
+  object_class->set_property  = gimp_mirror_guide_set_property;
+
+  object_class->finalize      = gimp_mirror_guide_finalize;
+
+  g_object_class_install_property (object_class, PROP_GIMP,
+                                   g_param_spec_object ("gimp",
+                                                        NULL, NULL,
+                                                        GIMP_TYPE_GIMP,
+                                                        GIMP_PARAM_READWRITE |
+                                                        G_PARAM_CONSTRUCT_ONLY));
+}
+
+static void
+gimp_mirror_guide_init (GimpMirrorGuide *mirror_guide)
+{
+}
+
+static void
+gimp_mirror_guide_finalize (GObject *object)
+{
+  G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+gimp_mirror_guide_get_property (GObject      *object,
+                                guint         property_id,
+                                GValue       *value,
+                                GParamSpec   *pspec)
+{
+  GimpMirrorGuide *guide = GIMP_MIRROR_GUIDE (object);
+
+  switch (property_id)
+    {
+    case PROP_GIMP:
+      g_value_set_object (value, guide->gimp);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+      break;
+    }
+}
+
+static void
+gimp_mirror_guide_set_property (GObject      *object,
+                                guint         property_id,
+                                const GValue *value,
+                                GParamSpec   *pspec)
+{
+  GimpMirrorGuide *guide = GIMP_MIRROR_GUIDE (object);
+
+  switch (property_id)
+    {
+    case PROP_GIMP:
+      guide->gimp = g_value_get_object (value);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+      break;
+    }
+}
+GimpMirrorGuide *
+gimp_mirror_guide_new (Gimp                *gimp,
+                       GimpOrientationType  orientation,
+                       guint32              guide_ID)
+{
+  return g_object_new (GIMP_TYPE_MIRROR_GUIDE,
+                       "id",          guide_ID,
+                       "orientation", orientation,
+                       "gimp",        gimp,
+                       NULL);
+}
diff --git a/app/core/gimpmirrorguide.h b/app/core/gimpmirrorguide.h
new file mode 100644
index 0000000..9b37c55
--- /dev/null
+++ b/app/core/gimpmirrorguide.h
@@ -0,0 +1,60 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * GimpMirrorGuide
+ * Copyright (C) 2015 Jehan <jehan gimp org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_MIRROR_GUIDE_H__
+#define __GIMP_MIRROR_GUIDE_H__
+
+
+#include "gimpguide.h"
+
+
+#define GIMP_TYPE_MIRROR_GUIDE            (gimp_mirror_guide_get_type ())
+#define GIMP_MIRROR_GUIDE(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_MIRROR_GUIDE, 
GimpMirrorGuide))
+#define GIMP_MIRROR_GUIDE_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_MIRROR_GUIDE, 
GimpMirrorGuideClass))
+#define GIMP_IS_MIRROR_GUIDE(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_MIRROR_GUIDE))
+#define GIMP_IS_MIRROR_GUIDE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_MIRROR_GUIDE))
+#define GIMP_MIRROR_GUIDE_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_MIRROR_GUIDE, 
GimpMirrorGuideClass))
+
+
+typedef struct _GimpMirrorGuideClass GimpMirrorGuideClass;
+
+struct _GimpMirrorGuide
+{
+  GimpGuide         parent_instance;
+
+  Gimp             *gimp;
+};
+
+struct _GimpMirrorGuideClass
+{
+  GimpGuideClass    parent_class;
+
+  /*  signals  */
+  void (* removed)  (GimpMirrorGuide  *mirror_guide);
+};
+
+
+GType               gimp_mirror_guide_get_type        (void) G_GNUC_CONST;
+
+GimpMirrorGuide *   gimp_mirror_guide_new             (Gimp                *gimp,
+                                                       GimpOrientationType  orientation,
+                                                       guint32              mirror_guide_ID);
+
+#endif /* __GIMP_MIRROR_GUIDE_H__ */
diff --git a/app/dialogs/preferences-dialog.c b/app/dialogs/preferences-dialog.c
index 5a2f278..3e55f14 100644
--- a/app/dialogs/preferences-dialog.c
+++ b/app/dialogs/preferences-dialog.c
@@ -1556,6 +1556,9 @@ prefs_dialog_new (Gimp       *gimp,
       button = prefs_check_button_add (object, "playground-seamless-clone-tool",
                                        _("_Seamless Clone tool"),
                                        GTK_BOX (vbox2));
+      button = prefs_check_button_add (object, "playground-multi-stroke",
+                                       _("_Multi-Stroke Painting"),
+                                       GTK_BOX (vbox2));
     }
 
 
diff --git a/app/display/display-enums.c b/app/display/display-enums.c
index 70e0958..7eda7dd 100644
--- a/app/display/display-enums.c
+++ b/app/display/display-enums.c
@@ -228,6 +228,37 @@ gimp_zoom_focus_get_type (void)
   return type;
 }
 
+GType
+gimp_guide_style_get_type (void)
+{
+  static const GEnumValue values[] =
+  {
+    { GIMP_GUIDE_STYLE_NONE, "GIMP_GUIDE_STYLE_NONE", "none" },
+    { GIMP_GUIDE_STYLE_NORMAL, "GIMP_GUIDE_STYLE_NORMAL", "normal" },
+    { GIMP_GUIDE_STYLE_MIRROR, "GIMP_GUIDE_STYLE_MIRROR", "mirror" },
+    { 0, NULL, NULL }
+  };
+
+  static const GimpEnumDesc descs[] =
+  {
+    { GIMP_GUIDE_STYLE_NONE, "GIMP_GUIDE_STYLE_NONE", NULL },
+    { GIMP_GUIDE_STYLE_NORMAL, "GIMP_GUIDE_STYLE_NORMAL", NULL },
+    { GIMP_GUIDE_STYLE_MIRROR, "GIMP_GUIDE_STYLE_MIRROR", NULL },
+    { 0, NULL, NULL }
+  };
+
+  static GType type = 0;
+
+  if (G_UNLIKELY (! type))
+    {
+      type = g_enum_register_static ("GimpGuideStyle", values);
+      gimp_type_set_translation_context (type, "guide-style");
+      gimp_enum_set_value_descriptions (type, descs);
+    }
+
+  return type;
+}
+
 
 /* Generated data ends here */
 
diff --git a/app/display/display-enums.h b/app/display/display-enums.h
index 01ef767..5d164ed 100644
--- a/app/display/display-enums.h
+++ b/app/display/display-enums.h
@@ -117,5 +117,15 @@ typedef enum
 
 } GimpZoomFocus;
 
+#define GIMP_TYPE_GUIDE_STYLE (gimp_guide_style_get_type ())
+
+GType gimp_guide_style_get_type (void) G_GNUC_CONST;
+
+typedef enum
+{
+    GIMP_GUIDE_STYLE_NONE,
+    GIMP_GUIDE_STYLE_NORMAL,
+    GIMP_GUIDE_STYLE_MIRROR
+} GimpGuideStyle;
 
 #endif /* __DISPLAY_ENUMS_H__ */
diff --git a/app/display/gimpcanvas-style.c b/app/display/gimpcanvas-style.c
index 81b840b..8af29b3 100644
--- a/app/display/gimpcanvas-style.c
+++ b/app/display/gimpcanvas-style.c
@@ -40,6 +40,11 @@ static const GimpRGB guide_normal_bg     = { 0.0, 0.5, 1.0, 1.0 };
 static const GimpRGB guide_active_fg     = { 0.0, 0.0, 0.0, 1.0 };
 static const GimpRGB guide_active_bg     = { 1.0, 0.0, 0.0, 1.0 };
 
+static const GimpRGB mirror_guide_normal_fg = { 1.0, 1.0, 1.0, 1.0 };
+static const GimpRGB mirror_guide_normal_bg = { 0.0, 1.0, 0.0, 1.0 };
+static const GimpRGB mirror_guide_active_fg = { 0.0, 1.0, 0.0, 1.0 };
+static const GimpRGB mirror_guide_active_bg = { 1.0, 0.0, 0.0, 1.0 };
+
 static const GimpRGB sample_point_normal = { 0.0, 0.5, 1.0, 1.0 };
 static const GimpRGB sample_point_active = { 1.0, 0.0, 0.0, 1.0 };
 
@@ -77,11 +82,14 @@ static const GimpRGB tool_fg_highlight   = { 1.0, 0.8, 0.2, 0.8 };
 /*  public functions  */
 
 void
-gimp_canvas_set_guide_style (GtkWidget *canvas,
-                             cairo_t   *cr,
-                             gboolean   active)
+gimp_canvas_set_guide_style (GtkWidget      *canvas,
+                             cairo_t        *cr,
+                             GimpGuideStyle  style,
+                             gboolean        active)
 {
   cairo_pattern_t *pattern;
+  const GimpRGB *fg;
+  const GimpRGB *bg;
 
   g_return_if_fail (GTK_IS_WIDGET (canvas));
   g_return_if_fail (cr != NULL);
@@ -89,13 +97,32 @@ gimp_canvas_set_guide_style (GtkWidget *canvas,
   cairo_set_line_width (cr, 1.0);
 
   if (active)
-    pattern = gimp_cairo_stipple_pattern_create (&guide_active_fg,
-                                                 &guide_active_bg,
-                                                 0);
+    {
+      if (style == GIMP_GUIDE_STYLE_MIRROR)
+        {
+          fg = &mirror_guide_active_fg;
+          bg = &mirror_guide_active_bg;
+        }
+      else /* style == GIMP_GUIDE_STYLE_NORMAL */
+        {
+          fg = &guide_active_fg;
+          bg = &guide_active_bg;
+        }
+    }
   else
-    pattern = gimp_cairo_stipple_pattern_create (&guide_normal_fg,
-                                                 &guide_normal_bg,
-                                                 0);
+    {
+      if (style == GIMP_GUIDE_STYLE_MIRROR)
+        {
+          fg = &mirror_guide_normal_fg;
+          bg = &mirror_guide_normal_bg;
+        }
+      else /* style == GIMP_GUIDE_STYLE_NORMAL */
+        {
+          fg = &guide_normal_fg;
+          bg = &guide_normal_bg;
+        }
+    }
+  pattern = gimp_cairo_stipple_pattern_create (fg, bg, 0);
 
   cairo_set_source (cr, pattern);
   cairo_pattern_destroy (pattern);
diff --git a/app/display/gimpcanvas-style.h b/app/display/gimpcanvas-style.h
index 9715a76..301f940 100644
--- a/app/display/gimpcanvas-style.h
+++ b/app/display/gimpcanvas-style.h
@@ -24,6 +24,7 @@
 
 void   gimp_canvas_set_guide_style         (GtkWidget     *canvas,
                                             cairo_t       *cr,
+                                            GimpGuideStyle style,
                                             gboolean       active);
 void   gimp_canvas_set_sample_point_style  (GtkWidget     *canvas,
                                             cairo_t       *cr,
diff --git a/app/display/gimpcanvasguide.c b/app/display/gimpcanvasguide.c
index b0788ce..ccc64c8 100644
--- a/app/display/gimpcanvasguide.c
+++ b/app/display/gimpcanvasguide.c
@@ -48,7 +48,7 @@ struct _GimpCanvasGuidePrivate
 {
   GimpOrientationType orientation;
   gint                position;
-  gboolean            guide_style;
+  GimpGuideStyle      guide_style;
 };
 
 #define GET_PRIVATE(guide) \
@@ -105,10 +105,10 @@ gimp_canvas_guide_class_init (GimpCanvasGuideClass *klass)
                                                      GIMP_PARAM_READWRITE));
 
   g_object_class_install_property (object_class, PROP_GUIDE_STYLE,
-                                   g_param_spec_boolean ("guide-style",
-                                                         NULL, NULL,
-                                                         FALSE,
-                                                         GIMP_PARAM_READWRITE));
+                                   g_param_spec_enum ("guide-style", NULL, NULL,
+                                                      GIMP_TYPE_GUIDE_STYLE,
+                                                      GIMP_GUIDE_STYLE_NONE,
+                                                      GIMP_PARAM_READWRITE));
 
   g_type_class_add_private (klass, sizeof (GimpCanvasGuidePrivate));
 }
@@ -135,7 +135,7 @@ gimp_canvas_guide_set_property (GObject      *object,
       private->position = g_value_get_int (value);
       break;
     case PROP_GUIDE_STYLE:
-      private->guide_style = g_value_get_boolean (value);
+      private->guide_style = g_value_get_enum (value);
       break;
 
     default:
@@ -161,7 +161,7 @@ gimp_canvas_guide_get_property (GObject    *object,
       g_value_set_int (value, private->position);
       break;
     case PROP_GUIDE_STYLE:
-      g_value_set_boolean (value, private->guide_style);
+      g_value_set_enum (value, private->guide_style);
       break;
 
     default:
@@ -247,9 +247,10 @@ gimp_canvas_guide_stroke (GimpCanvasItem *item,
 {
   GimpCanvasGuidePrivate *private = GET_PRIVATE (item);
 
-  if (private->guide_style)
+  if (private->guide_style != GIMP_GUIDE_STYLE_NONE)
     {
       gimp_canvas_set_guide_style (gimp_canvas_item_get_canvas (item), cr,
+                                   private->guide_style,
                                    gimp_canvas_item_get_highlight (item));
       cairo_stroke (cr);
     }
@@ -263,7 +264,7 @@ GimpCanvasItem *
 gimp_canvas_guide_new (GimpDisplayShell    *shell,
                        GimpOrientationType  orientation,
                        gint                 position,
-                       gboolean             guide_style)
+                       GimpGuideStyle       guide_style)
 {
   g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), NULL);
 
diff --git a/app/display/gimpcanvasguide.h b/app/display/gimpcanvasguide.h
index 29d6295..d825dd7 100644
--- a/app/display/gimpcanvasguide.h
+++ b/app/display/gimpcanvasguide.h
@@ -52,7 +52,7 @@ GType            gimp_canvas_guide_get_type (void) G_GNUC_CONST;
 GimpCanvasItem * gimp_canvas_guide_new      (GimpDisplayShell    *shell,
                                              GimpOrientationType  orientation,
                                              gint                 position,
-                                             gboolean             guide_style);
+                                             GimpGuideStyle       guide_style);
 
 void             gimp_canvas_guide_set      (GimpCanvasItem      *guide,
                                              GimpOrientationType  orientation,
diff --git a/app/display/gimpdisplayshell-handlers.c b/app/display/gimpdisplayshell-handlers.c
index 37c218e..2d5046d 100644
--- a/app/display/gimpdisplayshell-handlers.c
+++ b/app/display/gimpdisplayshell-handlers.c
@@ -32,6 +32,7 @@
 
 #include "core/gimp.h"
 #include "core/gimpguide.h"
+#include "core/gimpmirrorguide.h"
 #include "core/gimpimage.h"
 #include "core/gimpimage-grid.h"
 #include "core/gimpimage-guides.h"
@@ -626,7 +627,8 @@ gimp_display_shell_guide_add_handler (GimpImage        *image,
   item = gimp_canvas_guide_new (shell,
                                 gimp_guide_get_orientation (guide),
                                 gimp_guide_get_position (guide),
-                                TRUE);
+                                GIMP_IS_MIRROR_GUIDE (guide) ?
+                                GIMP_GUIDE_STYLE_MIRROR : GIMP_GUIDE_STYLE_NORMAL);
 
   gimp_canvas_proxy_group_add_item (group, guide, item);
   g_object_unref (item);
diff --git a/app/paint/Makefile.am b/app/paint/Makefile.am
index 22e4b25..cef8ec7 100644
--- a/app/paint/Makefile.am
+++ b/app/paint/Makefile.am
@@ -52,6 +52,12 @@ libapppaint_a_sources = \
        gimpinkoptions.h                \
        gimpinkundo.c                   \
        gimpinkundo.h                   \
+       gimpmirror.c                    \
+       gimpmirror.h                    \
+       gimpmultistroke.c                       \
+       gimpmultistroke.h                       \
+       gimpmultistroke-info.c                  \
+       gimpmultistroke-info.h                  \
        gimpmybrush.c                   \
        gimpmybrush.h                   \
        gimpmybrushoptions.c            \
diff --git a/app/paint/gimpairbrush.c b/app/paint/gimpairbrush.c
index 725b5a0..4c0fdfc 100644
--- a/app/paint/gimpairbrush.c
+++ b/app/paint/gimpairbrush.c
@@ -29,6 +29,8 @@
 #include "core/gimpgradient.h"
 #include "core/gimpimage.h"
 
+#include "gimpmultistroke.h"
+
 #include "gimpairbrush.h"
 #include "gimpairbrushoptions.h"
 
@@ -40,13 +42,13 @@ static void       gimp_airbrush_finalize (GObject          *object);
 static void       gimp_airbrush_paint    (GimpPaintCore    *paint_core,
                                           GimpDrawable     *drawable,
                                           GimpPaintOptions *paint_options,
-                                          const GimpCoords *coords,
+                                          GimpMultiStroke  *mstroke,
                                           GimpPaintState    paint_state,
                                           guint32           time);
 static void       gimp_airbrush_motion   (GimpPaintCore    *paint_core,
                                           GimpDrawable     *drawable,
                                           GimpPaintOptions *paint_options,
-                                          const GimpCoords *coords);
+                                          GimpMultiStroke  *mstroke);
 static gboolean   gimp_airbrush_timeout  (gpointer          data);
 
 
@@ -82,6 +84,7 @@ static void
 gimp_airbrush_init (GimpAirbrush *airbrush)
 {
   airbrush->timeout_id = 0;
+  airbrush->mstroke    = NULL;
 }
 
 static void
@@ -95,6 +98,9 @@ gimp_airbrush_finalize (GObject *object)
       airbrush->timeout_id = 0;
     }
 
+  if (airbrush->mstroke)
+    g_object_unref (airbrush->mstroke);
+
   G_OBJECT_CLASS (parent_class)->finalize (object);
 }
 
@@ -102,7 +108,7 @@ static void
 gimp_airbrush_paint (GimpPaintCore    *paint_core,
                      GimpDrawable     *drawable,
                      GimpPaintOptions *paint_options,
-                     const GimpCoords *coords,
+                     GimpMultiStroke  *mstroke,
                      GimpPaintState    paint_state,
                      guint32           time)
 {
@@ -121,7 +127,7 @@ gimp_airbrush_paint (GimpPaintCore    *paint_core,
 
       GIMP_PAINT_CORE_CLASS (parent_class)->paint (paint_core, drawable,
                                                    paint_options,
-                                                   coords,
+                                                   mstroke,
                                                    paint_state, time);
       break;
 
@@ -132,14 +138,15 @@ gimp_airbrush_paint (GimpPaintCore    *paint_core,
           airbrush->timeout_id = 0;
         }
 
-      gimp_airbrush_motion (paint_core, drawable, paint_options, coords);
+      gimp_airbrush_motion (paint_core, drawable, paint_options, mstroke);
 
       if ((options->rate != 0.0) && (!options->motion_only))
         {
-          GimpImage *image = gimp_item_get_image (GIMP_ITEM (drawable));
-          gdouble    fade_point;
-          gdouble    dynamic_rate;
-          gint       timeout;
+          GimpImage  *image = gimp_item_get_image (GIMP_ITEM (drawable));
+          gdouble     fade_point;
+          gdouble     dynamic_rate;
+          gint        timeout;
+          GimpCoords *coords;
 
           fade_point = gimp_paint_options_get_fade (paint_options, image,
                                                     paint_core->pixel_dist);
@@ -147,6 +154,13 @@ gimp_airbrush_paint (GimpPaintCore    *paint_core,
           airbrush->drawable      = drawable;
           airbrush->paint_options = paint_options;
 
+          if (airbrush->mstroke)
+            g_object_unref (airbrush->mstroke);
+          airbrush->mstroke = g_object_ref (mstroke);
+
+          /* Base our timeout on the original stroke. */
+          coords = gimp_multi_stroke_get_origin (mstroke);
+
           dynamic_rate = gimp_dynamics_get_linear_value (dynamics,
                                                          GIMP_DYNAMICS_OUTPUT_RATE,
                                                          coords,
@@ -170,7 +184,7 @@ gimp_airbrush_paint (GimpPaintCore    *paint_core,
 
       GIMP_PAINT_CORE_CLASS (parent_class)->paint (paint_core, drawable,
                                                    paint_options,
-                                                   coords,
+                                                   mstroke,
                                                    paint_state, time);
       break;
     }
@@ -180,7 +194,7 @@ static void
 gimp_airbrush_motion (GimpPaintCore    *paint_core,
                       GimpDrawable     *drawable,
                       GimpPaintOptions *paint_options,
-                      const GimpCoords *coords)
+                      GimpMultiStroke  *mstroke)
 
 {
   GimpAirbrushOptions *options  = GIMP_AIRBRUSH_OPTIONS (paint_options);
@@ -188,10 +202,13 @@ gimp_airbrush_motion (GimpPaintCore    *paint_core,
   GimpImage           *image    = gimp_item_get_image (GIMP_ITEM (drawable));
   gdouble              opacity;
   gdouble              fade_point;
+  GimpCoords          *coords;
 
   fade_point = gimp_paint_options_get_fade (paint_options, image,
                                             paint_core->pixel_dist);
 
+  coords = gimp_multi_stroke_get_origin (mstroke);
+
   opacity = (options->flow / 100.0 *
              gimp_dynamics_get_linear_value (dynamics,
                                              GIMP_DYNAMICS_OUTPUT_FLOW,
@@ -199,21 +216,19 @@ gimp_airbrush_motion (GimpPaintCore    *paint_core,
                                              paint_options,
                                              fade_point));
 
-  _gimp_paintbrush_motion (paint_core, drawable, paint_options, coords, opacity);
+  _gimp_paintbrush_motion (paint_core, drawable, paint_options,
+                           mstroke, opacity);
 }
 
 static gboolean
 gimp_airbrush_timeout (gpointer data)
 {
   GimpAirbrush *airbrush = GIMP_AIRBRUSH (data);
-  GimpCoords    coords;
-
-  gimp_paint_core_get_current_coords (GIMP_PAINT_CORE (airbrush), &coords);
 
   gimp_airbrush_paint (GIMP_PAINT_CORE (airbrush),
                        airbrush->drawable,
                        airbrush->paint_options,
-                       &coords,
+                       airbrush->mstroke,
                        GIMP_PAINT_STATE_MOTION, 0);
 
   gimp_image_flush (gimp_item_get_image (GIMP_ITEM (airbrush->drawable)));
diff --git a/app/paint/gimpairbrush.h b/app/paint/gimpairbrush.h
index 9b27f8d..c97910c 100644
--- a/app/paint/gimpairbrush.h
+++ b/app/paint/gimpairbrush.h
@@ -37,6 +37,8 @@ struct _GimpAirbrush
   GimpPaintbrush    parent_instance;
 
   guint             timeout_id;
+
+  GimpMultiStroke  *mstroke;
   GimpDrawable     *drawable;
   GimpPaintOptions *paint_options;
 };
diff --git a/app/paint/gimpbrushcore.c b/app/paint/gimpbrushcore.c
index 9201643..2e35d72 100644
--- a/app/paint/gimpbrushcore.c
+++ b/app/paint/gimpbrushcore.c
@@ -109,10 +109,12 @@ static const GimpTempBuf *
                                                      gdouble            y);
 static const GimpTempBuf *
                  gimp_brush_core_transform_mask     (GimpBrushCore     *core,
-                                                     GimpBrush         *brush);
+                                                     GimpBrush         *brush,
+                                                     GeglNode          *op);
 static const GimpTempBuf *
                  gimp_brush_core_transform_pixmap   (GimpBrushCore     *core,
-                                                     GimpBrush         *brush);
+                                                     GimpBrush         *brush,
+                                                     GeglNode          *op);
 
 static void      gimp_brush_core_invalidate_cache   (GimpBrush         *brush,
                                                      GimpBrushCore     *core);
@@ -960,11 +962,12 @@ gimp_brush_core_paste_canvas (GimpBrushCore            *core,
                               GimpLayerModeEffects      paint_mode,
                               GimpBrushApplicationMode  brush_hardness,
                               gdouble                   dynamic_force,
-                              GimpPaintApplicationMode  mode)
+                              GimpPaintApplicationMode  mode,
+                              GeglNode                 *op)
 {
   const GimpTempBuf *brush_mask;
 
-  brush_mask = gimp_brush_core_get_brush_mask (core, coords,
+  brush_mask = gimp_brush_core_get_brush_mask (core, coords, op,
                                                brush_hardness,
                                                dynamic_force);
 
@@ -1003,11 +1006,12 @@ gimp_brush_core_replace_canvas (GimpBrushCore            *core,
                                 gdouble                   image_opacity,
                                 GimpBrushApplicationMode  brush_hardness,
                                 gdouble                   dynamic_force,
-                                GimpPaintApplicationMode  mode)
+                                GimpPaintApplicationMode  mode,
+                                GeglNode                 *op)
 {
   const GimpTempBuf *brush_mask;
 
-  brush_mask = gimp_brush_core_get_brush_mask (core, coords,
+  brush_mask = gimp_brush_core_get_brush_mask (core, coords, op,
                                                brush_hardness,
                                                dynamic_force);
 
@@ -1407,7 +1411,8 @@ gimp_brush_core_solidify_mask (GimpBrushCore     *core,
 
 static const GimpTempBuf *
 gimp_brush_core_transform_mask (GimpBrushCore *core,
-                                GimpBrush     *brush)
+                                GimpBrush     *brush,
+                                GeglNode      *op)
 {
   const GimpTempBuf *mask;
 
@@ -1415,6 +1420,7 @@ gimp_brush_core_transform_mask (GimpBrushCore *core,
     return NULL;
 
   mask = gimp_brush_transform_mask (brush,
+                                    op,
                                     core->scale,
                                     core->aspect_ratio,
                                     core->angle,
@@ -1432,7 +1438,8 @@ gimp_brush_core_transform_mask (GimpBrushCore *core,
 
 static const GimpTempBuf *
 gimp_brush_core_transform_pixmap (GimpBrushCore *core,
-                                  GimpBrush     *brush)
+                                  GimpBrush     *brush,
+                                  GeglNode      *op)
 {
   const GimpTempBuf *pixmap;
 
@@ -1440,6 +1447,7 @@ gimp_brush_core_transform_pixmap (GimpBrushCore *core,
     return NULL;
 
   pixmap = gimp_brush_transform_pixmap (brush,
+                                        op,
                                         core->scale,
                                         core->aspect_ratio,
                                         core->angle,
@@ -1457,12 +1465,13 @@ gimp_brush_core_transform_pixmap (GimpBrushCore *core,
 const GimpTempBuf *
 gimp_brush_core_get_brush_mask (GimpBrushCore            *core,
                                 const GimpCoords         *coords,
+                                GeglNode                 *op,
                                 GimpBrushApplicationMode  brush_hardness,
                                 gdouble                   dynamic_force)
 {
   const GimpTempBuf *mask;
 
-  mask = gimp_brush_core_transform_mask (core, core->brush);
+  mask = gimp_brush_core_transform_mask (core, core->brush, op);
 
   if (! mask)
     return NULL;
@@ -1591,6 +1600,7 @@ void
 gimp_brush_core_color_area_with_pixmap (GimpBrushCore            *core,
                                         GimpDrawable             *drawable,
                                         const GimpCoords         *coords,
+                                        GeglNode                 *op,
                                         GeglBuffer               *area,
                                         gint                      area_x,
                                         gint                      area_y,
@@ -1609,13 +1619,13 @@ gimp_brush_core_color_area_with_pixmap (GimpBrushCore            *core,
   g_return_if_fail (gimp_brush_get_pixmap (core->brush) != NULL);
 
   /*  scale the brushes  */
-  pixmap_mask = gimp_brush_core_transform_pixmap (core, core->brush);
+  pixmap_mask = gimp_brush_core_transform_pixmap (core, core->brush, op);
 
   if (! pixmap_mask)
     return;
 
   if (mode != GIMP_BRUSH_HARD)
-    brush_mask = gimp_brush_core_transform_mask (core, core->brush);
+    brush_mask = gimp_brush_core_transform_mask (core, core->brush, op);
   else
     brush_mask = NULL;
 
diff --git a/app/paint/gimpbrushcore.h b/app/paint/gimpbrushcore.h
index 1110a14..436db95 100644
--- a/app/paint/gimpbrushcore.h
+++ b/app/paint/gimpbrushcore.h
@@ -107,7 +107,8 @@ void   gimp_brush_core_paste_canvas   (GimpBrushCore            *core,
                                        GimpLayerModeEffects      paint_mode,
                                        GimpBrushApplicationMode  brush_hardness,
                                        gdouble                   dynamic_hardness,
-                                       GimpPaintApplicationMode  mode);
+                                       GimpPaintApplicationMode  mode,
+                                       GeglNode                 *op);
 void   gimp_brush_core_replace_canvas (GimpBrushCore            *core,
                                        GimpDrawable             *drawable,
                                        const GimpCoords         *coords,
@@ -115,12 +116,14 @@ void   gimp_brush_core_replace_canvas (GimpBrushCore            *core,
                                        gdouble                   image_opacity,
                                        GimpBrushApplicationMode  brush_hardness,
                                        gdouble                   dynamic_hardness,
-                                       GimpPaintApplicationMode  mode);
+                                       GimpPaintApplicationMode  mode,
+                                       GeglNode                 *op);
 
 void   gimp_brush_core_color_area_with_pixmap
                                       (GimpBrushCore            *core,
                                        GimpDrawable             *drawable,
                                        const GimpCoords         *coords,
+                                       GeglNode                 *op,
                                        GeglBuffer               *area,
                                        gint                      area_x,
                                        gint                      area_y,
@@ -129,6 +132,7 @@ void   gimp_brush_core_color_area_with_pixmap
 const GimpTempBuf * gimp_brush_core_get_brush_mask
                                       (GimpBrushCore            *core,
                                        const GimpCoords         *coords,
+                                       GeglNode                 *op,
                                        GimpBrushApplicationMode  brush_hardness,
                                        gdouble                   dynamic_hardness);
 
diff --git a/app/paint/gimpclone.c b/app/paint/gimpclone.c
index b0b8872..410887d 100644
--- a/app/paint/gimpclone.c
+++ b/app/paint/gimpclone.c
@@ -37,6 +37,7 @@
 
 #include "gimpclone.h"
 #include "gimpcloneoptions.h"
+#include "gimpmultistroke.h"
 
 #include "gimp-intl.h"
 
@@ -51,6 +52,7 @@ static void       gimp_clone_motion     (GimpSourceCore    *source_core,
                                          GimpDrawable      *drawable,
                                          GimpPaintOptions  *paint_options,
                                          const GimpCoords  *coords,
+                                         GeglNode          *op,
                                          gdouble            opacity,
                                          GimpPickable      *src_pickable,
                                          GeglBuffer        *src_buffer,
@@ -137,6 +139,7 @@ gimp_clone_motion (GimpSourceCore   *source_core,
                    GimpDrawable     *drawable,
                    GimpPaintOptions *paint_options,
                    const GimpCoords *coords,
+                   GeglNode         *op,
                    gdouble           opacity,
                    GimpPickable     *src_pickable,
                    GeglBuffer       *src_buffer,
@@ -173,6 +176,26 @@ gimp_clone_motion (GimpSourceCore   *source_core,
                         GEGL_RECTANGLE (paint_area_offset_x,
                                         paint_area_offset_y,
                                         0, 0));
+      if (op)
+        {
+          GeglNode    *graph, *source, *target;
+
+          graph    = gegl_node_new ();
+          source   = gegl_node_new_child (graph,
+                                          "operation", "gegl:buffer-source",
+                                          "buffer", paint_buffer,
+                                          NULL);
+          gegl_node_add_child (graph, op);
+          target  = gegl_node_new_child (graph,
+                                         "operation", "gegl:write-buffer",
+                                         "buffer", paint_buffer,
+                                         NULL);
+
+          gegl_node_link_many (source, op, target, NULL);
+          gegl_node_process (target);
+
+          g_object_unref (graph);
+        }
     }
   else if (options->clone_type == GIMP_CLONE_PATTERN)
     {
@@ -223,7 +246,8 @@ gimp_clone_motion (GimpSourceCore   *source_core,
                                  */
                                 source_options->align_mode ==
                                 GIMP_SOURCE_ALIGN_FIXED ?
-                                GIMP_PAINT_INCREMENTAL : GIMP_PAINT_CONSTANT);
+                                GIMP_PAINT_INCREMENTAL : GIMP_PAINT_CONSTANT,
+                                op);
 }
 
 static gboolean
diff --git a/app/paint/gimpconvolve.c b/app/paint/gimpconvolve.c
index a6d3a13..1d10c76 100644
--- a/app/paint/gimpconvolve.c
+++ b/app/paint/gimpconvolve.c
@@ -32,6 +32,8 @@
 #include "core/gimppickable.h"
 #include "core/gimptempbuf.h"
 
+#include "gimpmultistroke.h"
+
 #include "gimpconvolve.h"
 #include "gimpconvolveoptions.h"
 
@@ -48,13 +50,13 @@
 static void    gimp_convolve_paint            (GimpPaintCore    *paint_core,
                                                GimpDrawable     *drawable,
                                                GimpPaintOptions *paint_options,
-                                               const GimpCoords *coords,
+                                               GimpMultiStroke  *mstroke,
                                                GimpPaintState    paint_state,
                                                guint32           time);
 static void    gimp_convolve_motion           (GimpPaintCore    *paint_core,
                                                GimpDrawable     *drawable,
                                                GimpPaintOptions *paint_options,
-                                               const GimpCoords *coords);
+                                               GimpMultiStroke  *mstroke);
 
 static void    gimp_convolve_calculate_matrix (GimpConvolve     *convolve,
                                                GimpConvolveType  type,
@@ -102,14 +104,15 @@ static void
 gimp_convolve_paint (GimpPaintCore    *paint_core,
                      GimpDrawable     *drawable,
                      GimpPaintOptions *paint_options,
-                     const GimpCoords *coords,
+                     GimpMultiStroke  *mstroke,
                      GimpPaintState    paint_state,
                      guint32           time)
 {
   switch (paint_state)
     {
     case GIMP_PAINT_STATE_MOTION:
-      gimp_convolve_motion (paint_core, drawable, paint_options, coords);
+      gimp_convolve_motion (paint_core, drawable, paint_options,
+                            mstroke);
       break;
 
     default:
@@ -121,7 +124,7 @@ static void
 gimp_convolve_motion (GimpPaintCore    *paint_core,
                       GimpDrawable     *drawable,
                       GimpPaintOptions *paint_options,
-                      const GimpCoords *coords)
+                      GimpMultiStroke  *mstroke)
 {
   GimpConvolve        *convolve   = GIMP_CONVOLVE (paint_core);
   GimpBrushCore       *brush_core = GIMP_BRUSH_CORE (paint_core);
@@ -137,73 +140,86 @@ gimp_convolve_motion (GimpPaintCore    *paint_core,
   gdouble              fade_point;
   gdouble              opacity;
   gdouble              rate;
+  const GimpCoords    *coords;
+  GeglNode            *op;
+  gint                 nstrokes;
+  gint                 i;
 
+  nstrokes = gimp_multi_stroke_get_size (mstroke);
   fade_point = gimp_paint_options_get_fade (paint_options, image,
                                             paint_core->pixel_dist);
 
-  opacity = gimp_dynamics_get_linear_value (dynamics,
-                                            GIMP_DYNAMICS_OUTPUT_OPACITY,
-                                            coords,
-                                            paint_options,
-                                            fade_point);
-  if (opacity == 0.0)
-    return;
-
-  paint_buffer = gimp_paint_core_get_paint_buffer (paint_core, drawable,
-                                                   paint_options, coords,
-                                                   &paint_buffer_x,
-                                                   &paint_buffer_y);
-  if (! paint_buffer)
-    return;
-
-  rate = (options->rate *
-          gimp_dynamics_get_linear_value (dynamics,
-                                          GIMP_DYNAMICS_OUTPUT_RATE,
-                                          coords,
-                                          paint_options,
-                                          fade_point));
-
-  gimp_convolve_calculate_matrix (convolve, options->type,
-                                  gimp_brush_get_width  (brush_core->brush) / 2,
-                                  gimp_brush_get_height (brush_core->brush) / 2,
-                                  rate);
-
-  /*  need a linear buffer for gimp_gegl_convolve()  */
-  temp_buf = gimp_temp_buf_new (gegl_buffer_get_width  (paint_buffer),
-                                gegl_buffer_get_height (paint_buffer),
-                                gegl_buffer_get_format (paint_buffer));
-  convolve_buffer = gimp_temp_buf_create_buffer (temp_buf);
-  gimp_temp_buf_unref (temp_buf);
-
-  gegl_buffer_copy (gimp_drawable_get_buffer (drawable),
-                    GEGL_RECTANGLE (paint_buffer_x,
-                                    paint_buffer_y,
-                                    gegl_buffer_get_width  (paint_buffer),
-                                    gegl_buffer_get_height (paint_buffer)),
-                    GEGL_ABYSS_NONE,
-                    convolve_buffer,
-                    GEGL_RECTANGLE (0, 0, 0, 0));
-
-  gimp_gegl_convolve (convolve_buffer,
-                      GEGL_RECTANGLE (0, 0,
-                                      gegl_buffer_get_width  (convolve_buffer),
-                                      gegl_buffer_get_height (convolve_buffer)),
-                      paint_buffer,
-                      GEGL_RECTANGLE (0, 0,
-                                      gegl_buffer_get_width  (paint_buffer),
-                                      gegl_buffer_get_height (paint_buffer)),
-                      convolve->matrix, 3, convolve->matrix_divisor,
-                      GIMP_NORMAL_CONVOL, TRUE);
-
-  g_object_unref (convolve_buffer);
-
-  gimp_brush_core_replace_canvas (brush_core, drawable,
-                                  coords,
-                                  MIN (opacity, GIMP_OPACITY_OPAQUE),
-                                  gimp_context_get_opacity (context),
-                                  gimp_paint_options_get_brush_mode (paint_options),
-                                  1.0,
-                                  GIMP_PAINT_INCREMENTAL);
+  for (i = 0; i < nstrokes; i++)
+    {
+      coords = gimp_multi_stroke_get_coords (mstroke, i);
+
+      opacity = gimp_dynamics_get_linear_value (dynamics,
+                                                GIMP_DYNAMICS_OUTPUT_OPACITY,
+                                                coords,
+                                                paint_options,
+                                                fade_point);
+      if (opacity == 0.0)
+        continue;
+
+      paint_buffer = gimp_paint_core_get_paint_buffer (paint_core, drawable,
+                                                       paint_options, coords,
+                                                       &paint_buffer_x,
+                                                       &paint_buffer_y);
+      if (! paint_buffer)
+        continue;
+
+      op = gimp_multi_stroke_get_operation (mstroke, paint_core,
+                                            paint_buffer, i);
+
+      rate = (options->rate *
+              gimp_dynamics_get_linear_value (dynamics,
+                                              GIMP_DYNAMICS_OUTPUT_RATE,
+                                              coords,
+                                              paint_options,
+                                              fade_point));
+
+      gimp_convolve_calculate_matrix (convolve, options->type,
+                                      gimp_brush_get_width  (brush_core->brush) / 2,
+                                      gimp_brush_get_height (brush_core->brush) / 2,
+                                      rate);
+
+      /*  need a linear buffer for gimp_gegl_convolve()  */
+      temp_buf = gimp_temp_buf_new (gegl_buffer_get_width  (paint_buffer),
+                                    gegl_buffer_get_height (paint_buffer),
+                                    gegl_buffer_get_format (paint_buffer));
+      convolve_buffer = gimp_temp_buf_create_buffer (temp_buf);
+      gimp_temp_buf_unref (temp_buf);
+
+      gegl_buffer_copy (gimp_drawable_get_buffer (drawable),
+                        GEGL_RECTANGLE (paint_buffer_x,
+                                        paint_buffer_y,
+                                        gegl_buffer_get_width  (paint_buffer),
+                                        gegl_buffer_get_height (paint_buffer)),
+                        GEGL_ABYSS_NONE,
+                        convolve_buffer,
+                        GEGL_RECTANGLE (0, 0, 0, 0));
+
+      gimp_gegl_convolve (convolve_buffer,
+                          GEGL_RECTANGLE (0, 0,
+                                          gegl_buffer_get_width  (convolve_buffer),
+                                          gegl_buffer_get_height (convolve_buffer)),
+                          paint_buffer,
+                          GEGL_RECTANGLE (0, 0,
+                                          gegl_buffer_get_width  (paint_buffer),
+                                          gegl_buffer_get_height (paint_buffer)),
+                          convolve->matrix, 3, convolve->matrix_divisor,
+                          GIMP_NORMAL_CONVOL, TRUE);
+
+      g_object_unref (convolve_buffer);
+
+      gimp_brush_core_replace_canvas (brush_core, drawable,
+                                      coords,
+                                      MIN (opacity, GIMP_OPACITY_OPAQUE),
+                                      gimp_context_get_opacity (context),
+                                      gimp_paint_options_get_brush_mode (paint_options),
+                                      1.0,
+                                      GIMP_PAINT_INCREMENTAL, op);
+    }
 }
 
 static void
diff --git a/app/paint/gimpdodgeburn.c b/app/paint/gimpdodgeburn.c
index e87e79a..bddf50c 100644
--- a/app/paint/gimpdodgeburn.c
+++ b/app/paint/gimpdodgeburn.c
@@ -32,6 +32,8 @@
 #include "core/gimpdynamics.h"
 #include "core/gimpimage.h"
 
+#include "gimpmultistroke.h"
+
 #include "gimpdodgeburn.h"
 #include "gimpdodgeburnoptions.h"
 
@@ -41,13 +43,13 @@
 static void   gimp_dodge_burn_paint  (GimpPaintCore    *paint_core,
                                       GimpDrawable     *drawable,
                                       GimpPaintOptions *paint_options,
-                                      const GimpCoords *coords,
+                                      GimpMultiStroke  *mstroke,
                                       GimpPaintState    paint_state,
                                       guint32           time);
 static void   gimp_dodge_burn_motion (GimpPaintCore    *paint_core,
                                       GimpDrawable     *drawable,
                                       GimpPaintOptions *paint_options,
-                                      const GimpCoords *coords);
+                                      GimpMultiStroke  *mstroke);
 
 
 G_DEFINE_TYPE (GimpDodgeBurn, gimp_dodge_burn, GIMP_TYPE_BRUSH_CORE)
@@ -87,7 +89,7 @@ static void
 gimp_dodge_burn_paint (GimpPaintCore    *paint_core,
                        GimpDrawable     *drawable,
                        GimpPaintOptions *paint_options,
-                       const GimpCoords *coords,
+                       GimpMultiStroke  *mstroke,
                        GimpPaintState    paint_state,
                        guint32           time)
 {
@@ -97,7 +99,7 @@ gimp_dodge_burn_paint (GimpPaintCore    *paint_core,
       break;
 
     case GIMP_PAINT_STATE_MOTION:
-      gimp_dodge_burn_motion (paint_core, drawable, paint_options, coords);
+      gimp_dodge_burn_motion (paint_core, drawable, paint_options, mstroke);
       break;
 
     case GIMP_PAINT_STATE_FINISH:
@@ -109,7 +111,7 @@ static void
 gimp_dodge_burn_motion (GimpPaintCore    *paint_core,
                         GimpDrawable     *drawable,
                         GimpPaintOptions *paint_options,
-                        const GimpCoords *coords)
+                        GimpMultiStroke  *mstroke)
 {
   GimpDodgeBurnOptions *options   = GIMP_DODGE_BURN_OPTIONS (paint_options);
   GimpContext          *context   = GIMP_CONTEXT (paint_options);
@@ -121,52 +123,65 @@ gimp_dodge_burn_motion (GimpPaintCore    *paint_core,
   gdouble               fade_point;
   gdouble               opacity;
   gdouble               force;
+  const GimpCoords     *coords;
+  GeglNode             *op;
+  gint                  nstrokes;
+  gint                  i;
 
-  fade_point = gimp_paint_options_get_fade (paint_options, image,
-                                            paint_core->pixel_dist);
-
-  opacity = gimp_dynamics_get_linear_value (dynamics,
-                                            GIMP_DYNAMICS_OUTPUT_OPACITY,
-                                            coords,
-                                            paint_options,
-                                            fade_point);
-  if (opacity == 0.0)
-    return;
-
-  paint_buffer = gimp_paint_core_get_paint_buffer (paint_core, drawable,
-                                                   paint_options, coords,
-                                                   &paint_buffer_x,
-                                                   &paint_buffer_y);
-  if (! paint_buffer)
-    return;
-
-  /*  DodgeBurn the region  */
-  gimp_gegl_dodgeburn (gimp_paint_core_get_orig_image (paint_core),
-                       GEGL_RECTANGLE (paint_buffer_x,
-                                       paint_buffer_y,
-                                       gegl_buffer_get_width  (paint_buffer),
-                                       gegl_buffer_get_height (paint_buffer)),
-                       paint_buffer,
-                       GEGL_RECTANGLE (0, 0, 0, 0),
-                       options->exposure / 100.0,
-                       options->type,
-                       options->mode);
-
-  if (gimp_dynamics_is_output_enabled (dynamics, GIMP_DYNAMICS_OUTPUT_FORCE))
-    force = gimp_dynamics_get_linear_value (dynamics,
-                                            GIMP_DYNAMICS_OUTPUT_FORCE,
-                                            coords,
-                                            paint_options,
-                                            fade_point);
-  else
-    force = paint_options->brush_force;
-
-  /* Replace the newly dodgedburned area (paint_area) to the image */
-  gimp_brush_core_replace_canvas (GIMP_BRUSH_CORE (paint_core), drawable,
-                                  coords,
-                                  MIN (opacity, GIMP_OPACITY_OPAQUE),
-                                  gimp_context_get_opacity (context),
-                                  gimp_paint_options_get_brush_mode (paint_options),
-                                  force,
-                                  GIMP_PAINT_CONSTANT);
+  nstrokes = gimp_multi_stroke_get_size (mstroke);
+  for (i = 0; i < nstrokes; i++)
+    {
+      coords = gimp_multi_stroke_get_coords (mstroke, i);
+
+      fade_point = gimp_paint_options_get_fade (paint_options, image,
+                                                paint_core->pixel_dist);
+
+      opacity = gimp_dynamics_get_linear_value (dynamics,
+                                                GIMP_DYNAMICS_OUTPUT_OPACITY,
+                                                coords,
+                                                paint_options,
+                                                fade_point);
+      if (opacity == 0.0)
+        continue;
+
+      paint_buffer = gimp_paint_core_get_paint_buffer (paint_core, drawable,
+                                                       paint_options, coords,
+                                                       &paint_buffer_x,
+                                                       &paint_buffer_y);
+      if (! paint_buffer)
+        continue;
+
+      op = gimp_multi_stroke_get_operation (mstroke, paint_core,
+                                            paint_buffer, i);
+
+      /*  DodgeBurn the region  */
+      gimp_gegl_dodgeburn (gimp_paint_core_get_orig_image (paint_core),
+                           GEGL_RECTANGLE (paint_buffer_x,
+                                           paint_buffer_y,
+                                           gegl_buffer_get_width  (paint_buffer),
+                                           gegl_buffer_get_height (paint_buffer)),
+                           paint_buffer,
+                           GEGL_RECTANGLE (0, 0, 0, 0),
+                           options->exposure / 100.0,
+                           options->type,
+                           options->mode);
+
+      if (gimp_dynamics_is_output_enabled (dynamics, GIMP_DYNAMICS_OUTPUT_FORCE))
+        force = gimp_dynamics_get_linear_value (dynamics,
+                                                GIMP_DYNAMICS_OUTPUT_FORCE,
+                                                coords,
+                                                paint_options,
+                                                fade_point);
+      else
+        force = paint_options->brush_force;
+
+      /* Replace the newly dodgedburned area (paint_area) to the image */
+      gimp_brush_core_replace_canvas (GIMP_BRUSH_CORE (paint_core), drawable,
+                                      coords,
+                                      MIN (opacity, GIMP_OPACITY_OPAQUE),
+                                      gimp_context_get_opacity (context),
+                                      gimp_paint_options_get_brush_mode (paint_options),
+                                      force,
+                                      GIMP_PAINT_CONSTANT, op);
+    }
 }
diff --git a/app/paint/gimperaser.c b/app/paint/gimperaser.c
index b3c6e46..d65f3d6 100644
--- a/app/paint/gimperaser.c
+++ b/app/paint/gimperaser.c
@@ -30,6 +30,8 @@
 #include "core/gimpdynamics.h"
 #include "core/gimpimage.h"
 
+#include "gimpmultistroke.h"
+
 #include "gimperaser.h"
 #include "gimperaseroptions.h"
 
@@ -39,13 +41,13 @@
 static void   gimp_eraser_paint  (GimpPaintCore    *paint_core,
                                   GimpDrawable     *drawable,
                                   GimpPaintOptions *paint_options,
-                                  const GimpCoords *coords,
+                                  GimpMultiStroke  *mstroke,
                                   GimpPaintState    paint_state,
                                   guint32           time);
 static void   gimp_eraser_motion (GimpPaintCore    *paint_core,
                                   GimpDrawable     *drawable,
                                   GimpPaintOptions *paint_options,
-                                  const GimpCoords *coords);
+                                  GimpMultiStroke  *mstroke);
 
 
 G_DEFINE_TYPE (GimpEraser, gimp_eraser, GIMP_TYPE_BRUSH_CORE)
@@ -83,7 +85,7 @@ static void
 gimp_eraser_paint (GimpPaintCore    *paint_core,
                    GimpDrawable     *drawable,
                    GimpPaintOptions *paint_options,
-                   const GimpCoords *coords,
+                   GimpMultiStroke  *mstroke,
                    GimpPaintState    paint_state,
                    guint32           time)
 {
@@ -106,7 +108,7 @@ gimp_eraser_paint (GimpPaintCore    *paint_core,
         }
       break;
     case GIMP_PAINT_STATE_MOTION:
-      gimp_eraser_motion (paint_core, drawable, paint_options, coords);
+      gimp_eraser_motion (paint_core, drawable, paint_options, mstroke);
       break;
 
     default:
@@ -118,7 +120,7 @@ static void
 gimp_eraser_motion (GimpPaintCore    *paint_core,
                     GimpDrawable     *drawable,
                     GimpPaintOptions *paint_options,
-                    const GimpCoords *coords)
+                    GimpMultiStroke  *mstroke)
 {
   GimpEraserOptions    *options  = GIMP_ERASER_OPTIONS (paint_options);
   GimpContext          *context  = GIMP_CONTEXT (paint_options);
@@ -133,31 +135,17 @@ gimp_eraser_motion (GimpPaintCore    *paint_core,
   GimpRGB               background;
   GeglColor            *color;
   gdouble               force;
+  const GimpCoords     *coords;
+  GeglNode             *op;
+  gint                  nstrokes;
+  gint                  i;
 
   fade_point = gimp_paint_options_get_fade (paint_options, image,
                                             paint_core->pixel_dist);
 
-  opacity = gimp_dynamics_get_linear_value (dynamics,
-                                            GIMP_DYNAMICS_OUTPUT_OPACITY,
-                                            coords,
-                                            paint_options,
-                                            fade_point);
-  if (opacity == 0.0)
-    return;
-
-  paint_buffer = gimp_paint_core_get_paint_buffer (paint_core, drawable,
-                                                   paint_options, coords,
-                                                   &paint_buffer_x,
-                                                   &paint_buffer_y);
-  if (! paint_buffer)
-    return;
-
   gimp_context_get_background (context, &background);
   color = gimp_gegl_color_new (&background);
 
-  gegl_buffer_set_color (paint_buffer, NULL, color);
-  g_object_unref (color);
-
   if (options->anti_erase)
     paint_mode = GIMP_ANTI_ERASE_MODE;
   else if (gimp_drawable_has_alpha (drawable))
@@ -174,12 +162,41 @@ gimp_eraser_motion (GimpPaintCore    *paint_core,
   else
     force = paint_options->brush_force;
 
-  gimp_brush_core_paste_canvas (GIMP_BRUSH_CORE (paint_core), drawable,
-                                coords,
-                                MIN (opacity, GIMP_OPACITY_OPAQUE),
-                                gimp_context_get_opacity (context),
-                                paint_mode,
-                                gimp_paint_options_get_brush_mode (paint_options),
-                                force,
-                                paint_options->application_mode);
+  nstrokes = gimp_multi_stroke_get_size (mstroke);
+
+  for (i = 0; i < nstrokes; i++)
+    {
+      coords = gimp_multi_stroke_get_coords (mstroke, i);
+
+      opacity = gimp_dynamics_get_linear_value (dynamics,
+                                                GIMP_DYNAMICS_OUTPUT_OPACITY,
+                                                coords,
+                                                paint_options,
+                                                fade_point);
+      if (opacity == 0.0)
+        continue;
+
+      paint_buffer = gimp_paint_core_get_paint_buffer (paint_core, drawable,
+                                                       paint_options, coords,
+                                                       &paint_buffer_x,
+                                                       &paint_buffer_y);
+      if (! paint_buffer)
+        continue;
+
+      op = gimp_multi_stroke_get_operation (mstroke, paint_core,
+                                            paint_buffer, i);
+
+      gegl_buffer_set_color (paint_buffer, NULL, color);
+
+      gimp_brush_core_paste_canvas (GIMP_BRUSH_CORE (paint_core), drawable,
+                                    coords,
+                                    MIN (opacity, GIMP_OPACITY_OPAQUE),
+                                    gimp_context_get_opacity (context),
+                                    paint_mode,
+                                    gimp_paint_options_get_brush_mode (paint_options),
+                                    force,
+                                    paint_options->application_mode, op);
+    }
+
+  g_object_unref (color);
 }
diff --git a/app/paint/gimpheal.c b/app/paint/gimpheal.c
index 3be8200..2c06b94 100644
--- a/app/paint/gimpheal.c
+++ b/app/paint/gimpheal.c
@@ -74,6 +74,7 @@ static void         gimp_heal_motion             (GimpSourceCore   *source_core,
                                                   GimpDrawable     *drawable,
                                                   GimpPaintOptions *paint_options,
                                                   const GimpCoords *coords,
+                                                  GeglNode         *op,
                                                   gdouble           opacity,
                                                   GimpPickable     *src_pickable,
                                                   GeglBuffer       *src_buffer,
@@ -461,6 +462,7 @@ gimp_heal_motion (GimpSourceCore   *source_core,
                   GimpDrawable     *drawable,
                   GimpPaintOptions *paint_options,
                   const GimpCoords *coords,
+                  GeglNode         *op,
                   gdouble           opacity,
                   GimpPickable     *src_pickable,
                   GeglBuffer       *src_buffer,
@@ -500,7 +502,7 @@ gimp_heal_motion (GimpSourceCore   *source_core,
     force = paint_options->brush_force;
 
   mask_buf = gimp_brush_core_get_brush_mask (GIMP_BRUSH_CORE (source_core),
-                                             coords,
+                                             coords, op,
                                              GIMP_BRUSH_HARD,
                                              force);
 
@@ -549,6 +551,27 @@ gimp_heal_motion (GimpSourceCore   *source_core,
     mask_off_y = (y < 0) ? -y : 0;
   }
 
+  if (op)
+    {
+      GeglNode    *graph, *source, *target;
+
+      graph    = gegl_node_new ();
+      source   = gegl_node_new_child (graph,
+                                      "operation", "gegl:buffer-source",
+                                      "buffer", src_copy,
+                                      NULL);
+      gegl_node_add_child (graph, op);
+      target  = gegl_node_new_child (graph,
+                                     "operation", "gegl:write-buffer",
+                                     "buffer", src_copy,
+                                     NULL);
+
+      gegl_node_link_many (source, op, target, NULL);
+      gegl_node_process (target);
+
+      g_object_unref (graph);
+    }
+
   gimp_heal (src_copy,
              GEGL_RECTANGLE (0, 0,
                              gegl_buffer_get_width  (src_copy),
@@ -573,5 +596,5 @@ gimp_heal_motion (GimpSourceCore   *source_core,
                                   gimp_context_get_opacity (context),
                                   gimp_paint_options_get_brush_mode (paint_options),
                                   force,
-                                  GIMP_PAINT_INCREMENTAL);
+                                  GIMP_PAINT_INCREMENTAL, NULL);
 }
diff --git a/app/paint/gimpink.c b/app/paint/gimpink.c
index ee9cc4f..be18ad2 100644
--- a/app/paint/gimpink.c
+++ b/app/paint/gimpink.c
@@ -34,6 +34,8 @@
 #include "core/gimpimage-undo.h"
 #include "core/gimptempbuf.h"
 
+#include "gimpmultistroke.h"
+
 #include "gimpinkoptions.h"
 #include "gimpink.h"
 #include "gimpink-blob.h"
@@ -52,7 +54,7 @@ static void         gimp_ink_finalize         (GObject          *object);
 static void         gimp_ink_paint            (GimpPaintCore    *paint_core,
                                                GimpDrawable     *drawable,
                                                GimpPaintOptions *paint_options,
-                                               const GimpCoords *coords,
+                                               GimpMultiStroke  *mstroke,
                                                GimpPaintState    paint_state,
                                                guint32           time);
 static GeglBuffer * gimp_ink_get_paint_buffer (GimpPaintCore    *paint_core,
@@ -68,7 +70,7 @@ static GimpUndo   * gimp_ink_push_undo        (GimpPaintCore    *core,
 static void         gimp_ink_motion           (GimpPaintCore    *paint_core,
                                                GimpDrawable     *drawable,
                                                GimpPaintOptions *paint_options,
-                                               const GimpCoords *coords,
+                                               GimpMultiStroke  *mstroke,
                                                guint32           time);
 
 static GimpBlob   * ink_pen_ellipse           (GimpInkOptions   *options,
@@ -124,16 +126,16 @@ gimp_ink_finalize (GObject *object)
 {
   GimpInk *ink = GIMP_INK (object);
 
-  if (ink->start_blob)
+  if (ink->start_blobs)
     {
-      g_free (ink->start_blob);
-      ink->start_blob = NULL;
+      g_list_free_full (ink->start_blobs, g_free);
+      ink->start_blobs = NULL;
     }
 
-  if (ink->last_blob)
+  if (ink->last_blobs)
     {
-      g_free (ink->last_blob);
-      ink->last_blob = NULL;
+      g_list_free_full (ink->last_blobs, g_free);
+      ink->last_blobs = NULL;
     }
 
   G_OBJECT_CLASS (parent_class)->finalize (object);
@@ -143,14 +145,16 @@ static void
 gimp_ink_paint (GimpPaintCore    *paint_core,
                 GimpDrawable     *drawable,
                 GimpPaintOptions *paint_options,
-                const GimpCoords *coords,
+                GimpMultiStroke  *mstroke,
                 GimpPaintState    paint_state,
                 guint32           time)
 {
-  GimpInk *ink = GIMP_INK (paint_core);
-  GimpCoords last_coords;
+  GimpInk    *ink = GIMP_INK (paint_core);
+  GimpCoords *cur_coords;
+  GimpCoords  last_coords;
 
   gimp_paint_core_get_last_coords (paint_core, &last_coords);
+  cur_coords = gimp_multi_stroke_get_origin (mstroke);
 
   switch (paint_state)
     {
@@ -163,37 +167,49 @@ gimp_ink_paint (GimpPaintCore    *paint_core,
           gimp_palettes_add_color_history (context->gimp,
                                            &foreground);
 
-          if (coords->x == last_coords.x &&
-              coords->y == last_coords.y)
+          if (cur_coords->x == last_coords.x &&
+              cur_coords->y == last_coords.y)
             {
-              /*  start with new blobs if we're not interpolating  */
-
-              if (ink->start_blob)
+              if (ink->start_blobs)
                 {
-                  g_free (ink->start_blob);
-                  ink->start_blob = NULL;
+                  g_list_free_full (ink->start_blobs, g_free);
+                  ink->start_blobs = NULL;
                 }
 
-              if (ink->last_blob)
+              if (ink->last_blobs)
                 {
-                  g_free (ink->last_blob);
-                  ink->last_blob = NULL;
+                  g_list_free_full (ink->last_blobs, g_free);
+                  ink->last_blobs = NULL;
                 }
             }
-          else if (ink->last_blob)
+          else if (ink->last_blobs)
             {
-              /*  save the start blob of the line for undo otherwise  */
+              GimpBlob *last_blob;
+              GList    *iter;
+              gint      i;
 
-              if (ink->start_blob)
-                g_free (ink->start_blob);
+              if (ink->start_blobs)
+                {
+                  g_list_free_full (ink->start_blobs, g_free);
+                  ink->start_blobs = NULL;
+                }
 
-              ink->start_blob = gimp_blob_duplicate (ink->last_blob);
+              /*  save the start blobs of each stroke for undo otherwise  */
+              for (iter = ink->last_blobs, i = 0; iter; iter = g_list_next (iter), i++)
+                {
+                  last_blob = g_list_nth_data (ink->last_blobs, i);
+
+                  ink->start_blobs = g_list_prepend (ink->start_blobs,
+                                                     gimp_blob_duplicate (last_blob));
+                }
+              ink->start_blobs = g_list_reverse (ink->start_blobs);
             }
         }
       break;
 
     case GIMP_PAINT_STATE_MOTION:
-      gimp_ink_motion (paint_core, drawable, paint_options, coords, time);
+      gimp_ink_motion (paint_core, drawable, paint_options,
+                       mstroke, time);
       break;
 
     case GIMP_PAINT_STATE_FINISH:
@@ -271,93 +287,136 @@ static void
 gimp_ink_motion (GimpPaintCore    *paint_core,
                  GimpDrawable     *drawable,
                  GimpPaintOptions *paint_options,
-                 const GimpCoords *coords,
+                 GimpMultiStroke  *mstroke,
                  guint32           time)
 {
-  GimpInk        *ink        = GIMP_INK (paint_core);
-  GimpInkOptions *options    = GIMP_INK_OPTIONS (paint_options);
-  GimpContext    *context    = GIMP_CONTEXT (paint_options);
-  GimpBlob       *blob_union = NULL;
-  GimpBlob       *blob_to_render;
+  GimpInk        *ink             = GIMP_INK (paint_core);
+  GimpInkOptions *options         = GIMP_INK_OPTIONS (paint_options);
+  GimpContext    *context         = GIMP_CONTEXT (paint_options);
+  GList          *blob_unions     = NULL;
+  GList          *blobs_to_render = NULL;
   GeglBuffer     *paint_buffer;
   gint            paint_buffer_x;
   gint            paint_buffer_y;
   GimpRGB         foreground;
   GeglColor      *color;
+  GimpBlob       *last_blob;
+  GimpCoords     *coords;
+  gint            nstrokes;
+  gint            i;
 
-  if (! ink->last_blob)
-    {
-      ink->last_blob = ink_pen_ellipse (options,
-                                        coords->x,
-                                        coords->y,
-                                        coords->pressure,
-                                        coords->xtilt,
-                                        coords->ytilt,
-                                        100);
+  nstrokes = gimp_multi_stroke_get_size (mstroke);
 
-      if (ink->start_blob)
-        g_free (ink->start_blob);
+  if (ink->last_blobs &&
+      g_list_length (ink->last_blobs) != nstrokes)
+    {
+      g_list_free_full (ink->last_blobs, g_free);
+      ink->last_blobs = NULL;
+    }
 
-      ink->start_blob = gimp_blob_duplicate (ink->last_blob);
+  if (! ink->last_blobs)
+    {
+      if (ink->start_blobs)
+        {
+          g_list_free_full (ink->start_blobs, g_free);
+          ink->start_blobs = NULL;
+        }
 
-      blob_to_render = ink->last_blob;
+      for (i = 0; i < nstrokes; i++)
+        {
+          coords = gimp_multi_stroke_get_coords (mstroke, i);
+
+          last_blob = ink_pen_ellipse (options,
+                                       coords->x,
+                                       coords->y,
+                                       coords->pressure,
+                                       coords->xtilt,
+                                       coords->ytilt,
+                                       100);
+
+          ink->last_blobs = g_list_prepend (ink->last_blobs,
+                                            last_blob);
+          ink->start_blobs = g_list_prepend (ink->start_blobs,
+                                             gimp_blob_duplicate (last_blob));
+          blobs_to_render = g_list_prepend (blobs_to_render, last_blob);
+        }
+      ink->start_blobs = g_list_reverse (ink->start_blobs);
+      ink->last_blobs = g_list_reverse (ink->last_blobs);
+      blobs_to_render = g_list_reverse (blobs_to_render);
     }
   else
     {
-      GimpBlob *blob = ink_pen_ellipse (options,
-                                        coords->x,
-                                        coords->y,
-                                        coords->pressure,
-                                        coords->xtilt,
-                                        coords->ytilt,
-                                        coords->velocity * 100);
-
-      blob_union = gimp_blob_convex_union (ink->last_blob, blob);
+      for (i = 0; i < nstrokes; i++)
+        {
+          GimpBlob *blob;
+          GimpBlob *blob_union = NULL;
+
+          coords = gimp_multi_stroke_get_coords (mstroke, i);
+          blob = ink_pen_ellipse (options,
+                                  coords->x,
+                                  coords->y,
+                                  coords->pressure,
+                                  coords->xtilt,
+                                  coords->ytilt,
+                                  coords->velocity * 100);
+
+          last_blob = g_list_nth_data (ink->last_blobs, i);
+          blob_union = gimp_blob_convex_union (last_blob, blob);
+
+          g_free (last_blob);
+          g_list_nth (ink->last_blobs, i)->data = blob;
+
+          blobs_to_render = g_list_prepend (blobs_to_render, blob_union);
+          blob_unions = g_list_prepend (blob_unions, blob_union);
+        }
+      blobs_to_render = g_list_reverse (blobs_to_render);
+    }
 
-      g_free (ink->last_blob);
-      ink->last_blob = blob;
+  /* Get the buffer */
+  for (i = 0; i < nstrokes; i++)
+    {
+      GimpBlob *blob_to_render = g_list_nth_data (blobs_to_render, i);
+
+      coords = gimp_multi_stroke_get_coords (mstroke, i);
+
+      ink->cur_blob = blob_to_render;
+      paint_buffer = gimp_paint_core_get_paint_buffer (paint_core, drawable,
+                                                       paint_options, coords,
+                                                       &paint_buffer_x,
+                                                       &paint_buffer_y);
+      ink->cur_blob = NULL;
+
+      if (! paint_buffer)
+        continue;
+
+      gimp_context_get_foreground (context, &foreground);
+      color = gimp_gegl_color_new (&foreground);
+
+      gegl_buffer_set_color (paint_buffer, NULL, color);
+      g_object_unref (color);
+
+      /*  draw the blob directly to the canvas_buffer  */
+      render_blob (paint_core->canvas_buffer,
+                   GEGL_RECTANGLE (paint_core->paint_buffer_x,
+                                   paint_core->paint_buffer_y,
+                                   gegl_buffer_get_width  (paint_core->paint_buffer),
+                                   gegl_buffer_get_height (paint_core->paint_buffer)),
+                   blob_to_render);
+
+      /*  draw the paint_area using the just rendered canvas_buffer as mask */
+      gimp_paint_core_paste (paint_core,
+                             NULL,
+                             paint_core->paint_buffer_x,
+                             paint_core->paint_buffer_y,
+                             drawable,
+                             GIMP_OPACITY_OPAQUE,
+                             gimp_context_get_opacity (context),
+                             gimp_context_get_paint_mode (context),
+                             GIMP_PAINT_CONSTANT);
 
-      blob_to_render = blob_union;
     }
 
-  /* Get the buffer */
-  ink->cur_blob = blob_to_render;
-  paint_buffer = gimp_paint_core_get_paint_buffer (paint_core, drawable,
-                                                   paint_options, coords,
-                                                   &paint_buffer_x,
-                                                   &paint_buffer_y);
-  ink->cur_blob = NULL;
-
-  if (! paint_buffer)
-    return;
-
-  gimp_context_get_foreground (context, &foreground);
-  color = gimp_gegl_color_new (&foreground);
-
-  gegl_buffer_set_color (paint_buffer, NULL, color);
-  g_object_unref (color);
-
-  /*  draw the blob directly to the canvas_buffer  */
-  render_blob (paint_core->canvas_buffer,
-               GEGL_RECTANGLE (paint_core->paint_buffer_x,
-                               paint_core->paint_buffer_y,
-                               gegl_buffer_get_width  (paint_core->paint_buffer),
-                               gegl_buffer_get_height (paint_core->paint_buffer)),
-               blob_to_render);
-
-  /*  draw the paint_area using the just rendered canvas_buffer as mask */
-  gimp_paint_core_paste (paint_core,
-                         NULL,
-                         paint_core->paint_buffer_x,
-                         paint_core->paint_buffer_y,
-                         drawable,
-                         GIMP_OPACITY_OPAQUE,
-                         gimp_context_get_opacity (context),
-                         gimp_context_get_paint_mode (context),
-                         GIMP_PAINT_CONSTANT);
-
-  if (blob_union)
-    g_free (blob_union);
+  g_list_free_full (blob_unions, g_free);
 }
 
 static GimpBlob *
diff --git a/app/paint/gimpink.h b/app/paint/gimpink.h
index 6cb185b..6c30f56 100644
--- a/app/paint/gimpink.h
+++ b/app/paint/gimpink.h
@@ -37,10 +37,10 @@ struct _GimpInk
 {
   GimpPaintCore  parent_instance;
 
-  GimpBlob      *start_blob;   /*  starting blob (for undo)       */
+  GList         *start_blobs;  /*  starting blobs per stroke (for undo) */
 
-  GimpBlob      *cur_blob;     /*  current blob                   */
-  GimpBlob      *last_blob;    /*  blob for last cursor position  */
+  GimpBlob      *cur_blob;     /*  current blob                         */
+  GList         *last_blobs;   /*  blobs for last stroke positions      */
 };
 
 struct _GimpInkClass
diff --git a/app/paint/gimpinkundo.c b/app/paint/gimpinkundo.c
index aede58f..b3aae31 100644
--- a/app/paint/gimpinkundo.c
+++ b/app/paint/gimpinkundo.c
@@ -58,6 +58,7 @@ gimp_ink_undo_class_init (GimpInkUndoClass *klass)
 static void
 gimp_ink_undo_init (GimpInkUndo *undo)
 {
+  undo->last_blobs = NULL;
 }
 
 static void
@@ -72,8 +73,20 @@ gimp_ink_undo_constructed (GObject *object)
 
   ink = GIMP_INK (GIMP_PAINT_CORE_UNDO (ink_undo)->paint_core);
 
-  if (ink->start_blob)
-    ink_undo->last_blob = gimp_blob_duplicate (ink->start_blob);
+  if (ink->start_blobs)
+    {
+      gint      i;
+      GimpBlob *blob;
+
+      for (i = 0; i < g_list_length (ink->start_blobs); i++)
+        {
+          blob = g_list_nth_data (ink->start_blobs, i);
+
+          ink_undo->last_blobs = g_list_prepend (ink_undo->last_blobs,
+                                                 gimp_blob_duplicate (blob));
+        }
+      ink_undo->last_blobs = g_list_reverse (ink_undo->last_blobs);
+    }
 }
 
 static void
@@ -88,12 +101,11 @@ gimp_ink_undo_pop (GimpUndo              *undo,
   if (GIMP_PAINT_CORE_UNDO (ink_undo)->paint_core)
     {
       GimpInk  *ink = GIMP_INK (GIMP_PAINT_CORE_UNDO (ink_undo)->paint_core);
-      GimpBlob *tmp_blob;
-
-      tmp_blob = ink->last_blob;
-      ink->last_blob = ink_undo->last_blob;
-      ink_undo->last_blob = tmp_blob;
+      GList    *tmp_blobs;
 
+      tmp_blobs = ink->last_blobs;
+      ink->last_blobs = ink_undo->last_blobs;
+      ink_undo->last_blobs = tmp_blobs;
     }
 }
 
@@ -103,10 +115,10 @@ gimp_ink_undo_free (GimpUndo     *undo,
 {
   GimpInkUndo *ink_undo = GIMP_INK_UNDO (undo);
 
-  if (ink_undo->last_blob)
+  if (ink_undo->last_blobs)
     {
-      g_free (ink_undo->last_blob);
-      ink_undo->last_blob = NULL;
+      g_list_free_full (ink_undo->last_blobs, g_free);
+      ink_undo->last_blobs = NULL;
     }
 
   GIMP_UNDO_CLASS (parent_class)->free (undo, undo_mode);
diff --git a/app/paint/gimpinkundo.h b/app/paint/gimpinkundo.h
index fbeecbf..e3a29cf 100644
--- a/app/paint/gimpinkundo.h
+++ b/app/paint/gimpinkundo.h
@@ -36,7 +36,7 @@ struct _GimpInkUndo
 {
   GimpPaintCoreUndo  parent_instance;
 
-  GimpBlob         *last_blob;
+  GList             *last_blobs;
 };
 
 struct _GimpInkUndoClass
diff --git a/app/paint/gimpmirror.c b/app/paint/gimpmirror.c
new file mode 100644
index 0000000..263a8f1
--- /dev/null
+++ b/app/paint/gimpmirror.c
@@ -0,0 +1,727 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpmirror.c
+ * Copyright (C) 2015 Jehan <jehan gimp org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <gegl.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+
+#include "libgimpconfig/gimpconfig.h"
+
+#include "paint-types.h"
+
+#include "core/gimp.h"
+#include "core/gimpbrush.h"
+#include "core/gimpmirrorguide.h"
+#include "core/gimpimage.h"
+#include "core/gimpimage-guides.h"
+#include "core/gimpitem.h"
+
+#include "gimpbrushcore.h"
+#include "gimpmirror.h"
+#include "gimpsourcecore.h"
+
+#include "gimp-intl.h"
+
+enum
+{
+  PROP_0,
+
+  PROP_HORIZONTAL_SYMMETRY,
+  PROP_VERTICAL_SYMMETRY,
+  PROP_POINT_SYMMETRY,
+  PROP_DISABLE_TRANSFORMATION,
+  PROP_HORIZONTAL_POSITION,
+  PROP_VERTICAL_POSITION
+};
+
+/* Local function prototypes */
+
+static void       gimp_mirror_finalize            (GObject         *object);
+static void       gimp_mirror_set_property        (GObject         *object,
+                                                   guint            property_id,
+                                                   const GValue    *value,
+                                                   GParamSpec      *pspec);
+static void       gimp_mirror_get_property        (GObject         *object,
+                                                   guint            property_id,
+                                                   GValue          *value,
+                                                   GParamSpec      *pspec);
+
+static void       gimp_mirror_update_strokes      (GimpMultiStroke *mirror,
+                                                   GimpDrawable    *drawable,
+                                                   GimpCoords      *origin);
+static GeglNode * gimp_mirror_get_operation       (GimpMultiStroke *mirror,
+                                                   GimpPaintCore   *core,
+                                                   GeglBuffer      *paint_buffer,
+                                                   gint             stroke);
+static void       gimp_mirror_reset               (GimpMirror      *mirror);
+static void       gimp_mirror_guide_removed_cb    (GObject         *object,
+                                                   GimpMirror      *mirror);
+static void       gimp_mirror_guide_position_cb   (GObject         *object,
+                                                   GParamSpec      *pspec,
+                                                   GimpMirror      *mirror);
+static GParamSpec ** gimp_mirror_get_settings     (GimpMultiStroke *mstroke,
+                                                   guint           *nsettings);
+static GParamSpec ** gimp_mirror_get_xcf_settings (GimpMultiStroke *mstroke,
+                                                   guint           *nsettings);
+static void  gimp_mirror_set_horizontal_symmetry  (GimpMirror      *mirror,
+                                                   gboolean         active);
+static void    gimp_mirror_set_vertical_symmetry  (GimpMirror      *mirror,
+                                                   gboolean         active);
+static void       gimp_mirror_set_point_symmetry  (GimpMirror      *mirror,
+                                                   gboolean         active);
+
+G_DEFINE_TYPE (GimpMirror, gimp_mirror, GIMP_TYPE_MULTI_STROKE)
+
+#define parent_class gimp_mirror_parent_class
+
+static void
+gimp_mirror_class_init (GimpMirrorClass *klass)
+{
+  GObjectClass         *object_class       = G_OBJECT_CLASS (klass);
+  GimpMultiStrokeClass *multi_stroke_class = GIMP_MULTI_STROKE_CLASS (klass);
+
+  object_class->finalize               = gimp_mirror_finalize;
+  object_class->set_property           = gimp_mirror_set_property;
+  object_class->get_property           = gimp_mirror_get_property;
+
+  multi_stroke_class->label            = "Mirror";
+  multi_stroke_class->update_strokes   = gimp_mirror_update_strokes;
+  multi_stroke_class->get_operation    = gimp_mirror_get_operation;
+  multi_stroke_class->get_settings     = gimp_mirror_get_settings;
+  multi_stroke_class->get_xcf_settings = gimp_mirror_get_xcf_settings;
+
+  /* Properties for user settings */
+  GIMP_CONFIG_INSTALL_PROP_BOOLEAN (object_class, PROP_HORIZONTAL_SYMMETRY,
+                                    "horizontal-symmetry",
+                                    _("Horizontal Mirror"),
+                                    FALSE,
+                                    GIMP_PARAM_STATIC_STRINGS);
+  GIMP_CONFIG_INSTALL_PROP_BOOLEAN (object_class, PROP_VERTICAL_SYMMETRY,
+                                    "vertical-symmetry",
+                                    _("Vertical Mirror"),
+                                    FALSE,
+                                    GIMP_PARAM_STATIC_STRINGS);
+  GIMP_CONFIG_INSTALL_PROP_BOOLEAN (object_class, PROP_POINT_SYMMETRY,
+                                    "point-symmetry",
+                                    _("Central Symmetry"),
+                                    FALSE,
+                                    GIMP_PARAM_STATIC_STRINGS);
+  GIMP_CONFIG_INSTALL_PROP_BOOLEAN (object_class, PROP_DISABLE_TRANSFORMATION,
+                                    "disable-transformation",
+                                    _("Disable Brush Transformation (faster)"),
+                                    FALSE,
+                                    GIMP_PARAM_STATIC_STRINGS);
+
+  /* Properties for XCF serialization only */
+  GIMP_CONFIG_INSTALL_PROP_DOUBLE (object_class, PROP_HORIZONTAL_POSITION,
+                                    "horizontal-position",
+                                    _("Horizontal guide position"),
+                                    0.0, G_MAXDOUBLE, 0.0,
+                                    GIMP_PARAM_STATIC_STRINGS);
+  GIMP_CONFIG_INSTALL_PROP_DOUBLE (object_class, PROP_VERTICAL_POSITION,
+                                    "vertical-position",
+                                    _("Vertical guide position"),
+                                    0.0, G_MAXDOUBLE, 0.0,
+                                    GIMP_PARAM_STATIC_STRINGS);
+}
+
+static void
+gimp_mirror_init (GimpMirror *mirror)
+{
+  mirror->horizontal_mirror = FALSE;
+  mirror->vertical_mirror   = FALSE;
+  mirror->point_symmetry    = FALSE;
+
+  mirror->horizontal_guide  = NULL;
+  mirror->vertical_guide    = NULL;
+}
+
+static void
+gimp_mirror_finalize (GObject *object)
+{
+  GimpMirror *mirror = GIMP_MIRROR (object);
+
+  if (mirror->horizontal_guide)
+    g_object_unref (mirror->horizontal_guide);
+  mirror->horizontal_guide = NULL;
+
+  if (mirror->vertical_guide)
+    g_object_unref (mirror->vertical_guide);
+  mirror->vertical_guide = NULL;
+
+  G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+gimp_mirror_set_property (GObject      *object,
+                          guint         property_id,
+                          const GValue *value,
+                          GParamSpec   *pspec)
+{
+  GimpMirror *mirror = GIMP_MIRROR (object);
+
+  switch (property_id)
+    {
+    case PROP_HORIZONTAL_SYMMETRY:
+      gimp_mirror_set_horizontal_symmetry (mirror,
+                                           g_value_get_boolean (value));
+      break;
+    case PROP_VERTICAL_SYMMETRY:
+      gimp_mirror_set_vertical_symmetry (mirror,
+                                         g_value_get_boolean (value));
+      break;
+    case PROP_POINT_SYMMETRY:
+      gimp_mirror_set_point_symmetry (mirror,
+                                      g_value_get_boolean (value));
+      break;
+    case PROP_DISABLE_TRANSFORMATION:
+      mirror->disable_transformation = g_value_get_boolean (value);
+      break;
+    case PROP_HORIZONTAL_POSITION:
+      mirror->horizontal_position = g_value_get_double (value);
+      if (mirror->horizontal_guide)
+        gimp_guide_set_position (GIMP_GUIDE (mirror->horizontal_guide),
+                                 mirror->horizontal_position);
+      break;
+    case PROP_VERTICAL_POSITION:
+      mirror->vertical_position = g_value_get_double (value);
+      if (mirror->vertical_guide)
+        gimp_guide_set_position (GIMP_GUIDE (mirror->vertical_guide),
+                                 mirror->vertical_position);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+      break;
+    }
+}
+
+static void
+gimp_mirror_get_property (GObject    *object,
+                          guint       property_id,
+                          GValue     *value,
+                          GParamSpec *pspec)
+{
+  GimpMirror *mirror = GIMP_MIRROR (object);
+
+  switch (property_id)
+    {
+    case PROP_HORIZONTAL_SYMMETRY:
+      g_value_set_boolean (value, mirror->horizontal_mirror);
+      break;
+    case PROP_VERTICAL_SYMMETRY:
+      g_value_set_boolean (value, mirror->vertical_mirror);
+      break;
+    case PROP_POINT_SYMMETRY:
+      g_value_set_boolean (value, mirror->point_symmetry);
+      break;
+    case PROP_DISABLE_TRANSFORMATION:
+      g_value_set_boolean (value, mirror->disable_transformation);
+      break;
+    case PROP_HORIZONTAL_POSITION:
+      g_value_set_double (value, mirror->horizontal_position);
+      break;
+    case PROP_VERTICAL_POSITION:
+      g_value_set_double (value, mirror->vertical_position);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+      break;
+    }
+}
+
+static void
+gimp_mirror_update_strokes (GimpMultiStroke *mstroke,
+                            GimpDrawable    *drawable,
+                            GimpCoords      *origin)
+{
+  GList      *strokes = NULL;
+  GimpMirror *mirror  = GIMP_MIRROR (mstroke);
+  GimpCoords *coords;
+
+  g_list_free_full (mstroke->strokes, g_free);
+  strokes = g_list_prepend (strokes,
+                            g_memdup (origin, sizeof (GimpCoords)));
+
+  if (mirror->horizontal_mirror)
+    {
+      coords = g_memdup (origin, sizeof (GimpCoords));
+      coords->y = 2.0 * mirror->horizontal_position - origin->y;
+      strokes = g_list_prepend (strokes, coords);
+    }
+  if (mirror->vertical_mirror)
+    {
+      coords = g_memdup (origin, sizeof (GimpCoords));
+      coords->x = 2.0 * mirror->vertical_position - origin->x;
+      strokes = g_list_prepend (strokes, coords);
+    }
+  if (mirror->point_symmetry)
+    {
+      coords = g_memdup (origin, sizeof (GimpCoords));
+      coords->x = 2.0 * mirror->vertical_position - origin->x;
+      coords->y = 2.0 * mirror->horizontal_position - origin->y;
+      strokes = g_list_prepend (strokes, coords);
+    }
+  mstroke->strokes = g_list_reverse (strokes);
+
+  g_signal_emit_by_name (mstroke, "strokes-updated", mstroke->image);
+}
+
+static GeglNode *
+gimp_mirror_get_operation (GimpMultiStroke *mstroke,
+                           GimpPaintCore   *core,
+                           GeglBuffer      *paint_buffer,
+                           gint             stroke)
+{
+  GimpMirror *mirror  = GIMP_MIRROR (mstroke);
+  GeglNode   *op;
+
+  g_return_val_if_fail (stroke >= 0 &&
+                        stroke < g_list_length (mstroke->strokes), NULL);
+
+  if (mirror->disable_transformation || stroke == 0 || ! paint_buffer)
+    {
+      op = NULL;
+    }
+  else if (stroke == 1 && mirror->horizontal_mirror)
+    {
+      gint height;
+
+      if (GIMP_IS_SOURCE_CORE (core) || ! GIMP_IS_BRUSH_CORE (core))
+        height = gegl_buffer_get_height (paint_buffer);
+      else
+        height = gimp_brush_get_height (GIMP_BRUSH_CORE (core)->brush);
+
+      op = gegl_node_new_child (NULL,
+                                "operation", "gegl:reflect",
+                                "origin-x", 0.0,
+                                "origin-y",
+                                (gdouble) height / 2.0,
+                                "x",
+                                1.0,
+                                "y",
+                                0.0,
+                                NULL);
+    }
+  else if ((stroke == 2 && mirror->horizontal_mirror &&
+            mirror->vertical_mirror) ||
+           (stroke == 1 && mirror->vertical_mirror &&
+            !  mirror->horizontal_mirror))
+    {
+      gint width;
+
+      if (GIMP_IS_SOURCE_CORE (core) || ! GIMP_IS_BRUSH_CORE (core))
+        width = gegl_buffer_get_width (paint_buffer);
+      else
+        width = gimp_brush_get_width (GIMP_BRUSH_CORE (core)->brush);
+
+      op = gegl_node_new_child (NULL,
+                                "operation", "gegl:reflect",
+                                "origin-x",
+                                (gdouble) width / 2.0,
+                                "origin-y", 0.0,
+                                "x",
+                                0.0,
+                                "y",
+                                1.0,
+                                NULL);
+    }
+  else
+    {
+      gint width;
+      gint height;
+
+      if (GIMP_IS_SOURCE_CORE (core) || ! GIMP_IS_BRUSH_CORE (core))
+        {
+          width  = gegl_buffer_get_width (paint_buffer);
+          height = gegl_buffer_get_height (paint_buffer);
+        }
+      else
+        {
+          width  = gimp_brush_get_width (GIMP_BRUSH_CORE (core)->brush);
+          height = gimp_brush_get_height (GIMP_BRUSH_CORE (core)->brush);
+        }
+
+      op = gegl_node_new_child (NULL,
+                                "operation", "gegl:rotate",
+                                "origin-x",
+                                (gdouble) width / 2.0,
+                                "origin-y",
+                                (gdouble) height / 2.0,
+                                "degrees",
+                                180.0,
+                                NULL);
+    }
+
+  return op;
+}
+
+static void
+gimp_mirror_reset (GimpMirror *mirror)
+{
+  GimpMultiStroke *mstroke;
+
+  g_return_if_fail (GIMP_IS_MIRROR (mirror));
+
+  mstroke = GIMP_MULTI_STROKE (mirror);
+
+  if (mstroke->origin)
+    {
+      gimp_multi_stroke_set_origin (mstroke, mstroke->drawable,
+                                    mstroke->origin);
+    }
+}
+
+static void
+gimp_mirror_guide_removed_cb (GObject    *object,
+                              GimpMirror *mirror)
+{
+  g_signal_handlers_disconnect_by_func (object,
+                                        gimp_mirror_guide_removed_cb,
+                                        mirror);
+  g_signal_handlers_disconnect_by_func (object,
+                                        gimp_mirror_guide_position_cb,
+                                        mirror);
+  if (GIMP_MIRROR_GUIDE (object) == mirror->horizontal_guide)
+    {
+      g_object_unref (mirror->horizontal_guide);
+
+      mirror->horizontal_guide    = NULL;
+
+      mirror->horizontal_mirror   = FALSE;
+      mirror->point_symmetry      = FALSE;
+      mirror->horizontal_position = 0.0;
+    }
+  else if (GIMP_MIRROR_GUIDE (object) == mirror->vertical_guide)
+    {
+      g_object_unref (mirror->vertical_guide);
+      mirror->vertical_guide    = NULL;
+
+      mirror->vertical_mirror   = FALSE;
+      mirror->point_symmetry    = FALSE;
+      mirror->vertical_position = 0.0;
+    }
+
+  if (mirror->horizontal_guide == NULL &&
+      mirror->vertical_guide   == NULL)
+    {
+      GimpMultiStroke *mstroke;
+
+      mstroke = GIMP_MULTI_STROKE (mirror);
+      gimp_image_remove_multi_stroke (mstroke->image,
+                                      GIMP_MULTI_STROKE (mirror));
+    }
+  else
+    {
+      gimp_mirror_reset (mirror);
+    }
+}
+
+static void
+gimp_mirror_guide_position_cb (GObject    *object,
+                               GParamSpec *pspec,
+                               GimpMirror *mirror)
+{
+  GimpGuide *guide;
+
+  guide = GIMP_GUIDE (object);
+
+  if (GIMP_MIRROR_GUIDE (guide) == mirror->horizontal_guide)
+    {
+      mirror->horizontal_position = (gdouble) gimp_guide_get_position (guide);
+    }
+  else if (GIMP_MIRROR_GUIDE (guide) == mirror->vertical_guide)
+    {
+      mirror->vertical_position = (gdouble) gimp_guide_get_position (guide);
+    }
+}
+
+static GParamSpec **
+gimp_mirror_get_settings (GimpMultiStroke *mstroke,
+                          guint           *nsettings)
+{
+  GParamSpec **pspecs;
+
+  *nsettings = 5;
+  pspecs = g_new (GParamSpec*, 5);
+
+  pspecs[0] = g_object_class_find_property (G_OBJECT_GET_CLASS (mstroke),
+                                            "horizontal-symmetry");
+  pspecs[1] = g_object_class_find_property (G_OBJECT_GET_CLASS (mstroke),
+                                            "vertical-symmetry");
+  pspecs[2] = g_object_class_find_property (G_OBJECT_GET_CLASS (mstroke),
+                                            "point-symmetry");
+  pspecs[3] = NULL;
+  pspecs[4] = g_object_class_find_property (G_OBJECT_GET_CLASS (mstroke),
+                                            "disable-transformation");
+
+  return pspecs;
+}
+
+static GParamSpec **
+gimp_mirror_get_xcf_settings (GimpMultiStroke *mstroke,
+                              guint           *nsettings)
+{
+  GParamSpec **pspecs;
+
+  *nsettings = 6;
+  pspecs = g_new (GParamSpec*, 6);
+
+  pspecs[0] = g_object_class_find_property (G_OBJECT_GET_CLASS (mstroke),
+                                            "horizontal-symmetry");
+  pspecs[1] = g_object_class_find_property (G_OBJECT_GET_CLASS (mstroke),
+                                            "vertical-symmetry");
+  pspecs[2] = g_object_class_find_property (G_OBJECT_GET_CLASS (mstroke),
+                                            "point-symmetry");
+  pspecs[3] = g_object_class_find_property (G_OBJECT_GET_CLASS (mstroke),
+                                            "horizontal-position");
+  pspecs[4] = g_object_class_find_property (G_OBJECT_GET_CLASS (mstroke),
+                                            "vertical-position");
+  pspecs[5] = g_object_class_find_property (G_OBJECT_GET_CLASS (mstroke),
+                                            "disable-transformation");
+
+  return pspecs;
+}
+
+static void
+gimp_mirror_set_horizontal_symmetry (GimpMirror *mirror,
+                                     gboolean    active)
+{
+  GimpMultiStroke *mstroke;
+  GimpImage       *image;
+
+  g_return_if_fail (GIMP_IS_MIRROR (mirror));
+
+  mstroke = GIMP_MULTI_STROKE (mirror);
+  image   = mstroke->image;
+
+  if (active == mirror->horizontal_mirror)
+    return;
+
+  mirror->horizontal_mirror = active;
+
+  if (active)
+    {
+      if (! mirror->horizontal_guide)
+        {
+          /* Create a new mirror guide. */
+          Gimp            *gimp  = GIMP (image->gimp);
+          GimpMirrorGuide *guide;
+
+          /* Mirror guide position at first activation is at canvas middle. */
+          mirror->horizontal_position = (gdouble) gimp_image_get_height (image) / 2.0;
+          guide = gimp_mirror_guide_new (gimp,
+                                         GIMP_ORIENTATION_HORIZONTAL,
+                                         gimp->next_guide_ID++);
+          mirror->horizontal_guide = guide;
+        } /* Else reuse existing guide at same position. */
+
+      g_signal_connect (G_OBJECT (mirror->horizontal_guide), "removed",
+                        G_CALLBACK (gimp_mirror_guide_removed_cb),
+                        mirror);
+      gimp_image_add_guide (image, GIMP_GUIDE (mirror->horizontal_guide),
+                            (gint) mirror->horizontal_position);
+      g_signal_connect (G_OBJECT (mirror->horizontal_guide), "notify::position",
+                        G_CALLBACK (gimp_mirror_guide_position_cb),
+                        mirror);
+    }
+  else if (! mirror->point_symmetry)
+    {
+      /* We remove the guide but keep the reference,
+       * so that when we reshow it, we can just reuse the same guide. */
+      g_signal_handlers_disconnect_by_func (G_OBJECT (mirror->horizontal_guide),
+                                            gimp_mirror_guide_removed_cb,
+                                            mirror);
+      g_signal_handlers_disconnect_by_func (G_OBJECT (mirror->horizontal_guide),
+                                            gimp_mirror_guide_position_cb,
+                                            mirror);
+      gimp_image_remove_guide (image, GIMP_GUIDE (mirror->horizontal_guide),
+                               FALSE);
+    }
+
+  gimp_mirror_reset (mirror);
+}
+
+static void
+gimp_mirror_set_vertical_symmetry (GimpMirror *mirror,
+                                   gboolean    active)
+{
+  GimpMultiStroke *mstroke;
+  GimpImage       *image;
+
+  g_return_if_fail (GIMP_IS_MIRROR (mirror));
+
+  mstroke = GIMP_MULTI_STROKE (mirror);
+  image   = mstroke->image;
+
+  if (active == mirror->vertical_mirror)
+    return;
+
+  mirror->vertical_mirror = active;
+
+  if (active)
+    {
+      if (! mirror->vertical_guide)
+        {
+          /* Create a new mirror guide. */
+          Gimp            *gimp  = GIMP (image->gimp);
+          GimpMirrorGuide *guide;
+
+          /* Mirror guide position at first activation is at canvas middle. */
+          mirror->vertical_position = (gdouble) gimp_image_get_width (image) / 2.0;
+          guide = gimp_mirror_guide_new (gimp,
+                                         GIMP_ORIENTATION_VERTICAL,
+                                         gimp->next_guide_ID++);
+          mirror->vertical_guide = guide;
+        } /* Else reuse existing guide at same position. */
+
+      g_signal_connect (G_OBJECT (mirror->vertical_guide), "removed",
+                        G_CALLBACK (gimp_mirror_guide_removed_cb),
+                        mirror);
+      gimp_image_add_guide (image, GIMP_GUIDE (mirror->vertical_guide),
+                            (gint) mirror->vertical_position);
+      g_signal_connect (G_OBJECT (mirror->vertical_guide), "notify::position",
+                        G_CALLBACK (gimp_mirror_guide_position_cb),
+                        mirror);
+    }
+  else if (! mirror->point_symmetry)
+    {
+      /* We remove the guide but keep the reference,
+       * so that when we reshow it, we can just reuse the same guide. */
+      g_signal_handlers_disconnect_by_func (G_OBJECT (mirror->vertical_guide),
+                                            gimp_mirror_guide_removed_cb,
+                                            mirror);
+      g_signal_handlers_disconnect_by_func (G_OBJECT (mirror->vertical_guide),
+                                            gimp_mirror_guide_position_cb,
+                                            mirror);
+      gimp_image_remove_guide (image, GIMP_GUIDE (mirror->vertical_guide),
+                               FALSE);
+    }
+
+  gimp_mirror_reset (mirror);
+}
+
+static void
+gimp_mirror_set_point_symmetry (GimpMirror *mirror,
+                                gboolean    active)
+{
+  GimpMultiStroke *mstroke;
+  GimpImage       *image;
+
+  g_return_if_fail (GIMP_IS_MIRROR (mirror));
+
+  mstroke = GIMP_MULTI_STROKE (mirror);
+  image   = mstroke->image;
+
+  if (active == mirror->point_symmetry)
+    return;
+
+  mirror->point_symmetry = active;
+
+  if (active)
+    {
+      /* Show the horizontal guide unless already shown */
+      if (! mirror->horizontal_mirror)
+        {
+          if (! mirror->horizontal_guide)
+            {
+              /* Create a new mirror guide. */
+              Gimp            *gimp  = GIMP (image->gimp);
+              GimpMirrorGuide *guide;
+
+              /* Mirror guide position at first activation is at canvas middle. */
+              mirror->horizontal_position = (gdouble) gimp_image_get_height (image) / 2.0;
+              guide = gimp_mirror_guide_new (gimp,
+                                             GIMP_ORIENTATION_HORIZONTAL,
+                                             gimp->next_guide_ID++);
+              mirror->horizontal_guide = guide;
+
+            } /* Else reuse existing guide at same position. */
+
+          g_signal_connect (G_OBJECT (mirror->horizontal_guide), "removed",
+                            G_CALLBACK (gimp_mirror_guide_removed_cb),
+                            mirror);
+          gimp_image_add_guide (image, GIMP_GUIDE (mirror->horizontal_guide),
+                                (gint) mirror->horizontal_position);
+          g_signal_connect (G_OBJECT (mirror->horizontal_guide), "notify::position",
+                            G_CALLBACK (gimp_mirror_guide_position_cb),
+                            mirror);
+        }
+
+      /* Show the vertical guide unless already shown */
+      if (! mirror->vertical_mirror)
+        {
+          if (! mirror->vertical_guide)
+            {
+              /* Create a new mirror guide. */
+              Gimp            *gimp  = GIMP (image->gimp);
+              GimpMirrorGuide *guide;
+
+              /* Mirror guide position at first activation is at canvas middle. */
+              mirror->vertical_position = (gdouble) gimp_image_get_width (image) / 2.0;
+              guide = gimp_mirror_guide_new (gimp,
+                                             GIMP_ORIENTATION_VERTICAL,
+                                             gimp->next_guide_ID++);
+              mirror->vertical_guide = guide;
+
+            } /* Else reuse existing guide at same position. */
+
+          g_signal_connect (G_OBJECT (mirror->vertical_guide), "removed",
+                            G_CALLBACK (gimp_mirror_guide_removed_cb),
+                            mirror);
+          gimp_image_add_guide (image, GIMP_GUIDE (mirror->vertical_guide),
+                                (gint) mirror->vertical_position);
+          g_signal_connect (G_OBJECT (mirror->vertical_guide), "notify::position",
+                            G_CALLBACK (gimp_mirror_guide_position_cb),
+                            mirror);
+        }
+    }
+  else
+    {
+      /* Hide the horizontal guide unless needed by horizontal mirror */
+      if (! mirror->horizontal_mirror)
+        {
+          g_signal_handlers_disconnect_by_func (G_OBJECT (mirror->horizontal_guide),
+                                                gimp_mirror_guide_removed_cb,
+                                                mirror);
+          g_signal_handlers_disconnect_by_func (G_OBJECT (mirror->horizontal_guide),
+                                                gimp_mirror_guide_position_cb,
+                                                mirror);
+          gimp_image_remove_guide (image, GIMP_GUIDE (mirror->horizontal_guide),
+                                   FALSE);
+        }
+      /* Hide the vertical guide unless needed by vertical mirror */
+      if (! mirror->vertical_mirror)
+        {
+          g_signal_handlers_disconnect_by_func (G_OBJECT (mirror->vertical_guide),
+                                                gimp_mirror_guide_removed_cb,
+                                                mirror);
+          g_signal_handlers_disconnect_by_func (G_OBJECT (mirror->vertical_guide),
+                                                gimp_mirror_guide_position_cb,
+                                                mirror);
+          gimp_image_remove_guide (image, GIMP_GUIDE (mirror->vertical_guide),
+                                   FALSE);
+        }
+    }
+
+  gimp_mirror_reset (mirror);
+}
diff --git a/app/paint/gimpmirror.h b/app/paint/gimpmirror.h
new file mode 100644
index 0000000..0ee9537
--- /dev/null
+++ b/app/paint/gimpmirror.h
@@ -0,0 +1,62 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpmirror.h
+ * Copyright (C) 2015 Jehan <jehan gimp org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_MIRROR_H__
+#define __GIMP_MIRROR_H__
+
+
+#include "gimpmultistroke.h"
+
+
+#define GIMP_TYPE_MIRROR            (gimp_mirror_get_type ())
+#define GIMP_MIRROR(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_MIRROR, GimpMirror))
+#define GIMP_MIRROR_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_MIRROR, GimpMirrorClass))
+#define GIMP_IS_MIRROR(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_MIRROR))
+#define GIMP_IS_MIRROR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_MIRROR))
+#define GIMP_MIRROR_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_MIRROR, GimpMirrorClass))
+
+typedef struct _GimpMirrorClass GimpMirrorClass;
+
+struct _GimpMirror
+{
+  GimpMultiStroke  parent_instance;
+
+  gboolean         horizontal_mirror;
+  gboolean         vertical_mirror;
+  gboolean         point_symmetry;
+  gboolean         disable_transformation;
+
+  gdouble          horizontal_position;
+  gdouble          vertical_position;
+  GimpMirrorGuide *horizontal_guide;
+  GimpMirrorGuide *vertical_guide;
+};
+
+struct _GimpMirrorClass
+{
+  GimpMultiStrokeClass  parent_class;
+};
+
+
+GType        gimp_mirror_get_type                (void) G_GNUC_CONST;
+
+#endif  /*  __GIMP_MIRROR_H__  */
+
+
diff --git a/app/paint/gimpmultistroke-info.c b/app/paint/gimpmultistroke-info.c
new file mode 100644
index 0000000..6ca4876
--- /dev/null
+++ b/app/paint/gimpmultistroke-info.c
@@ -0,0 +1,59 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpmultistroke-info.c
+ * Copyright (C) 2015 Jehan <jehan gimp org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gegl.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+
+#include "paint-types.h"
+
+#include "gimpmirror.h"
+#include "gimpmultistroke.h"
+#include "gimpmultistroke-info.h"
+
+GList *
+gimp_multi_stroke_list (void)
+{
+  GList *list = NULL;
+
+  list = g_list_prepend (list, GINT_TO_POINTER (GIMP_TYPE_MIRROR));
+  return list;
+}
+
+GimpMultiStroke *
+gimp_multi_stroke_new (GType      type,
+                       GimpImage *image)
+{
+  GimpMultiStroke *mstroke = NULL;
+
+  g_return_val_if_fail (g_type_is_a (type, GIMP_TYPE_MULTI_STROKE),
+                        NULL);
+
+  if (type != G_TYPE_NONE)
+    {
+      mstroke = g_object_new (type,
+                              "image", image,
+                              NULL);
+      mstroke->type = type;
+    }
+
+  return mstroke;
+}
diff --git a/app/paint/gimpmultistroke-info.h b/app/paint/gimpmultistroke-info.h
new file mode 100644
index 0000000..d93416e
--- /dev/null
+++ b/app/paint/gimpmultistroke-info.h
@@ -0,0 +1,29 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpmultistroke-info.h
+ * Copyright (C) 2015 Jehan <jehan gimp org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_MULTI_STROKE_INFO_H__
+#define __GIMP_MULTI_STROKE_INFO_H__
+
+GList           * gimp_multi_stroke_list      (void);
+
+GimpMultiStroke * gimp_multi_stroke_new       (GType      type,
+                                               GimpImage *image);
+
+#endif  /*  __GIMP_MULTI_STROKE_INFO_H__  */
diff --git a/app/paint/gimpmultistroke.c b/app/paint/gimpmultistroke.c
new file mode 100644
index 0000000..0c21637
--- /dev/null
+++ b/app/paint/gimpmultistroke.c
@@ -0,0 +1,374 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpmultistroke.c
+ * Copyright (C) 2015 Jehan <jehan gimp org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <gegl.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+
+#include "paint-types.h"
+
+#include "core/gimpdrawable.h"
+#include "core/gimpimage.h"
+#include "core/gimpitem.h"
+
+#include "gimpmultistroke.h"
+
+#include "gimp-intl.h"
+
+enum
+{
+  STROKES_UPDATED,
+  UPDATE_UI,
+  LAST_SIGNAL
+};
+
+enum
+{
+  PROP_0,
+  PROP_IMAGE,
+};
+
+/* Local function prototypes */
+
+static void gimp_multi_stroke_finalize        (GObject         *object);
+static void gimp_multi_stroke_set_property    (GObject         *object,
+                                               guint            property_id,
+                                               const GValue    *value,
+                                               GParamSpec      *pspec);
+static void gimp_multi_stroke_get_property    (GObject         *object,
+                                               guint            property_id,
+                                               GValue          *value,
+                                               GParamSpec      *pspec);
+
+static void
+        gimp_multi_stroke_real_update_strokes (GimpMultiStroke *mstroke,
+                                               GimpDrawable    *drawable,
+                                               GimpCoords      *origin);
+static GeglNode *
+            gimp_multi_stroke_real_get_op     (GimpMultiStroke *mstroke,
+                                               GimpPaintCore   *core,
+                                               GeglBuffer      *paint_buffer,
+                                               gint             stroke);
+static GParamSpec **
+            gimp_multi_stroke_real_get_settings (GimpMultiStroke *mstroke,
+                                                 guint           *nproperties);
+
+G_DEFINE_TYPE (GimpMultiStroke, gimp_multi_stroke, GIMP_TYPE_OBJECT)
+
+#define parent_class gimp_multi_stroke_parent_class
+
+static guint gimp_multi_stroke_signals[LAST_SIGNAL] = { 0 };
+
+static void
+gimp_multi_stroke_class_init (GimpMultiStrokeClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  /* This signal should likely be emitted at the end of update_strokes()
+   * if stroke coordinates were changed. */
+  gimp_multi_stroke_signals[STROKES_UPDATED] =
+    g_signal_new ("strokes-updated",
+                  G_TYPE_FROM_CLASS (klass),
+                  G_SIGNAL_RUN_FIRST,
+                  0,
+                  NULL, NULL,
+                  NULL,
+                  G_TYPE_NONE,
+                  1, GIMP_TYPE_IMAGE);
+  /* This signal should be emitted when you request a change in
+   * the settings UI. For instance adding some settings (therefore having a
+   * dynamic UI), or changing scale min/max extremes, etc. */
+  gimp_multi_stroke_signals[UPDATE_UI] =
+    g_signal_new ("update-ui",
+                  G_TYPE_FROM_CLASS (klass),
+                  G_SIGNAL_RUN_FIRST,
+                  0,
+                  NULL, NULL,
+                  NULL,
+                  G_TYPE_NONE,
+                  1, GIMP_TYPE_IMAGE);
+
+
+  object_class->finalize     = gimp_multi_stroke_finalize;
+  object_class->set_property = gimp_multi_stroke_set_property;
+  object_class->get_property = gimp_multi_stroke_get_property;
+
+  klass->label               = "None";
+  klass->update_strokes      = gimp_multi_stroke_real_update_strokes;
+  klass->get_operation       = gimp_multi_stroke_real_get_op;
+  klass->get_settings        = gimp_multi_stroke_real_get_settings;
+  klass->get_xcf_settings    = gimp_multi_stroke_real_get_settings;
+
+  g_object_class_install_property (object_class, PROP_IMAGE,
+                                   g_param_spec_object ("image",
+                                                        NULL, NULL,
+                                                        GIMP_TYPE_IMAGE,
+                                                        GIMP_PARAM_READWRITE |
+                                                        G_PARAM_CONSTRUCT_ONLY));
+}
+
+
+static void
+gimp_multi_stroke_init (GimpMultiStroke *mstroke)
+{
+  mstroke->image    = NULL;
+  mstroke->drawable = NULL;
+  mstroke->origin   = NULL;
+  mstroke->strokes  = NULL;
+  mstroke->type     = G_TYPE_NONE;
+}
+
+static void
+gimp_multi_stroke_finalize (GObject *object)
+{
+  GimpMultiStroke *mstroke = GIMP_MULTI_STROKE (object);
+
+  if (mstroke->drawable)
+    g_object_unref (mstroke->drawable);
+
+  g_free (mstroke->origin);
+  mstroke->origin = NULL;
+
+  g_list_free_full (mstroke->strokes, g_free);
+  mstroke->strokes = NULL;
+
+  G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+gimp_multi_stroke_set_property (GObject      *object,
+                                guint         property_id,
+                                const GValue *value,
+                                GParamSpec   *pspec)
+{
+  GimpMultiStroke *mstroke = GIMP_MULTI_STROKE (object);
+
+  switch (property_id)
+    {
+    case PROP_IMAGE:
+      mstroke->image = g_value_get_object (value);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+      break;
+    }
+}
+
+static void
+gimp_multi_stroke_get_property (GObject    *object,
+                                guint       property_id,
+                                GValue     *value,
+                                GParamSpec *pspec)
+{
+  GimpMultiStroke *mstroke = GIMP_MULTI_STROKE (object);
+
+  switch (property_id)
+    {
+    case PROP_IMAGE:
+      g_value_set_object (value, mstroke->image);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+      break;
+    }
+}
+
+static void
+gimp_multi_stroke_real_update_strokes (GimpMultiStroke *mstroke,
+                                       GimpDrawable    *drawable,
+                                       GimpCoords      *origin)
+{
+  /* The basic multi-stroke just uses the origin as is. */
+  mstroke->strokes = g_list_prepend (mstroke->strokes,
+                                     g_memdup (origin, sizeof (GimpCoords)));
+}
+
+static GeglNode *
+gimp_multi_stroke_real_get_op (GimpMultiStroke *mstroke,
+                               GimpPaintCore   *core,
+                               GeglBuffer      *paint_buffer,
+                               gint             stroke)
+{
+  /* The basic multi-stroke just returns NULL, since no transformation of the
+   * brush painting happen. */
+  return NULL;
+}
+
+static GParamSpec **
+gimp_multi_stroke_real_get_settings (GimpMultiStroke *mstroke,
+                                     guint           *nproperties)
+{
+  *nproperties = 0;
+
+  return NULL;
+}
+
+/***** Public Functions *****/
+
+/**
+ * gimp_multi_stroke_set_origin:
+ * @mstroke: the #GimpMultiStroke
+ * @drawable:     the #GimpDrawable where painting will happen
+ * @origin:       new base coordinates.
+ *
+ * Set the multi-stroke to new origin coordinates and drawable.
+ **/
+void
+gimp_multi_stroke_set_origin (GimpMultiStroke *mstroke,
+                              GimpDrawable    *drawable,
+                              GimpCoords      *origin)
+{
+  g_return_if_fail (GIMP_IS_MULTI_STROKE (mstroke));
+  g_return_if_fail (GIMP_IS_DRAWABLE (drawable));
+  g_return_if_fail (gimp_item_get_image (GIMP_ITEM (drawable)) == mstroke->image);
+
+  if (drawable != mstroke->drawable)
+    {
+      if (mstroke->drawable)
+        g_object_unref (mstroke->drawable);
+      mstroke->drawable = g_object_ref (drawable);
+    }
+
+  if (origin != mstroke->origin)
+    {
+      g_free (mstroke->origin);
+      mstroke->origin = g_memdup (origin, sizeof (GimpCoords));
+    }
+
+  g_list_free_full (mstroke->strokes, g_free);
+  mstroke->strokes = NULL;
+
+  GIMP_MULTI_STROKE_GET_CLASS (mstroke)->update_strokes (mstroke,
+                                                         drawable,
+                                                         origin);
+}
+
+/**
+ * gimp_multi_stroke_get_origin:
+ * @mstroke: the #GimpMultiStroke
+ *
+ * Returns the origin stroke coordinates.
+ * The returned value is owned by the #GimpMultiStroke and must not be freed.
+ **/
+GimpCoords *
+gimp_multi_stroke_get_origin (GimpMultiStroke *mstroke)
+{
+  g_return_val_if_fail (GIMP_IS_MULTI_STROKE (mstroke), NULL);
+
+  return mstroke->origin;
+}
+
+/**
+ * gimp_multi_stroke_get_size:
+ * @mstroke: the #GimpMultiStroke
+ *
+ * Returns the total number of strokes.
+ **/
+gint
+gimp_multi_stroke_get_size (GimpMultiStroke *mstroke)
+{
+  g_return_val_if_fail (GIMP_IS_MULTI_STROKE (mstroke), 0);
+
+  return g_list_length (mstroke->strokes);
+}
+
+/**
+ * gimp_multi_stroke_get_coords:
+ * @mstroke: the #GimpMultiStroke
+ * @stroke:       the stroke number
+ *
+ * Returns the coordinates of the stroke number @stroke.
+ * The returned value is owned by the #GimpMultiStroke and must not be freed.
+ **/
+GimpCoords *
+gimp_multi_stroke_get_coords (GimpMultiStroke *mstroke,
+                              gint             stroke)
+{
+  g_return_val_if_fail (GIMP_IS_MULTI_STROKE (mstroke), NULL);
+
+  return g_list_nth_data (mstroke->strokes, stroke);
+}
+
+/**
+ * gimp_multi_stroke_get_operation:
+ * @mstroke:      the #GimpMultiStroke
+ * @paint_buffer: the #GeglBuffer normally used for @stroke
+ * @stroke:       the stroke number
+ *
+ * Returns the operation to apply to the paint buffer for stroke number @stroke.
+ * NULL means to copy the original stroke as-is.
+ **/
+GeglNode *
+gimp_multi_stroke_get_operation (GimpMultiStroke *mstroke,
+                                 GimpPaintCore   *core,
+                                 GeglBuffer      *paint_buffer,
+                                 gint             stroke)
+{
+  g_return_val_if_fail (GIMP_IS_MULTI_STROKE (mstroke), NULL);
+
+  return GIMP_MULTI_STROKE_GET_CLASS (mstroke)->get_operation (mstroke,
+                                                               core,
+                                                               paint_buffer,
+                                                               stroke);
+}
+
+/**
+ * gimp_multi_stroke_get_settings:
+ * @mstroke:     the #GimpMultiStroke
+ * @nproperties: the number of properties in the returned array
+ *
+ * Returns an array of the Multi-Stroke properties which are supposed to
+ * be settable by the user.
+ * The returned array must be freed by the caller.
+ **/
+GParamSpec **
+gimp_multi_stroke_get_settings (GimpMultiStroke *mstroke,
+                                guint           *nproperties)
+{
+  g_return_val_if_fail (GIMP_IS_MULTI_STROKE (mstroke), NULL);
+
+  return GIMP_MULTI_STROKE_GET_CLASS (mstroke)->get_settings (mstroke,
+                                                              nproperties);
+}
+
+/**
+ * gimp_multi_stroke_get_xcf_settings:
+ * @mstroke:     the #GimpMultiStroke
+ * @nproperties: the number of properties in the returned array
+ *
+ * Returns an array of the Multi-Stroke properties which are to be serialized
+ * when saving to XCF.
+ * These properties may be different to `gimp_multi_stroke_get_settings()`
+ * (for instance some properties are not meant to be user-changeable but
+ * still saved)
+ * The returned array must be freed by the caller.
+ **/
+GParamSpec **
+gimp_multi_stroke_get_xcf_settings (GimpMultiStroke *mstroke,
+                                    guint           *nproperties)
+{
+  g_return_val_if_fail (GIMP_IS_MULTI_STROKE (mstroke), NULL);
+
+  return GIMP_MULTI_STROKE_GET_CLASS (mstroke)->get_xcf_settings (mstroke,
+                                                                  nproperties);
+}
diff --git a/app/paint/gimpmultistroke.h b/app/paint/gimpmultistroke.h
new file mode 100644
index 0000000..7b43151
--- /dev/null
+++ b/app/paint/gimpmultistroke.h
@@ -0,0 +1,94 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpmultistroke.h
+ * Copyright (C) 2015 Jehan <jehan gimp org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_MULTI_STROKE_H__
+#define __GIMP_MULTI_STROKE_H__
+
+
+#include "core/gimpobject.h"
+
+
+#define GIMP_TYPE_MULTI_STROKE            (gimp_multi_stroke_get_type ())
+#define GIMP_MULTI_STROKE(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_MULTI_STROKE, 
GimpMultiStroke))
+#define GIMP_MULTI_STROKE_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_MULTI_STROKE, 
GimpMultiStrokeClass))
+#define GIMP_IS_MULTI_STROKE(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_MULTI_STROKE))
+#define GIMP_IS_MULTI_STROKE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_MULTI_STROKE))
+#define GIMP_MULTI_STROKE_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_MULTI_STROKE, 
GimpMultiStrokeClass))
+
+typedef struct _GimpMultiStrokeClass   GimpMultiStrokeClass;
+
+struct _GimpMultiStroke
+{
+  GimpObject  parent_instance;
+
+  GimpImage    *image;
+  GimpDrawable *drawable;
+  GimpCoords   *origin;
+
+  GList        *strokes;
+
+  GType         type;
+};
+
+struct _GimpMultiStrokeClass
+{
+  GimpObjectClass  parent_class;
+
+  const gchar * label;
+
+  /* Virtual functions */
+  void       (* update_strokes)             (GimpMultiStroke *mstroke,
+                                             GimpDrawable    *drawable,
+                                             GimpCoords      *origin);
+  GeglNode * (* get_operation)              (GimpMultiStroke *mstroke,
+                                             GimpPaintCore   *core,
+                                             GeglBuffer      *paint_buffer,
+                                             gint             stroke);
+  GParamSpec **
+             (* get_settings)               (GimpMultiStroke *mstroke,
+                                             guint           *nproperties);
+  GParamSpec **
+             (* get_xcf_settings)           (GimpMultiStroke *mstroke,
+                                             guint           *nproperties);
+};
+
+
+GType        gimp_multi_stroke_get_type      (void) G_GNUC_CONST;
+
+void         gimp_multi_stroke_set_origin    (GimpMultiStroke *mstroke,
+                                              GimpDrawable    *drawable,
+                                              GimpCoords      *origin);
+
+GimpCoords * gimp_multi_stroke_get_origin    (GimpMultiStroke *mstroke);
+gint         gimp_multi_stroke_get_size      (GimpMultiStroke *mstroke);
+GimpCoords * gimp_multi_stroke_get_coords    (GimpMultiStroke *mstroke,
+                                              gint             stroke);
+GeglNode   * gimp_multi_stroke_get_operation (GimpMultiStroke *mstroke,
+                                              GimpPaintCore   *core,
+                                              GeglBuffer      *paint_buffer,
+                                              gint             stroke);
+GParamSpec ** gimp_multi_stroke_get_settings  (GimpMultiStroke *mstroke,
+                                               guint           *nproperties);
+GParamSpec **
+           gimp_multi_stroke_get_xcf_settings (GimpMultiStroke *mstroke,
+                                               guint           *nproperties);
+
+#endif  /*  __GIMP_MULTI_STROKE_H__  */
+
diff --git a/app/paint/gimppaintbrush.c b/app/paint/gimppaintbrush.c
index a41c278..46bd07a 100644
--- a/app/paint/gimppaintbrush.c
+++ b/app/paint/gimppaintbrush.c
@@ -38,6 +38,8 @@
 #include "core/gimpimage.h"
 #include "core/gimptempbuf.h"
 
+#include "gimpmultistroke.h"
+
 #include "gimppaintbrush.h"
 #include "gimppaintoptions.h"
 
@@ -47,7 +49,7 @@
 static void   gimp_paintbrush_paint (GimpPaintCore    *paint_core,
                                      GimpDrawable     *drawable,
                                      GimpPaintOptions *paint_options,
-                                     const GimpCoords *coords,
+                                     GimpMultiStroke  *mstroke,
                                      GimpPaintState    paint_state,
                                      guint32           time);
 
@@ -87,7 +89,7 @@ static void
 gimp_paintbrush_paint (GimpPaintCore    *paint_core,
                        GimpDrawable     *drawable,
                        GimpPaintOptions *paint_options,
-                       const GimpCoords *coords,
+                       GimpMultiStroke  *mstroke,
                        GimpPaintState    paint_state,
                        guint32           time)
 {
@@ -116,8 +118,8 @@ gimp_paintbrush_paint (GimpPaintCore    *paint_core,
         }
       break;
     case GIMP_PAINT_STATE_MOTION:
-      _gimp_paintbrush_motion (paint_core, drawable, paint_options, coords,
-                               GIMP_OPACITY_OPAQUE);
+      _gimp_paintbrush_motion (paint_core, drawable, paint_options,
+                               mstroke, GIMP_OPACITY_OPAQUE);
       break;
 
     default:
@@ -129,7 +131,7 @@ void
 _gimp_paintbrush_motion (GimpPaintCore    *paint_core,
                          GimpDrawable     *drawable,
                          GimpPaintOptions *paint_options,
-                         const GimpCoords *coords,
+                         GimpMultiStroke  *mstroke,
                          gdouble           opacity)
 {
   GimpBrushCore            *brush_core = GIMP_BRUSH_CORE (paint_core);
@@ -144,98 +146,111 @@ _gimp_paintbrush_motion (GimpPaintCore    *paint_core,
   gdouble                   fade_point;
   gdouble                   grad_point;
   gdouble                   force;
+  const GimpCoords         *coords;
+  GeglNode                 *op;
+  gint                      i;
+  gint                      nstrokes;
 
   image = gimp_item_get_image (GIMP_ITEM (drawable));
 
+  nstrokes = gimp_multi_stroke_get_size (mstroke);
+
   fade_point = gimp_paint_options_get_fade (paint_options, image,
                                             paint_core->pixel_dist);
 
-  opacity *= gimp_dynamics_get_linear_value (dynamics,
-                                             GIMP_DYNAMICS_OUTPUT_OPACITY,
-                                             coords,
-                                             paint_options,
-                                             fade_point);
-  if (opacity == 0.0)
-    return;
-
-  paint_buffer = gimp_paint_core_get_paint_buffer (paint_core, drawable,
-                                                   paint_options, coords,
-                                                   &paint_buffer_x,
-                                                   &paint_buffer_y);
-  if (! paint_buffer)
-    return;
-
-  paint_appl_mode = paint_options->application_mode;
-
-  grad_point = gimp_dynamics_get_linear_value (dynamics,
-                                               GIMP_DYNAMICS_OUTPUT_COLOR,
-                                               coords,
-                                               paint_options,
-                                               fade_point);
-
-  if (gimp_paint_options_get_gradient_color (paint_options, image,
-                                             grad_point,
-                                             paint_core->pixel_dist,
-                                             &gradient_color))
+  for (i = 0; i < nstrokes; i++)
     {
-      /* optionally take the color from the current gradient */
+      coords = gimp_multi_stroke_get_coords (mstroke, i);
+
+      opacity *= gimp_dynamics_get_linear_value (dynamics,
+                                                 GIMP_DYNAMICS_OUTPUT_OPACITY,
+                                                 coords,
+                                                 paint_options,
+                                                 fade_point);
+      if (opacity == 0.0)
+        continue;
+
+      paint_buffer = gimp_paint_core_get_paint_buffer (paint_core, drawable,
+                                                       paint_options, coords,
+                                                       &paint_buffer_x,
+                                                       &paint_buffer_y);
+      if (! paint_buffer)
+        continue;
+
+      op = gimp_multi_stroke_get_operation (mstroke, paint_core,
+                                            paint_buffer, i);
+      paint_appl_mode = paint_options->application_mode;
+
+      grad_point = gimp_dynamics_get_linear_value (dynamics,
+                                                   GIMP_DYNAMICS_OUTPUT_COLOR,
+                                                   coords,
+                                                   paint_options,
+                                                   fade_point);
+
+      if (gimp_paint_options_get_gradient_color (paint_options, image,
+                                                 grad_point,
+                                                 paint_core->pixel_dist,
+                                                 &gradient_color))
+        {
+          /* optionally take the color from the current gradient */
 
-      GeglColor *color;
+          GeglColor *color;
 
-      opacity *= gradient_color.a;
-      gimp_rgb_set_alpha (&gradient_color, GIMP_OPACITY_OPAQUE);
+          opacity *= gradient_color.a;
+          gimp_rgb_set_alpha (&gradient_color, GIMP_OPACITY_OPAQUE);
 
-      color = gimp_gegl_color_new (&gradient_color);
+          color = gimp_gegl_color_new (&gradient_color);
 
-      gegl_buffer_set_color (paint_buffer, NULL, color);
-      g_object_unref (color);
+          gegl_buffer_set_color (paint_buffer, NULL, color);
+          g_object_unref (color);
 
-      paint_appl_mode = GIMP_PAINT_INCREMENTAL;
-    }
-  else if (brush_core->brush && gimp_brush_get_pixmap (brush_core->brush))
-    {
-      /* otherwise check if the brush has a pixmap and use that to
-       * color the area
-       */
-      gimp_brush_core_color_area_with_pixmap (brush_core, drawable,
-                                              coords,
-                                              paint_buffer,
-                                              paint_buffer_x,
-                                              paint_buffer_y,
-                                              gimp_paint_options_get_brush_mode (paint_options));
-
-      paint_appl_mode = GIMP_PAINT_INCREMENTAL;
-    }
-  else
-    {
-      /* otherwise fill the area with the foreground color */
+          paint_appl_mode = GIMP_PAINT_INCREMENTAL;
+        }
+      else if (brush_core->brush && gimp_brush_get_pixmap (brush_core->brush))
+        {
+          /* otherwise check if the brush has a pixmap and use that to
+           * color the area
+           */
+          gimp_brush_core_color_area_with_pixmap (brush_core, drawable,
+                                                  coords, op,
+                                                  paint_buffer,
+                                                  paint_buffer_x,
+                                                  paint_buffer_y,
+                                                  gimp_paint_options_get_brush_mode (paint_options));
+
+          paint_appl_mode = GIMP_PAINT_INCREMENTAL;
+        }
+      else
+        {
+          /* otherwise fill the area with the foreground color */
 
-      GimpRGB    foreground;
-      GeglColor *color;
+          GimpRGB    foreground;
+          GeglColor *color;
 
-      gimp_context_get_foreground (context, &foreground);
-      color = gimp_gegl_color_new (&foreground);
+          gimp_context_get_foreground (context, &foreground);
+          color = gimp_gegl_color_new (&foreground);
 
-      gegl_buffer_set_color (paint_buffer, NULL, color);
-      g_object_unref (color);
-    }
+          gegl_buffer_set_color (paint_buffer, NULL, color);
+          g_object_unref (color);
+        }
 
-  if (gimp_dynamics_is_output_enabled (dynamics, GIMP_DYNAMICS_OUTPUT_FORCE))
-    force = gimp_dynamics_get_linear_value (dynamics,
-                                            GIMP_DYNAMICS_OUTPUT_FORCE,
-                                            coords,
-                                            paint_options,
-                                            fade_point);
-  else
-    force = paint_options->brush_force;
-
-  /* finally, let the brush core paste the colored area on the canvas */
-  gimp_brush_core_paste_canvas (brush_core, drawable,
-                                coords,
-                                MIN (opacity, GIMP_OPACITY_OPAQUE),
-                                gimp_context_get_opacity (context),
-                                gimp_context_get_paint_mode (context),
-                                gimp_paint_options_get_brush_mode (paint_options),
-                                force,
-                                paint_appl_mode);
+      if (gimp_dynamics_is_output_enabled (dynamics, GIMP_DYNAMICS_OUTPUT_FORCE))
+        force = gimp_dynamics_get_linear_value (dynamics,
+                                                GIMP_DYNAMICS_OUTPUT_FORCE,
+                                                coords,
+                                                paint_options,
+                                                fade_point);
+      else
+        force = paint_options->brush_force;
+
+      /* finally, let the brush core paste the colored area on the canvas */
+      gimp_brush_core_paste_canvas (brush_core, drawable,
+                                    coords,
+                                    MIN (opacity, GIMP_OPACITY_OPAQUE),
+                                    gimp_context_get_opacity (context),
+                                    gimp_context_get_paint_mode (context),
+                                    gimp_paint_options_get_brush_mode (paint_options),
+                                    force,
+                                    paint_appl_mode, op);
+    }
 }
diff --git a/app/paint/gimppaintbrush.h b/app/paint/gimppaintbrush.h
index 12756fc..fe3f571 100644
--- a/app/paint/gimppaintbrush.h
+++ b/app/paint/gimppaintbrush.h
@@ -54,7 +54,7 @@ GType   gimp_paintbrush_get_type (void) G_GNUC_CONST;
 void    _gimp_paintbrush_motion  (GimpPaintCore             *paint_core,
                                   GimpDrawable              *drawable,
                                   GimpPaintOptions          *paint_options,
-                                  const GimpCoords          *coords,
+                                  GimpMultiStroke           *mstroke,
                                   gdouble                    opacity);
 
 
diff --git a/app/paint/gimppaintcore-loops.c b/app/paint/gimppaintcore-loops.c
index 0a35704..08345bd 100644
--- a/app/paint/gimppaintcore-loops.c
+++ b/app/paint/gimppaintcore-loops.c
@@ -39,14 +39,20 @@ combine_paint_mask_to_canvas_mask (const GimpTempBuf *paint_mask,
   GeglRectangle roi;
   GeglBufferIterator *iter;
 
-  const gint mask_stride       = gimp_temp_buf_get_width (paint_mask);
-  const gint mask_start_offset = mask_y_offset * mask_stride + mask_x_offset;
-  const Babl *mask_format      = gimp_temp_buf_get_format (paint_mask);
+  const gint   mask_stride       = gimp_temp_buf_get_width (paint_mask);
+  const gint   mask_start_offset = mask_y_offset * mask_stride + mask_x_offset;
+  const Babl  *mask_format       = gimp_temp_buf_get_format (paint_mask);
+  GimpTempBuf *modified_mask     = gimp_temp_buf_copy (paint_mask);
+  gint         width;
+  gint         height;
+
+  width  = gimp_temp_buf_get_width (modified_mask);
+  height = gimp_temp_buf_get_height (modified_mask);
 
   roi.x = x_offset;
   roi.y = y_offset;
-  roi.width  = gimp_temp_buf_get_width (paint_mask) - mask_x_offset;
-  roi.height = gimp_temp_buf_get_height (paint_mask) - mask_y_offset;
+  roi.width  = width - mask_x_offset;
+  roi.height = height - mask_y_offset;
 
   iter = gegl_buffer_iterator_new (canvas_buffer, &roi, 0,
                                    babl_format ("Y float"),
@@ -56,7 +62,7 @@ combine_paint_mask_to_canvas_mask (const GimpTempBuf *paint_mask,
     {
       if (mask_format == babl_format ("Y u8"))
         {
-          const guint8 *mask_data = (const guint8 *) gimp_temp_buf_get_data (paint_mask);
+          const guint8 *mask_data = (const guint8 *) gimp_temp_buf_get_data (modified_mask);
           mask_data += mask_start_offset;
 
           while (gegl_buffer_iterator_next (iter))
@@ -81,7 +87,7 @@ combine_paint_mask_to_canvas_mask (const GimpTempBuf *paint_mask,
         }
       else if (mask_format == babl_format ("Y float"))
         {
-          const gfloat *mask_data = (const gfloat *) gimp_temp_buf_get_data (paint_mask);
+          const gfloat *mask_data = (const gfloat *) gimp_temp_buf_get_data (modified_mask);
           mask_data += mask_start_offset;
 
           while (gegl_buffer_iterator_next (iter))
@@ -113,7 +119,7 @@ combine_paint_mask_to_canvas_mask (const GimpTempBuf *paint_mask,
     {
       if (mask_format == babl_format ("Y u8"))
         {
-          const guint8 *mask_data = (const guint8 *) gimp_temp_buf_get_data (paint_mask);
+          const guint8 *mask_data = (const guint8 *) gimp_temp_buf_get_data (modified_mask);
           mask_data += mask_start_offset;
 
           while (gegl_buffer_iterator_next (iter))
@@ -139,7 +145,7 @@ combine_paint_mask_to_canvas_mask (const GimpTempBuf *paint_mask,
         }
       else if (mask_format == babl_format ("Y float"))
         {
-          const gfloat *mask_data = (const gfloat *) gimp_temp_buf_get_data (paint_mask);
+          const gfloat *mask_data = (const gfloat *) gimp_temp_buf_get_data (modified_mask);
           mask_data += mask_start_offset;
 
           while (gegl_buffer_iterator_next (iter))
@@ -168,6 +174,7 @@ combine_paint_mask_to_canvas_mask (const GimpTempBuf *paint_mask,
           g_warning("Mask format not supported: %s", babl_get_name (mask_format));
         }
     }
+  gimp_temp_buf_unref (modified_mask);
 }
 
 void
diff --git a/app/paint/gimppaintcore-loops.h b/app/paint/gimppaintcore-loops.h
index e8ceafa..cc27d4f 100644
--- a/app/paint/gimppaintcore-loops.h
+++ b/app/paint/gimppaintcore-loops.h
@@ -28,13 +28,13 @@ void canvas_buffer_to_paint_buf_alpha   (GimpTempBuf  *paint_buf,
                                          GeglBuffer   *canvas_buffer,
                                          gint          x_offset,
                                          gint          y_offset);
-
 void paint_mask_to_paint_buffer         (const GimpTempBuf  *paint_mask,
                                          gint                mask_x_offset,
                                          gint                mask_y_offset,
                                          GimpTempBuf        *paint_buf,
                                          gfloat              paint_opacity);
 
+
 void do_layer_blend                     (GeglBuffer  *src_buffer,
                                          GeglBuffer  *dst_buffer,
                                          GimpTempBuf *paint_buf,
diff --git a/app/paint/gimppaintcore.c b/app/paint/gimppaintcore.c
index ccca4b4..e6b6e72 100644
--- a/app/paint/gimppaintcore.c
+++ b/app/paint/gimppaintcore.c
@@ -37,11 +37,14 @@
 #include "core/gimp-utils.h"
 #include "core/gimpchannel.h"
 #include "core/gimpimage.h"
+#include "core/gimpimage-guides.h"
 #include "core/gimpimage-undo.h"
 #include "core/gimppickable.h"
 #include "core/gimpprojection.h"
 #include "core/gimptempbuf.h"
 
+#include "gimpmultistroke.h"
+#include "gimpmultistroke-info.h"
 #include "gimppaintcore.h"
 #include "gimppaintcoreundo.h"
 #include "gimppaintcore-loops.h"
@@ -86,7 +89,7 @@ static gboolean  gimp_paint_core_real_pre_paint      (GimpPaintCore    *core,
 static void      gimp_paint_core_real_paint          (GimpPaintCore    *core,
                                                       GimpDrawable     *drawable,
                                                       GimpPaintOptions *options,
-                                                      const GimpCoords *coords,
+                                                      GimpMultiStroke  *mstroke,
                                                       GimpPaintState    paint_state,
                                                       guint32           time);
 static void      gimp_paint_core_real_post_paint     (GimpPaintCore    *core,
@@ -231,7 +234,7 @@ static void
 gimp_paint_core_real_paint (GimpPaintCore    *core,
                             GimpDrawable     *drawable,
                             GimpPaintOptions *paint_options,
-                            const GimpCoords *coords,
+                            GimpMultiStroke  *mstroke,
                             GimpPaintState    paint_state,
                             guint32           time)
 {
@@ -304,6 +307,12 @@ gimp_paint_core_paint (GimpPaintCore    *core,
                              paint_options,
                              paint_state, time))
     {
+      GimpMultiStroke *mstroke;
+      GimpImage       *image;
+      GimpItem        *item;
+
+      item  = GIMP_ITEM (drawable);
+      image = gimp_item_get_image (item);
 
       if (paint_state == GIMP_PAINT_STATE_MOTION)
         {
@@ -312,10 +321,17 @@ gimp_paint_core_paint (GimpPaintCore    *core,
           core->last_paint.y = core->cur_coords.y;
         }
 
+      if (gimp_image_get_selected_multi_stroke (image))
+        mstroke = g_object_ref (gimp_image_get_selected_multi_stroke (image));
+      else
+        mstroke = g_object_ref (gimp_image_get_single_stroke (image));
+      gimp_multi_stroke_set_origin (mstroke, drawable, &core->cur_coords);
+
       core_class->paint (core, drawable,
                          paint_options,
-                         &core->cur_coords,
+                         mstroke,
                          paint_state, time);
+      g_object_unref (mstroke);
 
       core_class->post_paint (core, drawable,
                               paint_options,
@@ -818,8 +834,9 @@ gimp_paint_core_paste (GimpPaintCore            *core,
            */
           if (paint_mask != NULL)
             {
+              GimpTempBuf *modified_mask     = gimp_temp_buf_copy (paint_mask);
               GeglBuffer *paint_mask_buffer =
-                gimp_temp_buf_create_buffer ((GimpTempBuf *) paint_mask);
+                gimp_temp_buf_create_buffer ((GimpTempBuf *) modified_mask);
 
               gimp_gegl_combine_mask_weird (paint_mask_buffer,
                                             GEGL_RECTANGLE (paint_mask_offset_x,
@@ -833,6 +850,7 @@ gimp_paint_core_paste (GimpPaintCore            *core,
                                             GIMP_IS_AIRBRUSH (core));
 
               g_object_unref (paint_mask_buffer);
+              gimp_temp_buf_unref (modified_mask);
             }
 
           gimp_gegl_apply_mask (core->canvas_buffer,
diff --git a/app/paint/gimppaintcore.h b/app/paint/gimppaintcore.h
index b6b7d06..2682c7e 100644
--- a/app/paint/gimppaintcore.h
+++ b/app/paint/gimppaintcore.h
@@ -93,7 +93,7 @@ struct _GimpPaintCoreClass
   void         (* paint)            (GimpPaintCore    *core,
                                      GimpDrawable     *drawable,
                                      GimpPaintOptions *paint_options,
-                                     const GimpCoords *coords,
+                                     GimpMultiStroke  *mstroke,
                                      GimpPaintState    paint_state,
                                      guint32           time);
   void         (* post_paint)       (GimpPaintCore    *core,
diff --git a/app/paint/gimppaintoptions.c b/app/paint/gimppaintoptions.c
index de7caf3..32f0956 100644
--- a/app/paint/gimppaintoptions.c
+++ b/app/paint/gimppaintoptions.c
@@ -34,6 +34,9 @@
 #include "core/gimpgradient.h"
 #include "core/gimppaintinfo.h"
 
+#include "gimpmultistroke.h"
+#include "gimpmultistroke-info.h"
+
 #include "gimppaintoptions.h"
 
 #include "gimp-intl.h"
@@ -127,7 +130,9 @@ enum
   PROP_BRUSH_LINK_ASPECT_RATIO,
   PROP_BRUSH_LINK_ANGLE,
   PROP_BRUSH_LINK_SPACING,
-  PROP_BRUSH_LINK_HARDNESS
+  PROP_BRUSH_LINK_HARDNESS,
+
+  PROP_MULTI_STROKE
 };
 
 
@@ -365,6 +370,11 @@ gimp_paint_options_class_init (GimpPaintOptionsClass *klass)
                                     * less than velcoty results in numeric
                                     * instablility */
                                    GIMP_PARAM_STATIC_STRINGS);
+
+  GIMP_CONFIG_INSTALL_PROP_INT (object_class, PROP_MULTI_STROKE,
+                                "multi-stroke", _("Multi Stroke transformation"),
+                                G_TYPE_NONE, INT_MAX, G_TYPE_NONE,
+                                GIMP_PARAM_STATIC_STRINGS);
 }
 
 static void
@@ -422,12 +432,15 @@ gimp_paint_options_set_property (GObject      *object,
                                  const GValue *value,
                                  GParamSpec   *pspec)
 {
+  GimpContext          *context;
   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;
 
+  context = gimp_get_user_context (GIMP_CONTEXT (object)->gimp);
+
   switch (property_id)
     {
     case PROP_PAINT_INFO:
@@ -570,6 +583,28 @@ gimp_paint_options_set_property (GObject      *object,
       smoothing_options->smoothing_factor = g_value_get_double (value);
       break;
 
+    case PROP_MULTI_STROKE:
+      options->multi_stroke = g_value_get_int (value);
+      if (context && context->image)
+        {
+          if (! gimp_image_select_multi_stroke (context->image,
+                                                options->multi_stroke))
+            {
+              GimpMultiStroke *mstroke;
+
+              mstroke  = gimp_multi_stroke_new (options->multi_stroke,
+                                                context->image);
+              gimp_image_add_multi_stroke (context->image,
+                                           GIMP_MULTI_STROKE (mstroke));
+              g_object_unref (mstroke);
+            }
+        }
+      else
+        {
+          options->multi_stroke = G_TYPE_NONE;
+        }
+      break;
+
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
       break;
@@ -730,6 +765,10 @@ gimp_paint_options_get_property (GObject    *object,
       g_value_set_double (value, smoothing_options->smoothing_factor);
       break;
 
+    case PROP_MULTI_STROKE:
+      g_value_set_int (value, options->multi_stroke);
+      break;
+
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
       break;
@@ -1112,3 +1151,34 @@ gimp_paint_options_copy_gradient_props (GimpPaintOptions *src,
                 "gradient-reverse", gradient_reverse,
                 NULL);
 }
+
+/**
+ * Update the paint options of the current tool according to image settings.
+ * Multi-Stroke settings are actually attached to an image, and therefore
+ * depends on the current context image.
+ */
+void
+gimp_paint_options_set_mstroke_props (GimpPaintOptions *src,
+                                      GimpPaintOptions *dest)
+{
+  GimpContext *context;
+  GimpImage   *image;
+
+  g_return_if_fail (GIMP_IS_PAINT_OPTIONS (src));
+  g_return_if_fail (GIMP_IS_PAINT_OPTIONS (dest));
+
+  context = gimp_get_user_context (GIMP_CONTEXT (dest)->gimp);
+  image   = context->image;
+
+  if (image)
+    {
+      GimpMultiStroke  *mstroke;
+
+      mstroke = gimp_image_get_selected_multi_stroke (image);
+
+      g_object_set (dest,
+                    "multi-stroke",
+                    mstroke ? mstroke->type : G_TYPE_NONE,
+                    NULL);
+    }
+}
diff --git a/app/paint/gimppaintoptions.h b/app/paint/gimppaintoptions.h
index c4ec593..e38aa30 100644
--- a/app/paint/gimppaintoptions.h
+++ b/app/paint/gimppaintoptions.h
@@ -116,6 +116,8 @@ struct _GimpPaintOptions
   GimpViewSize              pattern_view_size;
   GimpViewType              gradient_view_type;
   GimpViewSize              gradient_view_size;
+
+  GType                     multi_stroke;
 };
 
 struct _GimpPaintOptionsClass
@@ -163,5 +165,7 @@ void    gimp_paint_options_copy_dynamics_props (GimpPaintOptions *src,
 void    gimp_paint_options_copy_gradient_props (GimpPaintOptions *src,
                                                 GimpPaintOptions *dest);
 
+void    gimp_paint_options_set_mstroke_props   (GimpPaintOptions *src,
+                                                GimpPaintOptions *dest);
 
 #endif  /*  __GIMP_PAINT_OPTIONS_H__  */
diff --git a/app/paint/gimpperspectiveclone.c b/app/paint/gimpperspectiveclone.c
index 6459eac..1984a62 100644
--- a/app/paint/gimpperspectiveclone.c
+++ b/app/paint/gimpperspectiveclone.c
@@ -39,6 +39,7 @@
 #include "core/gimppattern.h"
 #include "core/gimppickable.h"
 
+#include "gimpmultistroke.h"
 #include "gimpperspectiveclone.h"
 #include "gimpperspectivecloneoptions.h"
 
@@ -48,7 +49,7 @@
 static void         gimp_perspective_clone_paint      (GimpPaintCore     *paint_core,
                                                        GimpDrawable      *drawable,
                                                        GimpPaintOptions  *paint_options,
-                                                       const GimpCoords  *coords,
+                                                       GimpMultiStroke   *mstroke,
                                                        GimpPaintState     paint_state,
                                                        guint32            time);
 
@@ -120,7 +121,7 @@ static void
 gimp_perspective_clone_paint (GimpPaintCore    *paint_core,
                               GimpDrawable     *drawable,
                               GimpPaintOptions *paint_options,
-                              const GimpCoords *coords,
+                              GimpMultiStroke  *mstroke,
                               GimpPaintState    paint_state,
                               guint32           time)
 {
@@ -129,6 +130,10 @@ gimp_perspective_clone_paint (GimpPaintCore    *paint_core,
   GimpContext          *context       = GIMP_CONTEXT (paint_options);
   GimpCloneOptions     *clone_options = GIMP_CLONE_OPTIONS (paint_options);
   GimpSourceOptions    *options       = GIMP_SOURCE_OPTIONS (paint_options);
+  const GimpCoords     *coords;
+
+  /* The source is based on the original stroke */
+  coords = gimp_multi_stroke_get_origin (mstroke);
 
   switch (paint_state)
     {
@@ -282,38 +287,46 @@ gimp_perspective_clone_paint (GimpPaintCore    *paint_core,
         {
           /*  otherwise, update the target  */
 
-          gint dest_x;
-          gint dest_y;
-
-          dest_x = coords->x;
-          dest_y = coords->y;
+          gint        dest_x;
+          gint        dest_y;
+          gint        nstrokes;
+          gint        i;
 
-          if (options->align_mode == GIMP_SOURCE_ALIGN_REGISTERED)
-            {
-              source_core->offset_x = 0;
-              source_core->offset_y = 0;
-            }
-          else if (options->align_mode == GIMP_SOURCE_ALIGN_FIXED)
+          nstrokes = gimp_multi_stroke_get_size (mstroke);
+          for (i = 0; i < nstrokes; i++)
             {
-              source_core->offset_x = source_core->src_x - dest_x;
-              source_core->offset_y = source_core->src_y - dest_y;
-            }
-          else if (source_core->first_stroke)
-            {
-              source_core->offset_x = source_core->src_x - dest_x;
-              source_core->offset_y = source_core->src_y - dest_y;
-
-              /* get destination coordinates in front view perspective */
-              gimp_matrix3_transform_point (&clone->transform_inv,
-                                            dest_x,
-                                            dest_y,
-                                            &clone->dest_x_fv,
-                                            &clone->dest_y_fv);
-
-              source_core->first_stroke = FALSE;
+              coords = gimp_multi_stroke_get_coords (mstroke, i);
+
+              dest_x = coords->x;
+              dest_y = coords->y;
+
+              if (options->align_mode == GIMP_SOURCE_ALIGN_REGISTERED)
+                {
+                  source_core->offset_x = 0;
+                  source_core->offset_y = 0;
+                }
+              else if (options->align_mode == GIMP_SOURCE_ALIGN_FIXED)
+                {
+                  source_core->offset_x = source_core->src_x - dest_x;
+                  source_core->offset_y = source_core->src_y - dest_y;
+                }
+              else if (source_core->first_stroke)
+                {
+                  source_core->offset_x = source_core->src_x - dest_x;
+                  source_core->offset_y = source_core->src_y - dest_y;
+
+                  /* get destination coordinates in front view perspective */
+                  gimp_matrix3_transform_point (&clone->transform_inv,
+                                                dest_x,
+                                                dest_y,
+                                                &clone->dest_x_fv,
+                                                &clone->dest_y_fv);
+
+                  source_core->first_stroke = FALSE;
+                }
             }
 
-          gimp_source_core_motion (source_core, drawable, paint_options, coords);
+          gimp_source_core_motion (source_core, drawable, paint_options, mstroke);
         }
       break;
 
diff --git a/app/paint/gimpsmudge.c b/app/paint/gimpsmudge.c
index 055042a..2608857 100644
--- a/app/paint/gimpsmudge.c
+++ b/app/paint/gimpsmudge.c
@@ -34,6 +34,8 @@
 #include "core/gimppickable.h"
 #include "core/gimptempbuf.h"
 
+#include "gimpmultistroke.h"
+
 #include "gimpsmudge.h"
 #include "gimpsmudgeoptions.h"
 
@@ -45,20 +47,21 @@ static void       gimp_smudge_finalize     (GObject          *object);
 static void       gimp_smudge_paint        (GimpPaintCore    *paint_core,
                                             GimpDrawable     *drawable,
                                             GimpPaintOptions *paint_options,
-                                            const GimpCoords *coords,
+                                            GimpMultiStroke  *mstroke,
                                             GimpPaintState    paint_state,
                                             guint32           time);
 static gboolean   gimp_smudge_start        (GimpPaintCore    *paint_core,
                                             GimpDrawable     *drawable,
                                             GimpPaintOptions *paint_options,
-                                            const GimpCoords *coords);
+                                            GimpMultiStroke  *mstroke);
 static void       gimp_smudge_motion       (GimpPaintCore    *paint_core,
                                             GimpDrawable     *drawable,
                                             GimpPaintOptions *paint_options,
-                                            const GimpCoords *coords);
+                                            GimpMultiStroke  *mstroke);
 
 static void       gimp_smudge_accumulator_coords (GimpPaintCore    *paint_core,
                                                   const GimpCoords *coords,
+                                                  gint              stroke,
                                                   gint             *x,
                                                   gint             *y);
 
@@ -110,10 +113,16 @@ gimp_smudge_finalize (GObject *object)
 {
   GimpSmudge *smudge = GIMP_SMUDGE (object);
 
-  if (smudge->accum_buffer)
+  if (smudge->accum_buffers)
     {
-      g_object_unref (smudge->accum_buffer);
-      smudge->accum_buffer = NULL;
+      GList *iter;
+
+      for (iter = smudge->accum_buffers; iter; iter = g_list_next (iter))
+        {
+          if (iter->data)
+            g_object_unref (iter->data);
+          smudge->accum_buffers = NULL;
+        }
     }
 
   G_OBJECT_CLASS (parent_class)->finalize (object);
@@ -123,7 +132,7 @@ static void
 gimp_smudge_paint (GimpPaintCore    *paint_core,
                    GimpDrawable     *drawable,
                    GimpPaintOptions *paint_options,
-                   const GimpCoords *coords,
+                   GimpMultiStroke  *mstroke,
                    GimpPaintState    paint_state,
                    guint32           time)
 {
@@ -135,17 +144,23 @@ gimp_smudge_paint (GimpPaintCore    *paint_core,
       /* initialization fails if the user starts outside the drawable */
       if (! smudge->initialized)
         smudge->initialized = gimp_smudge_start (paint_core, drawable,
-                                                 paint_options, coords);
+                                                 paint_options, mstroke);
 
       if (smudge->initialized)
-        gimp_smudge_motion (paint_core, drawable, paint_options, coords);
+        gimp_smudge_motion (paint_core, drawable, paint_options, mstroke);
       break;
 
     case GIMP_PAINT_STATE_FINISH:
-      if (smudge->accum_buffer)
+      if (smudge->accum_buffers)
         {
-          g_object_unref (smudge->accum_buffer);
-          smudge->accum_buffer = NULL;
+          GList *iter;
+
+          for  (iter = smudge->accum_buffers; iter; iter = g_list_next (iter))
+            {
+              if (iter->data)
+                g_object_unref (iter->data);
+              smudge->accum_buffers = NULL;
+            }
         }
       smudge->initialized = FALSE;
       break;
@@ -159,71 +174,83 @@ static gboolean
 gimp_smudge_start (GimpPaintCore    *paint_core,
                    GimpDrawable     *drawable,
                    GimpPaintOptions *paint_options,
-                   const GimpCoords *coords)
+                   GimpMultiStroke  *mstroke)
 {
   GimpSmudge *smudge = GIMP_SMUDGE (paint_core);
   GeglBuffer *paint_buffer;
+  GimpCoords *coords;
   gint        paint_buffer_x;
   gint        paint_buffer_y;
   gint        accum_size;
+  gint        nstrokes;
+  gint        i;
   gint        x, y;
 
-  paint_buffer = gimp_paint_core_get_paint_buffer (paint_core, drawable,
-                                                   paint_options, coords,
-                                                   &paint_buffer_x,
-                                                   &paint_buffer_y);
-  if (! paint_buffer)
-    return FALSE;
-
-  gimp_smudge_accumulator_size (paint_options, coords, &accum_size);
-
-  /*  Allocate the accumulation buffer */
-  smudge->accum_buffer = gegl_buffer_new (GEGL_RECTANGLE (0, 0,
-                                                          accum_size,
-                                                          accum_size),
-                                          babl_format ("RGBA float"));
-
-  /*  adjust the x and y coordinates to the upper left corner of the
-   *  accumulator
-   */
-  gimp_smudge_accumulator_coords (paint_core, coords, &x, &y);
-
-  /*  If clipped, prefill the smudge buffer with the color at the
-   *  brush position.
-   */
-  if (x != paint_buffer_x ||
-      y != paint_buffer_y ||
-      accum_size != gegl_buffer_get_width  (paint_buffer) ||
-      accum_size != gegl_buffer_get_height (paint_buffer))
+  nstrokes = gimp_multi_stroke_get_size (mstroke);
+  for (i = 0; i < nstrokes; i++)
     {
-      GimpRGB    pixel;
-      GeglColor *color;
-
-      gimp_pickable_get_color_at (GIMP_PICKABLE (drawable),
-                                  CLAMP ((gint) coords->x,
-                                         0,
-                                         gimp_item_get_width (GIMP_ITEM (drawable)) - 1),
-                                  CLAMP ((gint) coords->y,
-                                         0,
-                                         gimp_item_get_height (GIMP_ITEM (drawable)) - 1),
-                                  &pixel);
-
-      color = gimp_gegl_color_new (&pixel);
-      gegl_buffer_set_color (smudge->accum_buffer, NULL, color);
-      g_object_unref (color);
-    }
+      GeglBuffer *accum_buffer;
+
+      coords = gimp_multi_stroke_get_coords (mstroke, i);
+      paint_buffer = gimp_paint_core_get_paint_buffer (paint_core, drawable,
+                                                       paint_options, coords,
+                                                       &paint_buffer_x,
+                                                       &paint_buffer_y);
+      if (! paint_buffer)
+        return FALSE;
+
+      gimp_smudge_accumulator_size (paint_options, coords, &accum_size);
+
+      /*  Allocate the accumulation buffer */
+      accum_buffer = gegl_buffer_new (GEGL_RECTANGLE (0, 0,
+                                                      accum_size,
+                                                      accum_size),
+                                      babl_format ("RGBA float"));
+      smudge->accum_buffers = g_list_prepend (smudge->accum_buffers,
+                                              accum_buffer);
+
+      /*  adjust the x and y coordinates to the upper left corner of the
+       *  accumulator
+       */
+      gimp_smudge_accumulator_coords (paint_core, coords, i, &x, &y);
+
+      /*  If clipped, prefill the smudge buffer with the color at the
+       *  brush position.
+       */
+      if (x != paint_buffer_x ||
+          y != paint_buffer_y ||
+          accum_size != gegl_buffer_get_width  (paint_buffer) ||
+          accum_size != gegl_buffer_get_height (paint_buffer))
+        {
+          GimpRGB    pixel;
+          GeglColor *color;
+
+          gimp_pickable_get_color_at (GIMP_PICKABLE (drawable),
+                                      CLAMP ((gint) coords->x,
+                                             0,
+                                             gimp_item_get_width (GIMP_ITEM (drawable)) - 1),
+                                      CLAMP ((gint) coords->y,
+                                             0,
+                                             gimp_item_get_height (GIMP_ITEM (drawable)) - 1),
+                                      &pixel);
+
+          color = gimp_gegl_color_new (&pixel);
+          gegl_buffer_set_color (accum_buffer, NULL, color);
+          g_object_unref (color);
+        }
 
-  /* copy the region under the original painthit. */
-  gegl_buffer_copy (gimp_drawable_get_buffer (drawable),
-                    GEGL_RECTANGLE (paint_buffer_x,
-                                    paint_buffer_y,
-                                    gegl_buffer_get_width  (paint_buffer),
-                                    gegl_buffer_get_height (paint_buffer)),
-                    GEGL_ABYSS_NONE,
-                    smudge->accum_buffer,
-                    GEGL_RECTANGLE (paint_buffer_x - x,
-                                    paint_buffer_y - y,
-                                    0, 0));
+      /* copy the region under the original painthit. */
+      gegl_buffer_copy (gimp_drawable_get_buffer (drawable),
+                        GEGL_RECTANGLE (paint_buffer_x,
+                                        paint_buffer_y,
+                                        gegl_buffer_get_width  (paint_buffer),
+                                        gegl_buffer_get_height (paint_buffer)),
+                        GEGL_ABYSS_NONE,
+                        accum_buffer,
+                        GEGL_RECTANGLE (paint_buffer_x - x,
+                                        paint_buffer_y - y,
+                                        0, 0));
+    }
 
   return TRUE;
 }
@@ -232,7 +259,7 @@ static void
 gimp_smudge_motion (GimpPaintCore    *paint_core,
                     GimpDrawable     *drawable,
                     GimpPaintOptions *paint_options,
-                    const GimpCoords *coords)
+                    GimpMultiStroke  *mstroke)
 {
   GimpSmudge        *smudge   = GIMP_SMUDGE (paint_core);
   GimpSmudgeOptions *options  = GIMP_SMUDGE_OPTIONS (paint_options);
@@ -250,102 +277,120 @@ gimp_smudge_motion (GimpPaintCore    *paint_core,
   gdouble            dynamic_rate;
   gint               x, y;
   gdouble            force;
+  GeglBuffer        *accum_buffer;
+  GimpCoords        *coords;
+  GeglNode          *op;
+  gint               nstrokes;
+  gint               i;
 
   fade_point = gimp_paint_options_get_fade (paint_options, image,
                                             paint_core->pixel_dist);
 
-  opacity = gimp_dynamics_get_linear_value (dynamics,
-                                            GIMP_DYNAMICS_OUTPUT_OPACITY,
-                                            coords,
-                                            paint_options,
-                                            fade_point);
-  if (opacity == 0.0)
-    return;
-
-  paint_buffer = gimp_paint_core_get_paint_buffer (paint_core, drawable,
-                                                   paint_options, coords,
-                                                   &paint_buffer_x,
-                                                   &paint_buffer_y);
-  if (! paint_buffer)
-    return;
-
-  paint_buffer_width  = gegl_buffer_get_width  (paint_buffer);
-  paint_buffer_height = gegl_buffer_get_height (paint_buffer);
-
-  /*  Get the unclipped acumulator coordinates  */
-  gimp_smudge_accumulator_coords (paint_core, coords, &x, &y);
-
-  /* Enable dynamic rate */
-  dynamic_rate = gimp_dynamics_get_linear_value (dynamics,
-                                                 GIMP_DYNAMICS_OUTPUT_RATE,
-                                                 coords,
-                                                 paint_options,
-                                                 fade_point);
-
-  rate = (options->rate / 100.0) * dynamic_rate;
-
-  /*  Smudge uses the buffer Accum.
-   *  For each successive painthit Accum is built like this
-   *    Accum =  rate*Accum  + (1-rate)*I.
-   *  where I is the pixels under the current painthit.
-   *  Then the paint area (paint_area) is built as
-   *    (Accum,1) (if no alpha),
-   */
-
-  gimp_gegl_smudge_blend (smudge->accum_buffer,
-                          GEGL_RECTANGLE (paint_buffer_x - x,
-                                          paint_buffer_y - y,
-                                          paint_buffer_width,
-                                          paint_buffer_height),
-                          gimp_drawable_get_buffer (drawable),
-                          GEGL_RECTANGLE (paint_buffer_x,
-                                          paint_buffer_y,
-                                          paint_buffer_width,
-                                          paint_buffer_height),
-                          smudge->accum_buffer,
-                          GEGL_RECTANGLE (paint_buffer_x - x,
-                                          paint_buffer_y - y,
-                                          paint_buffer_width,
-                                          paint_buffer_height),
-                          rate);
-
-  gegl_buffer_copy (smudge->accum_buffer,
-                    GEGL_RECTANGLE (paint_buffer_x - x,
-                                    paint_buffer_y - y,
-                                    paint_buffer_width,
-                                    paint_buffer_height),
-                    GEGL_ABYSS_NONE,
-                    paint_buffer,
-                    GEGL_RECTANGLE (0, 0, 0, 0));
-
-  if (gimp_dynamics_is_output_enabled (dynamics, GIMP_DYNAMICS_OUTPUT_FORCE))
-    force = gimp_dynamics_get_linear_value (dynamics,
-                                            GIMP_DYNAMICS_OUTPUT_FORCE,
-                                            coords,
-                                            paint_options,
-                                            fade_point);
-  else
-    force = paint_options->brush_force;
-
-  gimp_brush_core_replace_canvas (GIMP_BRUSH_CORE (paint_core), drawable,
-                                  coords,
-                                  MIN (opacity, GIMP_OPACITY_OPAQUE),
-                                  gimp_context_get_opacity (context),
-                                  gimp_paint_options_get_brush_mode (paint_options),
-                                  force,
-                                  GIMP_PAINT_INCREMENTAL);
+  nstrokes = gimp_multi_stroke_get_size (mstroke);
+  for (i = 0; i < nstrokes; i++)
+    {
+      coords = gimp_multi_stroke_get_coords (mstroke, i);
+
+      opacity = gimp_dynamics_get_linear_value (dynamics,
+                                                GIMP_DYNAMICS_OUTPUT_OPACITY,
+                                                coords,
+                                                paint_options,
+                                                fade_point);
+      if (opacity == 0.0)
+        continue;
+
+      paint_buffer = gimp_paint_core_get_paint_buffer (paint_core, drawable,
+                                                       paint_options, coords,
+                                                       &paint_buffer_x,
+                                                       &paint_buffer_y);
+      if (! paint_buffer)
+        continue;
+
+      op = gimp_multi_stroke_get_operation (mstroke, paint_core,
+                                            paint_buffer, i);
+
+      paint_buffer_width  = gegl_buffer_get_width  (paint_buffer);
+      paint_buffer_height = gegl_buffer_get_height (paint_buffer);
+
+      /*  Get the unclipped acumulator coordinates  */
+      gimp_smudge_accumulator_coords (paint_core, coords, i, &x, &y);
+
+      /* Enable dynamic rate */
+      dynamic_rate = gimp_dynamics_get_linear_value (dynamics,
+                                                     GIMP_DYNAMICS_OUTPUT_RATE,
+                                                     coords,
+                                                     paint_options,
+                                                     fade_point);
+
+      rate = (options->rate / 100.0) * dynamic_rate;
+
+      /*  Smudge uses the buffer Accum.
+       *  For each successive painthit Accum is built like this
+       *    Accum =  rate*Accum  + (1-rate)*I.
+       *  where I is the pixels under the current painthit.
+       *  Then the paint area (paint_area) is built as
+       *    (Accum,1) (if no alpha),
+       */
+
+      accum_buffer = g_list_nth_data (smudge->accum_buffers, i);
+      gimp_gegl_smudge_blend (accum_buffer,
+                              GEGL_RECTANGLE (paint_buffer_x - x,
+                                              paint_buffer_y - y,
+                                              paint_buffer_width,
+                                              paint_buffer_height),
+                              gimp_drawable_get_buffer (drawable),
+                              GEGL_RECTANGLE (paint_buffer_x,
+                                              paint_buffer_y,
+                                              paint_buffer_width,
+                                              paint_buffer_height),
+                              accum_buffer,
+                              GEGL_RECTANGLE (paint_buffer_x - x,
+                                              paint_buffer_y - y,
+                                              paint_buffer_width,
+                                              paint_buffer_height),
+                              rate);
+
+      gegl_buffer_copy (accum_buffer,
+                        GEGL_RECTANGLE (paint_buffer_x - x,
+                                        paint_buffer_y - y,
+                                        paint_buffer_width,
+                                        paint_buffer_height),
+                        GEGL_ABYSS_NONE,
+                        paint_buffer,
+                        GEGL_RECTANGLE (0, 0, 0, 0));
+
+      if (gimp_dynamics_is_output_enabled (dynamics, GIMP_DYNAMICS_OUTPUT_FORCE))
+        force = gimp_dynamics_get_linear_value (dynamics,
+                                                GIMP_DYNAMICS_OUTPUT_FORCE,
+                                                coords,
+                                                paint_options,
+                                                fade_point);
+      else
+        force = paint_options->brush_force;
+
+      gimp_brush_core_replace_canvas (GIMP_BRUSH_CORE (paint_core), drawable,
+                                      coords,
+                                      MIN (opacity, GIMP_OPACITY_OPAQUE),
+                                      gimp_context_get_opacity (context),
+                                      gimp_paint_options_get_brush_mode (paint_options),
+                                      force,
+                                      GIMP_PAINT_INCREMENTAL, op);
+    }
 }
 
 static void
 gimp_smudge_accumulator_coords (GimpPaintCore    *paint_core,
                                 const GimpCoords *coords,
+                                gint              stroke,
                                 gint             *x,
                                 gint             *y)
 {
   GimpSmudge *smudge = GIMP_SMUDGE (paint_core);
+  GeglBuffer *accum_buffer;
 
-  *x = (gint) coords->x - gegl_buffer_get_width  (smudge->accum_buffer) / 2;
-  *y = (gint) coords->y - gegl_buffer_get_height (smudge->accum_buffer) / 2;
+  accum_buffer = g_list_nth_data (smudge->accum_buffers, stroke);
+  *x = (gint) coords->x - gegl_buffer_get_width  (accum_buffer) / 2;
+  *y = (gint) coords->y - gegl_buffer_get_height (accum_buffer) / 2;
 }
 
 static void
diff --git a/app/paint/gimpsmudge.h b/app/paint/gimpsmudge.h
index fe5d07e..881a0ed 100644
--- a/app/paint/gimpsmudge.h
+++ b/app/paint/gimpsmudge.h
@@ -37,7 +37,7 @@ struct _GimpSmudge
   GimpBrushCore  parent_instance;
 
   gboolean       initialized;
-  GeglBuffer    *accum_buffer;
+  GList         *accum_buffers;
 };
 
 struct _GimpSmudgeClass
diff --git a/app/paint/gimpsourcecore.c b/app/paint/gimpsourcecore.c
index 46a04fa..36d968c 100644
--- a/app/paint/gimpsourcecore.c
+++ b/app/paint/gimpsourcecore.c
@@ -33,6 +33,7 @@
 #include "core/gimpimage.h"
 #include "core/gimppickable.h"
 
+#include "gimpmultistroke.h"
 #include "gimpsourcecore.h"
 #include "gimpsourceoptions.h"
 
@@ -65,7 +66,7 @@ static gboolean gimp_source_core_start           (GimpPaintCore     *paint_core,
 static void     gimp_source_core_paint           (GimpPaintCore     *paint_core,
                                                   GimpDrawable      *drawable,
                                                   GimpPaintOptions  *paint_options,
-                                                  const GimpCoords  *coords,
+                                                  GimpMultiStroke   *mstroke,
                                                   GimpPaintState     paint_state,
                                                   guint32            time);
 
@@ -73,7 +74,7 @@ static void     gimp_source_core_paint           (GimpPaintCore     *paint_core,
 static void     gimp_source_core_motion          (GimpSourceCore    *source_core,
                                                   GimpDrawable      *drawable,
                                                   GimpPaintOptions  *paint_options,
-                                                  const GimpCoords  *coords);
+                                                  GimpMultiStroke   *mstroke);
 #endif
 
 static gboolean gimp_source_core_real_use_source (GimpSourceCore    *source_core,
@@ -253,12 +254,16 @@ static void
 gimp_source_core_paint (GimpPaintCore    *paint_core,
                         GimpDrawable     *drawable,
                         GimpPaintOptions *paint_options,
-                        const GimpCoords *coords,
+                        GimpMultiStroke  *mstroke,
                         GimpPaintState    paint_state,
                         guint32           time)
 {
   GimpSourceCore    *source_core = GIMP_SOURCE_CORE (paint_core);
   GimpSourceOptions *options     = GIMP_SOURCE_OPTIONS (paint_options);
+  const GimpCoords  *coords;
+
+  /* The source is based on the original stroke */
+  coords = gimp_multi_stroke_get_origin (mstroke);
 
   switch (paint_state)
     {
@@ -322,7 +327,8 @@ gimp_source_core_paint (GimpPaintCore    *paint_core,
           source_core->src_x = dest_x + source_core->offset_x;
           source_core->src_y = dest_y + source_core->offset_y;
 
-          gimp_source_core_motion (source_core, drawable, paint_options, coords);
+          gimp_source_core_motion (source_core, drawable, paint_options,
+                                   mstroke);
         }
       break;
 
@@ -347,7 +353,7 @@ void
 gimp_source_core_motion (GimpSourceCore   *source_core,
                          GimpDrawable     *drawable,
                          GimpPaintOptions *paint_options,
-                         const GimpCoords *coords)
+                         GimpMultiStroke  *mstroke)
 
 {
   GimpPaintCore     *paint_core   = GIMP_PAINT_CORE (source_core);
@@ -368,98 +374,118 @@ gimp_source_core_motion (GimpSourceCore   *source_core,
   gint               paint_area_height;
   gdouble            fade_point;
   gdouble            opacity;
+  GeglNode          *op;
+  GimpCoords        *origin;
+  GimpCoords        *coords;
+  gint               nstrokes;
+  gint               i;
 
   fade_point = gimp_paint_options_get_fade (paint_options, image,
                                             paint_core->pixel_dist);
 
-  opacity = gimp_dynamics_get_linear_value (dynamics,
-                                            GIMP_DYNAMICS_OUTPUT_OPACITY,
-                                            coords,
-                                            paint_options,
-                                            fade_point);
-  if (opacity == 0.0)
-    return;
+  origin   = gimp_multi_stroke_get_origin (mstroke);
+  nstrokes = gimp_multi_stroke_get_size (mstroke);
 
-  src_offset_x = source_core->offset_x;
-  src_offset_y = source_core->offset_y;
-
-  if (gimp_source_core_use_source (source_core, options))
+  for (i = 0; i < nstrokes; i++)
     {
-      src_pickable = GIMP_PICKABLE (source_core->src_drawable);
+      coords = gimp_multi_stroke_get_coords (mstroke, i);
 
-      if (options->sample_merged)
+      opacity = gimp_dynamics_get_linear_value (dynamics,
+                                                GIMP_DYNAMICS_OUTPUT_OPACITY,
+                                                coords,
+                                                paint_options,
+                                                fade_point);
+      if (opacity == 0.0)
+        continue;
+
+      src_offset_x = source_core->offset_x;
+      src_offset_y = source_core->offset_y;
+
+      if (gimp_source_core_use_source (source_core, options))
         {
-          GimpImage *src_image = gimp_pickable_get_image (src_pickable);
-          gint       off_x, off_y;
+          src_pickable = GIMP_PICKABLE (source_core->src_drawable);
+
+          if (options->sample_merged)
+            {
+              GimpImage *src_image = gimp_pickable_get_image (src_pickable);
+              gint       off_x, off_y;
 
-          src_pickable = GIMP_PICKABLE (src_image);
+              src_pickable = GIMP_PICKABLE (src_image);
 
-          gimp_item_get_offset (GIMP_ITEM (source_core->src_drawable),
-                                &off_x, &off_y);
+              gimp_item_get_offset (GIMP_ITEM (source_core->src_drawable),
+                                    &off_x, &off_y);
 
-          src_offset_x += off_x;
-          src_offset_y += off_y;
+              src_offset_x += off_x;
+              src_offset_y += off_y;
+            }
+
+          gimp_pickable_flush (src_pickable);
         }
 
-      gimp_pickable_flush (src_pickable);
-    }
+      paint_buffer = gimp_paint_core_get_paint_buffer (paint_core, drawable,
+                                                       paint_options, coords,
+                                                       &paint_buffer_x,
+                                                       &paint_buffer_y);
+      if (! paint_buffer)
+        continue;
 
-  paint_buffer = gimp_paint_core_get_paint_buffer (paint_core, drawable,
-                                                   paint_options, coords,
-                                                   &paint_buffer_x,
-                                                   &paint_buffer_y);
-  if (! paint_buffer)
-    return;
+      paint_area_offset_x = 0;
+      paint_area_offset_y = 0;
+      paint_area_width    = gegl_buffer_get_width  (paint_buffer);
+      paint_area_height   = gegl_buffer_get_height (paint_buffer);
 
-  paint_area_offset_x = 0;
-  paint_area_offset_y = 0;
-  paint_area_width    = gegl_buffer_get_width  (paint_buffer);
-  paint_area_height   = gegl_buffer_get_height (paint_buffer);
+      if (gimp_source_core_use_source (source_core, options))
+        {
+          /* When using a source, use the same for every stroke. */
+          src_offset_x = src_offset_x - coords->x + origin->x;
+          src_offset_y = src_offset_y - coords->y + origin->y;
+          src_buffer =
+            GIMP_SOURCE_CORE_GET_CLASS (source_core)->get_source (source_core,
+                                                                  drawable,
+                                                                  paint_options,
+                                                                  src_pickable,
+                                                                  src_offset_x,
+                                                                  src_offset_y,
+                                                                  paint_buffer,
+                                                                  paint_buffer_x,
+                                                                  paint_buffer_y,
+                                                                  &paint_area_offset_x,
+                                                                  &paint_area_offset_y,
+                                                                  &paint_area_width,
+                                                                  &paint_area_height,
+                                                                  &src_rect);
+
+          if (! src_buffer)
+            continue;
+        }
 
-  if (gimp_source_core_use_source (source_core, options))
-    {
-      src_buffer =
-        GIMP_SOURCE_CORE_GET_CLASS (source_core)->get_source (source_core,
-                                                              drawable,
-                                                              paint_options,
-                                                              src_pickable,
-                                                              src_offset_x,
-                                                              src_offset_y,
-                                                              paint_buffer,
-                                                              paint_buffer_x,
-                                                              paint_buffer_y,
-                                                              &paint_area_offset_x,
-                                                              &paint_area_offset_y,
-                                                              &paint_area_width,
-                                                              &paint_area_height,
-                                                              &src_rect);
-      if (! src_buffer)
-        return;
+      /*  Set the paint buffer to transparent  */
+      gegl_buffer_clear (paint_buffer, NULL);
+
+      op = gimp_multi_stroke_get_operation (mstroke, paint_core,
+                                            paint_buffer, i);
+      GIMP_SOURCE_CORE_GET_CLASS (source_core)->motion (source_core,
+                                                        drawable,
+                                                        paint_options,
+                                                        coords,
+                                                        op,
+                                                        opacity,
+                                                        src_pickable,
+                                                        src_buffer,
+                                                        &src_rect,
+                                                        src_offset_x,
+                                                        src_offset_y,
+                                                        paint_buffer,
+                                                        paint_buffer_x,
+                                                        paint_buffer_y,
+                                                        paint_area_offset_x,
+                                                        paint_area_offset_y,
+                                                        paint_area_width,
+                                                        paint_area_height);
+
+      if (src_buffer)
+        g_object_unref (src_buffer);
     }
-
-  /*  Set the paint buffer to transparent  */
-  gegl_buffer_clear (paint_buffer, NULL);
-
-  GIMP_SOURCE_CORE_GET_CLASS (source_core)->motion (source_core,
-                                                    drawable,
-                                                    paint_options,
-                                                    coords,
-                                                    opacity,
-                                                    src_pickable,
-                                                    src_buffer,
-                                                    &src_rect,
-                                                    src_offset_x,
-                                                    src_offset_y,
-                                                    paint_buffer,
-                                                    paint_buffer_x,
-                                                    paint_buffer_y,
-                                                    paint_area_offset_x,
-                                                    paint_area_offset_y,
-                                                    paint_area_width,
-                                                    paint_area_height);
-
-  if (src_buffer)
-    g_object_unref (src_buffer);
 }
 
 gboolean
diff --git a/app/paint/gimpsourcecore.h b/app/paint/gimpsourcecore.h
index 4f71bb0..c056cb1 100644
--- a/app/paint/gimpsourcecore.h
+++ b/app/paint/gimpsourcecore.h
@@ -77,6 +77,7 @@ struct _GimpSourceCoreClass
                                GimpDrawable      *drawable,
                                GimpPaintOptions  *paint_options,
                                const GimpCoords  *coords,
+                               GeglNode          *op,
                                gdouble            opacity,
                                GimpPickable      *src_pickable,
                                GeglBuffer        *src_buffer,
@@ -103,7 +104,7 @@ gboolean gimp_source_core_use_source (GimpSourceCore    *source_core,
 void     gimp_source_core_motion     (GimpSourceCore    *source_core,
                                       GimpDrawable      *drawable,
                                       GimpPaintOptions  *paint_options,
-                                      const GimpCoords  *coords);
+                                      GimpMultiStroke   *mstroke);
 
 
 #endif  /*  __GIMP_SOURCE_CORE_H__  */
diff --git a/app/paint/paint-types.h b/app/paint/paint-types.h
index ccbedb2..3e60bac 100644
--- a/app/paint/paint-types.h
+++ b/app/paint/paint-types.h
@@ -42,6 +42,9 @@ typedef struct _GimpPencil           GimpPencil;
 typedef struct _GimpPerspectiveClone GimpPerspectiveClone;
 typedef struct _GimpSmudge           GimpSmudge;
 
+/* Multi-stroke transformations */
+typedef struct _GimpMultiStroke      GimpMultiStroke;
+typedef struct _GimpMirror           GimpMirror;
 
 /*  paint options  */
 
diff --git a/app/tools/gimppaintoptions-gui.c b/app/tools/gimppaintoptions-gui.c
index be3d546..c63a843 100644
--- a/app/tools/gimppaintoptions-gui.c
+++ b/app/tools/gimppaintoptions-gui.c
@@ -24,8 +24,14 @@
 
 #include "tools-types.h"
 
+#include "config/gimpguiconfig.h"
+
+#include "core/gimp.h"
+#include "core/gimpimage.h"
 #include "core/gimptoolinfo.h"
 
+#include "paint/gimpmultistroke.h"
+#include "paint/gimpmultistroke-info.h"
 #include "paint/gimppaintoptions.h"
 
 #include "widgets/gimppropwidgets.h"
@@ -92,6 +98,17 @@ static GtkWidget * gimp_paint_options_gui_scale_with_buttons
                                                 GCallback     reset_callback,
                                                 GtkSizeGroup *link_group);
 
+static void
+     gimp_paint_options_multi_stroke_update_cb (GimpMultiStroke *mstroke,
+                                                GimpImage       *image,
+                                                GtkWidget       *frame);
+static void
+      gimp_paint_options_multi_stroke_callback (GimpPaintOptions *options,
+                                                GParamSpec       *pspec,
+                                                GtkWidget        *frame);
+static void gimp_paint_options_multi_stroke_ui (GimpMultiStroke *mstroke,
+                                                GtkWidget       *frame);
+
 
 /*  public functions  */
 
@@ -233,11 +250,64 @@ gimp_paint_options_gui (GimpToolOptions *tool_options)
       gtk_widget_show (frame);
     }
 
-  /*  the "smooth stroke" options  */
   if (g_type_is_a (tool_type, GIMP_TYPE_PAINT_TOOL))
     {
       GtkWidget *frame;
-
+      GimpGuiConfig *guiconfig;
+
+      guiconfig = GIMP_GUI_CONFIG (tool_options->tool_info->gimp->config);
+      if (guiconfig->playground_multi_stroke)
+        {
+          /* Multi-Stroke Painting */
+          GtkListStore *store;
+          GtkTreeIter   iter;
+          GList        *mstrokes;
+
+          store = gimp_int_store_new ();
+
+          mstrokes = gimp_multi_stroke_list ();
+          for (mstrokes = gimp_multi_stroke_list (); mstrokes; mstrokes = g_list_next (mstrokes))
+            {
+              GimpMultiStrokeClass *klass;
+              GType                 type;
+
+              type = (GType) mstrokes->data;
+              klass = g_type_class_ref (type);
+
+              gtk_list_store_prepend (store, &iter);
+              gtk_list_store_set (store, &iter,
+                                  GIMP_INT_STORE_LABEL,
+                                  klass->label,
+                                  GIMP_INT_STORE_VALUE,
+                                  mstrokes->data,
+                                  -1);
+              g_type_class_unref (klass);
+            }
+          gtk_list_store_prepend (store, &iter);
+          gtk_list_store_set (store, &iter,
+                              GIMP_INT_STORE_LABEL, _("None"),
+                              GIMP_INT_STORE_VALUE, G_TYPE_NONE,
+                              -1);
+          menu = gimp_prop_int_combo_box_new (config, "multi-stroke",
+                                              GIMP_INT_STORE (store));
+          g_object_unref (store);
+
+          gimp_int_combo_box_set_label (GIMP_INT_COMBO_BOX (menu), _("Multi-Stroke"));
+          gimp_int_combo_box_set_active (GIMP_INT_COMBO_BOX (menu), G_TYPE_NONE);
+          g_object_set (menu, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
+
+          gtk_box_pack_start (GTK_BOX (vbox), menu, FALSE, FALSE, 0);
+          gtk_widget_show (menu);
+
+          frame = gimp_frame_new ("");
+          gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, FALSE, 0);
+          g_signal_connect (options, "notify::multi-stroke",
+                            G_CALLBACK (gimp_paint_options_multi_stroke_callback),
+                            frame);
+
+        }
+
+      /*  the "smooth stroke" options  */
       frame = smoothing_options_gui (options, tool_type);
       gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, FALSE, 0);
       gtk_widget_show (frame);
@@ -561,3 +631,160 @@ gimp_paint_options_gui_scale_with_buttons (GObject      *config,
 
   return hbox;
 }
+
+static void
+gimp_paint_options_multi_stroke_update_cb (GimpMultiStroke *mstroke,
+                                           GimpImage       *image,
+                                           GtkWidget       *frame)
+{
+  GimpContext *context;
+
+  g_return_if_fail (GIMP_IS_MULTI_STROKE (mstroke));
+
+  context = gimp_get_user_context (mstroke->image->gimp);
+  if (image != context->image ||
+      mstroke != gimp_image_get_selected_multi_stroke (image))
+    {
+      g_signal_handlers_disconnect_by_func (G_OBJECT (mstroke),
+                                            gimp_paint_options_multi_stroke_update_cb,
+                                            frame);
+      return;
+    }
+
+  gimp_paint_options_multi_stroke_ui (mstroke, frame);
+}
+
+static void
+gimp_paint_options_multi_stroke_callback (GimpPaintOptions *options,
+                                          GParamSpec       *pspec,
+                                          GtkWidget        *frame)
+{
+  GimpMultiStroke      *mstroke = NULL;
+  GimpContext          *context;
+  GimpImage            *image;
+
+  g_return_if_fail (GIMP_IS_PAINT_OPTIONS (options));
+
+  context = gimp_get_user_context (GIMP_CONTEXT (options)->gimp);
+  image   = context->image;
+
+  if (image &&
+      (mstroke = gimp_image_get_selected_multi_stroke (image)))
+    {
+      g_signal_connect (mstroke, "update-ui",
+                        G_CALLBACK (gimp_paint_options_multi_stroke_update_cb),
+                        frame);
+    }
+
+  gimp_paint_options_multi_stroke_ui (mstroke, frame);
+}
+
+static void
+gimp_paint_options_multi_stroke_ui (GimpMultiStroke *mstroke,
+                                    GtkWidget       *frame)
+{
+  GimpMultiStrokeClass *klass;
+  GtkWidget            *vbox;
+  GParamSpec          **specs;
+  guint                 nproperties;
+  gint                  i;
+
+  /* Clean the old frame */
+  gtk_widget_hide (frame);
+  gtk_container_foreach (GTK_CONTAINER (frame),
+                         (GtkCallback) gtk_widget_destroy, NULL);
+
+  if (! mstroke)
+    return;
+
+  klass = g_type_class_ref (mstroke->type);
+  gtk_frame_set_label (GTK_FRAME (frame),
+                       klass->label);
+  g_type_class_unref (klass);
+
+  vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 2);
+  gtk_container_add (GTK_CONTAINER (frame), vbox);
+  gtk_widget_show (vbox);
+
+  specs = gimp_multi_stroke_get_settings (mstroke, &nproperties);
+
+  for (i = 0; i < (gint) nproperties; i++)
+    {
+      GParamSpec  *spec;
+      const gchar *name;
+      const gchar *blurb;
+
+      if (specs[i] == NULL)
+        {
+          GtkWidget *separator;
+
+          separator = gtk_hseparator_new ();
+          gtk_box_pack_start (GTK_BOX (vbox), separator,
+                              FALSE, FALSE, 0);
+          gtk_widget_show (separator);
+          continue;
+        }
+      spec = G_PARAM_SPEC (specs[i]);
+
+      name = g_param_spec_get_name (spec);
+      blurb = g_param_spec_get_blurb (spec);
+
+      switch (spec->value_type)
+        {
+        case G_TYPE_BOOLEAN:
+            {
+              GtkWidget *checkbox;
+
+              checkbox = gimp_prop_check_button_new (G_OBJECT (mstroke),
+                                                     name,
+                                                     blurb);
+              gtk_box_pack_start (GTK_BOX (vbox), checkbox,
+                                  FALSE, FALSE, 0);
+              gtk_widget_show (checkbox);
+            }
+          break;
+        case G_TYPE_DOUBLE:
+        case G_TYPE_INT:
+        case G_TYPE_UINT:
+            {
+              GtkWidget *scale;
+              gdouble    minimum;
+              gdouble    maximum;
+
+              if (spec->value_type == G_TYPE_DOUBLE)
+                {
+                  minimum = G_PARAM_SPEC_DOUBLE (spec)->minimum;
+                  maximum = G_PARAM_SPEC_DOUBLE (spec)->maximum;
+                }
+              else if (spec->value_type == G_TYPE_INT)
+                {
+                  minimum = G_PARAM_SPEC_INT (spec)->minimum;
+                  maximum = G_PARAM_SPEC_INT (spec)->maximum;
+                }
+              else
+                {
+                  minimum = G_PARAM_SPEC_UINT (spec)->minimum;
+                  maximum = G_PARAM_SPEC_UINT (spec)->maximum;
+                }
+
+              scale = gimp_prop_spin_scale_new (G_OBJECT (mstroke),
+                                                name, blurb,
+                                                1.0, 10.0, 1);
+              gimp_spin_scale_set_scale_limits (GIMP_SPIN_SCALE (scale),
+                                                minimum,
+                                                maximum);
+              gtk_box_pack_start (GTK_BOX (vbox), scale, TRUE, TRUE, 0);
+              gtk_widget_show (scale);
+            }
+          break;
+        default:
+          /* Type of parameter we haven't handled yet. */
+          continue;
+        }
+    }
+
+  g_free (specs);
+
+  /* Finally show the frame. */
+  gtk_widget_show (frame);
+}
diff --git a/app/tools/tool_manager.c b/app/tools/tool_manager.c
index 48d7bfa..f923b7e 100644
--- a/app/tools/tool_manager.c
+++ b/app/tools/tool_manager.c
@@ -774,6 +774,9 @@ tool_manager_preset_changed (GimpContext     *user_context,
       if (config->global_gradient && preset->use_gradient)
         gimp_paint_options_copy_gradient_props (GIMP_PAINT_OPTIONS (src),
                                                 GIMP_PAINT_OPTIONS (dest));
+
+      gimp_paint_options_set_mstroke_props (GIMP_PAINT_OPTIONS (src),
+                                            GIMP_PAINT_OPTIONS (dest));
     }
 }
 
@@ -842,6 +845,9 @@ tool_manager_connect_options (GimpToolManager *tool_manager,
           if (config->global_gradient)
             gimp_paint_options_copy_gradient_props (tool_manager->shared_paint_options,
                                                     GIMP_PAINT_OPTIONS (tool_info->tool_options));
+
+          gimp_paint_options_set_mstroke_props (tool_manager->shared_paint_options,
+                                                GIMP_PAINT_OPTIONS (tool_info->tool_options));
         }
     }
 }
@@ -866,6 +872,9 @@ tool_manager_disconnect_options (GimpToolManager *tool_manager,
 
           gimp_paint_options_copy_gradient_props (GIMP_PAINT_OPTIONS (tool_info->tool_options),
                                                   tool_manager->shared_paint_options);
+
+          gimp_paint_options_set_mstroke_props (GIMP_PAINT_OPTIONS (tool_info->tool_options),
+                                                tool_manager->shared_paint_options);
         }
 
       gimp_context_set_parent (GIMP_CONTEXT (tool_info->tool_options), NULL);
diff --git a/app/xcf/xcf-load.c b/app/xcf/xcf-load.c
index b1c2322..7abf51a 100644
--- a/app/xcf/xcf-load.c
+++ b/app/xcf/xcf-load.c
@@ -55,6 +55,9 @@
 #include "core/gimpselection.h"
 #include "core/gimptemplate.h"
 
+#include "paint/gimpmultistroke.h"
+#include "paint/gimpmultistroke-info.h"
+
 #include "text/gimptextlayer.h"
 #include "text/gimptextlayer-xcf.h"
 
@@ -737,6 +740,130 @@ xcf_load_image_props (XcfInfo   *info,
           }
           break;
 
+        case PROP_MULTI_STROKE:
+            {
+              GimpMultiStroke *mstroke;
+              GimpMultiStroke *active_mstroke = NULL;
+              gint32           active;
+              gint32           n_mstrokes;
+              gchar           *name;
+              GType            type;
+              GParamSpec     **settings;
+              GParamSpec      *spec;
+              guint            nsettings;
+              gint             i, j;
+
+              info->cp += xcf_read_int32 (info->input,
+                                          (guint32 *) &active, 1);
+              info->cp += xcf_read_int32 (info->input,
+                                          (guint32 *) &n_mstrokes, 1);
+              for (i = 1; i <= n_mstrokes; i++)
+                {
+                  info->cp += xcf_read_string (info->input, &name, 1);
+                  type = g_type_from_name (name);
+                  if (! type || ! g_type_is_a (type, GIMP_TYPE_MULTI_STROKE))
+                    {
+                      gimp_message (info->gimp, G_OBJECT (info->progress),
+                                    GIMP_MESSAGE_ERROR,
+                                    "Unknown Multi-Stroke: %s",
+                                    name);
+                      g_free (name);
+                      return FALSE;
+                    }
+                  mstroke = gimp_multi_stroke_new (type, image);
+                  gimp_image_add_multi_stroke (image, mstroke);
+
+                  settings = gimp_multi_stroke_get_xcf_settings (mstroke,
+                                                                 &nsettings);
+                  for (j = 0; j < nsettings; j++)
+                    {
+                      if (settings[j] == NULL)
+                        continue;
+
+                      spec = settings[j];
+                      switch (spec->value_type)
+                        {
+                        case G_TYPE_BOOLEAN:
+                            {
+                              guint8 value;
+
+                              info->cp += xcf_read_int8 (info->input,
+                                                         &value, 1);
+                              g_object_set (mstroke,
+                                            g_param_spec_get_name (spec),
+                                            (gboolean) value,
+                                            NULL);
+                            }
+                          break;
+                        case G_TYPE_FLOAT:
+                        case G_TYPE_DOUBLE:
+                            {
+                              gfloat value;
+
+                              info->cp += xcf_read_float (info->input,
+                                                          &value, 1);
+                              g_object_set (mstroke,
+                                            g_param_spec_get_name (spec),
+                                            (spec->value_type == G_TYPE_FLOAT) ?
+                                            value : (gdouble) value,
+                                            NULL);
+                            }
+                          break;
+                        case G_TYPE_UINT:
+                            {
+                              guint32 value;
+
+                              info->cp += xcf_read_int32 (info->input,
+                                                          &value, 1);
+                              g_object_set (mstroke,
+                                            g_param_spec_get_name (spec),
+                                            (guint) value,
+                                            NULL);
+                            }
+                          break;
+                        case G_TYPE_INT:
+                            {
+                              guint32 value;
+
+                              info->cp += xcf_read_int32 (info->input,
+                                                          &value, 1);
+                              g_object_set (mstroke,
+                                            g_param_spec_get_name (spec),
+                                            (gint) value,
+                                            NULL);
+                            }
+                          break;
+                        case G_TYPE_STRING:
+                            {
+                              gchar* value;
+
+                              info->cp += xcf_read_string (info->input,
+                                                           &value, 1);
+                              g_object_set (mstroke,
+                                            g_param_spec_get_name (spec),
+                                            value,
+                                            NULL);
+                              g_free (value);
+                            }
+                          break;
+                        default:
+                          /* We don't handle this settings. */
+                          gimp_message (info->gimp, G_OBJECT (info->progress),
+                                        GIMP_MESSAGE_ERROR,
+                                        "Unknown settings '%s' for '%s'",
+                                        name, g_param_spec_get_name (spec));
+                          g_free (name);
+                          return FALSE;
+                        }
+                    }
+                  if (active == i)
+                    active_mstroke = mstroke;
+                }
+              gimp_image_select_multi_stroke (image, active_mstroke->type);
+              g_free (name);
+            }
+          break;
+
         case PROP_SAMPLE_POINTS:
           {
             gint32 x, y;
diff --git a/app/xcf/xcf-private.h b/app/xcf/xcf-private.h
index 3c3edf6..2212e82 100644
--- a/app/xcf/xcf-private.h
+++ b/app/xcf/xcf-private.h
@@ -56,7 +56,8 @@ typedef enum
   PROP_GROUP_ITEM         = 29,
   PROP_ITEM_PATH          = 30,
   PROP_GROUP_ITEM_FLAGS   = 31,
-  PROP_LOCK_POSITION      = 32
+  PROP_LOCK_POSITION      = 32,
+  PROP_MULTI_STROKE       = 33
 } PropType;
 
 typedef enum
diff --git a/app/xcf/xcf-save.c b/app/xcf/xcf-save.c
index 514dc13..f0c3ce2 100644
--- a/app/xcf/xcf-save.c
+++ b/app/xcf/xcf-save.c
@@ -47,10 +47,14 @@
 #include "core/gimpimage-sample-points.h"
 #include "core/gimplayer.h"
 #include "core/gimplayermask.h"
+#include "core/gimpmirrorguide.h"
 #include "core/gimpparasitelist.h"
 #include "core/gimpprogress.h"
 #include "core/gimpsamplepoint.h"
 
+#include "paint/gimpmultistroke.h"
+#include "paint/gimpmultistroke-info.h"
+
 #include "text/gimptextlayer.h"
 #include "text/gimptextlayer-xcf.h"
 
@@ -361,6 +365,10 @@ xcf_save_image_props (XcfInfo    *info,
     xcf_check_error (xcf_save_prop (info, image, PROP_GUIDES, error,
                                     gimp_image_get_guides (image)));
 
+  if (gimp_image_get_multi_strokes (image))
+    xcf_check_error (xcf_save_prop (info, image, PROP_MULTI_STROKE, error,
+                                    gimp_image_get_multi_strokes (image)));
+
   if (gimp_image_get_sample_points (image))
     xcf_check_error (xcf_save_prop (info, image, PROP_SAMPLE_POINTS, error,
                                     gimp_image_get_sample_points (image)));
@@ -871,11 +879,18 @@ xcf_save_prop (XcfInfo    *info,
     case PROP_GUIDES:
       {
         GList *guides;
+        GList *iter;
         gint   n_guides;
 
         guides = va_arg (args, GList *);
         n_guides = g_list_length (guides);
 
+        for (iter = guides; iter; iter = g_list_next (iter))
+          {
+            /* Do not write down mirror guides here. */
+            if (GIMP_IS_MIRROR_GUIDE (iter->data))
+              n_guides--;
+          }
         size = n_guides * (4 + 1);
 
         xcf_write_prop_type_check_error (info, prop_type);
@@ -887,6 +902,9 @@ xcf_save_prop (XcfInfo    *info,
             gint32     position = gimp_guide_get_position (guide);
             gint8      orientation;
 
+            if (GIMP_IS_MIRROR_GUIDE (guide))
+              continue;
+
             switch (gimp_guide_get_orientation (guide))
               {
               case GIMP_ORIENTATION_HORIZONTAL:
@@ -909,6 +927,163 @@ xcf_save_prop (XcfInfo    *info,
       }
       break;
 
+    case PROP_MULTI_STROKE:
+      {
+        GList            *mstrokes;
+        GList            *iter;
+        GimpMultiStroke  *mstroke;
+        GParamSpec      **settings;
+        GParamSpec       *spec;
+        guint             nsettings;
+        guint32           base;
+        glong             pos;
+        gint              i = 0;
+
+        xcf_write_prop_type_check_error (info, prop_type);
+        /* because we don't know how much room the Multi-Stroke list
+         * will take we save the file position and write the length
+         * later.
+         */
+        pos = info->cp;
+        size = 0;
+        xcf_write_int32_check_error (info, &size, 1);
+        base = info->cp;
+
+        mstrokes = va_arg (args, GList *);
+
+        /* Index of active multi-stroke, starting at 1
+         * (because 0 means none active) */
+        if (gimp_image_get_selected_multi_stroke (image))
+          {
+            for (i = 1, iter = mstrokes; iter; iter = g_list_next (iter), i++)
+              {
+                mstroke = GIMP_MULTI_STROKE (iter->data);
+                if (mstroke == gimp_image_get_selected_multi_stroke (image))
+                  break;
+              }
+          }
+        xcf_write_int32_check_error (info, (guint32 *)  &i, 1);
+        /* Number of multi-strokes that follows. */
+        i = g_list_length (mstrokes);
+        xcf_write_int32_check_error (info, (guint32 *)  &i, 1);
+
+        for (iter = mstrokes; iter; iter = g_list_next (iter))
+          {
+            const gchar *name;
+
+            mstroke = GIMP_MULTI_STROKE (iter->data);
+
+            name = g_type_name (mstroke->type);
+            xcf_write_string_check_error (info, (gchar **) &name, 1);
+
+            settings = gimp_multi_stroke_get_xcf_settings (mstroke,
+                                                           &nsettings);
+
+            for (i = 0; i < nsettings; i++)
+              {
+                if (settings[i] == NULL)
+                  continue;
+
+                spec = settings[i];
+
+                switch (spec->value_type)
+                  {
+                  case G_TYPE_BOOLEAN:
+                      {
+                        gboolean value;
+                        guint8   uint_value;
+
+                        g_object_get (mstroke,
+                                      g_param_spec_get_name (spec),
+                                      &value,
+                                      NULL);
+                        uint_value = (guint8) value;
+                        xcf_write_int8_check_error (info, &uint_value, 1);
+                      }
+                    break;
+                  case G_TYPE_FLOAT:
+                      {
+                        gfloat value;
+
+                        g_object_get (mstroke,
+                                      g_param_spec_get_name (spec),
+                                      &value,
+                                      NULL);
+                        xcf_write_float_check_error (info, &value, 1);
+                      }
+                    break;
+                  case G_TYPE_DOUBLE:
+                      {
+                        gdouble value;
+                        gfloat  float_value;
+
+                        g_object_get (mstroke,
+                                      g_param_spec_get_name (spec),
+                                      &value,
+                                      NULL);
+                        float_value = (gfloat) value;
+                        xcf_write_float_check_error (info, &float_value, 1);
+                      }
+                    break;
+                  case G_TYPE_UINT:
+                      {
+                        guint   value;
+                        guint32 uint_value;
+
+                        g_object_get (mstroke,
+                                      g_param_spec_get_name (spec),
+                                      &value,
+                                      NULL);
+                        uint_value = (guint32) value;
+                        xcf_write_int32_check_error (info, &uint_value, 1);
+                      }
+                    break;
+                  case G_TYPE_INT:
+                      {
+                        gint    value;
+                        guint32 uint_value;
+
+                        g_object_get (mstroke,
+                                      g_param_spec_get_name (spec),
+                                      &value,
+                                      NULL);
+                        uint_value = (guint32) value;
+                        xcf_write_int32_check_error (info, &uint_value, 1);
+                      }
+                    break;
+                  case G_TYPE_STRING:
+                      {
+                        gchar* value;
+
+                        g_object_get (mstroke,
+                                      g_param_spec_get_name (spec),
+                                      &value,
+                                      NULL);
+                        xcf_write_string_check_error (info, &value, 1);
+                        g_free (value);
+                      }
+                    break;
+                  default:
+                    /* We don't handle this settings. */
+                    gimp_message (info->gimp, G_OBJECT (info->progress),
+                                  GIMP_MESSAGE_ERROR,
+                                  "Unknown settings '%s' for '%s'",
+                                  name, g_param_spec_get_name (spec));
+                    return FALSE;
+                  }
+              }
+          }
+
+        size = info->cp - base;
+
+        /* go back to the saved position and write the length */
+        xcf_check_error (xcf_seek_pos (info, pos, error));
+        xcf_write_int32_check_error (info, &size, 1);
+
+        xcf_check_error (xcf_seek_pos (info, base + size, error));
+      }
+      break;
+
     case PROP_SAMPLE_POINTS:
       {
         GList *sample_points;
diff --git a/devel-docs/xcf.txt b/devel-docs/xcf.txt
index c18f8a7..bccfa6b 100644
--- a/devel-docs/xcf.txt
+++ b/devel-docs/xcf.txt
@@ -747,6 +747,24 @@ PROP_GUIDES (editing state)
   Some old XCF files define guides with negative coordinates; those
   should be ignored by readers.
 
+PROP_MULTI_STROKE (editing state)
+  uint32  33       Type identification
+  uint32  plength  Total length of the following payload in bytes
+                   (sum of mlength for all registered multi-strokes)
+  uint32  active   Index of the active multi-stroke, starting at 1
+                   (0 means none is active)
+  uint32  n        Number of multi-strokes that follow
+  ,--------------- Repeat for each registered multi-stroke:
+  | string type    name of multi-stroke
+  | ...    params  various parameters.
+                   Type and order depends on the
+                   gimp_multi_stroke_get_settings()
+  `--
+
+  PROP_MULTI_STROKE stores the existence and settings of any multi-stroke so
+  that the user do not lose them upon restart (for instance, if you are drawing
+  with a mirror, you want to find it precisely at the same place later).
+
 PROP_PATHS
   uint32  23       Type identification
   uint32  plength  Total length of the following payload in bytes



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