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.
This commit is contained in:
Jehan 2016-01-27 19:13:17 +01:00
parent b8fadf3ad7
commit 76f573c981
61 changed files with 3600 additions and 702 deletions

View File

@ -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"),

View File

@ -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;

View File

@ -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;

View File

@ -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.")

View File

@ -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 \

View File

@ -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;

View File

@ -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)

View File

@ -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);
}

View File

@ -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,

View File

@ -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);
GimpBrushCacheUnit *unit = iter->data;
return (gconstpointer) cache->last_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 (cache->last_data)
cache->data_destroy (cache->last_data);
if (g_list_length (cache->cached_units) > MAX_CACHED_DATA &&
(iter = g_list_last (cache->cached_units)))
{
unit = iter->data;
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->data_destroy (unit->data);
cache->cached_units = g_list_delete_link (cache->cached_units, iter);
}
unit = g_new (GimpBrushCacheUnit, 1);
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->cached_units = g_list_prepend (cache->cached_units, unit);
}

View File

@ -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,

View File

@ -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 */

View File

@ -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;
}

View File

@ -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__ */

View File

@ -56,6 +56,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"
@ -69,6 +70,7 @@
#include "gimpprojection.h"
#include "gimpsamplepoint.h"
#include "gimpselection.h"
#include "gimpsymmetry.h"
#include "gimptempbuf.h"
#include "gimptemplate.h"
#include "gimpundostack.h"
@ -130,7 +132,8 @@ enum
PROP_BASE_TYPE,
PROP_PRECISION,
PROP_METADATA,
PROP_BUFFER
PROP_BUFFER,
PROP_SYMMETRY
};
@ -624,6 +627,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));
}
@ -696,6 +704,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;
@ -859,6 +870,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;
@ -900,6 +947,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;
@ -1050,6 +1102,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);

View File

@ -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);
}

View File

@ -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__ */

451
app/core/gimpsymmetry.c Normal file
View File

@ -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;
}

94
app/core/gimpsymmetry.h Normal file
View File

@ -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__ */

View File

@ -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"
@ -736,6 +737,17 @@ dialogs_selection_editor_new (GimpDialogFactory *factory,
return gimp_selection_editor_new (gimp_dialog_factory_get_menu_factory (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,

View File

@ -244,6 +244,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,

View File

@ -385,6 +385,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,

View File

@ -1601,6 +1601,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));
}

View File

@ -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 */

View File

@ -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__ */

View File

@ -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)));

View File

@ -37,6 +37,8 @@ struct _GimpAirbrush
GimpPaintbrush parent_instance;
guint timeout_id;
GimpSymmetry *sym;
GimpDrawable *drawable;
GimpPaintOptions *paint_options;
};

View File

@ -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;

View File

@ -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);

View File

@ -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

View File

