[gimp/multi-stroke: 1/31] Bug 648776 - mirror symmetries.
- From: Jehan Pagès <jehanp src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gimp/multi-stroke: 1/31] Bug 648776 - mirror symmetries.
- Date: Thu, 8 Oct 2015 19:03:00 +0000 (UTC)
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]