[gimp/symmetry: 1/8] Bug 648776 - mirror symmetries.
- From: Jehan Pagès <jehanp src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gimp/symmetry: 1/8] Bug 648776 - mirror symmetries.
- Date: Thu, 17 Dec 2015 19:48:22 +0000 (UTC)
commit f53155ff49b52b2af83f81743b9a0e29d7342457
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 | 8 +
app/core/core-types.h | 6 +
app/core/gimpbrush-boundary.c | 2 +-
app/core/gimpbrush.c | 64 +++-
app/core/gimpbrush.h | 2 +
app/core/gimpbrushcache.c | 121 ++++--
app/core/gimpbrushcache.h | 10 +-
app/core/gimpcontext.c | 20 +
app/core/gimpimage-private.h | 3 +
app/core/gimpimage-symmetry.c | 210 +++++++++
app/core/gimpimage-symmetry.h | 36 ++
app/core/gimpimage.c | 27 +-
app/core/gimpmirrorguide.c | 131 ++++++
app/core/gimpmirrorguide.h | 57 +++
app/core/gimpsymmetry-mirror.c | 732 +++++++++++++++++++++++++++++++
app/core/gimpsymmetry-mirror.h | 69 +++
app/core/gimpsymmetry.c | 370 ++++++++++++++++
app/core/gimpsymmetry.h | 92 ++++
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/gimpairbrush.c | 46 ++-
app/paint/gimpairbrush.h | 2 +
app/paint/gimpbrushcore.c | 45 ++-
app/paint/gimpbrushcore.h | 8 +-
app/paint/gimpclone.c | 26 +-
app/paint/gimpconvolve.c | 155 ++++---
app/paint/gimpdodgeburn.c | 124 +++---
app/paint/gimperaser.c | 100 +++--
app/paint/gimpheal.c | 27 +-
app/paint/gimpink.c | 264 +++++++----
app/paint/gimpink.h | 6 +-
app/paint/gimpinkundo.c | 32 +-
app/paint/gimpinkundo.h | 2 +-
app/paint/gimpmybrush.c | 262 ++++++++----
app/paint/gimppaintbrush.c | 156 ++++---
app/paint/gimppaintbrush.h | 2 +-
app/paint/gimppaintcore-loops.c | 25 +-
app/paint/gimppaintcore-loops.h | 2 +-
app/paint/gimppaintcore.c | 40 ++-
app/paint/gimppaintcore.h | 10 +-
app/paint/gimppaintoptions.c | 66 +++-
app/paint/gimppaintoptions.h | 4 +
app/paint/gimpperspectiveclone.c | 73 ++--
app/paint/gimpsmudge.c | 381 +++++++++-------
app/paint/gimpsmudge.h | 2 +-
app/paint/gimpsourcecore.c | 161 ++++---
app/paint/gimpsourcecore.h | 3 +-
app/paint/paint-types.h | 1 -
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 +
63 files changed, 3862 insertions(+), 819 deletions(-)
---
diff --git a/app/config/gimpguiconfig.c b/app/config/gimpguiconfig.c
index c11f6b4..69cbc4a 100644
--- a/app/config/gimpguiconfig.c
+++ b/app/config/gimpguiconfig.c
@@ -87,6 +87,7 @@ enum
PROP_PLAYGROUND_HANDLE_TRANSFORM_TOOL,
PROP_PLAYGROUND_MYBRUSH_TOOL,
PROP_PLAYGROUND_SEAMLESS_CLONE_TOOL,
+ PROP_PLAYGROUND_SYMMETRY,
PROP_HIDE_DOCKS,
PROP_SINGLE_WINDOW_MODE,
@@ -305,6 +306,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_SYMMETRY,
+ "playground-symmetry",
+ PLAYGROUND_SYMMETRY_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,
@@ -539,6 +547,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_SYMMETRY:
+ gui_config->playground_symmetry = g_value_get_boolean (value);
+ break;
case PROP_PLAYGROUND_MYBRUSH_TOOL:
gui_config->playground_mybrush_tool = g_value_get_boolean (value);
break;
@@ -692,6 +703,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_SYMMETRY:
+ g_value_set_boolean (value, gui_config->playground_symmetry);
+ 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 642025d..d4d85f6 100644
--- a/app/config/gimpguiconfig.h
+++ b/app/config/gimpguiconfig.h
@@ -80,6 +80,7 @@ struct _GimpGuiConfig
gboolean playground_handle_transform_tool;
gboolean playground_mybrush_tool;
gboolean playground_seamless_clone_tool;
+ gboolean playground_symmetry;
/* saved in sessionrc */
gboolean hide_docks;
diff --git a/app/config/gimprc-blurbs.h b/app/config/gimprc-blurbs.h
index 76a4ab5..8b614a2 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_SYMMETRY_BLURB \
+_("Enable symmetry 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..ee3e632 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 \
@@ -256,6 +258,8 @@ libappcore_a_sources = \
gimpimage-scale.h \
gimpimage-snap.c \
gimpimage-snap.h \
+ gimpimage-symmetry.c \
+ gimpimage-symmetry.h \
gimpimage-undo.c \
gimpimage-undo.h \
gimpimage-undo-push.c \
@@ -359,6 +363,10 @@ libappcore_a_sources = \
gimpstrokeoptions.h \
gimpsubprogress.c \
gimpsubprogress.h \
+ gimpsymmetry.c \
+ gimpsymmetry.h \
+ gimpsymmetry-mirror.c \
+ gimpsymmetry-mirror.h \
gimptag.c \
gimptag.h \
gimptagcache.c \
diff --git a/app/core/core-types.h b/app/core/core-types.h
index 018857c..996c758 100644
--- a/app/core/core-types.h
+++ b/app/core/core-types.h
@@ -174,11 +174,17 @@ typedef struct _GimpUndoStack GimpUndoStack;
typedef struct _GimpUndoAccumulator GimpUndoAccumulator;
+/* Symmetry transformations */
+
+typedef struct _GimpSymmetry GimpSymmetry;
+typedef struct _GimpMirror GimpMirror;
+
/* misc objects */
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..11c74b8 100644
--- a/app/core/gimpbrushcache.c
+++ b/app/core/gimpbrushcache.c
@@ -29,6 +29,7 @@
#include "gimp-log.h"
#include "gimp-intl.h"
+#define MAX_CACHED_DATA 20
enum
{
@@ -36,6 +37,20 @@ enum
PROP_DATA_DESTROY
};
+typedef struct _GimpBrushCacheUnit GimpBrushCacheUnit;
+
+struct _GimpBrushCacheUnit
+{
+ gpointer data;
+
+ gint width;
+ gint height;
+ gdouble scale;
+ gdouble aspect_ratio;
+ gdouble angle;
+ gdouble hardness;
+ GeglNode *op;
+};
static void gimp_brush_cache_constructed (GObject *object);
static void gimp_brush_cache_finalize (GObject *object);
@@ -91,10 +106,18 @@ gimp_brush_cache_finalize (GObject *object)
{
GimpBrushCache *cache = GIMP_BRUSH_CACHE (object);
- if (cache->last_data)
+ if (cache->cached_units)
{
- cache->data_destroy (cache->last_data);
- cache->last_data = NULL;
+ GList *iter;
+
+ for (iter = cache->cached_units; iter; iter = g_list_next (iter))
+ {
+ GimpBrushCacheUnit *unit = iter->data;
+
+ cache->data_destroy (unit->data);
+ }
+ g_list_free_full (cache->cached_units, g_free);
+ cache->cached_units = NULL;
}
G_OBJECT_CLASS (parent_class)->finalize (object);
@@ -167,15 +190,24 @@ gimp_brush_cache_clear (GimpBrushCache *cache)
{
g_return_if_fail (GIMP_IS_BRUSH_CACHE (cache));
- if (cache->last_data)
+ if (cache->cached_units)
{
- cache->data_destroy (cache->last_data);
- cache->last_data = NULL;
+ GList *iter;
+
+ for (iter = cache->cached_units; iter; iter = g_list_next (iter))
+ {
+ GimpBrushCacheUnit *unit = iter->data;
+
+ cache->data_destroy (unit->data);
+ }
+ g_list_free_full (cache->cached_units, g_free);
+ cache->cached_units = NULL;
}
}
gconstpointer
gimp_brush_cache_get (GimpBrushCache *cache,
+ GeglNode *op,
gint width,
gint height,
gdouble scale,
@@ -183,20 +215,34 @@ gimp_brush_cache_get (GimpBrushCache *cache,
gdouble angle,
gdouble hardness)
{
+ GList *iter;
+
g_return_val_if_fail (GIMP_IS_BRUSH_CACHE (cache), NULL);
- if (cache->last_data &&
- cache->last_width == width &&
- cache->last_height == height &&
- cache->last_scale == scale &&
- cache->last_aspect_ratio == aspect_ratio &&
- cache->last_angle == angle &&
- cache->last_hardness == hardness)
+ for (iter = cache->cached_units; iter; iter = g_list_next (iter))
{
- if (gimp_log_flags & GIMP_LOG_BRUSH_CACHE)
- g_printerr ("%c", cache->debug_hit);
-
- return (gconstpointer) cache->last_data;
+ GimpBrushCacheUnit *unit = iter->data;
+
+ if (unit->data &&
+ unit->width == width &&
+ unit->height == height &&
+ unit->scale == scale &&
+ unit->aspect_ratio == aspect_ratio &&
+ unit->angle == angle &&
+ unit->hardness == hardness &&
+ unit->op == op)
+ {
+ if (gimp_log_flags & GIMP_LOG_BRUSH_CACHE)
+ g_printerr ("%c", cache->debug_hit);
+
+ /* Make the returned cached brush first in the list. */
+ cache->cached_units = g_list_remove_link (cache->cached_units, iter);
+ iter->next = cache->cached_units;
+ if (cache->cached_units)
+ cache->cached_units->prev = iter;
+ cache->cached_units = iter;
+ return (gconstpointer) unit->data;
+ }
}
if (gimp_log_flags & GIMP_LOG_BRUSH_CACHE)
@@ -208,6 +254,7 @@ gimp_brush_cache_get (GimpBrushCache *cache,
void
gimp_brush_cache_add (GimpBrushCache *cache,
gpointer data,
+ GeglNode *op,
gint width,
gint height,
gdouble scale,
@@ -215,20 +262,38 @@ gimp_brush_cache_add (GimpBrushCache *cache,
gdouble angle,
gdouble hardness)
{
+ GList *iter;
+ GimpBrushCacheUnit *unit;
+
g_return_if_fail (GIMP_IS_BRUSH_CACHE (cache));
g_return_if_fail (data != NULL);
- if (data == cache->last_data)
- return;
+ for (iter = cache->cached_units; iter; iter = g_list_next (iter))
+ {
+ unit = iter->data;
+ if (data == unit->data)
+ return;
+ }
+
+ if (g_list_length (cache->cached_units) > MAX_CACHED_DATA &&
+ (iter = g_list_last (cache->cached_units)))
+ {
+ unit = iter->data;
+
+ cache->data_destroy (unit->data);
+ cache->cached_units = g_list_delete_link (cache->cached_units, iter);
+ }
+
+ unit = g_new (GimpBrushCacheUnit, 1);
- if (cache->last_data)
- cache->data_destroy (cache->last_data);
+ unit->data = data;
+ unit->width = width;
+ unit->height = height;
+ unit->scale = scale;
+ unit->aspect_ratio = aspect_ratio;
+ unit->angle = angle;
+ unit->hardness = hardness;
+ unit->op = op;
- cache->last_data = data;
- cache->last_width = width;
- cache->last_height = height;
- cache->last_scale = scale;
- cache->last_aspect_ratio = aspect_ratio;
- cache->last_angle = angle;
- cache->last_hardness = hardness;
+ cache->cached_units = g_list_prepend (cache->cached_units, unit);
}
diff --git a/app/core/gimpbrushcache.h b/app/core/gimpbrushcache.h
index 5c879cc..6fa48ec 100644
--- a/app/core/gimpbrushcache.h
+++ b/app/core/gimpbrushcache.h
@@ -41,13 +41,7 @@ struct _GimpBrushCache
GDestroyNotify data_destroy;
- gpointer last_data;
- gint last_width;
- gint last_height;
- gdouble last_scale;
- gdouble last_aspect_ratio;
- gdouble last_angle;
- gdouble last_hardness;
+ GList *cached_units;
gchar debug_hit;
gchar debug_miss;
@@ -68,6 +62,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 +71,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 daca6e6..b29b283 100644
--- a/app/core/gimpcontext.c
+++ b/app/core/gimpcontext.c
@@ -43,14 +43,18 @@
#include "gimpimagefile.h"
#include "gimpgradient.h"
#include "gimpimage.h"
+#include "gimpimage-symmetry.h"
#include "gimpmarshal.h"
#include "gimppaintinfo.h"
#include "gimppalette.h"
#include "gimppattern.h"
+#include "gimpsymmetry.h"
#include "gimptemplate.h"
#include "gimptoolinfo.h"
#include "gimptoolpreset.h"
+#include "paint/gimppaintoptions.h"
+
#include "text/gimpfont.h"
#include "gimp-intl.h"
@@ -1923,6 +1927,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;
+ GimpSymmetry *sym = NULL;
+
+ paint_options = GIMP_PAINT_OPTIONS (context->tool_info->tool_options);
+
+ if (image)
+ sym = gimp_image_symmetry_selected (image);
+
+ g_object_set (paint_options, "symmetry",
+ sym ? sym->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..5fc38eb 100644
--- a/app/core/gimpimage-private.h
+++ b/app/core/gimpimage-private.h
@@ -87,6 +87,9 @@ struct _GimpImagePrivate
GeglNode *graph; /* GEGL projection graph */
GeglNode *visible_mask; /* component visibility node */
+ GList *symmetries; /* Painting symmetries */
+ GimpSymmetry *selected_symmetry; /* Selected symmetry */
+
GList *guides; /* guides */
GimpGrid *grid; /* grid */
GList *sample_points; /* color sample points */
diff --git a/app/core/gimpimage-symmetry.c b/app/core/gimpimage-symmetry.c
new file mode 100644
index 0000000..1c93bfa
--- /dev/null
+++ b/app/core/gimpimage-symmetry.c
@@ -0,0 +1,210 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpimage-symmetry.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 "core-types.h"
+
+#include "gimpsymmetry.h"
+#include "gimpimage.h"
+#include "gimpimage-private.h"
+#include "gimpimage-symmetry.h"
+#include "gimpsymmetry-mirror.h"
+
+static GimpSymmetry * gimp_image_symmetry_new (GimpImage *image,
+ GType type);
+
+/*** Private Functions ***/
+
+static GimpSymmetry *
+gimp_image_symmetry_new (GimpImage *image,
+ GType type)
+{
+ GimpSymmetry *sym = NULL;
+
+ if (type != G_TYPE_NONE)
+ {
+ sym = g_object_new (type,
+ "image", image,
+ NULL);
+ sym->type = type;
+ }
+
+ return sym;
+}
+
+/*** Public Functions ***/
+
+GList *
+gimp_image_symmetry_list (void)
+{
+ GList *list = NULL;
+
+ list = g_list_prepend (list, GINT_TO_POINTER (GIMP_TYPE_MIRROR));
+ return list;
+}
+
+/**
+ * gimp_image_symmetry_add:
+ * @image: the #GimpImage
+ * @type: the #GType of the symmetry
+ *
+ * Add a symmetry of type @type to @image and make it the
+ * selected transformation.
+ **/
+GimpSymmetry *
+gimp_image_symmetry_add (GimpImage *image,
+ GType type)
+{
+ GimpImagePrivate *private;
+ GimpSymmetry *sym;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+ g_return_val_if_fail (g_type_is_a (type, GIMP_TYPE_SYMMETRY), NULL);
+
+
+ sym = gimp_image_symmetry_new (image, type);
+
+ private = GIMP_IMAGE_GET_PRIVATE (image);
+
+ private->symmetries = g_list_prepend (private->symmetries,
+ sym);
+ private->selected_symmetry = sym;
+
+ return sym;
+}
+
+/**
+ * gimp_image_symmetry_remove:
+ * @image: the #GimpImage
+ * @sym: the #GimpSymmetry
+ *
+ * Remove @sym from the list of symmetries of @image.
+ * If it was the selected transformation, unselect it first.
+ **/
+void
+gimp_image_symmetry_remove (GimpImage *image,
+ GimpSymmetry *sym)
+{
+ GimpImagePrivate *private;
+
+ g_return_if_fail (GIMP_IS_SYMMETRY (sym));
+ g_return_if_fail (GIMP_IS_IMAGE (image));
+
+ private = GIMP_IMAGE_GET_PRIVATE (image);
+
+ if (private->selected_symmetry == sym)
+ private->selected_symmetry = NULL;
+ private->symmetries = g_list_remove (private->symmetries,
+ sym);
+ g_object_unref (sym);
+}
+
+/**
+ * gimp_image_symmetry_get:
+ * @image: the #GimpImage
+ *
+ * Returns the list of #GimpSymmetry set on @image.
+ * The returned list belongs to @image and should not be freed.
+ **/
+GList *
+gimp_image_symmetry_get (GimpImage *image)
+{
+ GimpImagePrivate *private;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), FALSE);
+
+ private = GIMP_IMAGE_GET_PRIVATE (image);
+
+ return private->symmetries;
+}
+
+/**
+ * gimp_image_symmetry_select:
+ * @image: the #GimpImage
+ * @type: the #GType of the symmetry
+ *
+ * Select the symmetry 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 symmetry was found.
+ **/
+gboolean
+gimp_image_symmetry_select (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_symmetry = NULL;
+ return TRUE;
+ }
+ else
+ {
+ for (iter = private->symmetries; iter; iter = g_list_next (iter))
+ {
+ GimpSymmetry *sym = iter->data;
+ if (g_type_is_a (sym->type, type))
+ {
+ private->selected_symmetry = iter->data;
+ return TRUE;
+ }
+ }
+ }
+ return FALSE;
+}
+
+/**
+ * gimp_image_symmetry_selected:
+ * @image: the #GimpImage
+ *
+ * Returns the #GimpSymmetry transformation selected on @image.
+ **/
+GimpSymmetry *
+gimp_image_symmetry_selected (GimpImage *image)
+{
+ static GimpImage *last_image = NULL;
+ static GimpSymmetry *identity = NULL;
+ GimpImagePrivate *private;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), FALSE);
+
+ if (last_image != image)
+ {
+ if (identity)
+ g_object_unref (identity);
+ identity = gimp_image_symmetry_new (image,
+ GIMP_TYPE_SYMMETRY);
+ }
+
+ private = GIMP_IMAGE_GET_PRIVATE (image);
+
+ return private->selected_symmetry ? private->selected_symmetry : identity;
+}
diff --git a/app/core/gimpimage-symmetry.h b/app/core/gimpimage-symmetry.h
new file mode 100644
index 0000000..b9dacc9
--- /dev/null
+++ b/app/core/gimpimage-symmetry.h
@@ -0,0 +1,36 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpimage-symmetry.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_IMAGE_SYMMETRY_H__
+#define __GIMP_IMAGE_SYMMETRY_H__
+
+GList * gimp_image_symmetry_list (void);
+
+GimpSymmetry * gimp_image_symmetry_add (GimpImage *image,
+ GType type);
+void gimp_image_symmetry_remove (GimpImage *image,
+ GimpSymmetry *sym);
+GList * gimp_image_symmetry_get (GimpImage *image);
+
+gboolean gimp_image_symmetry_select (GimpImage *image,
+ GType type);
+GimpSymmetry * gimp_image_symmetry_selected (GimpImage *image);
+
+#endif /* __GIMP_IMAGE_SYMMETRY_H__ */
diff --git a/app/core/gimpimage.c b/app/core/gimpimage.c
index ac44c29..1cfb594 100644
--- a/app/core/gimpimage.c
+++ b/app/core/gimpimage.c
@@ -55,6 +55,7 @@
#include "gimpimage-preview.h"
#include "gimpimage-private.h"
#include "gimpimage-quick-mask.h"
+#include "gimpimage-symmetry.h"
#include "gimpimage-undo.h"
#include "gimpimage-undo-push.h"
#include "gimpitemtree.h"
@@ -68,6 +69,7 @@
#include "gimpprojection.h"
#include "gimpsamplepoint.h"
#include "gimpselection.h"
+#include "gimpsymmetry.h"
#include "gimptempbuf.h"
#include "gimptemplate.h"
#include "gimpundostack.h"
@@ -697,6 +699,9 @@ gimp_image_init (GimpImage *image)
private->projection = gimp_projection_new (GIMP_PROJECTABLE (image));
+ private->symmetries = NULL;
+ private->selected_symmetry = NULL;
+
private->guides = NULL;
private->grid = NULL;
private->sample_points = NULL;
@@ -1051,6 +1056,12 @@ gimp_image_finalize (GObject *object)
private->guides = NULL;
}
+ if (private->symmetries)
+ {
+ g_list_free_full (private->symmetries, g_object_unref);
+ private->symmetries = NULL;
+ }
+
if (private->grid)
{
g_object_unref (private->grid);
@@ -2281,9 +2292,12 @@ gimp_image_get_xcf_version (GimpImage *image,
gint *gimp_version,
const gchar **version_string)
{
- GList *layers;
- GList *list;
- gint version = 0; /* default to oldest */
+ GList *layers;
+ 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))
@@ -2333,6 +2347,12 @@ gimp_image_get_xcf_version (GimpImage *image,
if (zlib_compression)
version = MAX (8, version);
+ /* need version 9 for symmetry */
+ if (private->symmetries)
+ {
+ version = MAX (9, version);
+ }
+
switch (version)
{
case 0:
@@ -4571,3 +4591,4 @@ gimp_image_invalidate_previews (GimpImage *image)
gimp_item_stack_invalidate_previews (layers);
gimp_item_stack_invalidate_previews (channels);
}
+
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..d464443
--- /dev/null
+++ b/app/core/gimpmirrorguide.h
@@ -0,0 +1,57 @@
+/* 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;
+};
+
+
+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/core/gimpsymmetry-mirror.c b/app/core/gimpsymmetry-mirror.c
new file mode 100644
index 0000000..96585ad
--- /dev/null
+++ b/app/core/gimpsymmetry-mirror.c
@@ -0,0 +1,732 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpsymmetry-mirror.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 "core-types.h"
+
+#include "gimp.h"
+#include "gimpbrush.h"
+#include "gimpmirrorguide.h"
+#include "gimpimage.h"
+#include "gimpimage-guides.h"
+#include "gimpimage-symmetry.h"
+#include "gimpitem.h"
+#include "gimpsymmetry-mirror.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 (GimpSymmetry *mirror,
+ GimpDrawable *drawable,
+ GimpCoords *origin);
+static void gimp_mirror_prepare_operations (GimpMirror *mirror,
+ gint paint_width,
+ gint paint_height);
+static GeglNode * gimp_mirror_get_operation (GimpSymmetry *mirror,
+ gint stroke,
+ gint paint_width,
+ gint paint_height);
+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 (GimpSymmetry *sym,
+ gint *n_settings);
+static GParamSpec ** gimp_mirror_get_xcf_settings (GimpSymmetry *sym,
+ gint *n_settings);
+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_SYMMETRY)
+
+#define parent_class gimp_mirror_parent_class
+
+static void
+gimp_mirror_class_init (GimpMirrorClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpSymmetryClass *symmetry_class = GIMP_SYMMETRY_CLASS (klass);
+
+ object_class->finalize = gimp_mirror_finalize;
+ object_class->set_property = gimp_mirror_set_property;
+ object_class->get_property = gimp_mirror_get_property;
+
+ symmetry_class->label = _("Mirror");
+ symmetry_class->update_strokes = gimp_mirror_update_strokes;
+ symmetry_class->get_operation = gimp_mirror_get_operation;
+ symmetry_class->get_settings = gimp_mirror_get_settings;
+ symmetry_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)
+{
+}
+
+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;
+
+ if (mirror->horizontal_op)
+ g_object_unref (mirror->horizontal_op);
+ mirror->horizontal_op = NULL;
+
+ if (mirror->vertical_op)
+ g_object_unref (mirror->vertical_op);
+ mirror->vertical_op = NULL;
+
+ if (mirror->central_op)
+ g_object_unref (mirror->central_op);
+ mirror->central_op = 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 (GimpSymmetry *sym,
+ GimpDrawable *drawable,
+ GimpCoords *origin)
+{
+ GList *strokes = NULL;
+ GimpMirror *mirror = GIMP_MIRROR (sym);
+ GimpCoords *coords;
+
+ g_list_free_full (sym->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);
+ }
+ sym->strokes = g_list_reverse (strokes);
+
+ g_signal_emit_by_name (sym, "strokes-updated", sym->image);
+}
+
+static void gimp_mirror_prepare_operations (GimpMirror *mirror,
+ gint paint_width,
+ gint paint_height)
+{
+ if (paint_width == mirror->last_paint_width &&
+ paint_height == mirror->last_paint_height)
+ return;
+
+ mirror->last_paint_width = paint_width;
+ mirror->last_paint_height = paint_height;
+
+ if (mirror->horizontal_op)
+ g_object_unref (mirror->horizontal_op);
+
+ mirror->horizontal_op = gegl_node_new_child (NULL,
+ "operation", "gegl:reflect",
+ "origin-x", 0.0,
+ "origin-y",
+ (gdouble) paint_height / 2.0,
+ "x",
+ 1.0,
+ "y",
+ 0.0,
+ NULL);
+
+ if (mirror->vertical_op)
+ g_object_unref (mirror->vertical_op);
+
+ mirror->vertical_op = gegl_node_new_child (NULL,
+ "operation", "gegl:reflect",
+ "origin-x",
+ (gdouble) paint_width / 2.0,
+ "origin-y", 0.0,
+ "x",
+ 0.0,
+ "y",
+ 1.0,
+ NULL);
+
+ if (mirror->central_op)
+ g_object_unref (mirror->central_op);
+
+ mirror->central_op = gegl_node_new_child (NULL,
+ "operation", "gegl:rotate",
+ "origin-x",
+ (gdouble) paint_width / 2.0,
+ "origin-y",
+ (gdouble) paint_height / 2.0,
+ "degrees",
+ 180.0,
+ NULL);
+}
+
+static GeglNode *
+gimp_mirror_get_operation (GimpSymmetry *sym,
+ gint stroke,
+ gint paint_width,
+ gint paint_height)
+{
+ GimpMirror *mirror = GIMP_MIRROR (sym);
+ GeglNode *op;
+
+ g_return_val_if_fail (stroke >= 0 &&
+ stroke < g_list_length (sym->strokes), NULL);
+
+ gimp_mirror_prepare_operations (mirror, paint_width, paint_height);
+
+ if (mirror->disable_transformation || stroke == 0 ||
+ paint_width == 0 || paint_height == 0)
+ op = NULL;
+ else if (stroke == 1 && mirror->horizontal_mirror)
+ op = g_object_ref (mirror->horizontal_op);
+ else if ((stroke == 2 && mirror->horizontal_mirror &&
+ mirror->vertical_mirror) ||
+ (stroke == 1 && mirror->vertical_mirror &&
+ ! mirror->horizontal_mirror))
+ op = g_object_ref (mirror->vertical_op);
+ else
+ op = g_object_ref (mirror->central_op);
+
+ return op;
+}
+
+static void
+gimp_mirror_reset (GimpMirror *mirror)
+{
+ GimpSymmetry *sym;
+
+ g_return_if_fail (GIMP_IS_MIRROR (mirror));
+
+ sym = GIMP_SYMMETRY (mirror);
+
+ if (sym->origin)
+ {
+ gimp_symmetry_set_origin (sym, sym->drawable,
+ sym->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)
+ {
+ GimpSymmetry *sym;
+
+ sym = GIMP_SYMMETRY (mirror);
+ gimp_image_symmetry_remove (sym->image,
+ GIMP_SYMMETRY (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 (GimpSymmetry *sym,
+ gint *n_settings)
+{
+ GParamSpec **pspecs;
+
+ *n_settings = 5;
+ pspecs = g_new (GParamSpec*, 5);
+
+ pspecs[0] = g_object_class_find_property (G_OBJECT_GET_CLASS (sym),
+ "horizontal-symmetry");
+ pspecs[1] = g_object_class_find_property (G_OBJECT_GET_CLASS (sym),
+ "vertical-symmetry");
+ pspecs[2] = g_object_class_find_property (G_OBJECT_GET_CLASS (sym),
+ "point-symmetry");
+ pspecs[3] = NULL;
+ pspecs[4] = g_object_class_find_property (G_OBJECT_GET_CLASS (sym),
+ "disable-transformation");
+
+ return pspecs;
+}
+
+static GParamSpec **
+gimp_mirror_get_xcf_settings (GimpSymmetry *sym,
+ gint *n_settings)
+{
+ GParamSpec **pspecs;
+
+ *n_settings = 6;
+ pspecs = g_new (GParamSpec*, 6);
+
+ pspecs[0] = g_object_class_find_property (G_OBJECT_GET_CLASS (sym),
+ "horizontal-symmetry");
+ pspecs[1] = g_object_class_find_property (G_OBJECT_GET_CLASS (sym),
+ "vertical-symmetry");
+ pspecs[2] = g_object_class_find_property (G_OBJECT_GET_CLASS (sym),
+ "point-symmetry");
+ pspecs[3] = g_object_class_find_property (G_OBJECT_GET_CLASS (sym),
+ "horizontal-position");
+ pspecs[4] = g_object_class_find_property (G_OBJECT_GET_CLASS (sym),
+ "vertical-position");
+ pspecs[5] = g_object_class_find_property (G_OBJECT_GET_CLASS (sym),
+ "disable-transformation");
+
+ return pspecs;
+}
+
+static void
+gimp_mirror_set_horizontal_symmetry (GimpMirror *mirror,
+ gboolean active)
+{
+ GimpSymmetry *sym;
+ GimpImage *image;
+
+ g_return_if_fail (GIMP_IS_MIRROR (mirror));
+
+ sym = GIMP_SYMMETRY (mirror);
+ image = sym->image;
+
+ if (active == mirror->horizontal_mirror)
+ return;
+
+ mirror->horizontal_mirror = active;
+
+ if (active && ! 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. */
+ if (mirror->horizontal_position < 1.0)
+ 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;
+
+ 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 (! active && ! mirror->point_symmetry)
+ {
+ 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);
+ g_object_unref (mirror->horizontal_guide);
+ mirror->horizontal_guide = NULL;
+ }
+
+ gimp_mirror_reset (mirror);
+}
+
+static void
+gimp_mirror_set_vertical_symmetry (GimpMirror *mirror,
+ gboolean active)
+{
+ GimpSymmetry *sym;
+ GimpImage *image;
+
+ g_return_if_fail (GIMP_IS_MIRROR (mirror));
+
+ sym = GIMP_SYMMETRY (mirror);
+ image = sym->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. */
+ if (mirror->vertical_position < 1.0)
+ 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;
+
+ 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)
+ {
+ 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);
+ g_object_unref (mirror->vertical_guide);
+ mirror->vertical_guide = NULL;
+ }
+
+ gimp_mirror_reset (mirror);
+}
+
+static void
+gimp_mirror_set_point_symmetry (GimpMirror *mirror,
+ gboolean active)
+{
+ GimpSymmetry *sym;
+ GimpImage *image;
+
+ g_return_if_fail (GIMP_IS_MIRROR (mirror));
+
+ sym = GIMP_SYMMETRY (mirror);
+ image = sym->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. */
+ if (mirror->horizontal_position < 1.0)
+ 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;
+
+
+ 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. */
+ if (mirror->vertical_position < 1.0)
+ 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;
+
+ 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);
+ g_object_unref (mirror->horizontal_guide);
+ mirror->horizontal_guide = NULL;
+ }
+ /* 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);
+ g_object_unref (mirror->vertical_guide);
+ mirror->vertical_guide = NULL;
+ }
+ }
+
+ gimp_mirror_reset (mirror);
+}
diff --git a/app/core/gimpsymmetry-mirror.h b/app/core/gimpsymmetry-mirror.h
new file mode 100644
index 0000000..abe6e51
--- /dev/null
+++ b/app/core/gimpsymmetry-mirror.h
@@ -0,0 +1,69 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpsymmetry-mirror.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 "gimpsymmetry.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
+{
+ GimpSymmetry 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;
+
+ /* Cached data */
+ gint last_paint_width;
+ gint last_paint_height;
+ GeglNode *horizontal_op;
+ GeglNode *vertical_op;
+ GeglNode *central_op;
+};
+
+struct _GimpMirrorClass
+{
+ GimpSymmetryClass parent_class;
+};
+
+
+GType gimp_mirror_get_type (void) G_GNUC_CONST;
+
+#endif /* __GIMP_MIRROR_H__ */
+
+
diff --git a/app/core/gimpsymmetry.c b/app/core/gimpsymmetry.c
new file mode 100644
index 0000000..5c0eb4c
--- /dev/null
+++ b/app/core/gimpsymmetry.c
@@ -0,0 +1,370 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpsymmetry.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 "core-types.h"
+
+#include "gimpdrawable.h"
+#include "gimpimage.h"
+#include "gimpitem.h"
+#include "gimpsymmetry.h"
+
+#include "gimp-intl.h"
+
+enum
+{
+ STROKES_UPDATED,
+ UPDATE_UI,
+ LAST_SIGNAL
+};
+
+enum
+{
+ PROP_0,
+ PROP_IMAGE,
+};
+
+/* Local function prototypes */
+
+static void gimp_symmetry_finalize (GObject *object);
+static void gimp_symmetry_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_symmetry_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+static void
+ gimp_symmetry_real_update_strokes (GimpSymmetry *sym,
+ GimpDrawable *drawable,
+ GimpCoords *origin);
+static GeglNode *
+ gimp_symmetry_real_get_op (GimpSymmetry *sym,
+ gint stroke,
+ gint paint_width,
+ gint paint_height);
+static GParamSpec **
+ gimp_symmetry_real_get_settings (GimpSymmetry *sym,
+ gint *n_properties);
+
+G_DEFINE_TYPE (GimpSymmetry, gimp_symmetry, GIMP_TYPE_OBJECT)
+
+#define parent_class gimp_symmetry_parent_class
+
+static guint gimp_symmetry_signals[LAST_SIGNAL] = { 0 };
+
+static void
+gimp_symmetry_class_init (GimpSymmetryClass *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_symmetry_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_symmetry_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_symmetry_finalize;
+ object_class->set_property = gimp_symmetry_set_property;
+ object_class->get_property = gimp_symmetry_get_property;
+
+ klass->label = _("None");
+ klass->update_strokes = gimp_symmetry_real_update_strokes;
+ klass->get_operation = gimp_symmetry_real_get_op;
+ klass->get_settings = gimp_symmetry_real_get_settings;
+ klass->get_xcf_settings = gimp_symmetry_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_symmetry_init (GimpSymmetry *sym)
+{
+ sym->type = G_TYPE_NONE;
+}
+
+static void
+gimp_symmetry_finalize (GObject *object)
+{
+ GimpSymmetry *sym = GIMP_SYMMETRY (object);
+
+ if (sym->drawable)
+ g_object_unref (sym->drawable);
+
+ g_free (sym->origin);
+ sym->origin = NULL;
+
+ g_list_free_full (sym->strokes, g_free);
+ sym->strokes = NULL;
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+gimp_symmetry_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpSymmetry *sym = GIMP_SYMMETRY (object);
+
+ switch (property_id)
+ {
+ case PROP_IMAGE:
+ sym->image = g_value_get_object (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_symmetry_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpSymmetry *sym = GIMP_SYMMETRY (object);
+
+ switch (property_id)
+ {
+ case PROP_IMAGE:
+ g_value_set_object (value, sym->image);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_symmetry_real_update_strokes (GimpSymmetry *sym,
+ GimpDrawable *drawable,
+ GimpCoords *origin)
+{
+ /* The basic symmetry just uses the origin as is. */
+ sym->strokes = g_list_prepend (sym->strokes,
+ g_memdup (origin, sizeof (GimpCoords)));
+}
+
+static GeglNode *
+gimp_symmetry_real_get_op (GimpSymmetry *sym,
+ gint stroke,
+ gint paint_width,
+ gint paint_height)
+{
+ /* The basic symmetry just returns NULL, since no transformation of the
+ * brush painting happen. */
+ return NULL;
+}
+
+static GParamSpec **
+gimp_symmetry_real_get_settings (GimpSymmetry *sym,
+ gint *n_properties)
+{
+ *n_properties = 0;
+
+ return NULL;
+}
+
+/***** Public Functions *****/
+
+/**
+ * gimp_symmetry_set_origin:
+ * @sym: the #GimpSymmetry
+ * @drawable: the #GimpDrawable where painting will happen
+ * @origin: new base coordinates.
+ *
+ * Set the symmetry to new origin coordinates and drawable.
+ **/
+void
+gimp_symmetry_set_origin (GimpSymmetry *sym,
+ GimpDrawable *drawable,
+ GimpCoords *origin)
+{
+ g_return_if_fail (GIMP_IS_SYMMETRY (sym));
+ g_return_if_fail (GIMP_IS_DRAWABLE (drawable));
+ g_return_if_fail (gimp_item_get_image (GIMP_ITEM (drawable)) == sym->image);
+
+ if (drawable != sym->drawable)
+ {
+ if (sym->drawable)
+ g_object_unref (sym->drawable);
+ sym->drawable = g_object_ref (drawable);
+ }
+
+ if (origin != sym->origin)
+ {
+ g_free (sym->origin);
+ sym->origin = g_memdup (origin, sizeof (GimpCoords));
+ }
+
+ g_list_free_full (sym->strokes, g_free);
+ sym->strokes = NULL;
+
+ GIMP_SYMMETRY_GET_CLASS (sym)->update_strokes (sym,
+ drawable,
+ origin);
+}
+
+/**
+ * gimp_symmetry_get_origin:
+ * @sym: the #GimpSymmetry
+ *
+ * Returns the origin stroke coordinates.
+ * The returned value is owned by the #GimpSymmetry and must not be freed.
+ **/
+GimpCoords *
+gimp_symmetry_get_origin (GimpSymmetry *sym)
+{
+ g_return_val_if_fail (GIMP_IS_SYMMETRY (sym), NULL);
+
+ return sym->origin;
+}
+
+/**
+ * gimp_symmetry_get_size:
+ * @sym: the #GimpSymmetry
+ *
+ * Returns the total number of strokes.
+ **/
+gint
+gimp_symmetry_get_size (GimpSymmetry *sym)
+{
+ g_return_val_if_fail (GIMP_IS_SYMMETRY (sym), 0);
+
+ return g_list_length (sym->strokes);
+}
+
+/**
+ * gimp_symmetry_get_coords:
+ * @sym: the #GimpSymmetry
+ * @stroke: the stroke number
+ *
+ * Returns the coordinates of the stroke number @stroke.
+ * The returned value is owned by the #GimpSymmetry and must not be freed.
+ **/
+GimpCoords *
+gimp_symmetry_get_coords (GimpSymmetry *sym,
+ gint stroke)
+{
+ g_return_val_if_fail (GIMP_IS_SYMMETRY (sym), NULL);
+
+ return g_list_nth_data (sym->strokes, stroke);
+}
+
+/**
+ * gimp_symmetry_get_operation:
+ * @sym: the #GimpSymmetry
+ * @stroke: the stroke number
+ * @paint_width: the width of the painting area
+ * @paint_height: the height of the painting area
+ *
+ * Returns the operation to apply to the paint buffer for stroke number @stroke.
+ * NULL means to copy the original stroke as-is.
+ **/
+GeglNode *
+gimp_symmetry_get_operation (GimpSymmetry *sym,
+ gint stroke,
+ gint paint_width,
+ gint paint_height)
+{
+ g_return_val_if_fail (GIMP_IS_SYMMETRY (sym), NULL);
+
+ return GIMP_SYMMETRY_GET_CLASS (sym)->get_operation (sym,
+ stroke,
+ paint_width,
+ paint_height);
+}
+
+/**
+ * gimp_symmetry_get_settings:
+ * @sym: the #GimpSymmetry
+ * @n_properties: the number of properties in the returned array
+ *
+ * Returns an array of the symmetry properties which are supposed to
+ * be settable by the user.
+ * The returned array must be freed by the caller.
+ **/
+GParamSpec **
+gimp_symmetry_get_settings (GimpSymmetry *sym,
+ gint *n_properties)
+{
+ g_return_val_if_fail (GIMP_IS_SYMMETRY (sym), NULL);
+
+ return GIMP_SYMMETRY_GET_CLASS (sym)->get_settings (sym,
+ n_properties);
+}
+
+/**
+ * gimp_symmetry_get_xcf_settings:
+ * @sym: the #GimpSymmetry
+ * @n_properties: the number of properties in the returned array
+ *
+ * Returns an array of the symmetry properties which are to be serialized
+ * when saving to XCF.
+ * These properties may be different to `gimp_symmetry_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_symmetry_get_xcf_settings (GimpSymmetry *sym,
+ gint *n_properties)
+{
+ g_return_val_if_fail (GIMP_IS_SYMMETRY (sym), NULL);
+
+ return GIMP_SYMMETRY_GET_CLASS (sym)->get_xcf_settings (sym,
+ n_properties);
+}
diff --git a/app/core/gimpsymmetry.h b/app/core/gimpsymmetry.h
new file mode 100644
index 0000000..44f1d8c
--- /dev/null
+++ b/app/core/gimpsymmetry.h
@@ -0,0 +1,92 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpsymmetry.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_SYMMETRY_H__
+#define __GIMP_SYMMETRY_H__
+
+
+#include "gimpobject.h"
+
+
+#define GIMP_TYPE_SYMMETRY (gimp_symmetry_get_type ())
+#define GIMP_SYMMETRY(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_SYMMETRY, GimpSymmetry))
+#define GIMP_SYMMETRY_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_SYMMETRY,
GimpSymmetryClass))
+#define GIMP_IS_SYMMETRY(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_SYMMETRY))
+#define GIMP_IS_SYMMETRY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_SYMMETRY))
+#define GIMP_SYMMETRY_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_SYMMETRY,
GimpSymmetryClass))
+
+typedef struct _GimpSymmetryClass GimpSymmetryClass;
+
+struct _GimpSymmetry
+{
+ GimpObject parent_instance;
+
+ GimpImage *image;
+ GimpDrawable *drawable;
+ GimpCoords *origin;
+
+ GList *strokes;
+
+ GType type;
+};
+
+struct _GimpSymmetryClass
+{
+ GimpObjectClass parent_class;
+
+ const gchar * label;
+
+ /* Virtual functions */
+ void (* update_strokes) (GimpSymmetry *sym,
+ GimpDrawable *drawable,
+ GimpCoords *origin);
+ GeglNode * (* get_operation) (GimpSymmetry *sym,
+ gint stroke,
+ gint paint_width,
+ gint paint_height);
+ GParamSpec **
+ (* get_settings) (GimpSymmetry *sym,
+ gint *n_properties);
+ GParamSpec **
+ (* get_xcf_settings) (GimpSymmetry *sym,
+ gint *n_properties);
+};
+
+
+GType gimp_symmetry_get_type (void) G_GNUC_CONST;
+
+void gimp_symmetry_set_origin (GimpSymmetry *sym,
+ GimpDrawable *drawable,
+ GimpCoords *origin);
+
+GimpCoords * gimp_symmetry_get_origin (GimpSymmetry *sym);
+gint gimp_symmetry_get_size (GimpSymmetry *sym);
+GimpCoords * gimp_symmetry_get_coords (GimpSymmetry *sym,
+ gint stroke);
+GeglNode * gimp_symmetry_get_operation (GimpSymmetry *sym,
+ gint stroke,
+ gint paint_width,
+ gint paint_height);
+GParamSpec ** gimp_symmetry_get_settings (GimpSymmetry *sym,
+ gint *n_properties);
+GParamSpec ** gimp_symmetry_get_xcf_settings (GimpSymmetry *sym,
+ gint *n_properties);
+
+#endif /* __GIMP_SYMMETRY_H__ */
diff --git a/app/dialogs/preferences-dialog.c b/app/dialogs/preferences-dialog.c
index efecff0..8a7de20 100644
--- a/app/dialogs/preferences-dialog.c
+++ b/app/dialogs/preferences-dialog.c
@@ -1609,6 +1609,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-symmetry",
+ _("_Symmetry 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/gimpairbrush.c b/app/paint/gimpairbrush.c
index 725b5a0..29dcc85 100644
--- a/app/paint/gimpairbrush.c
+++ b/app/paint/gimpairbrush.c
@@ -28,6 +28,7 @@
#include "core/gimpdynamics.h"
#include "core/gimpgradient.h"
#include "core/gimpimage.h"
+#include "core/gimpsymmetry.h"
#include "gimpairbrush.h"
#include "gimpairbrushoptions.h"
@@ -40,13 +41,13 @@ static void gimp_airbrush_finalize (GObject *object);
static void gimp_airbrush_paint (GimpPaintCore *paint_core,
GimpDrawable *drawable,
GimpPaintOptions *paint_options,
- const GimpCoords *coords,
+ GimpSymmetry *sym,
GimpPaintState paint_state,
guint32 time);
static void gimp_airbrush_motion (GimpPaintCore *paint_core,
GimpDrawable *drawable,
GimpPaintOptions *paint_options,
- const GimpCoords *coords);
+ GimpSymmetry *sym);
static gboolean gimp_airbrush_timeout (gpointer data);
@@ -82,6 +83,7 @@ static void
gimp_airbrush_init (GimpAirbrush *airbrush)
{
airbrush->timeout_id = 0;
+ airbrush->sym = NULL;
}
static void
@@ -95,6 +97,9 @@ gimp_airbrush_finalize (GObject *object)
airbrush->timeout_id = 0;
}
+ if (airbrush->sym)
+ g_object_unref (airbrush->sym);
+
G_OBJECT_CLASS (parent_class)->finalize (object);
}
@@ -102,7 +107,7 @@ static void
gimp_airbrush_paint (GimpPaintCore *paint_core,
GimpDrawable *drawable,
GimpPaintOptions *paint_options,
- const GimpCoords *coords,
+ GimpSymmetry *sym,
GimpPaintState paint_state,
guint32 time)
{
@@ -121,7 +126,7 @@ gimp_airbrush_paint (GimpPaintCore *paint_core,
GIMP_PAINT_CORE_CLASS (parent_class)->paint (paint_core, drawable,
paint_options,
- coords,
+ sym,
paint_state, time);
break;
@@ -132,14 +137,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, sym);
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 +153,13 @@ gimp_airbrush_paint (GimpPaintCore *paint_core,
airbrush->drawable = drawable;
airbrush->paint_options = paint_options;
+ if (airbrush->sym)
+ g_object_unref (airbrush->sym);
+ airbrush->sym = g_object_ref (sym);
+
+ /* Base our timeout on the original stroke. */
+ coords = gimp_symmetry_get_origin (sym);
+
dynamic_rate = gimp_dynamics_get_linear_value (dynamics,
GIMP_DYNAMICS_OUTPUT_RATE,
coords,
@@ -170,7 +183,7 @@ gimp_airbrush_paint (GimpPaintCore *paint_core,
GIMP_PAINT_CORE_CLASS (parent_class)->paint (paint_core, drawable,
paint_options,
- coords,
+ sym,
paint_state, time);
break;
}
@@ -180,7 +193,7 @@ static void
gimp_airbrush_motion (GimpPaintCore *paint_core,
GimpDrawable *drawable,
GimpPaintOptions *paint_options,
- const GimpCoords *coords)
+ GimpSymmetry *sym)
{
GimpAirbrushOptions *options = GIMP_AIRBRUSH_OPTIONS (paint_options);
@@ -188,10 +201,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_symmetry_get_origin (sym);
+
opacity = (options->flow / 100.0 *
gimp_dynamics_get_linear_value (dynamics,
GIMP_DYNAMICS_OUTPUT_FLOW,
@@ -199,21 +215,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,
+ sym, 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->sym,
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..a3f9a8a 100644
--- a/app/paint/gimpairbrush.h
+++ b/app/paint/gimpairbrush.h
@@ -37,6 +37,8 @@ struct _GimpAirbrush
GimpPaintbrush parent_instance;
guint timeout_id;
+
+ GimpSymmetry *sym;
GimpDrawable *drawable;
GimpPaintOptions *paint_options;
};
diff --git a/app/paint/gimpbrushcore.c b/app/paint/gimpbrushcore.c
index 9201643..aaeeb8e 100644
--- a/app/paint/gimpbrushcore.c
+++ b/app/paint/gimpbrushcore.c
@@ -84,7 +84,9 @@ static GeglBuffer * gimp_brush_core_get_paint_buffer(GimpPaintCore *paint_cor
GimpPaintOptions *paint_options,
const GimpCoords *coords,
gint *paint_buffer_x,
- gint *paint_buffer_y);
+ gint *paint_buffer_y,
+ gint *paint_width,
+ gint *paint_height);
static void gimp_brush_core_real_set_brush (GimpBrushCore *core,
GimpBrush *brush);
@@ -109,10 +111,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);
@@ -811,7 +815,9 @@ gimp_brush_core_get_paint_buffer (GimpPaintCore *paint_core,
GimpPaintOptions *paint_options,
const GimpCoords *coords,
gint *paint_buffer_x,
- gint *paint_buffer_y)
+ gint *paint_buffer_y,
+ gint *paint_width,
+ gint *paint_height)
{
GimpBrushCore *core = GIMP_BRUSH_CORE (paint_core);
gint x, y;
@@ -831,6 +837,11 @@ gimp_brush_core_get_paint_buffer (GimpPaintCore *paint_core,
core->scale, core->aspect_ratio, core->angle,
&brush_width, &brush_height);
+ if (paint_width)
+ *paint_width = brush_width;
+ if (paint_height)
+ *paint_height = brush_height;
+
/* adjust the x and y coordinates to the upper left corner of the brush */
x = (gint) floor (coords->x) - (brush_width / 2);
y = (gint) floor (coords->y) - (brush_height / 2);
@@ -960,11 +971,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 +1015,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 +1420,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 +1429,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 +1447,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 +1456,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 +1474,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 +1609,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 +1628,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..ec54fac 100644
--- a/app/paint/gimpclone.c
+++ b/app/paint/gimpclone.c
@@ -34,6 +34,7 @@
#include "core/gimpimage.h"
#include "core/gimppattern.h"
#include "core/gimppickable.h"
+#include "core/gimpsymmetry.h"
#include "gimpclone.h"
#include "gimpcloneoptions.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,
+ NULL);
}
static gboolean
diff --git a/app/paint/gimpconvolve.c b/app/paint/gimpconvolve.c
index a6d3a13..c4a85a3 100644
--- a/app/paint/gimpconvolve.c
+++ b/app/paint/gimpconvolve.c
@@ -30,6 +30,7 @@
#include "core/gimpdynamics.h"
#include "core/gimpimage.h"
#include "core/gimppickable.h"
+#include "core/gimpsymmetry.h"
#include "core/gimptempbuf.h"
#include "gimpconvolve.h"
@@ -48,13 +49,13 @@
static void gimp_convolve_paint (GimpPaintCore *paint_core,
GimpDrawable *drawable,
GimpPaintOptions *paint_options,
- const GimpCoords *coords,
+ GimpSymmetry *sym,
GimpPaintState paint_state,
guint32 time);
static void gimp_convolve_motion (GimpPaintCore *paint_core,
GimpDrawable *drawable,
GimpPaintOptions *paint_options,
- const GimpCoords *coords);
+ GimpSymmetry *sym);
static void gimp_convolve_calculate_matrix (GimpConvolve *convolve,
GimpConvolveType type,
@@ -102,14 +103,15 @@ static void
gimp_convolve_paint (GimpPaintCore *paint_core,
GimpDrawable *drawable,
GimpPaintOptions *paint_options,
- const GimpCoords *coords,
+ GimpSymmetry *sym,
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,
+ sym);
break;
default:
@@ -121,7 +123,7 @@ static void
gimp_convolve_motion (GimpPaintCore *paint_core,
GimpDrawable *drawable,
GimpPaintOptions *paint_options,
- const GimpCoords *coords)
+ GimpSymmetry *sym)
{
GimpConvolve *convolve = GIMP_CONVOLVE (paint_core);
GimpBrushCore *brush_core = GIMP_BRUSH_CORE (paint_core);
@@ -137,73 +139,90 @@ gimp_convolve_motion (GimpPaintCore *paint_core,
gdouble fade_point;
gdouble opacity;
gdouble rate;
+ const GimpCoords *coords;
+ GeglNode *op;
+ gint paint_width, paint_height;
+ gint n_strokes;
+ gint i;
+ n_strokes = gimp_symmetry_get_size (sym);
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 < n_strokes; i++)
+ {
+ coords = gimp_symmetry_get_coords (sym, 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,
+ &paint_width,
+ &paint_height);
+ if (! paint_buffer)
+ continue;
+
+ op = gimp_symmetry_get_operation (sym, i,
+ paint_width,
+ paint_height);
+
+ 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..4ed2605 100644
--- a/app/paint/gimpdodgeburn.c
+++ b/app/paint/gimpdodgeburn.c
@@ -31,6 +31,7 @@
#include "core/gimpdrawable.h"
#include "core/gimpdynamics.h"
#include "core/gimpimage.h"
+#include "core/gimpsymmetry.h"
#include "gimpdodgeburn.h"
#include "gimpdodgeburnoptions.h"
@@ -41,13 +42,13 @@
static void gimp_dodge_burn_paint (GimpPaintCore *paint_core,
GimpDrawable *drawable,
GimpPaintOptions *paint_options,
- const GimpCoords *coords,
+ GimpSymmetry *sym,
GimpPaintState paint_state,
guint32 time);
static void gimp_dodge_burn_motion (GimpPaintCore *paint_core,
GimpDrawable *drawable,
GimpPaintOptions *paint_options,
- const GimpCoords *coords);
+ GimpSymmetry *sym);
G_DEFINE_TYPE (GimpDodgeBurn, gimp_dodge_burn, GIMP_TYPE_BRUSH_CORE)
@@ -87,7 +88,7 @@ static void
gimp_dodge_burn_paint (GimpPaintCore *paint_core,
GimpDrawable *drawable,
GimpPaintOptions *paint_options,
- const GimpCoords *coords,
+ GimpSymmetry *sym,
GimpPaintState paint_state,
guint32 time)
{
@@ -97,7 +98,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, sym);
break;
case GIMP_PAINT_STATE_FINISH:
@@ -109,7 +110,7 @@ static void
gimp_dodge_burn_motion (GimpPaintCore *paint_core,
GimpDrawable *drawable,
GimpPaintOptions *paint_options,
- const GimpCoords *coords)
+ GimpSymmetry *sym)
{
GimpDodgeBurnOptions *options = GIMP_DODGE_BURN_OPTIONS (paint_options);
GimpContext *context = GIMP_CONTEXT (paint_options);
@@ -121,52 +122,69 @@ gimp_dodge_burn_motion (GimpPaintCore *paint_core,
gdouble fade_point;
gdouble opacity;
gdouble force;
-
- 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);
+ const GimpCoords *coords;
+ GeglNode *op;
+ gint paint_width, paint_height;
+ gint n_strokes;
+ gint i;
+
+ n_strokes = gimp_symmetry_get_size (sym);
+ for (i = 0; i < n_strokes; i++)
+ {
+ coords = gimp_symmetry_get_coords (sym, 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,
+ &paint_width,
+ &paint_height);
+ if (! paint_buffer)
+ continue;
+
+ op = gimp_symmetry_get_operation (sym, i,
+ paint_width,
+ paint_height);
+
+ /* 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..79dc385 100644
--- a/app/paint/gimperaser.c
+++ b/app/paint/gimperaser.c
@@ -29,6 +29,7 @@
#include "core/gimpdrawable.h"
#include "core/gimpdynamics.h"
#include "core/gimpimage.h"
+#include "core/gimpsymmetry.h"
#include "gimperaser.h"
#include "gimperaseroptions.h"
@@ -39,13 +40,13 @@
static void gimp_eraser_paint (GimpPaintCore *paint_core,
GimpDrawable *drawable,
GimpPaintOptions *paint_options,
- const GimpCoords *coords,
+ GimpSymmetry *sym,
GimpPaintState paint_state,
guint32 time);
static void gimp_eraser_motion (GimpPaintCore *paint_core,
GimpDrawable *drawable,
GimpPaintOptions *paint_options,
- const GimpCoords *coords);
+ GimpSymmetry *sym);
G_DEFINE_TYPE (GimpEraser, gimp_eraser, GIMP_TYPE_BRUSH_CORE)
@@ -83,7 +84,7 @@ static void
gimp_eraser_paint (GimpPaintCore *paint_core,
GimpDrawable *drawable,
GimpPaintOptions *paint_options,
- const GimpCoords *coords,
+ GimpSymmetry *sym,
GimpPaintState paint_state,
guint32 time)
{
@@ -106,7 +107,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, sym);
break;
default:
@@ -118,7 +119,7 @@ static void
gimp_eraser_motion (GimpPaintCore *paint_core,
GimpDrawable *drawable,
GimpPaintOptions *paint_options,
- const GimpCoords *coords)
+ GimpSymmetry *sym)
{
GimpEraserOptions *options = GIMP_ERASER_OPTIONS (paint_options);
GimpContext *context = GIMP_CONTEXT (paint_options);
@@ -133,31 +134,18 @@ gimp_eraser_motion (GimpPaintCore *paint_core,
GimpRGB background;
GeglColor *color;
gdouble force;
+ const GimpCoords *coords;
+ GeglNode *op;
+ gint n_strokes;
+ gint paint_width, paint_height;
+ 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))
@@ -165,21 +153,53 @@ gimp_eraser_motion (GimpPaintCore *paint_core,
else
paint_mode = GIMP_NORMAL_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;
-
- 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);
+ n_strokes = gimp_symmetry_get_size (sym);
+
+ for (i = 0; i < n_strokes; i++)
+ {
+ coords = gimp_symmetry_get_coords (sym, i);
+
+ 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;
+
+ 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,
+ &paint_width,
+ &paint_height);
+ if (! paint_buffer)
+ continue;
+
+ op = gimp_symmetry_get_operation (sym, i,
+ paint_width,
+ paint_height);
+
+ 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..b8fa988 100644
--- a/app/paint/gimpink.c
+++ b/app/paint/gimpink.c
@@ -32,6 +32,7 @@
#include "core/gimpdrawable.h"
#include "core/gimpimage.h"
#include "core/gimpimage-undo.h"
+#include "core/gimpsymmetry.h"
#include "core/gimptempbuf.h"
#include "gimpinkoptions.h"
@@ -52,7 +53,7 @@ static void gimp_ink_finalize (GObject *object);
static void gimp_ink_paint (GimpPaintCore *paint_core,
GimpDrawable *drawable,
GimpPaintOptions *paint_options,
- const GimpCoords *coords,
+ GimpSymmetry *sym,
GimpPaintState paint_state,
guint32 time);
static GeglBuffer * gimp_ink_get_paint_buffer (GimpPaintCore *paint_core,
@@ -60,7 +61,9 @@ static GeglBuffer * gimp_ink_get_paint_buffer (GimpPaintCore *paint_core,
GimpPaintOptions *paint_options,
const GimpCoords *coords,
gint *paint_buffer_x,
- gint *paint_buffer_y);
+ gint *paint_buffer_y,
+ gint *paint_width,
+ gint *paint_height);
static GimpUndo * gimp_ink_push_undo (GimpPaintCore *core,
GimpImage *image,
const gchar *undo_desc);
@@ -68,7 +71,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,
+ GimpSymmetry *sym,
guint32 time);
static GimpBlob * ink_pen_ellipse (GimpInkOptions *options,
@@ -124,16 +127,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 +146,16 @@ static void
gimp_ink_paint (GimpPaintCore *paint_core,
GimpDrawable *drawable,
GimpPaintOptions *paint_options,
- const GimpCoords *coords,
+ GimpSymmetry *sym,
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_symmetry_get_origin (sym);
switch (paint_state)
{
@@ -163,37 +168,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,
+ sym, time);
break;
case GIMP_PAINT_STATE_FINISH:
@@ -207,7 +224,9 @@ gimp_ink_get_paint_buffer (GimpPaintCore *paint_core,
GimpPaintOptions *paint_options,
const GimpCoords *coords,
gint *paint_buffer_x,
- gint *paint_buffer_y)
+ gint *paint_buffer_y,
+ gint *paint_width,
+ gint *paint_height)
{
GimpInk *ink = GIMP_INK (paint_core);
gint x, y;
@@ -225,6 +244,11 @@ gimp_ink_get_paint_buffer (GimpPaintCore *paint_core,
x2 = CLAMP ((x + width) / SUBSAMPLE + 2, 0, dwidth);
y2 = CLAMP ((y + height) / SUBSAMPLE + 2, 0, dheight);
+ if (paint_width)
+ *paint_width = width / SUBSAMPLE + 3;
+ if (paint_height)
+ *paint_height = height / SUBSAMPLE + 3;
+
/* configure the canvas buffer */
if ((x2 - x1) && (y2 - y1))
{
@@ -271,93 +295,137 @@ static void
gimp_ink_motion (GimpPaintCore *paint_core,
GimpDrawable *drawable,
GimpPaintOptions *paint_options,
- const GimpCoords *coords,
+ GimpSymmetry *sym,
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 n_strokes;
+ gint i;
- if (! ink->last_blob)
- {
- ink->last_blob = ink_pen_ellipse (options,
- coords->x,
- coords->y,
- coords->pressure,
- coords->xtilt,
- coords->ytilt,
- 100);
+ n_strokes = gimp_symmetry_get_size (sym);
- if (ink->start_blob)
- g_free (ink->start_blob);
+ if (ink->last_blobs &&
+ g_list_length (ink->last_blobs) != n_strokes)
+ {
+ 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 < n_strokes; i++)
+ {
+ coords = gimp_symmetry_get_coords (sym, 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 < n_strokes; i++)
+ {
+ GimpBlob *blob;
+ GimpBlob *blob_union = NULL;
+
+ coords = gimp_symmetry_get_coords (sym, 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 < n_strokes; i++)
+ {
+ GimpBlob *blob_to_render = g_list_nth_data (blobs_to_render, i);
+
+ coords = gimp_symmetry_get_coords (sym, 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,
+ NULL, NULL);
+ 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/gimpmybrush.c b/app/paint/gimpmybrush.c
index 69aa5d9..5985ac4 100644
--- a/app/paint/gimpmybrush.c
+++ b/app/paint/gimpmybrush.c
@@ -43,6 +43,7 @@
#include "core/gimpdrawable.h"
#include "core/gimpimage.h"
#include "core/gimpimage-undo.h"
+#include "core/gimpsymmetry.h"
#include "core/gimptempbuf.h"
#include "gimpmybrushoptions.h"
@@ -54,23 +55,24 @@
struct _GimpMybrushPrivate
{
MyPaintGeglTiledSurface *surface;
- MyPaintBrush *brush;
+ GList *brushes;
};
/* local function prototypes */
-static void gimp_mybrush_paint (GimpPaintCore *paint_core,
- GimpDrawable *drawable,
- GimpPaintOptions *paint_options,
- const GimpCoords *coords,
- GimpPaintState paint_state,
- guint32 time);
-static void gimp_mybrush_motion (GimpPaintCore *paint_core,
- GimpDrawable *drawable,
- GimpPaintOptions *paint_options,
- const GimpCoords *coords,
- guint32 time);
+static void gimp_mybrush_finalize (GObject *object);
+static void gimp_mybrush_paint (GimpPaintCore *paint_core,
+ GimpDrawable *drawable,
+ GimpPaintOptions *paint_options,
+ GimpSymmetry *sym,
+ GimpPaintState paint_state,
+ guint32 time);
+static void gimp_mybrush_motion (GimpPaintCore *paint_core,
+ GimpDrawable *drawable,
+ GimpPaintOptions *paint_options,
+ GimpSymmetry *sym,
+ guint32 time);
G_DEFINE_TYPE (GimpMybrush, gimp_mybrush, GIMP_TYPE_PAINT_CORE)
@@ -94,8 +96,11 @@ gimp_mybrush_register (Gimp *gimp,
static void
gimp_mybrush_class_init (GimpMybrushClass *klass)
{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
GimpPaintCoreClass *paint_core_class = GIMP_PAINT_CORE_CLASS (klass);
+ object_class->finalize = gimp_mybrush_finalize;
+
paint_core_class->paint = gimp_mybrush_paint;
g_type_class_add_private (klass, sizeof (GimpMybrushPrivate));
@@ -110,16 +115,33 @@ gimp_mybrush_init (GimpMybrush *mybrush)
}
static void
+gimp_mybrush_finalize (GObject *object)
+{
+ GimpMybrush *mybrush = GIMP_MYBRUSH (object);
+
+ if (mybrush->private->brushes)
+ {
+ g_list_free_full (mybrush->private->brushes,
+ (GDestroyNotify) mypaint_brush_unref);
+ mybrush->private->brushes = NULL;
+ }
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
gimp_mybrush_paint (GimpPaintCore *paint_core,
GimpDrawable *drawable,
GimpPaintOptions *paint_options,
- const GimpCoords *coords,
+ GimpSymmetry *sym,
GimpPaintState paint_state,
guint32 time)
{
GimpMybrush *mybrush = GIMP_MYBRUSH (paint_core);
GimpMybrushOptions *options = GIMP_MYBRUSH_OPTIONS (paint_options);
GeglBuffer *buffer;
+ gint n_strokes;
+ gint i;
switch (paint_state)
{
@@ -145,38 +167,55 @@ gimp_mybrush_paint (GimpPaintCore *paint_core,
mypaint_gegl_tiled_surface_set_buffer (mybrush->private->surface, buffer);
g_object_unref (buffer);
- mybrush->private->brush = mypaint_brush_new ();
- mypaint_brush_from_defaults (mybrush->private->brush);
+ if (mybrush->private->brushes)
+ {
+ g_list_free_full (mybrush->private->brushes,
+ (GDestroyNotify) mypaint_brush_unref);
+ mybrush->private->brushes = NULL;
+ }
- if (options->mybrush)
+ n_strokes = gimp_symmetry_get_size (sym);
+ for (i = 0; i < n_strokes; i++)
{
- gchar *string;
- gsize length;
+ MyPaintBrush *brush;
- if (g_file_get_contents (options->mybrush,
- &string, &length, NULL))
+ brush = mypaint_brush_new ();
+ mypaint_brush_from_defaults (brush);
+
+ if (options->mybrush)
{
- if (! mypaint_brush_from_string (mybrush->private->brush, string))
- g_printerr ("Failed to deserialize MyPaint brush\n");
+ gchar *string;
+ gsize length;
- g_free (string);
+ if (g_file_get_contents (options->mybrush,
+ &string, &length, NULL))
+ {
+ if (! mypaint_brush_from_string (brush, string))
+ g_printerr ("Failed to deserialize MyPaint brush\n");
+
+ g_free (string);
+ }
}
- }
- mypaint_brush_new_stroke (mybrush->private->brush);
+ mypaint_brush_new_stroke (brush);
+
+ mybrush->private->brushes = g_list_prepend (mybrush->private->brushes, brush);
+ }
+ mybrush->private->brushes = g_list_reverse (mybrush->private->brushes);
}
break;
case GIMP_PAINT_STATE_MOTION:
- gimp_mybrush_motion (paint_core, drawable, paint_options, coords, time);
+ gimp_mybrush_motion (paint_core, drawable, paint_options, sym, time);
break;
case GIMP_PAINT_STATE_FINISH:
mypaint_surface_unref ((MyPaintSurface *) mybrush->private->surface);
mybrush->private->surface = NULL;
- mypaint_brush_unref (mybrush->private->brush);
- mybrush->private->brush = NULL;
+ g_list_free_full (mybrush->private->brushes,
+ (GDestroyNotify) mypaint_brush_unref);
+ mybrush->private->brushes = NULL;
break;
}
}
@@ -185,82 +224,131 @@ static void
gimp_mybrush_motion (GimpPaintCore *paint_core,
GimpDrawable *drawable,
GimpPaintOptions *paint_options,
- const GimpCoords *coords,
+ GimpSymmetry *sym,
guint32 time)
{
GimpMybrush *mybrush = GIMP_MYBRUSH (paint_core);
GimpMybrushOptions *options = GIMP_MYBRUSH_OPTIONS (paint_options);
GimpContext *context = GIMP_CONTEXT (paint_options);
+ GimpCoords *coords;
GimpComponentMask active_mask;
GimpRGB fg;
GimpHSV hsv;
MyPaintRectangle rect;
+ gint n_strokes;
+ gint i;
+ GList *iter;
active_mask = gimp_drawable_get_active_mask (drawable);
- mypaint_brush_set_base_value (mybrush->private->brush,
- MYPAINT_BRUSH_SETTING_LOCK_ALPHA,
- (active_mask & GIMP_COMPONENT_MASK_ALPHA) ?
- FALSE : TRUE);
-
- gimp_context_get_foreground (context, &fg);
- gimp_rgb_to_hsv (&fg, &hsv);
-
- mypaint_brush_set_base_value (mybrush->private->brush,
- MYPAINT_BRUSH_SETTING_COLOR_H,
- hsv.h);
- mypaint_brush_set_base_value (mybrush->private->brush,
- MYPAINT_BRUSH_SETTING_COLOR_S,
- hsv.s);
- mypaint_brush_set_base_value (mybrush->private->brush,
- MYPAINT_BRUSH_SETTING_COLOR_V,
- hsv.v);
-
- mypaint_brush_set_base_value (mybrush->private->brush,
- MYPAINT_BRUSH_SETTING_OPAQUE,
- gimp_context_get_opacity (context));
- mypaint_brush_set_base_value (mybrush->private->brush,
- MYPAINT_BRUSH_SETTING_RADIUS_LOGARITHMIC,
- options->radius);
- mypaint_brush_set_base_value (mybrush->private->brush,
- MYPAINT_BRUSH_SETTING_HARDNESS,
- options->hardness);
-
- mypaint_surface_begin_atomic ((MyPaintSurface *) mybrush->private->surface);
-
- mypaint_brush_stroke_to (mybrush->private->brush,
- (MyPaintSurface *) mybrush->private->surface,
- coords->x,
- coords->y,
- coords->pressure,
- coords->xtilt,
- coords->ytilt,
- 1);
-
- mypaint_surface_end_atomic ((MyPaintSurface *) mybrush->private->surface,
- &rect);
-
- g_printerr ("painted rect: %d %d %d %d\n",
- rect.x, rect.y, rect.width, rect.height);
-
- if (rect.width > 0 && rect.height > 0)
+ n_strokes = gimp_symmetry_get_size (sym);
+
+ /* Number of strokes may change during a motion, depending on the type
+ * of symmetry. When that happens, we reset the brushes. */
+ if (g_list_length (mybrush->private->brushes) != n_strokes)
+ {
+ g_list_free_full (mybrush->private->brushes,
+ (GDestroyNotify) mypaint_brush_unref);
+ mybrush->private->brushes = NULL;
+
+ for (i = 0; i < n_strokes; i++)
+ {
+ MyPaintBrush *brush;
+
+ brush = mypaint_brush_new ();
+ mypaint_brush_from_defaults (brush);
+
+ if (options->mybrush)
+ {
+ gchar *string;
+ gsize length;
+
+ if (g_file_get_contents (options->mybrush,
+ &string, &length, NULL))
+ {
+ if (! mypaint_brush_from_string (brush, string))
+ g_printerr ("Failed to deserialize MyPaint brush\n");
+
+ g_free (string);
+ }
+ }
+
+ mypaint_brush_new_stroke (brush);
+
+ mybrush->private->brushes = g_list_prepend (mybrush->private->brushes, brush);
+ }
+ }
+
+ for (iter = mybrush->private->brushes, i = 0; iter ; iter = g_list_next (iter), i++)
{
- GeglBuffer *src;
+ MyPaintBrush *brush = iter->data;
+
+ mypaint_brush_set_base_value (brush,
+ MYPAINT_BRUSH_SETTING_LOCK_ALPHA,
+ (active_mask & GIMP_COMPONENT_MASK_ALPHA) ?
+ FALSE : TRUE);
+
+ gimp_context_get_foreground (context, &fg);
+ gimp_rgb_to_hsv (&fg, &hsv);
+
+ mypaint_brush_set_base_value (brush,
+ MYPAINT_BRUSH_SETTING_COLOR_H,
+ hsv.h);
+ mypaint_brush_set_base_value (brush,
+ MYPAINT_BRUSH_SETTING_COLOR_S,
+ hsv.s);
+ mypaint_brush_set_base_value (brush,
+ MYPAINT_BRUSH_SETTING_COLOR_V,
+ hsv.v);
+
+ mypaint_brush_set_base_value (brush,
+ MYPAINT_BRUSH_SETTING_OPAQUE,
+ gimp_context_get_opacity (context));
+ mypaint_brush_set_base_value (brush,
+ MYPAINT_BRUSH_SETTING_RADIUS_LOGARITHMIC,
+ options->radius);
+ mypaint_brush_set_base_value (brush,
+ MYPAINT_BRUSH_SETTING_HARDNESS,
+ options->hardness);
+
+ mypaint_surface_begin_atomic ((MyPaintSurface *) mybrush->private->surface);
+
+ coords = gimp_symmetry_get_coords (sym, i);
+
+ mypaint_brush_stroke_to (brush,
+ (MyPaintSurface *) mybrush->private->surface,
+ coords->x,
+ coords->y,
+ coords->pressure,
+ coords->xtilt,
+ coords->ytilt,
+ 1);
+
+ mypaint_surface_end_atomic ((MyPaintSurface *) mybrush->private->surface,
+ &rect);
+
+ g_printerr ("painted rect: %d %d %d %d\n",
+ rect.x, rect.y, rect.width, rect.height);
+
+ if (rect.width > 0 && rect.height > 0)
+ {
+ GeglBuffer *src;
- src = mypaint_gegl_tiled_surface_get_buffer (mybrush->private->surface);
+ src = mypaint_gegl_tiled_surface_get_buffer (mybrush->private->surface);
- gegl_buffer_copy (src,
- (GeglRectangle *) &rect,
- GEGL_ABYSS_NONE,
- gimp_drawable_get_buffer (drawable),
- NULL);
+ gegl_buffer_copy (src,
+ (GeglRectangle *) &rect,
+ GEGL_ABYSS_NONE,
+ gimp_drawable_get_buffer (drawable),
+ NULL);
- paint_core->x1 = MIN (paint_core->x1, rect.x);
- paint_core->y1 = MIN (paint_core->y1, rect.y);
- paint_core->x2 = MAX (paint_core->x2, rect.x + rect.width);
- paint_core->y2 = MAX (paint_core->y2, rect.y + rect.height);
+ paint_core->x1 = MIN (paint_core->x1, rect.x);
+ paint_core->y1 = MIN (paint_core->y1, rect.y);
+ paint_core->x2 = MAX (paint_core->x2, rect.x + rect.width);
+ paint_core->y2 = MAX (paint_core->y2, rect.y + rect.height);
- gimp_drawable_update (drawable, rect.x, rect.y, rect.width, rect.height);
+ gimp_drawable_update (drawable, rect.x, rect.y, rect.width, rect.height);
+ }
}
}
diff --git a/app/paint/gimppaintbrush.c b/app/paint/gimppaintbrush.c
index a41c278..bbcd68a 100644
--- a/app/paint/gimppaintbrush.c
+++ b/app/paint/gimppaintbrush.c
@@ -36,6 +36,7 @@
#include "core/gimpdynamics.h"
#include "core/gimpgradient.h"
#include "core/gimpimage.h"
+#include "core/gimpsymmetry.h"
#include "core/gimptempbuf.h"
#include "gimppaintbrush.h"
@@ -47,7 +48,7 @@
static void gimp_paintbrush_paint (GimpPaintCore *paint_core,
GimpDrawable *drawable,
GimpPaintOptions *paint_options,
- const GimpCoords *coords,
+ GimpSymmetry *sym,
GimpPaintState paint_state,
guint32 time);
@@ -87,7 +88,7 @@ static void
gimp_paintbrush_paint (GimpPaintCore *paint_core,
GimpDrawable *drawable,
GimpPaintOptions *paint_options,
- const GimpCoords *coords,
+ GimpSymmetry *sym,
GimpPaintState paint_state,
guint32 time)
{
@@ -116,8 +117,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,
+ sym, GIMP_OPACITY_OPAQUE);
break;
default:
@@ -129,7 +130,7 @@ void
_gimp_paintbrush_motion (GimpPaintCore *paint_core,
GimpDrawable *drawable,
GimpPaintOptions *paint_options,
- const GimpCoords *coords,
+ GimpSymmetry *sym,
gdouble opacity)
{
GimpBrushCore *brush_core = GIMP_BRUSH_CORE (paint_core);
@@ -144,12 +145,20 @@ _gimp_paintbrush_motion (GimpPaintCore *paint_core,
gdouble fade_point;
gdouble grad_point;
gdouble force;
+ const GimpCoords *coords;
+ GeglNode *op;
+ gint i;
+ gint n_strokes;
image = gimp_item_get_image (GIMP_ITEM (drawable));
+ n_strokes = gimp_symmetry_get_size (sym);
+
fade_point = gimp_paint_options_get_fade (paint_options, image,
paint_core->pixel_dist);
+ coords = gimp_symmetry_get_origin (sym);
+ /* Some settings are based on the original stroke. */
opacity *= gimp_dynamics_get_linear_value (dynamics,
GIMP_DYNAMICS_OUTPUT_OPACITY,
coords,
@@ -158,13 +167,6 @@ _gimp_paintbrush_motion (GimpPaintCore *paint_core,
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,
@@ -173,69 +175,89 @@ _gimp_paintbrush_motion (GimpPaintCore *paint_core,
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 < n_strokes; i++)
{
- /* optionally take the color from the current gradient */
+ gint paint_width, paint_height;
+
+ coords = gimp_symmetry_get_coords (sym, i);
+
+ paint_buffer = gimp_paint_core_get_paint_buffer (paint_core, drawable,
+ paint_options, coords,
+ &paint_buffer_x,
+ &paint_buffer_y,
+ &paint_width,
+ &paint_height);
+ if (! paint_buffer)
+ continue;
+
+ op = gimp_symmetry_get_operation (sym, i,
+ paint_width,
+ paint_height);
+ 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..e60c590 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,
+ GimpSymmetry *sym,
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..71bf152 100644
--- a/app/paint/gimppaintcore.c
+++ b/app/paint/gimppaintcore.c
@@ -37,9 +37,12 @@
#include "core/gimp-utils.h"
#include "core/gimpchannel.h"
#include "core/gimpimage.h"
+#include "core/gimpimage-guides.h"
+#include "core/gimpimage-symmetry.h"
#include "core/gimpimage-undo.h"
#include "core/gimppickable.h"
#include "core/gimpprojection.h"
+#include "core/gimpsymmetry.h"
#include "core/gimptempbuf.h"
#include "gimppaintcore.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,
+ GimpSymmetry *sym,
GimpPaintState paint_state,
guint32 time);
static void gimp_paint_core_real_post_paint (GimpPaintCore *core,
@@ -104,7 +107,9 @@ static GeglBuffer *
GimpPaintOptions *options,
const GimpCoords *coords,
gint *paint_buffer_x,
- gint *paint_buffer_y);
+ gint *paint_buffer_y,
+ gint *paint_width,
+ gint *paint_height);
static GimpUndo* gimp_paint_core_real_push_undo (GimpPaintCore *core,
GimpImage *image,
const gchar *undo_desc);
@@ -231,7 +236,7 @@ static void
gimp_paint_core_real_paint (GimpPaintCore *core,
GimpDrawable *drawable,
GimpPaintOptions *paint_options,
- const GimpCoords *coords,
+ GimpSymmetry *sym,
GimpPaintState paint_state,
guint32 time)
{
@@ -264,7 +269,9 @@ gimp_paint_core_real_get_paint_buffer (GimpPaintCore *core,
GimpPaintOptions *paint_options,
const GimpCoords *coords,
gint *paint_buffer_x,
- gint *paint_buffer_y)
+ gint *paint_buffer_y,
+ gint *paint_width,
+ gint *paint_height)
{
return NULL;
}
@@ -304,6 +311,12 @@ gimp_paint_core_paint (GimpPaintCore *core,
paint_options,
paint_state, time))
{
+ GimpSymmetry *sym;
+ GimpImage *image;
+ GimpItem *item;
+
+ item = GIMP_ITEM (drawable);
+ image = gimp_item_get_image (item);
if (paint_state == GIMP_PAINT_STATE_MOTION)
{
@@ -312,10 +325,13 @@ gimp_paint_core_paint (GimpPaintCore *core,
core->last_paint.y = core->cur_coords.y;
}
+ sym = g_object_ref (gimp_image_symmetry_selected (image));
+ gimp_symmetry_set_origin (sym, drawable, &core->cur_coords);
+
core_class->paint (core, drawable,
paint_options,
- &core->cur_coords,
- paint_state, time);
+ sym, paint_state, time);
+ g_object_unref (sym);
core_class->post_paint (core, drawable,
paint_options,
@@ -749,7 +765,9 @@ gimp_paint_core_get_paint_buffer (GimpPaintCore *core,
GimpPaintOptions *paint_options,
const GimpCoords *coords,
gint *paint_buffer_x,
- gint *paint_buffer_y)
+ gint *paint_buffer_y,
+ gint *paint_width,
+ gint *paint_height)
{
GeglBuffer *paint_buffer;
@@ -766,7 +784,9 @@ gimp_paint_core_get_paint_buffer (GimpPaintCore *core,
paint_options,
coords,
paint_buffer_x,
- paint_buffer_y);
+ paint_buffer_y,
+ paint_width,
+ paint_height);
core->paint_buffer_x = *paint_buffer_x;
core->paint_buffer_y = *paint_buffer_y;
@@ -818,8 +838,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 +854,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..e880c83 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,
+ GimpSymmetry *sym,
GimpPaintState paint_state,
guint32 time);
void (* post_paint) (GimpPaintCore *core,
@@ -112,7 +112,9 @@ struct _GimpPaintCoreClass
GimpPaintOptions *paint_options,
const GimpCoords *coords,
gint *paint_buffer_x,
- gint *paint_buffer_y);
+ gint *paint_buffer_y,
+ gint *paint_width,
+ gint *paint_height);
GimpUndo * (* push_undo) (GimpPaintCore *core,
GimpImage *image,
@@ -168,7 +170,9 @@ GeglBuffer * gimp_paint_core_get_paint_buffer (GimpPaintCore *core,
GimpPaintOptions *options,
const GimpCoords *coords,
gint *paint_buffer_x,
- gint *paint_buffer_y);
+ gint *paint_buffer_y,
+ gint *paint_width,
+ gint *paint_height);
GeglBuffer * gimp_paint_core_get_orig_image (GimpPaintCore *core);
GeglBuffer * gimp_paint_core_get_orig_proj (GimpPaintCore *core);
diff --git a/app/paint/gimppaintoptions.c b/app/paint/gimppaintoptions.c
index 90e224d..255de56 100644
--- a/app/paint/gimppaintoptions.c
+++ b/app/paint/gimppaintoptions.c
@@ -29,10 +29,12 @@
#include "core/gimp.h"
#include "core/gimpbrushgenerated.h"
#include "core/gimpimage.h"
+#include "core/gimpimage-symmetry.h"
#include "core/gimpdynamics.h"
#include "core/gimpdynamicsoutput.h"
#include "core/gimpgradient.h"
#include "core/gimppaintinfo.h"
+#include "core/gimpsymmetry.h"
#include "gimppaintoptions.h"
@@ -127,7 +129,9 @@ enum
PROP_BRUSH_LINK_ASPECT_RATIO,
PROP_BRUSH_LINK_ANGLE,
PROP_BRUSH_LINK_SPACING,
- PROP_BRUSH_LINK_HARDNESS
+ PROP_BRUSH_LINK_HARDNESS,
+
+ PROP_SYMMETRY
};
@@ -368,6 +372,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_SYMMETRY,
+ "symmetry", _("Symmetry"),
+ G_TYPE_NONE, INT_MAX, G_TYPE_NONE,
+ GIMP_PARAM_STATIC_STRINGS);
}
static void
@@ -426,12 +435,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:
@@ -574,6 +586,23 @@ gimp_paint_options_set_property (GObject *object,
smoothing_options->smoothing_factor = g_value_get_double (value);
break;
+ case PROP_SYMMETRY:
+ options->symmetry = g_value_get_int (value);
+ if (context && context->image)
+ {
+ if (! gimp_image_symmetry_select (context->image,
+ options->symmetry))
+ {
+ gimp_image_symmetry_add (context->image,
+ options->symmetry);
+ }
+ }
+ else
+ {
+ options->symmetry = G_TYPE_NONE;
+ }
+ break;
+
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
@@ -734,6 +763,10 @@ gimp_paint_options_get_property (GObject *object,
g_value_set_double (value, smoothing_options->smoothing_factor);
break;
+ case PROP_SYMMETRY:
+ g_value_set_int (value, options->symmetry);
+ break;
+
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
@@ -1143,3 +1176,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.
+ * Symmetry 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)
+ {
+ GimpSymmetry *mstroke;
+
+ mstroke = gimp_image_symmetry_selected (image);
+
+ g_object_set (dest,
+ "symmetry",
+ mstroke ? mstroke->type : G_TYPE_NONE,
+ NULL);
+ }
+}
diff --git a/app/paint/gimppaintoptions.h b/app/paint/gimppaintoptions.h
index c4ec593..20f6148 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 symmetry;
};
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..ac94005 100644
--- a/app/paint/gimpperspectiveclone.c
+++ b/app/paint/gimpperspectiveclone.c
@@ -38,6 +38,7 @@
#include "core/gimpimage.h"
#include "core/gimppattern.h"
#include "core/gimppickable.h"
+#include "core/gimpsymmetry.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,
+ GimpSymmetry *sym,
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,
+ GimpSymmetry *sym,
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_symmetry_get_origin (sym);
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 n_strokes;
+ 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)
+ n_strokes = gimp_symmetry_get_size (sym);
+ for (i = 0; i < n_strokes; 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_symmetry_get_coords (sym, 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, sym);
}
break;
diff --git a/app/paint/gimpsmudge.c b/app/paint/gimpsmudge.c
index 055042a..6180df3 100644
--- a/app/paint/gimpsmudge.c
+++ b/app/paint/gimpsmudge.c
@@ -32,6 +32,7 @@
#include "core/gimpdynamics.h"
#include "core/gimpimage.h"
#include "core/gimppickable.h"
+#include "core/gimpsymmetry.h"
#include "core/gimptempbuf.h"
#include "gimpsmudge.h"
@@ -45,20 +46,21 @@ static void gimp_smudge_finalize (GObject *object);
static void gimp_smudge_paint (GimpPaintCore *paint_core,
GimpDrawable *drawable,
GimpPaintOptions *paint_options,
- const GimpCoords *coords,
+ GimpSymmetry *sym,
GimpPaintState paint_state,
guint32 time);
static gboolean gimp_smudge_start (GimpPaintCore *paint_core,
GimpDrawable *drawable,
GimpPaintOptions *paint_options,
- const GimpCoords *coords);
+ GimpSymmetry *sym);
static void gimp_smudge_motion (GimpPaintCore *paint_core,
GimpDrawable *drawable,
GimpPaintOptions *paint_options,
- const GimpCoords *coords);
+ GimpSymmetry *sym);
static void gimp_smudge_accumulator_coords (GimpPaintCore *paint_core,
const GimpCoords *coords,
+ gint stroke,
gint *x,
gint *y);
@@ -110,10 +112,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 +131,7 @@ static void
gimp_smudge_paint (GimpPaintCore *paint_core,
GimpDrawable *drawable,
GimpPaintOptions *paint_options,
- const GimpCoords *coords,
+ GimpSymmetry *sym,
GimpPaintState paint_state,
guint32 time)
{
@@ -135,17 +143,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, sym);
if (smudge->initialized)
- gimp_smudge_motion (paint_core, drawable, paint_options, coords);
+ gimp_smudge_motion (paint_core, drawable, paint_options, sym);
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 +173,84 @@ static gboolean
gimp_smudge_start (GimpPaintCore *paint_core,
GimpDrawable *drawable,
GimpPaintOptions *paint_options,
- const GimpCoords *coords)
+ GimpSymmetry *sym)
{
GimpSmudge *smudge = GIMP_SMUDGE (paint_core);
GeglBuffer *paint_buffer;
+ GimpCoords *coords;
gint paint_buffer_x;
gint paint_buffer_y;
gint accum_size;
+ gint n_strokes;
+ 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))
+ n_strokes = gimp_symmetry_get_size (sym);
+ for (i = 0; i < n_strokes; 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_symmetry_get_coords (sym, i);
+ paint_buffer = gimp_paint_core_get_paint_buffer (paint_core, drawable,
+ paint_options, coords,
+ &paint_buffer_x,
+ &paint_buffer_y,
+ NULL, NULL);
+ 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,120 +259,142 @@ static void
gimp_smudge_motion (GimpPaintCore *paint_core,
GimpDrawable *drawable,
GimpPaintOptions *paint_options,
- const GimpCoords *coords)
+ GimpSymmetry *sym)
{
- GimpSmudge *smudge = GIMP_SMUDGE (paint_core);
- GimpSmudgeOptions *options = GIMP_SMUDGE_OPTIONS (paint_options);
- GimpContext *context = GIMP_CONTEXT (paint_options);
- GimpDynamics *dynamics = GIMP_BRUSH_CORE (paint_core)->dynamics;
- GimpImage *image = gimp_item_get_image (GIMP_ITEM (drawable));
- GeglBuffer *paint_buffer;
- gint paint_buffer_x;
- gint paint_buffer_y;
- gint paint_buffer_width;
- gint paint_buffer_height;
- gdouble fade_point;
- gdouble opacity;
- gdouble rate;
- gdouble dynamic_rate;
- gint x, y;
- gdouble force;
+ GimpSmudge *smudge = GIMP_SMUDGE (paint_core);
+ GimpSmudgeOptions *options = GIMP_SMUDGE_OPTIONS (paint_options);
+ GimpContext *context = GIMP_CONTEXT (paint_options);
+ GimpDynamics *dynamics = GIMP_BRUSH_CORE (paint_core)->dynamics;
+ GimpImage *image = gimp_item_get_image (GIMP_ITEM (drawable));
+ GeglBuffer *paint_buffer;
+ gint paint_buffer_x;
+ gint paint_buffer_y;
+ gint paint_buffer_width;
+ gint paint_buffer_height;
+ gdouble fade_point;
+ gdouble opacity;
+ gdouble rate;
+ gdouble dynamic_rate;
+ gint x, y;
+ gdouble force;
+ GeglBuffer *accum_buffer;
+ GimpCoords *coords;
+ GeglNode *op;
+ gint paint_width, paint_height;
+ gint n_strokes;
+ 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);
+ n_strokes = gimp_symmetry_get_size (sym);
+ for (i = 0; i < n_strokes; i++)
+ {
+ coords = gimp_symmetry_get_coords (sym, 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,
+ &paint_width,
+ &paint_height);
+ if (! paint_buffer)
+ continue;
+
+ op = gimp_symmetry_get_operation (sym, i,
+ paint_width,
+ paint_height);
+
+ 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..3a2060d 100644
--- a/app/paint/gimpsourcecore.c
+++ b/app/paint/gimpsourcecore.c
@@ -32,6 +32,7 @@
#include "core/gimperror.h"
#include "core/gimpimage.h"
#include "core/gimppickable.h"
+#include "core/gimpsymmetry.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,
+ GimpSymmetry *sym,
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);
+ GimpSymmetry *sym);
#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,
+ GimpSymmetry *sym,
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_symmetry_get_origin (sym);
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,
+ sym);
}
break;
@@ -347,7 +353,7 @@ void
gimp_source_core_motion (GimpSourceCore *source_core,
GimpDrawable *drawable,
GimpPaintOptions *paint_options,
- const GimpCoords *coords)
+ GimpSymmetry *sym)
{
GimpPaintCore *paint_core = GIMP_PAINT_CORE (source_core);
@@ -357,6 +363,8 @@ gimp_source_core_motion (GimpSourceCore *source_core,
GimpPickable *src_pickable = NULL;
GeglBuffer *src_buffer = NULL;
GeglRectangle src_rect;
+ gint base_src_offset_x;
+ gint base_src_offset_y;
gint src_offset_x;
gint src_offset_y;
GeglBuffer *paint_buffer;
@@ -368,20 +376,29 @@ gimp_source_core_motion (GimpSourceCore *source_core,
gint paint_area_height;
gdouble fade_point;
gdouble opacity;
+ GeglNode *op;
+ GimpCoords *origin;
+ GimpCoords *coords;
+ gint n_strokes;
+ gint i;
fade_point = gimp_paint_options_get_fade (paint_options, image,
paint_core->pixel_dist);
+ origin = gimp_symmetry_get_origin (sym);
+ n_strokes = gimp_symmetry_get_size (sym);
+
+ /* Some settings are based on the original stroke. */
opacity = gimp_dynamics_get_linear_value (dynamics,
GIMP_DYNAMICS_OUTPUT_OPACITY,
- coords,
+ origin,
paint_options,
fade_point);
if (opacity == 0.0)
return;
- src_offset_x = source_core->offset_x;
- src_offset_y = source_core->offset_y;
+ base_src_offset_x = source_core->offset_x;
+ base_src_offset_y = source_core->offset_y;
if (gimp_source_core_use_source (source_core, options))
{
@@ -397,69 +414,85 @@ gimp_source_core_motion (GimpSourceCore *source_core,
gimp_item_get_offset (GIMP_ITEM (source_core->src_drawable),
&off_x, &off_y);
- src_offset_x += off_x;
- src_offset_y += off_y;
+ base_src_offset_x += off_x;
+ base_src_offset_y += off_y;
}
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)
- 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);
-
- if (gimp_source_core_use_source (source_core, options))
+ for (i = 0; i < n_strokes; i++)
{
- 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;
- }
+ coords = gimp_symmetry_get_coords (sym, i);
+
+ paint_buffer = gimp_paint_core_get_paint_buffer (paint_core, drawable,
+ paint_options, coords,
+ &paint_buffer_x,
+ &paint_buffer_y,
+ NULL, NULL);
+ if (! paint_buffer)
+ continue;
+
+ 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);
+
+ src_offset_x = base_src_offset_x;
+ src_offset_y = base_src_offset_y;
+ 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;
+ }
- /* 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);
+ /* Set the paint buffer to transparent */
+ gegl_buffer_clear (paint_buffer, NULL);
+
+ op = gimp_symmetry_get_operation (sym, i,
+ gegl_buffer_get_width (paint_buffer),
+ gegl_buffer_get_height (paint_buffer));
+ 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);
+ }
}
gboolean
diff --git a/app/paint/gimpsourcecore.h b/app/paint/gimpsourcecore.h
index 4f71bb0..583f3ab 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);
+ GimpSymmetry *sym);
#endif /* __GIMP_SOURCE_CORE_H__ */
diff --git a/app/paint/paint-types.h b/app/paint/paint-types.h
index ccbedb2..1bd9fb0 100644
--- a/app/paint/paint-types.h
+++ b/app/paint/paint-types.h
@@ -42,7 +42,6 @@ typedef struct _GimpPencil GimpPencil;
typedef struct _GimpPerspectiveClone GimpPerspectiveClone;
typedef struct _GimpSmudge GimpSmudge;
-
/* paint options */
typedef struct _GimpPaintOptions GimpPaintOptions;
diff --git a/app/tools/gimppaintoptions-gui.c b/app/tools/gimppaintoptions-gui.c
index be3d546..67aa3ef 100644
--- a/app/tools/gimppaintoptions-gui.c
+++ b/app/tools/gimppaintoptions-gui.c
@@ -24,6 +24,12 @@
#include "tools-types.h"
+#include "config/gimpguiconfig.h"
+
+#include "core/gimp.h"
+#include "core/gimpimage.h"
+#include "core/gimpimage-symmetry.h"
+#include "core/gimpsymmetry.h"
#include "core/gimptoolinfo.h"
#include "paint/gimppaintoptions.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_symmetry_update_cb (GimpSymmetry *sym,
+ GimpImage *image,
+ GtkWidget *frame);
+static void
+ gimp_paint_options_symmetry_callback (GimpPaintOptions *options,
+ GParamSpec *pspec,
+ GtkWidget *frame);
+static void gimp_paint_options_symmetry_ui (GimpSymmetry *sym,
+ 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_symmetry)
+ {
+ /* Symmetry Painting */
+ GtkListStore *store;
+ GtkTreeIter iter;
+ GList *syms;
+
+ store = gimp_int_store_new ();
+
+ syms = gimp_image_symmetry_list ();
+ for (syms = gimp_image_symmetry_list (); syms; syms = g_list_next (syms))
+ {
+ GimpSymmetryClass *klass;
+ GType type;
+
+ type = (GType) syms->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,
+ syms->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, "symmetry",
+ GIMP_INT_STORE (store));
+ g_object_unref (store);
+
+ gimp_int_combo_box_set_label (GIMP_INT_COMBO_BOX (menu), _("Symmetry"));
+ 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::symmetry",
+ G_CALLBACK (gimp_paint_options_symmetry_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_symmetry_update_cb (GimpSymmetry *sym,
+ GimpImage *image,
+ GtkWidget *frame)
+{
+ GimpContext *context;
+
+ g_return_if_fail (GIMP_IS_SYMMETRY (sym));
+
+ context = gimp_get_user_context (sym->image->gimp);
+ if (image != context->image ||
+ sym != gimp_image_symmetry_selected (image))
+ {
+ g_signal_handlers_disconnect_by_func (G_OBJECT (sym),
+ gimp_paint_options_symmetry_update_cb,
+ frame);
+ return;
+ }
+
+ gimp_paint_options_symmetry_ui (sym, frame);
+}
+
+static void
+gimp_paint_options_symmetry_callback (GimpPaintOptions *options,
+ GParamSpec *pspec,
+ GtkWidget *frame)
+{
+ GimpSymmetry *sym = 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 &&
+ (sym = gimp_image_symmetry_selected (image)))
+ {
+ g_signal_connect (sym, "update-ui",
+ G_CALLBACK (gimp_paint_options_symmetry_update_cb),
+ frame);
+ }
+
+ gimp_paint_options_symmetry_ui (sym, frame);
+}
+
+static void
+gimp_paint_options_symmetry_ui (GimpSymmetry *sym,
+ GtkWidget *frame)
+{
+ GimpSymmetryClass *klass;
+ GtkWidget *vbox;
+ GParamSpec **specs;
+ gint n_properties;
+ gint i;
+
+ /* Clean the old frame */
+ gtk_widget_hide (frame);
+ gtk_container_foreach (GTK_CONTAINER (frame),
+ (GtkCallback) gtk_widget_destroy, NULL);
+
+ if (! sym)
+ return;
+
+ klass = g_type_class_ref (sym->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_symmetry_get_settings (sym, &n_properties);
+
+ for (i = 0; i < n_properties; 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 (sym),
+ 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 (sym),
+ 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 72e45ed..fd2d06b 100644
--- a/app/xcf/xcf-load.c
+++ b/app/xcf/xcf-load.c
@@ -45,6 +45,7 @@
#include "core/gimpimage-metadata.h"
#include "core/gimpimage-private.h"
#include "core/gimpimage-sample-points.h"
+#include "core/gimpimage-symmetry.h"
#include "core/gimpimage-undo.h"
#include "core/gimpitemstack.h"
#include "core/gimplayer-floating-sel.h"
@@ -53,6 +54,7 @@
#include "core/gimpparasitelist.h"
#include "core/gimpprogress.h"
#include "core/gimpselection.h"
+#include "core/gimpsymmetry.h"
#include "core/gimptemplate.h"
#include "text/gimptextlayer.h"
@@ -737,6 +739,131 @@ xcf_load_image_props (XcfInfo *info,
}
break;
+ case PROP_SYMMETRY:
+ {
+ GimpSymmetry *sym;
+ GimpSymmetry *active_sym = NULL;
+ gint32 active;
+ gint32 n_syms;
+ gchar *name;
+ GType type;
+ GParamSpec **settings;
+ GParamSpec *spec;
+ gint n_settings;
+ gint i, j;
+
+ info->cp += xcf_read_int32 (info->input,
+ (guint32 *) &active, 1);
+ info->cp += xcf_read_int32 (info->input,
+ (guint32 *) &n_syms, 1);
+ for (i = 1; i <= n_syms; 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_SYMMETRY))
+ {
+ gimp_message (info->gimp, G_OBJECT (info->progress),
+ GIMP_MESSAGE_ERROR,
+ "Unknown Symmetry: %s",
+ name);
+ g_free (name);
+ return FALSE;
+ }
+ sym = gimp_image_symmetry_add (image, type);
+
+ settings = gimp_symmetry_get_xcf_settings (sym,
+ &n_settings);
+ for (j = 0; j < n_settings; 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 (sym,
+ 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 (sym,
+ 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 (sym,
+ 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 (sym,
+ 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 (sym,
+ 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;
+ }
+ }
+ g_free (settings);
+
+ if (active == i)
+ active_sym = sym;
+ }
+ gimp_image_symmetry_select (image, active_sym->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 5653339..0c3681b 100644
--- a/app/xcf/xcf-private.h
+++ b/app/xcf/xcf-private.h
@@ -57,7 +57,8 @@ typedef enum
PROP_ITEM_PATH = 30,
PROP_GROUP_ITEM_FLAGS = 31,
PROP_LOCK_POSITION = 32,
- PROP_FLOAT_OPACITY = 33
+ PROP_FLOAT_OPACITY = 33,
+ PROP_SYMMETRY = 34
} PropType;
typedef enum
diff --git a/app/xcf/xcf-save.c b/app/xcf/xcf-save.c
index 5ada022..17ebe3b 100644
--- a/app/xcf/xcf-save.c
+++ b/app/xcf/xcf-save.c
@@ -45,11 +45,14 @@
#include "core/gimpimage-metadata.h"
#include "core/gimpimage-private.h"
#include "core/gimpimage-sample-points.h"
+#include "core/gimpimage-symmetry.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 "core/gimpsymmetry.h"
#include "text/gimptextlayer.h"
#include "text/gimptextlayer-xcf.h"
@@ -361,6 +364,10 @@ xcf_save_image_props (XcfInfo *info,
xcf_check_error (xcf_save_prop (info, image, PROP_GUIDES, error,
gimp_image_get_guides (image)));
+ if (g_list_length (gimp_image_symmetry_get (image)))
+ xcf_check_error (xcf_save_prop (info, image, PROP_SYMMETRY, error,
+ gimp_image_symmetry_get (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)));
@@ -891,11 +898,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);
@@ -907,6 +921,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:
@@ -929,6 +946,164 @@ xcf_save_prop (XcfInfo *info,
}
break;
+ case PROP_SYMMETRY:
+ {
+ GList *syms;
+ GList *iter;
+ GimpSymmetry *sym;
+ GParamSpec **settings;
+ GParamSpec *spec;
+ gint n_settings;
+ 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 Symmetry 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;
+
+ syms = va_arg (args, GList *);
+
+ /* Index of active symmetry starting at 1
+ * (because 0 means none active) */
+ if (gimp_image_symmetry_selected (image))
+ {
+ for (i = 1, iter = syms; iter; iter = g_list_next (iter), i++)
+ {
+ sym = GIMP_SYMMETRY (iter->data);
+ if (sym == gimp_image_symmetry_selected (image))
+ break;
+ }
+ }
+ xcf_write_int32_check_error (info, (guint32 *) &i, 1);
+ /* Number of symmetry that follows. */
+ i = g_list_length (syms);
+ xcf_write_int32_check_error (info, (guint32 *) &i, 1);
+
+ for (iter = syms; iter; iter = g_list_next (iter))
+ {
+ const gchar *name;
+
+ sym = GIMP_SYMMETRY (iter->data);
+
+ name = g_type_name (sym->type);
+ xcf_write_string_check_error (info, (gchar **) &name, 1);
+
+ settings = gimp_symmetry_get_xcf_settings (sym,
+ &n_settings);
+
+ for (i = 0; i < n_settings; 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 (sym,
+ 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 (sym,
+ 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 (sym,
+ 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 (sym,
+ 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 (sym,
+ 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 (sym,
+ 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;
+ }
+ }
+ g_free (settings);
+ }
+
+ 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..712a9f2 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_SYMMETRY (editing state)
+ uint32 33 Type identification
+ uint32 plength Total length of the following payload in bytes
+ (sum of mlength for all registered symmetries)
+ uint32 active Index of the active symmetry, starting at 1
+ (0 means none is active)
+ uint32 n Number of symmetrythat follow
+ ,--------------- Repeat for each registered symmetry:
+ | string type name of symmetry
+ | ... params various parameters.
+ Type and order depends on the
+ gimp_symmetry_get_xcf_settings()
+ `--
+
+ PROP_SYMMETRY stores the existence and settings of any symmetry 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]