@ -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;
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);
rate = (options->rate *
gimp_dynamics_get_linear_value (dynamics,
GIMP_DYNAMICS_OUTPUT_RATE,
coords,
paint_options,
fade_point));
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;
gimp_convolve_calculate_matrix (convolve, options->type,
gimp_brush_get_width (brush_core->brush) / 2,
gimp_brush_get_height (brush_core->brush) / 2,
rate);
op = gimp_symmetry_get_operation (sym, i,
paint_width,
paint_height);
/* 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);
rate = (options->rate *
gimp_dynamics_get_linear_value (dynamics,
GIMP_DYNAMICS_OUTPUT_RATE,
coords,
paint_options,
fade_point));
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_convolve_calculate_matrix (convolve, options->type,
gimp_brush_get_width (brush_core->brush) / 2,
gimp_brush_get_height (brush_core->brush) / 2,
rate);
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);
/* 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);
g_object_unref (convolve_buffer);
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_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_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

View File

@ -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;
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);
/* 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);
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;
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;
op = gimp_symmetry_get_operation (sym, i,
paint_width,
paint_height);
/* 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);
/* 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);
}
}

View File

@ -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_eval_transform_dynamics (GIMP_BRUSH_CORE (paint_core),
drawable,
paint_options,
coords);
gimp_brush_core_paste_canvas (GIMP_BRUSH_CORE (paint_core), drawable,
coords,
MIN (opacity, GIMP_OPACITY_OPAQUE),
gimp_context_get_opacity (context),
paint_mode,
gimp_paint_options_get_brush_mode (paint_options),
force,
paint_options->application_mode);
n_strokes = gimp_symmetry_get_size (sym);
for (i = 0; i < n_strokes; i++)
{
coords = gimp_symmetry_get_coords (sym, i);
if (gimp_dynamics_is_output_enabled (dynamics, GIMP_DYNAMICS_OUTPUT_FORCE))
force = gimp_dynamics_get_linear_value (dynamics,
GIMP_DYNAMICS_OUTPUT_FORCE,
coords,
paint_options,
fade_point);
else
force = paint_options->brush_force;
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);
}

View File

@ -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);
}

View File

@ -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)
n_strokes = gimp_symmetry_get_size (sym);
if (ink->last_blobs &&
g_list_length (ink->last_blobs) != n_strokes)
{
ink->last_blob = ink_pen_ellipse (options,
coords->x,
coords->y,
coords->pressure,
coords->xtilt,
coords->ytilt,
100);
g_list_free_full (ink->last_blobs, g_free);
ink->last_blobs = NULL;
}
if (ink->start_blob)
g_free (ink->start_blob);
if (! ink->last_blobs)
{
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);
for (i = 0; i < n_strokes; i++)
{
coords = gimp_symmetry_get_coords (sym, i);
blob_to_render = ink->last_blob;
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);
for (i = 0; i < n_strokes; i++)
{
GimpBlob *blob;
GimpBlob *blob_union = NULL;
blob_union = gimp_blob_convex_union (ink->last_blob, blob);
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);
g_free (ink->last_blob);
ink->last_blob = blob;
last_blob = g_list_nth_data (ink->last_blobs, i);
blob_union = gimp_blob_convex_union (last_blob, blob);
blob_to_render = blob_union;
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);
}
/* 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;
for (i = 0; i < n_strokes; i++)
{
GimpBlob *blob_to_render = g_list_nth_data (blobs_to_render, i);
if (! paint_buffer)
return;
coords = gimp_symmetry_get_coords (sym, i);
gimp_context_get_foreground (context, &foreground);
color = gimp_gegl_color_new (&foreground);
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;
gegl_buffer_set_color (paint_buffer, NULL, color);
g_object_unref (color);
if (! paint_buffer)
continue;
/* 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);
gimp_context_get_foreground (context, &foreground);
color = gimp_gegl_color_new (&foreground);
/* 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);
gegl_buffer_set_color (paint_buffer, NULL, color);
g_object_unref (color);
if (blob_union)
g_free (blob_union);
/* 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);
}
g_list_free_full (blob_unions, g_free);
}
static GimpBlob *

View File

@ -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

View File

@ -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);

View File

@ -36,7 +36,7 @@ struct _GimpInkUndo
{
GimpPaintCoreUndo parent_instance;
GimpBlob *last_blob;
GList *last_blobs;
};
struct _GimpInkUndoClass

View File

@ -37,6 +37,7 @@
#include "core/gimpdrawable.h"
#include "core/gimperror.h"
#include "core/gimpmybrush.h"
#include "core/gimpsymmetry.h"
#include "gimpmybrushcore.h"
#include "gimpmybrushsurface.h"
@ -49,7 +50,7 @@ struct _GimpMybrushCorePrivate
{
GimpMybrush *mybrush;
GimpMybrushSurface *surface;
MyPaintBrush *brush;
GList *brushes;
gboolean synthetic;
gint64 last_time;
};
@ -57,6 +58,8 @@ struct _GimpMybrushCorePrivate
/* local function prototypes */
static void gimp_mybrush_core_finalize (GObject *object);
static gboolean gimp_mybrush_core_start (GimpPaintCore *paint_core,
GimpDrawable *drawable,
GimpPaintOptions *paint_options,
@ -69,13 +72,13 @@ static void gimp_mybrush_core_interpolate (GimpPaintCore *paint_core,
static void gimp_mybrush_core_paint (GimpPaintCore *paint_core,
GimpDrawable *drawable,
GimpPaintOptions *paint_options,
const GimpCoords *coords,
GimpSymmetry *sym,
GimpPaintState paint_state,
guint32 time);
static void gimp_mybrush_core_motion (GimpPaintCore *paint_core,
GimpDrawable *drawable,
GimpPaintOptions *paint_options,
const GimpCoords *coords,
GimpSymmetry *sym,
guint32 time);
@ -99,8 +102,11 @@ gimp_mybrush_core_register (Gimp *gimp,
static void
gimp_mybrush_core_class_init (GimpMybrushCoreClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
GimpPaintCoreClass *paint_core_class = GIMP_PAINT_CORE_CLASS (klass);
object_class->finalize = gimp_mybrush_core_finalize;
paint_core_class->start = gimp_mybrush_core_start;
paint_core_class->paint = gimp_mybrush_core_paint;
paint_core_class->interpolate = gimp_mybrush_core_interpolate;
@ -116,6 +122,21 @@ gimp_mybrush_core_init (GimpMybrushCore *mybrush)
GimpMybrushCorePrivate);
}
static void
gimp_mybrush_core_finalize (GObject *object)
{
GimpMybrushCore *core = GIMP_MYBRUSH_CORE (object);
if (core->private->brushes)
{
g_list_free_full (core->private->brushes,
(GDestroyNotify) mypaint_brush_unref);
core->private->brushes = NULL;
}
G_OBJECT_CLASS (parent_class)->finalize (object);
}
static gboolean
gimp_mybrush_core_start (GimpPaintCore *paint_core,
GimpDrawable *drawable,
@ -172,7 +193,7 @@ static void
gimp_mybrush_core_paint (GimpPaintCore *paint_core,
GimpDrawable *drawable,
GimpPaintOptions *paint_options,
const GimpCoords *coords,
GimpSymmetry *sym,
GimpPaintState paint_state,
guint32 time)
{
@ -182,6 +203,8 @@ gimp_mybrush_core_paint (GimpPaintCore *paint_core,
const gchar *brush_data;
GimpRGB fg;
GimpHSV hsv;
gint n_strokes;
gint i;
switch (paint_state)
{
@ -195,53 +218,69 @@ gimp_mybrush_core_paint (GimpPaintCore *paint_core,
paint_core->mask_x_offset,
paint_core->mask_y_offset);
mybrush->private->brush = mypaint_brush_new ();
mypaint_brush_from_defaults (mybrush->private->brush);
brush_data = gimp_mybrush_get_brush_json (mybrush->private->mybrush);
if (brush_data)
mypaint_brush_from_string (mybrush->private->brush, brush_data);
gimp_rgb_to_hsv (&fg, &hsv);
mypaint_brush_set_base_value (mybrush->private->brush,
MYPAINT_BRUSH_SETTING_COLOR_H,
hsv.h);
mypaint_brush_set_base_value (mybrush->private->brush,
MYPAINT_BRUSH_SETTING_COLOR_S,
hsv.s);
mypaint_brush_set_base_value (mybrush->private->brush,
MYPAINT_BRUSH_SETTING_COLOR_V,
hsv.v);
if (mybrush->private->brushes)
{
g_list_free_full (mybrush->private->brushes,
(GDestroyNotify) mypaint_brush_unref);
mybrush->private->brushes = NULL;
}
mypaint_brush_set_base_value (mybrush->private->brush,
MYPAINT_BRUSH_SETTING_RADIUS_LOGARITHMIC,
options->radius);
mypaint_brush_set_base_value (mybrush->private->brush,
MYPAINT_BRUSH_SETTING_OPAQUE,
options->opaque * gimp_context_get_opacity (context));
mypaint_brush_set_base_value (mybrush->private->brush,
MYPAINT_BRUSH_SETTING_HARDNESS,
options->hardness);
mypaint_brush_set_base_value (mybrush->private->brush,
MYPAINT_BRUSH_SETTING_ERASER,
options->eraser ? 1.0f : 0.0f);
n_strokes = gimp_symmetry_get_size (sym);
for (i = 0; i < n_strokes; i++)
{
MyPaintBrush *brush = mypaint_brush_new ();
mypaint_brush_new_stroke (mybrush->private->brush);
mypaint_brush_from_defaults (brush);
brush_data = gimp_mybrush_get_brush_json (mybrush->private->mybrush);
if (brush_data)
mypaint_brush_from_string (brush, brush_data);
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_set_base_value (brush,
MYPAINT_BRUSH_SETTING_ERASER,
options->eraser ? 1.0f : 0.0f);
mypaint_brush_new_stroke (brush);
mybrush->private->brushes = g_list_prepend (mybrush->private->brushes, brush);
}
mybrush->private->brushes = g_list_reverse (mybrush->private->brushes);
mybrush->private->last_time = -1;
mybrush->private->synthetic = FALSE;
break;
case GIMP_PAINT_STATE_MOTION:
gimp_mybrush_core_motion (paint_core, drawable, paint_options,
coords, time);
sym, time);
break;
case GIMP_PAINT_STATE_FINISH:
mypaint_surface_unref ((MyPaintSurface *) mybrush->private->surface);
mybrush->private->surface = NULL;
mypaint_brush_unref (mybrush->private->brush);
mybrush->private->brush = NULL;
g_list_free_full (mybrush->private->brushes,
(GDestroyNotify) mypaint_brush_unref);
mybrush->private->brushes = NULL;
break;
}
}
@ -250,27 +289,93 @@ static void
gimp_mybrush_core_motion (GimpPaintCore *paint_core,
GimpDrawable *drawable,
GimpPaintOptions *paint_options,
const GimpCoords *coords,
GimpSymmetry *sym,
guint32 time)
{
GimpMybrushCore *mybrush = GIMP_MYBRUSH_CORE (paint_core);
GimpContext *context = GIMP_CONTEXT (paint_options);
MyPaintBrush *brush;
GimpCoords *coords;
MyPaintRectangle rect;
gdouble pressure;
gdouble dt = 0.0;
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;
gimp_context_get_foreground (context, &fg);
gimp_rgb_to_hsv (&fg, &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 ();
mypaint_brush_from_defaults (brush);
brush_data = gimp_mybrush_get_brush_json (mybrush->private->mybrush);
if (brush_data)
mypaint_brush_from_string (brush, brush_data);
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_set_base_value (brush,
MYPAINT_BRUSH_SETTING_ERASER,
options->eraser ? 1.0f : 0.0f);
mypaint_brush_new_stroke (brush);
mybrush->private->brushes = g_list_prepend (mybrush->private->brushes, brush);
}
mybrush->private->brushes = g_list_reverse (mybrush->private->brushes);
}
mypaint_surface_begin_atomic ((MyPaintSurface *) mybrush->private->surface);
if (mybrush->private->last_time < 0)
{
/* First motion, so we need a zero pressure event to start the stroke */
mypaint_brush_stroke_to (mybrush->private->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 */);
/* First motion, so we need zero pressure events to start the strokes */
for (iter = mybrush->private->brushes, i = 0; iter ; iter = g_list_next (iter), i++)
{
brush = iter->data;
coords = gimp_symmetry_get_coords (sym, i);
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 */);
}
dt = 0.015;
}
else if (mybrush->private->synthetic)
@ -283,20 +388,25 @@ gimp_mybrush_core_motion (GimpPaintCore *paint_core,
dt = (time - mybrush->private->last_time) * 0.001;
}
pressure = coords->pressure;
for (iter = mybrush->private->brushes, i = 0; iter ; iter = g_list_next (iter), i++)
{
brush = iter->data;
coords = gimp_symmetry_get_coords (sym, i);
pressure = coords->pressure;
/* libmypaint expects non-extended devices to default to 0.5 pressure */
if (! coords->extended)
pressure = 0.5f;
/* libmypaint expects non-extended devices to default to 0.5 pressure */
if (! coords->extended)
pressure = 0.5f;
mypaint_brush_stroke_to (mybrush->private->brush,
(MyPaintSurface *) mybrush->private->surface,
coords->x,
coords->y,
pressure,
coords->xtilt,
coords->ytilt,
dt);
mypaint_brush_stroke_to (brush,
(MyPaintSurface *) mybrush->private->surface,
coords->x,
coords->y,
pressure,
coords->xtilt,
coords->ytilt,
dt);
}
mybrush->private->last_time = time;

View File

@ -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 */
GeglColor *color;
opacity *= gradient_color.a;
gimp_rgb_set_alpha (&gradient_color, GIMP_OPACITY_OPAQUE);
color = gimp_gegl_color_new (&gradient_color);
gegl_buffer_set_color (paint_buffer, NULL, color);
g_object_unref (color);
paint_appl_mode = GIMP_PAINT_INCREMENTAL;
}
else if (brush_core->brush && gimp_brush_get_pixmap (brush_core->brush))
{
/* otherwise check if the brush has a pixmap and use that to
* color the area
*/
gimp_brush_core_color_area_with_pixmap (brush_core, drawable,
coords,
paint_buffer,
paint_buffer_x,
paint_buffer_y,
gimp_paint_options_get_brush_mode (paint_options));
paint_appl_mode = GIMP_PAINT_INCREMENTAL;
}
else
{
/* otherwise fill the area with the foreground color */
GimpRGB foreground;
GeglColor *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);
gimp_brush_core_eval_transform_dynamics (brush_core,
drawable,
paint_options,
coords);
}
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;
n_strokes = gimp_symmetry_get_size (sym);
for (i = 0; i < n_strokes; i++)
{
gint paint_width, paint_height;
/* 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);
coords = gimp_symmetry_get_coords (sym, i);
paint_buffer = gimp_paint_core_get_paint_buffer (paint_core, drawable,
paint_options, coords,
&paint_buffer_x,
&paint_buffer_y,
&paint_width,
&paint_height);
if (! paint_buffer)
continue;
op = gimp_symmetry_get_operation (sym, i,
paint_width,
paint_height);
if (gimp_paint_options_get_gradient_color (paint_options, image,
grad_point,
paint_core->pixel_dist,
&gradient_color))
{
/* optionally take the color from the current gradient */
GeglColor *color;
opacity *= gradient_color.a;
gimp_rgb_set_alpha (&gradient_color, GIMP_OPACITY_OPAQUE);
color = gimp_gegl_color_new (&gradient_color);
gegl_buffer_set_color (paint_buffer, NULL, color);
g_object_unref (color);
paint_appl_mode = GIMP_PAINT_INCREMENTAL;
}
else if (brush_core->brush && gimp_brush_get_pixmap (brush_core->brush))
{
/* otherwise check if the brush has a pixmap and use that to
* color the area
*/
gimp_brush_core_color_area_with_pixmap (brush_core, drawable,
coords, op,
paint_buffer,
paint_buffer_x,
paint_buffer_y,
gimp_paint_options_get_brush_mode (paint_options));
paint_appl_mode = GIMP_PAINT_INCREMENTAL;
}
else
{
/* otherwise fill the area with the foreground color */
GimpRGB foreground;
GeglColor *color;
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, op);
}
}

