Commit 76f573c9 authored by Jehan's avatar Jehan

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.
parent b8fadf3a
......@@ -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"),
......
......@@ -83,6 +83,7 @@ enum
PROP_PLAYGROUND_NPD_TOOL,
PROP_PLAYGROUND_HANDLE_TRANSFORM_TOOL,
PROP_PLAYGROUND_SEAMLESS_CLONE_TOOL,
PROP_PLAYGROUND_SYMMETRY,
PROP_HIDE_DOCKS,
PROP_SINGLE_WINDOW_MODE,
......@@ -300,6 +301,13 @@ gimp_gui_config_class_init (GimpGuiConfigClass *klass)
FALSE,
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_SEAMLESS_CLONE_TOOL,
"playground-seamless-clone-tool",
......@@ -528,6 +536,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_SEAMLESS_CLONE_TOOL:
gui_config->playground_seamless_clone_tool = g_value_get_boolean (value);
break;
......@@ -678,6 +689,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_SEAMLESS_CLONE_TOOL:
g_value_set_boolean (value, gui_config->playground_seamless_clone_tool);
break;
......
......@@ -83,6 +83,7 @@ struct _GimpGuiConfig
gboolean playground_npd_tool;
gboolean playground_handle_transform_tool;
gboolean playground_seamless_clone_tool;
gboolean playground_symmetry;
/* saved in sessionrc */
gboolean hide_docks;
......
......@@ -390,6 +390,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.")
......
......@@ -259,6 +259,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 \
......@@ -367,6 +369,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 \
......
......@@ -175,6 +175,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;
......
......@@ -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)
......
......@@ -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);
}
......
......@@ -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,
......
......@@ -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);
}
......@@ -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,
......
......@@ -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 */
......
/* 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;
}
/* 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__ */
......@@ -56,6 +56,7 @@
#include "gimpimage-preview.h"