[gimp/symmetry: 2/5] Bug 648776 - mirror symmetries.
- From: Jehan Pagès <jehanp src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gimp/symmetry: 2/5] Bug 648776 - mirror symmetries.
- Date: Thu, 17 Dec 2015 19:55:00 +0000 (UTC)
commit 5012dd746712a2b5037e5247c97f895404432133
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 painting 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 (default of size 1).
This is also the first usage of custom guides for symmetry guiding.
Current version has to be activated in the playground.
app/actions/dialogs-actions.c | 6 +
app/config/gimpguiconfig.c | 14 +
app/config/gimpguiconfig.h | 1 +
app/config/gimprc-blurbs.h | 3 +
app/core/Makefile.am | 6 +
app/core/core-types.h | 5 +
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/gimpimage-private.h | 3 +
app/core/gimpimage-symmetry.c | 192 ++++++++++
app/core/gimpimage-symmetry.h | 38 ++
app/core/gimpimage.c | 60 +++-
app/core/gimpsymmetry-mirror.c | 701 ++++++++++++++++++++++++++++++++++++
app/core/gimpsymmetry-mirror.h | 67 ++++
app/core/gimpsymmetry.c | 451 +++++++++++++++++++++++
app/core/gimpsymmetry.h | 94 +++++
app/dialogs/dialogs-constructors.c | 12 +
app/dialogs/dialogs-constructors.h | 4 +
app/dialogs/dialogs.c | 4 +
app/dialogs/preferences-dialog.c | 3 +
app/display/display-enums.c | 31 ++
app/display/display-enums.h | 10 +
app/paint/gimpairbrush.c | 46 ++-
app/paint/gimpairbrush.h | 2 +
app/paint/gimpbrushcore.c | 53 ++--
app/paint/gimpbrushcore.h | 8 +-
app/paint/gimpclone.c | 26 ++-
app/paint/gimpconvolve.c | 143 +++++---
app/paint/gimpdodgeburn.c | 105 ++++---
app/paint/gimperaser.c | 90 +++--
app/paint/gimpheal.c | 27 ++-
app/paint/gimpink.c | 263 +++++++++-----
app/paint/gimpink.h | 6 +-
app/paint/gimpinkundo.c | 32 ++-
app/paint/gimpinkundo.h | 2 +-
app/paint/gimpmybrush.c | 246 +++++++++----
app/paint/gimppaintbrush.c | 163 +++++----
app/paint/gimppaintbrush.h | 2 +-
app/paint/gimppaintcore-loops.c | 25 +-
app/paint/gimppaintcore.c | 42 ++-
app/paint/gimppaintcore.h | 10 +-
app/paint/gimpperspectiveclone.c | 69 +++--
app/paint/gimpsmudge.c | 373 +++++++++++--------
app/paint/gimpsmudge.h | 2 +-
app/paint/gimpsourcecore.c | 163 +++++----
app/paint/gimpsourcecore.h | 3 +-
app/pdb/image-guides-cmds.c | 1 +
app/widgets/Makefile.am | 2 +
app/widgets/gimphelp-ids.h | 1 +
app/widgets/gimpsymmetryeditor.c | 506 ++++++++++++++++++++++++++
app/widgets/gimpsymmetryeditor.h | 58 +++
app/widgets/widgets-types.h | 1 +
app/xcf/xcf-load.c | 37 ++
app/xcf/xcf-save.c | 37 ++-
icons/Default/16/gimp-symmetry.png | Bin 0 -> 4828 bytes
icons/Default/24/gimp-symmetry.png | Bin 0 -> 1352 bytes
icons/Default/24/gimp-symmetry.xcf | Bin 0 -> 2456 bytes
icons/Default/Makefile.am | 2 +
libgimpwidgets/gimpicons.h | 1 +
menus/dialogs-menuitems.xml | 1 +
63 files changed, 3706 insertions(+), 746 deletions(-)
---
diff --git a/app/actions/dialogs-actions.c b/app/actions/dialogs-actions.c
index 23e7893..cd4032a 100644
--- a/app/actions/dialogs-actions.c
+++ b/app/actions/dialogs-actions.c
@@ -61,6 +61,12 @@ const GimpStringActionEntry dialogs_dockable_actions[] =
"gimp-device-status",
GIMP_HELP_DEVICE_STATUS_DIALOG },
+ { "dialogs-symmetry", GIMP_STOCK_SYMMETRY,
+ NC_("dialogs-action", "_Symmetry painting"), NULL,
+ NC_("dialogs-action", "Open the symmetry dialog"),
+ "gimp-symmetry-editor",
+ GIMP_HELP_SYMMETRY_DIALOG },
+
{ "dialogs-layers", GIMP_STOCK_LAYERS,
NC_("dialogs-action", "_Layers"), "<primary>L",
NC_("dialogs-action", "Open the layers dialog"),
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..c9dc262 100644
--- a/app/core/Makefile.am
+++ b/app/core/Makefile.am
@@ -256,6 +256,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 +361,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..242191b 100644
--- a/app/core/core-types.h
+++ b/app/core/core-types.h
@@ -174,6 +174,11 @@ typedef struct _GimpUndoStack GimpUndoStack;
typedef struct _GimpUndoAccumulator GimpUndoAccumulator;
+/* Symmetry transformations */
+
+typedef struct _GimpSymmetry GimpSymmetry;
+typedef struct _GimpMirror GimpMirror;
+
/* misc objects */
typedef struct _GimpBuffer GimpBuffer;
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/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..f974689
--- /dev/null
+++ b/app/core/gimpimage-symmetry.c
@@ -0,0 +1,192 @@
+/* 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"
+
+/**
+ * gimp_image_symmetry_list:
+ *
+ * Returns a list of #GType of all existing symmetries.
+ **/
+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_new:
+ * @image: the #GimpImage
+ * @type: the #GType of the symmetry
+ *
+ * Creates a new #GimpSymmetry of @type attached to @image.
+ *
+ * Returns: the new #GimpSymmetry.
+ **/
+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;
+}
+
+/**
+ * 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.
+ **/
+void
+gimp_image_symmetry_add (GimpImage *image,
+ GimpSymmetry *sym)
+{
+ GimpImagePrivate *private;
+
+ g_return_if_fail (GIMP_IS_IMAGE (image));
+ g_return_if_fail (GIMP_IS_SYMMETRY (sym));
+
+ private = GIMP_IMAGE_GET_PRIVATE (image);
+
+ private->symmetries = g_list_prepend (private->symmetries,
+ 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)
+ gimp_image_symmetry_select (image, G_TYPE_NONE);
+ 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)
+{
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), FALSE);
+
+ g_object_set (image,
+ "symmetry", type,
+ NULL);
+
+ return TRUE;
+}
+
+/**
+ * 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..109bd53
--- /dev/null
+++ b/app/core/gimpimage-symmetry.h
@@ -0,0 +1,38 @@
+/* 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_new (GimpImage *image,
+ GType type);
+void gimp_image_symmetry_add (GimpImage *image,
+ GimpSymmetry *sym);
+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 0b335f4..61cf9fe 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"
@@ -131,7 +133,8 @@ enum
PROP_BASE_TYPE,
PROP_PRECISION,
PROP_METADATA,
- PROP_BUFFER
+ PROP_BUFFER,
+ PROP_SYMMETRY
};
@@ -625,6 +628,11 @@ gimp_image_class_init (GimpImageClass *klass)
g_object_class_override_property (object_class, PROP_BUFFER, "buffer");
+ g_object_class_install_property (object_class, PROP_SYMMETRY,
+ g_param_spec_int ("symmetry",
+ NULL, _("Symmetry"),
+ G_TYPE_NONE, G_MAXINT, G_TYPE_NONE,
+ GIMP_PARAM_READWRITE));
g_type_class_add_private (klass, sizeof (GimpImagePrivate));
}
@@ -697,6 +705,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;
@@ -860,6 +871,42 @@ gimp_image_set_property (GObject *object,
break;
case PROP_METADATA:
case PROP_BUFFER:
+ break;
+ case PROP_SYMMETRY:
+ {
+ GType type = g_value_get_int (value);
+
+ if (private->selected_symmetry)
+ g_object_set (private->selected_symmetry,
+ "active", FALSE,
+ NULL);
+ private->selected_symmetry = NULL;
+
+ if (type != G_TYPE_NONE)
+ {
+ GList *iter;
+
+ 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;
+ }
+
+ if (private->selected_symmetry == NULL)
+ {
+ GimpSymmetry *sym;
+
+ sym = gimp_image_symmetry_new (image, type);
+ gimp_image_symmetry_add (image, sym);
+ private->selected_symmetry = sym;
+ }
+ g_object_set (private->selected_symmetry,
+ "active", TRUE,
+ NULL);
+ }
+ }
+ break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
@@ -901,6 +948,11 @@ gimp_image_get_property (GObject *object,
case PROP_BUFFER:
g_value_set_object (value, gimp_image_get_buffer (GIMP_PICKABLE (image)));
break;
+ case PROP_SYMMETRY:
+ g_value_set_int (value,
+ private->selected_symmetry ?
+ private->selected_symmetry->type : G_TYPE_NONE);
+ break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
@@ -1051,6 +1103,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);
diff --git a/app/core/gimpsymmetry-mirror.c b/app/core/gimpsymmetry-mirror.c
new file mode 100644
index 0000000..878f760
--- /dev/null
+++ b/app/core/gimpsymmetry-mirror.c
@@ -0,0 +1,701 @@
+/* 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 <cairo.h>
+#include <gegl.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+
+#include "libgimpconfig/gimpconfig.h"
+
+#include "core-types.h"
+
+#include "gimp.h"
+#include "gimp-cairo.h"
+#include "gimpbrush.h"
+#include "gimpguide.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_add_guide (GimpMirror *mirror,
+ GimpOrientationType orientation);
+static void gimp_mirror_remove_guide (GimpMirror *mirror,
+ GimpOrientationType orientation);
+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 void gimp_mirror_active_changed (GimpSymmetry *sym);
+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->active_changed = gimp_mirror_active_changed;
+
+ /* 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 (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 (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_add_guide (GimpMirror *mirror,
+ GimpOrientationType orientation)
+{
+ static GimpRGB normal_fg = { 1.0, 1.0, 1.0, 1.0 };
+ static GimpRGB normal_bg = { 0.0, 1.0, 0.0, 1.0 };
+ static GimpRGB active_fg = { 0.0, 1.0, 0.0, 1.0 };
+ static GimpRGB active_bg = { 1.0, 0.0, 0.0, 1.0 };
+ GimpSymmetry *sym;
+ GimpImage *image;
+ Gimp *gimp;
+ GimpGuide *guide;
+ gint position;
+
+ g_return_if_fail (GIMP_IS_MIRROR (mirror));
+
+ sym = GIMP_SYMMETRY (mirror);
+ image = sym->image;
+ gimp = GIMP (image->gimp);
+
+ guide = gimp_guide_custom_new (orientation,
+ gimp->next_guide_ID++,
+ &normal_fg, &normal_bg,
+ &active_fg, &active_bg,
+ 1.0);
+
+ if (orientation == GIMP_ORIENTATION_HORIZONTAL)
+ {
+ mirror->horizontal_guide = 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;
+ position = (gint) mirror->horizontal_position;
+ }
+ else
+ {
+ mirror->vertical_guide = 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;
+ position = (gint) mirror->vertical_position;
+ }
+ g_signal_connect (G_OBJECT (guide), "removed",
+ G_CALLBACK (gimp_mirror_guide_removed_cb),
+ mirror);
+
+ gimp_image_add_guide (image, guide,
+ (gint) position);
+
+ g_signal_connect (G_OBJECT (guide), "notify::position",
+ G_CALLBACK (gimp_mirror_guide_position_cb),
+ mirror);
+}
+
+static void
+gimp_mirror_remove_guide (GimpMirror *mirror,
+ GimpOrientationType orientation)
+{
+ GimpSymmetry *sym;
+ GimpImage *image;
+ GimpGuide *guide;
+
+ g_return_if_fail (GIMP_IS_MIRROR (mirror));
+
+ sym = GIMP_SYMMETRY (mirror);
+ image = sym->image;
+ guide = (orientation == GIMP_ORIENTATION_HORIZONTAL) ?
+ mirror->horizontal_guide : mirror->vertical_guide;
+
+ g_signal_handlers_disconnect_by_func (G_OBJECT (guide),
+ gimp_mirror_guide_removed_cb,
+ mirror);
+ g_signal_handlers_disconnect_by_func (G_OBJECT (guide),
+ gimp_mirror_guide_position_cb,
+ mirror);
+ gimp_image_remove_guide (image, guide, FALSE);
+ g_object_unref (guide);
+
+ if (orientation == GIMP_ORIENTATION_HORIZONTAL)
+ mirror->horizontal_guide = NULL;
+ else
+ mirror->vertical_guide = NULL;
+}
+
+static void
+gimp_mirror_guide_removed_cb (GObject *object,
+ GimpMirror *mirror)
+{
+ GimpSymmetry *symmetry = GIMP_SYMMETRY (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_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;
+
+ if (mirror->vertical_guide &&
+ ! 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 (symmetry->image,
+ mirror->vertical_guide,
+ FALSE);
+ g_clear_object (&mirror->vertical_guide);
+ }
+ }
+ else if (GIMP_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 &&
+ ! 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 (symmetry->image,
+ mirror->horizontal_guide,
+ FALSE);
+ g_clear_object (&mirror->horizontal_guide);
+ }
+ }
+
+ if (mirror->horizontal_guide == NULL &&
+ mirror->vertical_guide == NULL)
+ {
+ gimp_image_symmetry_remove (symmetry->image,
+ GIMP_SYMMETRY (mirror));
+ }
+ else
+ {
+ gimp_mirror_reset (mirror);
+ g_signal_emit_by_name (mirror, "update-ui",
+ GIMP_SYMMETRY (mirror)->image);
+ }
+}
+
+static void
+gimp_mirror_guide_position_cb (GObject *object,
+ GParamSpec *pspec,
+ GimpMirror *mirror)
+{
+ GimpGuide *guide;
+
+ guide = GIMP_GUIDE (object);
+
+ if (guide == mirror->horizontal_guide)
+ {
+ mirror->horizontal_position = (gdouble) gimp_guide_get_position (guide);
+ }
+ else if (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 void
+gimp_mirror_active_changed (GimpSymmetry *sym)
+{
+ GimpMirror *mirror = GIMP_MIRROR (sym);
+
+ if (sym->active)
+ {
+ if ((mirror->horizontal_mirror || mirror->point_symmetry) &&
+ ! mirror->horizontal_guide)
+ gimp_mirror_add_guide (mirror, GIMP_ORIENTATION_HORIZONTAL);
+ if ((mirror->vertical_mirror || mirror->point_symmetry) &&
+ ! mirror->vertical_guide)
+ gimp_mirror_add_guide (mirror, GIMP_ORIENTATION_VERTICAL);
+ }
+ else
+ {
+ if (mirror->horizontal_guide)
+ gimp_mirror_remove_guide (mirror, GIMP_ORIENTATION_HORIZONTAL);
+ if (mirror->vertical_guide)
+ gimp_mirror_remove_guide (mirror, GIMP_ORIENTATION_VERTICAL);
+ }
+}
+
+static void
+gimp_mirror_set_horizontal_symmetry (GimpMirror *mirror,
+ gboolean active)
+{
+ g_return_if_fail (GIMP_IS_MIRROR (mirror));
+
+ if (active == mirror->horizontal_mirror)
+ return;
+
+ mirror->horizontal_mirror = active;
+
+ if (active)
+ {
+ if (! mirror->horizontal_guide)
+ gimp_mirror_add_guide (mirror, GIMP_ORIENTATION_HORIZONTAL);
+ }
+ else if (! mirror->point_symmetry)
+ gimp_mirror_remove_guide (mirror, GIMP_ORIENTATION_HORIZONTAL);
+
+ gimp_mirror_reset (mirror);
+}
+
+static void
+gimp_mirror_set_vertical_symmetry (GimpMirror *mirror,
+ gboolean active)
+{
+ g_return_if_fail (GIMP_IS_MIRROR (mirror));
+
+ if (active == mirror->vertical_mirror)
+ return;
+
+ mirror->vertical_mirror = active;
+
+ if (active)
+ {
+ if (! mirror->vertical_guide)
+ gimp_mirror_add_guide (mirror, GIMP_ORIENTATION_VERTICAL);
+ }
+ else if (! mirror->point_symmetry)
+ gimp_mirror_remove_guide (mirror, GIMP_ORIENTATION_VERTICAL);
+
+ gimp_mirror_reset (mirror);
+}
+
+static void
+gimp_mirror_set_point_symmetry (GimpMirror *mirror,
+ gboolean active)
+{
+ g_return_if_fail (GIMP_IS_MIRROR (mirror));
+
+ if (active == mirror->point_symmetry)
+ return;
+
+ mirror->point_symmetry = active;
+
+ if (active)
+ {
+ /* Show the horizontal guide unless already shown */
+ if (! mirror->horizontal_guide)
+ gimp_mirror_add_guide (mirror, GIMP_ORIENTATION_HORIZONTAL);
+
+ /* Show the vertical guide unless already shown */
+ if (! mirror->vertical_guide)
+ gimp_mirror_add_guide (mirror, GIMP_ORIENTATION_VERTICAL);
+ }
+ else
+ {
+ /* Remove the horizontal guide unless needed by horizontal mirror */
+ if (! mirror->horizontal_mirror)
+ gimp_mirror_remove_guide (mirror, GIMP_ORIENTATION_HORIZONTAL);
+ /* Remove the vertical guide unless needed by vertical mirror */
+ if (! mirror->vertical_mirror)
+ gimp_mirror_remove_guide (mirror, GIMP_ORIENTATION_VERTICAL);
+ }
+
+ gimp_mirror_reset (mirror);
+}
diff --git a/app/core/gimpsymmetry-mirror.h b/app/core/gimpsymmetry-mirror.h
new file mode 100644
index 0000000..6d83de0
--- /dev/null
+++ b/app/core/gimpsymmetry-mirror.h
@@ -0,0 +1,67 @@
+/* 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;
+ GimpGuide *horizontal_guide;
+ GimpGuide *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..5724707
--- /dev/null
+++ b/app/core/gimpsymmetry.c
@@ -0,0 +1,451 @@
+/* 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 "libgimpbase/gimpbase.h"
+#include "libgimpconfig/gimpconfig.h"
+
+#include "core-types.h"
+
+#include "gimpdrawable.h"
+#include "gimpimage.h"
+#include "gimpimage-symmetry.h"
+#include "gimpitem.h"
+#include "gimpsymmetry.h"
+
+#include "gimp-intl.h"
+
+enum
+{
+ STROKES_UPDATED,
+ UPDATE_UI,
+ ACTIVE_CHANGED,
+ LAST_SIGNAL
+};
+
+enum
+{
+ PROP_0,
+ PROP_IMAGE,
+ PROP_ACTIVE,
+};
+
+/* 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_WITH_CODE (GimpSymmetry, gimp_symmetry, GIMP_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (GIMP_TYPE_CONFIG, NULL))
+
+
+#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);
+
+ gimp_symmetry_signals[ACTIVE_CHANGED] =
+ g_signal_new ("active-changed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpSymmetryClass, active_changed),
+ NULL, NULL,
+ NULL,
+ G_TYPE_NONE, 0);
+
+ 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->active_changed = NULL;
+
+ 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));
+ GIMP_CONFIG_INSTALL_PROP_BOOLEAN (object_class, PROP_ACTIVE,
+ "active",
+ _("Activate symmetry painting"),
+ FALSE,
+ GIMP_PARAM_STATIC_STRINGS);
+}
+
+
+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;
+ case PROP_ACTIVE:
+ sym->active = g_value_get_boolean (value);
+ g_signal_emit (sym, gimp_symmetry_signals[ACTIVE_CHANGED], 0,
+ sym->active);
+ 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;
+ case PROP_ACTIVE:
+ g_value_set_boolean (value, sym->active);
+ 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_parasite_name:
+ * @type: the #GimpSymmetry's #GType
+ *
+ * Returns: a newly allocated string.
+ */
+gchar *
+gimp_symmetry_parasite_name (GType type)
+{
+ GimpSymmetryClass *klass;
+
+ klass = g_type_class_ref (type);
+
+ return g_strconcat ("gimp-image-symmetry:", klass->label, NULL);
+}
+
+GimpParasite *
+gimp_symmetry_to_parasite (const GimpSymmetry *sym)
+{
+ GimpParasite *parasite;
+ gchar *parasite_name;
+ gchar *str;
+
+ g_return_val_if_fail (GIMP_IS_SYMMETRY (sym), NULL);
+
+ str = gimp_config_serialize_to_string (GIMP_CONFIG (sym), NULL);
+ g_return_val_if_fail (str != NULL, NULL);
+
+ parasite_name = gimp_symmetry_parasite_name (sym->type);
+ parasite = gimp_parasite_new (parasite_name,
+ GIMP_PARASITE_PERSISTENT,
+ strlen (str) + 1, str);
+ g_free (parasite_name);
+ g_free (str);
+
+ return parasite;
+}
+
+GimpSymmetry *
+gimp_symmetry_from_parasite (const GimpParasite *parasite,
+ GimpImage *image,
+ GType type)
+{
+ GimpSymmetry *symmetry;
+ gchar *parasite_name;
+ const gchar *str;
+ GError *error = NULL;
+
+ parasite_name = gimp_symmetry_parasite_name (type);
+
+ g_return_val_if_fail (parasite != NULL, NULL);
+ g_return_val_if_fail (strcmp (gimp_parasite_name (parasite),
+ parasite_name) == 0,
+ NULL);
+ g_free (parasite_name);
+
+ str = gimp_parasite_data (parasite);
+ g_return_val_if_fail (str != NULL, NULL);
+
+ symmetry = gimp_image_symmetry_new (image, type);
+
+ if (! gimp_config_deserialize_string (GIMP_CONFIG (symmetry),
+ str,
+ gimp_parasite_data_size (parasite),
+ NULL,
+ &error))
+ {
+ g_warning ("Failed to deserialize symmetry parasite: %s", error->message);
+ g_error_free (error);
+ }
+
+ return symmetry;
+}
diff --git a/app/core/gimpsymmetry.h b/app/core/gimpsymmetry.h
new file mode 100644
index 0000000..50c3d4e
--- /dev/null
+++ b/app/core/gimpsymmetry.h
@@ -0,0 +1,94 @@
+/* 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;
+ gboolean active;
+
+ GList *strokes;
+
+ GType type;
+};
+
+struct _GimpSymmetryClass
+{
+ GimpObjectClass parent_class;
+
+ const gchar * label;
+
+ /* Virtual functions */
+ void (* update_strokes) (GimpSymmetry *symmetry,
+ GimpDrawable *drawable,
+ GimpCoords *origin);
+ GeglNode * (* get_operation) (GimpSymmetry *symmetry,
+ gint stroke,
+ gint paint_width,
+ gint paint_height);
+ GParamSpec **
+ (* get_settings) (GimpSymmetry *symmetry,
+ gint *n_properties);
+ void (* active_changed) (GimpSymmetry *symmetry);
+};
+
+
+GType gimp_symmetry_get_type (void) G_GNUC_CONST;
+
+void gimp_symmetry_set_origin (GimpSymmetry *symmetry,
+ GimpDrawable *drawable,
+ GimpCoords *origin);
+
+GimpCoords * gimp_symmetry_get_origin (GimpSymmetry *symmetry);
+gint gimp_symmetry_get_size (GimpSymmetry *symmetry);
+GimpCoords * gimp_symmetry_get_coords (GimpSymmetry *symmetry,
+ gint stroke);
+GeglNode * gimp_symmetry_get_operation (GimpSymmetry *symmetry,
+ gint stroke,
+ gint paint_width,
+ gint paint_height);
+GParamSpec ** gimp_symmetry_get_settings (GimpSymmetry *symmetry,
+ gint *n_properties);
+
+gchar * gimp_symmetry_parasite_name (GType type);
+GimpParasite * gimp_symmetry_to_parasite (const GimpSymmetry *symmetry);
+GimpSymmetry * gimp_symmetry_from_parasite (const GimpParasite *parasite,
+ GimpImage *image,
+ GType type);
+#endif /* __GIMP_SYMMETRY_H__ */
diff --git a/app/dialogs/dialogs-constructors.c b/app/dialogs/dialogs-constructors.c
index 267625f..9dfba17 100644
--- a/app/dialogs/dialogs-constructors.c
+++ b/app/dialogs/dialogs-constructors.c
@@ -53,6 +53,7 @@
#include "widgets/gimppatternfactoryview.h"
#include "widgets/gimpsamplepointeditor.h"
#include "widgets/gimpselectioneditor.h"
+#include "widgets/gimpsymmetryeditor.h"
#include "widgets/gimptemplateview.h"
#include "widgets/gimptoolbox.h"
#include "widgets/gimptooloptionseditor.h"
@@ -705,6 +706,17 @@ dialogs_selection_editor_new (GimpDialogFactory *factory,
}
GtkWidget *
+dialogs_symmetry_editor_new (GimpDialogFactory *factory,
+ GimpContext *context,
+ GimpUIManager *ui_manager,
+ gint view_size)
+{
+ return gimp_symmetry_editor_new (context->gimp,
+ gimp_context_get_image (context),
+ gimp_dialog_factory_get_menu_factory (factory));
+}
+
+GtkWidget *
dialogs_undo_editor_new (GimpDialogFactory *factory,
GimpContext *context,
GimpUIManager *ui_manager,
diff --git a/app/dialogs/dialogs-constructors.h b/app/dialogs/dialogs-constructors.h
index 53b8d53..c033505 100644
--- a/app/dialogs/dialogs-constructors.h
+++ b/app/dialogs/dialogs-constructors.h
@@ -238,6 +238,10 @@ GtkWidget * dialogs_selection_editor_new (GimpDialogFactory *factory,
GimpContext *context,
GimpUIManager *ui_manager,
gint view_size);
+GtkWidget * dialogs_symmetry_editor_new (GimpDialogFactory *factory,
+ GimpContext *context,
+ GimpUIManager *ui_manager,
+ gint view_size);
GtkWidget * dialogs_undo_editor_new (GimpDialogFactory *factory,
GimpContext *context,
GimpUIManager *ui_manager,
diff --git a/app/dialogs/dialogs.c b/app/dialogs/dialogs.c
index 17eae74..c3ee20d 100644
--- a/app/dialogs/dialogs.c
+++ b/app/dialogs/dialogs.c
@@ -371,6 +371,10 @@ static const GimpDialogFactoryEntry entries[] =
N_("Selection"), N_("Selection Editor"), GIMP_STOCK_SELECTION,
GIMP_HELP_SELECTION_DIALOG,
dialogs_selection_editor_new, 0, FALSE),
+ DOCKABLE ("gimp-symmetry-editor",
+ N_("Symmetry Painting"), NULL, GIMP_STOCK_SYMMETRY,
+ GIMP_HELP_SYMMETRY_DIALOG,
+ dialogs_symmetry_editor_new, 0, FALSE),
DOCKABLE ("gimp-undo-history",
N_("Undo"), N_("Undo History"), GIMP_STOCK_UNDO_HISTORY,
GIMP_HELP_UNDO_DIALOG,
diff --git a/app/dialogs/preferences-dialog.c b/app/dialogs/preferences-dialog.c
index e9efbcb..f32474d 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/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..ce1320e 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;
@@ -819,18 +825,15 @@ gimp_brush_core_get_paint_buffer (GimpPaintCore *paint_core,
gint drawable_width, drawable_height;
gint brush_width, brush_height;
- if (GIMP_BRUSH_CORE_GET_CLASS (core)->handles_transforming_brush)
- {
- gimp_brush_core_eval_transform_dynamics (core,
- drawable,
- paint_options,
- coords);
- }
-
gimp_brush_transform_size (core->brush,
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 +963,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 +1007,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 +1412,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 +1421,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 +1439,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 +1448,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 +1466,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 +1601,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 +1620,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 6cc5f09..78a24b0 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..15e4112 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,14 @@ 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 +122,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,10 +138,16 @@ 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;
fade_point = gimp_paint_options_get_fade (paint_options, image,
paint_core->pixel_dist);
+ coords = gimp_symmetry_get_origin (sym);
opacity = gimp_dynamics_get_linear_value (dynamics,
GIMP_DYNAMICS_OUTPUT_OPACITY,
coords,
@@ -149,61 +156,77 @@ gimp_convolve_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;
-
- 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);
+ gimp_brush_core_eval_transform_dynamics (GIMP_BRUSH_CORE (paint_core),
+ drawable,
+ paint_options,
+ coords);
+ n_strokes = gimp_symmetry_get_size (sym);
+ for (i = 0; i < n_strokes; i++)
+ {
+ 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);
+
+ 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..05fc9e0 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,10 +122,16 @@ gimp_dodge_burn_motion (GimpPaintCore *paint_core,
gdouble fade_point;
gdouble opacity;
gdouble force;
+ const 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);
+ coords = gimp_symmetry_get_origin (sym);
opacity = gimp_dynamics_get_linear_value (dynamics,
GIMP_DYNAMICS_OUTPUT_OPACITY,
coords,
@@ -133,40 +140,56 @@ gimp_dodge_burn_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;
-
- /* 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);
+ gimp_brush_core_eval_transform_dynamics (GIMP_BRUSH_CORE (paint_core),
+ drawable,
+ paint_options,
+ coords);
+ n_strokes = gimp_symmetry_get_size (sym);
+ for (i = 0; i < n_strokes; i++)
+ {
+ 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);
+
+ /* 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..175c598 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,10 +134,16 @@ 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);
+ coords = gimp_symmetry_get_origin (sym);
opacity = gimp_dynamics_get_linear_value (dynamics,
GIMP_DYNAMICS_OUTPUT_OPACITY,
coords,
@@ -145,19 +152,9 @@ gimp_eraser_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;
-
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 +162,50 @@ 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);
+ gimp_brush_core_eval_transform_dynamics (GIMP_BRUSH_CORE (paint_core),
+ drawable,
+ paint_options,
+ coords);
+
+ 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;
+
+
+ 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..34a0941 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,48 @@ 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 +223,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 +243,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 +294,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 2e47ca2..a98a93a 100644
--- a/app/paint/gimpmybrush.c
+++ b/app/paint/gimpmybrush.c
@@ -45,6 +45,7 @@
#include "core/gimpdrawable.h"
#include "core/gimpimage.h"
#include "core/gimpimage-undo.h"
+#include "core/gimpsymmetry.h"
#include "core/gimptempbuf.h"
#include "gimpmybrushsurface.h"
@@ -61,24 +62,25 @@ struct _GimpMybrushPrivate
#else
GimpMybrushSurface *surface;
#endif
- MyPaintBrush *brush;
+ GList *brushes;
gint64 lastTime;
};
/* 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)
@@ -102,8 +104,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));
@@ -118,10 +123,25 @@ 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)
{
@@ -134,6 +154,8 @@ gimp_mybrush_paint (GimpPaintCore *paint_core,
#endif
GimpRGB fg;
GimpHSV hsv;
+ gint n_strokes;
+ gint i;
switch (paint_state)
{
@@ -160,107 +182,193 @@ gimp_mybrush_paint (GimpPaintCore *paint_core,
mypaint_gegl_tiled_surface_set_buffer (mybrush->private->surface, buffer);
g_object_unref (buffer);
#else
+ brush_data = gimp_mybrush_options_get_brush_data (options);
+
mybrush->private->surface = gimp_mypaint_surface_new (gimp_drawable_get_buffer (drawable),
gimp_drawable_get_active_mask (drawable));
#endif
- 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;
+ }
+
+ n_strokes = gimp_symmetry_get_size (sym);
+ for (i = 0; i < n_strokes; i++)
+ {
+ MyPaintBrush *brush;
+
+ brush = mypaint_brush_new ();
+ mypaint_brush_from_defaults (brush);
+ if (brush_data)
+ mypaint_brush_from_string (brush, brush_data);
+
+#if 0
+ active_mask = gimp_drawable_get_active_mask (drawable);
+
+ mypaint_brush_set_base_value (brush,
+ MYPAINT_BRUSH_SETTING_LOCK_ALPHA,
+ (active_mask & GIMP_COMPONENT_MASK_ALPHA) ?
+ FALSE : TRUE);
+#endif
+ 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_RADIUS_LOGARITHMIC,
+ options->radius);
+ mypaint_brush_set_base_value (brush,
+ MYPAINT_BRUSH_SETTING_OPAQUE,
+ options->opaque * gimp_context_get_opacity (context));
+ mypaint_brush_set_base_value (brush,
+ MYPAINT_BRUSH_SETTING_HARDNESS,
+ options->hardness);
+
+ mypaint_brush_new_stroke (brush);
+
+ mybrush->private->brushes = g_list_prepend (mybrush->private->brushes, brush);
+ mybrush->private->lastTime = 0;
+ }
+ mybrush->private->brushes = g_list_reverse (mybrush->private->brushes);
+ }
+ break;
+
+ case GIMP_PAINT_STATE_MOTION:
+ 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;
+
+ g_list_free_full (mybrush->private->brushes,
+ (GDestroyNotify) mypaint_brush_unref);
+ mybrush->private->brushes = NULL;
+ break;
+ }
+}
+
+static void
+gimp_mybrush_motion (GimpPaintCore *paint_core,
+ GimpDrawable *drawable,
+ GimpPaintOptions *paint_options,
+ GimpSymmetry *sym,
+ guint32 time)
+{
+ GimpMybrush *mybrush = GIMP_MYBRUSH (paint_core);
+ GimpContext *context = GIMP_CONTEXT (paint_options);
+ MyPaintBrush *brush;
+ GimpCoords *coords;
+ MyPaintRectangle rect;
+ gint n_strokes;
+ gint i;
+ GList *iter;
+
+ 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)
+ {
+ const gchar *brush_data;
+ GimpMybrushOptions *options = GIMP_MYBRUSH_OPTIONS (paint_options);
+ GimpRGB fg;
+ GimpHSV hsv;
+
+ g_list_free_full (mybrush->private->brushes,
+ (GDestroyNotify) mypaint_brush_unref);
+ mybrush->private->brushes = NULL;
+ for (i = 0; i < n_strokes; i++)
+ {
+ brush = mypaint_brush_new ();
brush_data = gimp_mybrush_options_get_brush_data (options);
+ mypaint_brush_from_defaults (brush);
if (brush_data)
- mypaint_brush_from_string (mybrush->private->brush, brush_data);
+ mypaint_brush_from_string (brush, brush_data);
#if 0
active_mask = gimp_drawable_get_active_mask (drawable);
- mypaint_brush_set_base_value (mybrush->private->brush,
+ mypaint_brush_set_base_value (brush,
MYPAINT_BRUSH_SETTING_LOCK_ALPHA,
(active_mask & GIMP_COMPONENT_MASK_ALPHA) ?
FALSE : TRUE);
#endif
-
gimp_context_get_foreground (context, &fg);
gimp_rgb_to_hsv (&fg, &hsv);
- mypaint_brush_set_base_value (mybrush->private->brush,
+ mypaint_brush_set_base_value (brush,
MYPAINT_BRUSH_SETTING_COLOR_H,
hsv.h);
- mypaint_brush_set_base_value (mybrush->private->brush,
+ mypaint_brush_set_base_value (brush,
MYPAINT_BRUSH_SETTING_COLOR_S,
hsv.s);
- mypaint_brush_set_base_value (mybrush->private->brush,
+ mypaint_brush_set_base_value (brush,
MYPAINT_BRUSH_SETTING_COLOR_V,
hsv.v);
- mypaint_brush_set_base_value (mybrush->private->brush,
+ mypaint_brush_set_base_value (brush,
MYPAINT_BRUSH_SETTING_RADIUS_LOGARITHMIC,
options->radius);
- mypaint_brush_set_base_value (mybrush->private->brush,
+ mypaint_brush_set_base_value (brush,
MYPAINT_BRUSH_SETTING_OPAQUE,
options->opaque * gimp_context_get_opacity (context));
- mypaint_brush_set_base_value (mybrush->private->brush,
+ mypaint_brush_set_base_value (brush,
MYPAINT_BRUSH_SETTING_HARDNESS,
options->hardness);
- mypaint_brush_new_stroke (mybrush->private->brush);
- mybrush->private->lastTime = 0;
- }
- break;
+ mypaint_brush_new_stroke (brush);
- case GIMP_PAINT_STATE_MOTION:
- gimp_mybrush_motion (paint_core, drawable, paint_options, coords, 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;
- break;
+ mybrush->private->brushes = g_list_prepend (mybrush->private->brushes, brush);
+ }
}
-}
-
-static void
-gimp_mybrush_motion (GimpPaintCore *paint_core,
- GimpDrawable *drawable,
- GimpPaintOptions *paint_options,
- const GimpCoords *coords,
- guint32 time)
-{
- GimpMybrush *mybrush = GIMP_MYBRUSH (paint_core);
- MyPaintRectangle rect;
mypaint_surface_begin_atomic ((MyPaintSurface *) mybrush->private->surface);
-
-
- if (mybrush->private->lastTime == 0)
+ for (iter = mybrush->private->brushes, i = 0; iter ; iter = g_list_next (iter), i++)
{
- /* First motion, so we need a zero pressure event to start the stroke */
- mybrush->private->lastTime = (gint64)time - 15;
+ gint64 lastTime = mybrush->private->lastTime;
+
+ brush = iter->data;
- mypaint_brush_stroke_to (mybrush->private->brush,
+ coords = gimp_symmetry_get_coords (sym, i);
+ if (lastTime == 0)
+ {
+ /* First motion, so we need zero pressure events to start the strokes */
+ lastTime = (gint64)time - 15;
+
+ mypaint_brush_stroke_to (brush,
+ (MyPaintSurface *) mybrush->private->surface,
+ coords->x,
+ coords->y,
+ 0.0f,
+ coords->xtilt,
+ coords->ytilt,
+ 1.0f /* Pretend the cursor hasn't moved in a while */);
+ }
+ mypaint_brush_stroke_to (brush,
(MyPaintSurface *) mybrush->private->surface,
coords->x,
coords->y,
- 0.0f,
+ coords->pressure,
coords->xtilt,
coords->ytilt,
- 1.0f /* Pretend the cursor hasn't moved in a while */);
+ (time - lastTime) * 0.001f);
}
-
- mypaint_brush_stroke_to (mybrush->private->brush,
- (MyPaintSurface *) mybrush->private->surface,
- coords->x,
- coords->y,
- coords->pressure,
- coords->xtilt,
- coords->ytilt,
- (time - mybrush->private->lastTime) * 0.001f);
- mybrush->private->lastTime = time;
-
mypaint_surface_end_atomic ((MyPaintSurface *) mybrush->private->surface,
&rect);
+ mybrush->private->lastTime = time;
if (rect.width > 0 && rect.height > 0)
{
diff --git a/app/paint/gimppaintbrush.c b/app/paint/gimppaintbrush.c
index a41c278..f767657 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,18 @@ _gimp_paintbrush_motion (GimpPaintCore *paint_core,
gdouble fade_point;
gdouble grad_point;
gdouble force;
+ const GimpCoords *coords;
+ GeglNode *op;
+ gint n_strokes;
+ gint i;
image = gimp_item_get_image (GIMP_ITEM (drawable));
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 +165,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 +173,98 @@ _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))
+
+ if (GIMP_BRUSH_CORE_GET_CLASS (brush_core)->handles_transforming_brush)
{
- /* optionally take the color from the current gradient */
+ gimp_brush_core_eval_transform_dynamics (brush_core,
+ drawable,
+ paint_options,
+ coords);
+ }
- GeglColor *color;
+ n_strokes = gimp_symmetry_get_size (sym);
+ for (i = 0; i < n_strokes; i++)
+ {
+ 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 */
- opacity *= gradient_color.a;
- gimp_rgb_set_alpha (&gradient_color, GIMP_OPACITY_OPAQUE);
+ GeglColor *color;
- color = gimp_gegl_color_new (&gradient_color);
+ opacity *= gradient_color.a;
+ gimp_rgb_set_alpha (&gradient_color, GIMP_OPACITY_OPAQUE);
- gegl_buffer_set_color (paint_buffer, NULL, color);
- g_object_unref (color);
+ color = gimp_gegl_color_new (&gradient_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 */
+ gegl_buffer_set_color (paint_buffer, NULL, color);
+ g_object_unref (color);
- GimpRGB foreground;
- GeglColor *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 */
- gimp_context_get_foreground (context, &foreground);
- color = gimp_gegl_color_new (&foreground);
+ GimpRGB foreground;
+ GeglColor *color;
- gegl_buffer_set_color (paint_buffer, NULL, color);
- g_object_unref (color);
- }
+ gimp_context_get_foreground (context, &foreground);
+ color = gimp_gegl_color_new (&foreground);
+
+ 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.c b/app/paint/gimppaintcore.c
index ccca4b4..d88da5e 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)
{
- GeglBuffer *paint_mask_buffer =
- gimp_temp_buf_create_buffer ((GimpTempBuf *) paint_mask);
+ GimpTempBuf *modified_mask = gimp_temp_buf_copy (paint_mask);
+ GeglBuffer *paint_mask_buffer =
+ 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/gimpperspectiveclone.c b/app/paint/gimpperspectiveclone.c
index 6459eac..fcd5452 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)
{
@@ -284,36 +289,44 @@ gimp_perspective_clone_paint (GimpPaintCore *paint_core,
gint dest_x;
gint dest_y;
+ gint n_strokes;
+ gint 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)
+ 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..397132b 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,90 @@ 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);
+ coords = gimp_symmetry_get_origin (sym);
+ gimp_brush_core_eval_transform_dynamics (GIMP_BRUSH_CORE (paint_core),
+ drawable,
+ paint_options,
+ coords);
- /* 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,28 +265,35 @@ 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);
+ coords = gimp_symmetry_get_origin (sym);
opacity = gimp_dynamics_get_linear_value (dynamics,
GIMP_DYNAMICS_OUTPUT_OPACITY,
coords,
@@ -262,90 +302,111 @@ gimp_smudge_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_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_brush_core_eval_transform_dynamics (GIMP_BRUSH_CORE (paint_core),
+ drawable,
+ paint_options,
+ coords);
- 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);
+
+ 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 cad949b..364c70e 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,27 @@ 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);
+ /* 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 +412,91 @@ 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;
+ gimp_brush_core_eval_transform_dynamics (GIMP_BRUSH_CORE (paint_core),
+ drawable,
+ paint_options,
+ origin);
- 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))
+ n_strokes = gimp_symmetry_get_size (sym);
+ 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/pdb/image-guides-cmds.c b/app/pdb/image-guides-cmds.c
index 10e78c9..e81335c 100644
--- a/app/pdb/image-guides-cmds.c
+++ b/app/pdb/image-guides-cmds.c
@@ -27,6 +27,7 @@
#include "pdb-types.h"
+#include "cairo.h"
#include "core/gimpguide.h"
#include "core/gimpimage-guides.h"
#include "core/gimpimage-undo-push.h"
diff --git a/app/widgets/Makefile.am b/app/widgets/Makefile.am
index 2b8c82e..53f3e58 100644
--- a/app/widgets/Makefile.am
+++ b/app/widgets/Makefile.am
@@ -335,6 +335,8 @@ libappwidgets_a_sources = \
gimpstringaction.h \
gimpstrokeeditor.c \
gimpstrokeeditor.h \
+ gimpsymmetryeditor.c \
+ gimpsymmetryeditor.h \
gimptagentry.c \
gimptagentry.h \
gimptagpopup.c \
diff --git a/app/widgets/gimphelp-ids.h b/app/widgets/gimphelp-ids.h
index 96a178b..0d72981 100644
--- a/app/widgets/gimphelp-ids.h
+++ b/app/widgets/gimphelp-ids.h
@@ -548,6 +548,7 @@
#define GIMP_HELP_INFO_DIALOG "gimp-info-dialog"
#define GIMP_HELP_MODULE_DIALOG "gimp-module-dialog"
#define GIMP_HELP_NAVIGATION_DIALOG "gimp-navigation-dialog"
+#define GIMP_HELP_SYMMETRY_DIALOG "gimp-symmetry-dialog"
#define GIMP_HELP_TEXT_EDITOR_DIALOG "gimp-text-editor-dialog"
#define GIMP_HELP_TIPS_DIALOG "gimp-tips-dialog"
#define GIMP_HELP_UNDO_DIALOG "gimp-undo-dialog"
diff --git a/app/widgets/gimpsymmetryeditor.c b/app/widgets/gimpsymmetryeditor.c
new file mode 100644
index 0000000..eb5169d
--- /dev/null
+++ b/app/widgets/gimpsymmetryeditor.c
@@ -0,0 +1,506 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpsymmetryeditor.c
+ * Copyright (C) 2015 Jehan <jehan girinstud io>
+ *
+ * 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 <gtk/gtk.h>
+
+#include "libgimpwidgets/gimpwidgets.h"
+
+#include "widgets-types.h"
+
+#include "config/gimpguiconfig.h"
+
+#include "core/gimp.h"
+#include "core/gimpcontext.h"
+#include "core/gimpimage.h"
+#include "core/gimpimage-symmetry.h"
+#include "core/gimpsymmetry.h"
+
+#include "gimpdocked.h"
+#include "gimpmenufactory.h"
+#include "gimppropwidgets.h"
+#include "gimpspinscale.h"
+#include "gimpsymmetryeditor.h"
+
+#include "gimp-intl.h"
+
+enum
+{
+ PROP_0,
+ PROP_GIMP,
+};
+
+struct _GimpSymmetryEditorPrivate
+{
+ Gimp *gimp;
+ GimpImage *image;
+
+ GtkWidget *menu;
+ GtkWidget *options_frame;
+};
+
+static void gimp_symmetry_editor_docked_iface_init (GimpDockedInterface *iface);
+
+/* Signal handlers on the GObject. */
+static void gimp_symmetry_editor_constructed (GObject *object);
+static void gimp_symmetry_editor_dispose (GObject *object);
+static void gimp_symmetry_editor_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_symmetry_editor_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+/* Signal handlers on the context. */
+static void gimp_symmetry_editor_image_changed (GimpContext *context,
+ GimpImage *image,
+ GimpSymmetryEditor *editor);
+
+/* Signal handlers on the contextual image. */
+static void gimp_symmetry_editor_symmetry_notify (GimpImage *image,
+ GParamSpec *pspec,
+ GimpSymmetryEditor *editor);
+
+/* Signal handlers on the symmetry. */
+static void gimp_symmetry_editor_symmetry_updated (GimpSymmetry *symmetry,
+ GimpImage *image,
+ GimpSymmetryEditor *editor);
+
+/* Private functions. */
+static void gimp_symmetry_editor_set_options (GimpSymmetryEditor *editor,
+ GimpSymmetry *symmetry);
+
+
+G_DEFINE_TYPE_WITH_CODE (GimpSymmetryEditor, gimp_symmetry_editor,
+ GIMP_TYPE_EDITOR,
+ G_IMPLEMENT_INTERFACE (GIMP_TYPE_DOCKED,
+ gimp_symmetry_editor_docked_iface_init))
+
+#define parent_class gimp_symmetry_editor_parent_class
+
+static void
+gimp_symmetry_editor_class_init (GimpSymmetryEditorClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->constructed = gimp_symmetry_editor_constructed;
+ object_class->dispose = gimp_symmetry_editor_dispose;
+ object_class->set_property = gimp_symmetry_editor_set_property;
+ object_class->get_property = gimp_symmetry_editor_get_property;
+
+ 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));
+
+ g_type_class_add_private (klass, sizeof (GimpSymmetryEditorPrivate));
+}
+
+static void
+gimp_symmetry_editor_docked_iface_init (GimpDockedInterface *docked_iface)
+{
+}
+
+static void
+gimp_symmetry_editor_init (GimpSymmetryEditor *editor)
+{
+ GtkScrolledWindow *scrolled_window;
+
+ editor->p = G_TYPE_INSTANCE_GET_PRIVATE (editor,
+ GIMP_TYPE_SYMMETRY_EDITOR,
+ GimpSymmetryEditorPrivate);
+
+ gtk_widget_set_size_request (GTK_WIDGET (editor), -1, 200);
+
+ /* Scrolled window to keep the dock size reasonable. */
+ scrolled_window = GTK_SCROLLED_WINDOW (gtk_scrolled_window_new (NULL, NULL));
+
+ gtk_scrolled_window_set_policy (scrolled_window,
+ GTK_POLICY_AUTOMATIC,
+ GTK_POLICY_AUTOMATIC);
+
+ gtk_box_pack_start (GTK_BOX (editor),
+ GTK_WIDGET (scrolled_window),
+ TRUE, TRUE, 0);
+ gtk_widget_show (GTK_WIDGET (scrolled_window));
+
+ /* A frame to hold the symmetry options. */
+ editor->p->options_frame = gimp_frame_new ("");
+ gtk_scrolled_window_add_with_viewport (scrolled_window,
+ editor->p->options_frame);
+}
+
+static void
+gimp_symmetry_editor_constructed (GObject *object)
+{
+ GimpSymmetryEditor *editor = GIMP_SYMMETRY_EDITOR (object);
+ GimpContext *user_context;
+
+ G_OBJECT_CLASS (parent_class)->constructed (object);
+
+ user_context = gimp_get_user_context (editor->p->gimp);
+
+ g_signal_connect_object (user_context, "image-changed",
+ G_CALLBACK (gimp_symmetry_editor_image_changed),
+ editor,
+ 0);
+
+ gimp_symmetry_editor_image_changed (user_context,
+ gimp_context_get_image (user_context),
+ editor);
+}
+
+static void
+gimp_symmetry_editor_dispose (GObject *object)
+{
+ GimpSymmetryEditor *editor = GIMP_SYMMETRY_EDITOR (object);
+
+ g_clear_object (&editor->p->image);
+
+ G_OBJECT_CLASS (parent_class)->dispose (object);
+}
+
+static void
+gimp_symmetry_editor_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpSymmetryEditor *editor = GIMP_SYMMETRY_EDITOR (object);
+
+ switch (property_id)
+ {
+ case PROP_GIMP:
+ editor->p->gimp = g_value_get_object (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_symmetry_editor_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpSymmetryEditor *editor = GIMP_SYMMETRY_EDITOR (object);
+
+ switch (property_id)
+ {
+ case PROP_GIMP:
+ g_value_set_object (value, editor->p->gimp);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_symmetry_editor_image_changed (GimpContext *context,
+ GimpImage *image,
+ GimpSymmetryEditor *editor)
+{
+ GimpGuiConfig *guiconfig;
+
+ if (image == editor->p->image)
+ return;
+
+ guiconfig = GIMP_GUI_CONFIG (editor->p->gimp->config);
+
+ /* Disconnect and unref the previous image. */
+ if (editor->p->image)
+ {
+ g_signal_handlers_disconnect_by_func (editor->p->image,
+ G_CALLBACK (gimp_symmetry_editor_symmetry_notify),
+ editor);
+ g_object_unref (editor->p->image);
+ editor->p->image = NULL;
+ }
+
+ /* Destroy the previous menu. */
+ if (editor->p->menu)
+ gtk_widget_destroy (editor->p->menu);
+ editor->p->menu = NULL;
+
+ if (image && guiconfig->playground_symmetry)
+ {
+ GtkListStore *store;
+ GtkTreeIter iter;
+ GList *syms;
+ GList *sym_iter;
+ GimpSymmetry *symmetry;
+
+ store = gimp_int_store_new ();
+
+ /* The menu of available symmetries. */
+ syms = gimp_image_symmetry_list ();
+ for (sym_iter = syms; sym_iter; sym_iter = g_list_next (sym_iter))
+ {
+ GimpSymmetryClass *klass;
+ GType type;
+
+ type = (GType) sym_iter->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,
+ sym_iter->data,
+ -1);
+ g_type_class_unref (klass);
+ }
+ g_list_free (syms);
+
+ 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);
+ editor->p->menu = gimp_prop_int_combo_box_new (G_OBJECT (image),
+ "symmetry",
+ GIMP_INT_STORE (store));
+ g_object_unref (store);
+
+ gimp_int_combo_box_set_label (GIMP_INT_COMBO_BOX (editor->p->menu),
+ _("Symmetry"));
+ g_object_set (editor->p->menu, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
+
+ gtk_box_pack_start (GTK_BOX (editor), editor->p->menu,
+ FALSE, FALSE, 0);
+ gtk_box_reorder_child (GTK_BOX (editor), editor->p->menu, 0);
+ gtk_widget_show (editor->p->menu);
+
+ /* Connect to symmetry change. */
+ g_signal_connect (image, "notify::symmetry",
+ G_CALLBACK (gimp_symmetry_editor_symmetry_notify),
+ editor);
+
+ /* Update the symmetry options. */
+ symmetry = gimp_image_symmetry_selected (image);
+ gimp_symmetry_editor_set_options (editor, symmetry);
+ editor->p->image = g_object_ref (image);
+ }
+ else if (! guiconfig->playground_symmetry)
+ {
+ GtkWidget *label;
+
+ /* Display a text when the feature is disabled. */
+ label = gtk_label_new (_("Symmetry Painting is disabled.\n"
+ "You can enable the feature in the "
+ "\"Experimental Playground\" section of \"Preferences\"."));
+ gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
+ gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.0);
+ gimp_label_set_attributes (GTK_LABEL (label),
+ PANGO_ATTR_WEIGHT, PANGO_WEIGHT_BOLD,
+ -1);
+ gtk_container_add (GTK_CONTAINER (editor->p->options_frame), label);
+ gtk_widget_show (label);
+ gtk_widget_show (editor->p->options_frame);
+ }
+}
+
+static void
+gimp_symmetry_editor_symmetry_notify (GimpImage *image,
+ GParamSpec *pspec,
+ GimpSymmetryEditor *editor)
+{
+ GimpSymmetry *symmetry = NULL;
+
+ if (image &&
+ (symmetry = gimp_image_symmetry_selected (image)))
+ {
+ g_signal_connect (symmetry, "update-ui",
+ G_CALLBACK (gimp_symmetry_editor_symmetry_updated),
+ editor);
+ }
+
+ gimp_symmetry_editor_set_options (editor, symmetry);
+}
+
+static void
+gimp_symmetry_editor_symmetry_updated (GimpSymmetry *symmetry,
+ GimpImage *image,
+ GimpSymmetryEditor *editor)
+{
+ GimpContext *context;
+
+ g_return_if_fail (GIMP_IS_SYMMETRY (symmetry));
+
+ context = gimp_get_user_context (editor->p->gimp);
+ if (image != context->image ||
+ symmetry != gimp_image_symmetry_selected (image))
+ {
+ g_signal_handlers_disconnect_by_func (G_OBJECT (symmetry),
+ gimp_symmetry_editor_symmetry_updated,
+ editor);
+ return;
+ }
+
+ gimp_symmetry_editor_set_options (editor, symmetry);
+}
+
+/* private functions */
+
+static void
+gimp_symmetry_editor_set_options (GimpSymmetryEditor *editor,
+ GimpSymmetry *symmetry)
+{
+ GimpSymmetryClass *klass;
+ GtkWidget *frame;
+ GtkWidget *vbox;
+ GParamSpec **specs;
+ gint n_properties;
+ gint i;
+
+ frame = editor->p->options_frame;
+
+ /* Clean the old frame */
+ gtk_widget_hide (frame);
+ gtk_container_foreach (GTK_CONTAINER (frame),
+ (GtkCallback) gtk_widget_destroy, NULL);
+
+ if (! symmetry || symmetry->type == GIMP_TYPE_SYMMETRY)
+ return;
+
+ klass = g_type_class_ref (symmetry->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 (symmetry, &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 (symmetry),
+ 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 (symmetry),
+ 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);
+}
+
+/* public functions */
+
+GtkWidget *
+gimp_symmetry_editor_new (Gimp *gimp,
+ GimpImage *image,
+ GimpMenuFactory *menu_factory)
+{
+ GimpSymmetryEditor *editor;
+
+ g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL);
+ g_return_val_if_fail (GIMP_IS_MENU_FACTORY (menu_factory), NULL);
+ g_return_val_if_fail (image == NULL || GIMP_IS_IMAGE (image), NULL);
+
+ editor = g_object_new (GIMP_TYPE_SYMMETRY_EDITOR,
+ "gimp", gimp,
+ "menu-factory", menu_factory,
+ NULL);
+
+ return GTK_WIDGET (editor);
+}
diff --git a/app/widgets/gimpsymmetryeditor.h b/app/widgets/gimpsymmetryeditor.h
new file mode 100644
index 0000000..fd4d1cc
--- /dev/null
+++ b/app/widgets/gimpsymmetryeditor.h
@@ -0,0 +1,58 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpsymmetryeditor.h
+ * Copyright (C) 2015 Jehan <jehan girinstud io>
+ *
+ * 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_EDITOR_H__
+#define __GIMP_SYMMETRY_EDITOR_H__
+
+
+#include "gimpeditor.h"
+
+
+#define GIMP_TYPE_SYMMETRY_EDITOR (gimp_symmetry_editor_get_type ())
+#define GIMP_SYMMETRY_EDITOR(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_SYMMETRY_EDITOR,
GimpSymmetryEditor))
+#define GIMP_SYMMETRY_EDITOR_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_SYMMETRY_EDITOR,
GimpSymmetryEditorClass))
+#define GIMP_IS_SYMMETRY_EDITOR(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_SYMMETRY_EDITOR))
+#define GIMP_IS_SYMMETRY_EDITOR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_SYMMETRY_EDITOR))
+#define GIMP_SYMMETRY_EDITOR_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_SYMMETRY_EDITOR,
GimpSymmetryEditorClass))
+
+
+typedef struct _GimpSymmetryEditorPrivate GimpSymmetryEditorPrivate;
+typedef struct _GimpSymmetryEditorClass GimpSymmetryEditorClass;
+
+struct _GimpSymmetryEditor
+{
+ GimpEditor parent_instance;
+
+ GimpSymmetryEditorPrivate *p;
+};
+
+struct _GimpSymmetryEditorClass
+{
+ GimpEditorClass parent_class;
+};
+
+
+GType gimp_symmetry_editor_get_type (void) G_GNUC_CONST;
+GtkWidget * gimp_symmetry_editor_new (Gimp *gimp,
+ GimpImage *image,
+ GimpMenuFactory *menu_factory);
+
+
+#endif /* __GIMP_SYMMETRY_EDITOR_H__ */
diff --git a/app/widgets/widgets-types.h b/app/widgets/widgets-types.h
index 98a1620..813c227 100644
--- a/app/widgets/widgets-types.h
+++ b/app/widgets/widgets-types.h
@@ -76,6 +76,7 @@ typedef struct _GimpHistogramEditor GimpHistogramEditor;
typedef struct _GimpImageEditor GimpImageEditor;
typedef struct _GimpSamplePointEditor GimpSamplePointEditor;
typedef struct _GimpSelectionEditor GimpSelectionEditor;
+typedef struct _GimpSymmetryEditor GimpSymmetryEditor;
typedef struct _GimpUndoEditor GimpUndoEditor;
diff --git a/app/xcf/xcf-load.c b/app/xcf/xcf-load.c
index 72e45ed..e433edf 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"
@@ -157,6 +159,8 @@ xcf_load_image (Gimp *gimp,
gint image_type;
GimpPrecision precision = GIMP_PRECISION_U8_GAMMA;
gint num_successful_elements = 0;
+ GList *syms;
+ GList *iter;
/* read in the image width, height and type */
info->cp += xcf_read_int32 (info->input, (guint32 *) &width, 1);
@@ -269,6 +273,39 @@ xcf_load_image (Gimp *gimp,
gimp_parasite_name (parasite));
}
+ /* check for symmetry parasites */
+ syms = gimp_image_symmetry_list ();
+ for (iter = syms; iter; iter = g_list_next (iter))
+ {
+ GType type = (GType) iter->data;
+ gchar *parasite_name = gimp_symmetry_parasite_name (type);
+
+ parasite = gimp_image_parasite_find (image,
+ parasite_name);
+ g_free (parasite_name);
+ if (parasite)
+ {
+ GimpSymmetry *sym = gimp_symmetry_from_parasite (parasite,
+ image,
+ type);
+
+ if (sym)
+ {
+ GimpImagePrivate *private = GIMP_IMAGE_GET_PRIVATE (image);
+
+ gimp_parasite_list_remove (private->parasites,
+ gimp_parasite_name (parasite));
+
+ gimp_image_symmetry_add (image, sym);
+
+ g_signal_emit_by_name (sym, "active-changed", NULL);
+ if (sym->active)
+ gimp_image_symmetry_select (image, type);
+ }
+ }
+ }
+ g_list_free (syms);
+
/* migrate the old "exif-data" parasite */
parasite = gimp_image_parasite_find (GIMP_IMAGE (image),
"exif-data");
diff --git a/app/xcf/xcf-save.c b/app/xcf/xcf-save.c
index 7bf2179..15a414f 100644
--- a/app/xcf/xcf-save.c
+++ b/app/xcf/xcf-save.c
@@ -45,11 +45,13 @@
#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/gimpparasitelist.h"
#include "core/gimpprogress.h"
#include "core/gimpsamplepoint.h"
+#include "core/gimpsymmetry.h"
#include "text/gimptextlayer.h"
#include "text/gimptextlayer-xcf.h"
@@ -337,11 +339,13 @@ xcf_save_image_props (XcfInfo *info,
GimpImage *image,
GError **error)
{
- GimpImagePrivate *private = GIMP_IMAGE_GET_PRIVATE (image);
- GimpParasite *grid_parasite = NULL;
- GimpParasite *meta_parasite = NULL;
- GimpParasite *compat_parasite = NULL;
- GimpUnit unit = gimp_image_get_unit (image);
+ GimpImagePrivate *private = GIMP_IMAGE_GET_PRIVATE (image);
+ GimpParasite *grid_parasite = NULL;
+ GimpParasite *meta_parasite = NULL;
+ GimpParasite *compat_parasite = NULL;
+ GList *symmetry_parasites = NULL;
+ GList *iter;
+ GimpUnit unit = gimp_image_get_unit (image);
gdouble xres;
gdouble yres;
@@ -428,6 +432,18 @@ xcf_save_image_props (XcfInfo *info,
gimp_parasite_list_add (private->parasites, compat_parasite);
}
+ if (g_list_length (gimp_image_symmetry_get (image)))
+ {
+ GimpParasite *parasite = NULL;
+
+ for (iter = gimp_image_symmetry_get (image); iter; iter = g_list_next (iter))
+ {
+ parasite = gimp_symmetry_to_parasite (GIMP_SYMMETRY (iter->data));
+ gimp_parasite_list_add (private->parasites, parasite);
+ symmetry_parasites = g_list_prepend (symmetry_parasites, parasite);
+ }
+ }
+
if (gimp_parasite_list_length (private->parasites) > 0)
{
xcf_check_error (xcf_save_prop (info, image, PROP_PARASITES, error,
@@ -454,6 +470,17 @@ xcf_save_image_props (XcfInfo *info,
gimp_parasite_name (compat_parasite));
gimp_parasite_free (compat_parasite);
}
+
+ for (iter = symmetry_parasites; iter; iter = g_list_next (iter))
+ {
+ GimpParasite *parasite = iter->data;
+
+ gimp_parasite_list_remove (private->parasites,
+ gimp_parasite_name (parasite));
+ }
+ g_list_free_full (symmetry_parasites,
+ (GDestroyNotify) gimp_parasite_free);
+
xcf_check_error (xcf_save_prop (info, image, PROP_END, error));
return TRUE;
diff --git a/icons/Default/16/gimp-symmetry.png b/icons/Default/16/gimp-symmetry.png
new file mode 100644
index 0000000..32fa5a4
Binary files /dev/null and b/icons/Default/16/gimp-symmetry.png differ
diff --git a/icons/Default/24/gimp-symmetry.png b/icons/Default/24/gimp-symmetry.png
new file mode 100644
index 0000000..d7f4b2f
Binary files /dev/null and b/icons/Default/24/gimp-symmetry.png differ
diff --git a/icons/Default/24/gimp-symmetry.xcf b/icons/Default/24/gimp-symmetry.xcf
new file mode 100644
index 0000000..fb593e9
Binary files /dev/null and b/icons/Default/24/gimp-symmetry.xcf differ
diff --git a/icons/Default/Makefile.am b/icons/Default/Makefile.am
index 2dace18..2999fd3 100644
--- a/icons/Default/Makefile.am
+++ b/icons/Default/Makefile.am
@@ -161,6 +161,7 @@ icons16_DATA = \
16/gimp-shape-circle.png \
16/gimp-shape-diamond.png \
16/gimp-shape-square.png \
+ 16/gimp-symmetry.png \
16/gimp-template.png \
16/gimp-text-layer.png \
16/gimp-toilet-paper.png \
@@ -376,6 +377,7 @@ icons24_DATA = \
24/gimp-move-to-screen.png \
24/gimp-print-resolution.png \
24/gimp-sample-point.png \
+ 24/gimp-symmetry.png \
24/gimp-template.png \
24/gimp-text-dir-ltr.png \
24/gimp-text-dir-rtl.png \
diff --git a/libgimpwidgets/gimpicons.h b/libgimpwidgets/gimpicons.h
index 1a0a285..76aa4fc 100644
--- a/libgimpwidgets/gimpicons.h
+++ b/libgimpwidgets/gimpicons.h
@@ -232,6 +232,7 @@ G_BEGIN_DECLS
#define GIMP_STOCK_INPUT_DEVICE "gimp-input-device"
#define GIMP_STOCK_CURSOR "gimp-cursor"
#define GIMP_STOCK_SAMPLE_POINT "gimp-sample-point"
+#define GIMP_STOCK_SYMMETRY "gimp-symmetry"
#define GIMP_STOCK_DYNAMICS "gimp-dynamics"
#define GIMP_STOCK_TOOL_PRESET "gimp-tool-preset"
diff --git a/menus/dialogs-menuitems.xml b/menus/dialogs-menuitems.xml
index bf06bcb..28c745a 100644
--- a/menus/dialogs-menuitems.xml
+++ b/menus/dialogs-menuitems.xml
@@ -14,6 +14,7 @@
<menuitem action="dialogs-undo-history" />
<menuitem action="dialogs-cursor" />
<menuitem action="dialogs-sample-points" />
+ <menuitem action="dialogs-symmetry"/>
<separator />
<menuitem action="dialogs-colors" />
<menuitem action="dialogs-brushes" />
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]