View File

@ -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);

View File

@ -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

View File

@ -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,

View File

@ -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);

View File

@ -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)
n_strokes = gimp_symmetry_get_size (sym);
for (i = 0; i < n_strokes; i++)
{
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;
coords = gimp_symmetry_get_coords (sym, i);
/* 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);
dest_x = coords->x;
dest_y = coords->y;
source_core->first_stroke = FALSE;
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;

View File

@ -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,72 +173,91 @@ 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;
coords = gimp_symmetry_get_origin (sym);
gimp_brush_core_eval_transform_dynamics (GIMP_BRUSH_CORE (paint_core),
drawable,
paint_options,
coords);
gimp_smudge_accumulator_size (paint_options, coords, &accum_size);
/* Allocate the accumulation buffer */
smudge->accum_buffer = gegl_buffer_new (GEGL_RECTANGLE (0, 0,
accum_size,
accum_size),
babl_format ("RGBA float"));
/* adjust the x and y coordinates to the upper left corner of the
* accumulator
*/
gimp_smudge_accumulator_coords (paint_core, coords, &x, &y);
/* If clipped, prefill the smudge buffer with the color at the
* brush position.
*/
if (x != paint_buffer_x ||
y != paint_buffer_y ||
accum_size != gegl_buffer_get_width (paint_buffer) ||
accum_size != gegl_buffer_get_height (paint_buffer))
n_strokes = gimp_symmetry_get_size (sym);
for (i = 0; i < n_strokes; i++)
{
GimpRGB pixel;
GeglColor *color;
GeglBuffer *accum_buffer;
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);
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;
color = gimp_gegl_color_new (&pixel);
gegl_buffer_set_color (smudge->accum_buffer, NULL, color);
g_object_unref (color);
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,
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,
smudge->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;
gimp_brush_core_eval_transform_dynamics (GIMP_BRUSH_CORE (paint_core),
drawable,
paint_options,
coords);
paint_buffer_width = gegl_buffer_get_width (paint_buffer);
paint_buffer_height = gegl_buffer_get_height (paint_buffer);
n_strokes = gimp_symmetry_get_size (sym);
for (i = 0; i < n_strokes; i++)
{
coords = gimp_symmetry_get_coords (sym, i);
/* Get the unclipped acumulator coordinates */
gimp_smudge_accumulator_coords (paint_core, coords, &x, &y);
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;
/* Enable dynamic rate */
dynamic_rate = gimp_dynamics_get_linear_value (dynamics,
GIMP_DYNAMICS_OUTPUT_RATE,
coords,
paint_options,
fade_point);
op = gimp_symmetry_get_operation (sym, i,
paint_width,
paint_height);
rate = (options->rate / 100.0) * dynamic_rate;
paint_buffer_width = gegl_buffer_get_width (paint_buffer);
paint_buffer_height = gegl_buffer_get_height (paint_buffer);
/* 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),
*/
/* Get the unclipped acumulator coordinates */
gimp_smudge_accumulator_coords (paint_core, coords, i, &x, &y);
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);
/* Enable dynamic rate */
dynamic_rate = gimp_dynamics_get_linear_value (dynamics,
GIMP_DYNAMICS_OUTPUT_RATE,
coords,
paint_options,
fade_point);
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));
rate = (options->rate / 100.0) * dynamic_rate;
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;
/* 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_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);
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

View File

@ -37,7 +37,7 @@ struct _GimpSmudge
GimpBrushCore parent_instance;
gboolean initialized;
GeglBuffer *accum_buffer;
GList *accum_buffers;
};
struct _GimpSmudgeClass

View File

@ -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);
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);
}
/* Set the paint buffer to transparent */
gegl_buffer_clear (paint_buffer, NULL);
GIMP_SOURCE_CORE_GET_CLASS (source_core)->motion (source_core,
drawable,
paint_options,
coords,
opacity,
src_pickable,
src_buffer,
&src_rect,
src_offset_x,
src_offset_y,
paint_buffer,
paint_buffer_x,
paint_buffer_y,
paint_area_offset_x,
paint_area_offset_y,
paint_area_width,
paint_area_height);
if (src_buffer)
g_object_unref (src_buffer);
}
gboolean

View File

@ -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__ */

View File

@ -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"

View File

@ -337,6 +337,8 @@ libappwidgets_a_sources = \
gimpstringaction.h \
gimpstrokeeditor.c \
gimpstrokeeditor.h \
gimpsymmetryeditor.c \
gimpsymmetryeditor.h \
gimptagentry.c \
gimptagentry.h \
gimptagpopup.c \

View File

@ -558,6 +558,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"

View File

@ -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);
}

View File

@ -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__ */

View File

@ -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;

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

View File

@ -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 \

View File

@ -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"

View File

@ -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" />