Gimp/app/paint/gimppaintcore.c
Michael Natterer 69a321ac29 app: turn GimpApplicator into a general-purpose "input + aux -> output" thing
which means adding a lot of proper API. Input, output and aux can be
pads or buffers. Make sure it uses the minimum possible graph in all
cases and doesn't reconfigure nodes unless needed. Port GimpPaintCore
to the new API.
2013-04-14 22:44:21 +02:00

970 lines
33 KiB
C

/* GIMP - The GNU Image Manipulation Program
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
*
* 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 "libgimpbase/gimpbase.h"
#include "libgimpmath/gimpmath.h"
#include "paint-types.h"
#include "gegl/gimp-gegl-loops.h"
#include "gegl/gimp-gegl-nodes.h"
#include "gegl/gimp-gegl-utils.h"
#include "gegl/gimpapplicator.h"
#include "core/gimp.h"
#include "core/gimp-utils.h"
#include "core/gimpchannel.h"
#include "core/gimpimage.h"
#include "core/gimpimage-undo.h"
#include "core/gimppickable.h"
#include "core/gimpprojection.h"
#include "core/gimptempbuf.h"
#include "gimppaintcore.h"
#include "gimppaintcoreundo.h"
#include "gimppaintoptions.h"
#include "gimpairbrush.h"
#include "gimp-intl.h"
#define STROKE_BUFFER_INIT_SIZE 2000
enum
{
PROP_0,
PROP_UNDO_DESC
};
/* local function prototypes */
static void gimp_paint_core_finalize (GObject *object);
static void gimp_paint_core_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec);
static void gimp_paint_core_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec);
static gboolean gimp_paint_core_real_start (GimpPaintCore *core,
GimpDrawable *drawable,
GimpPaintOptions *paint_options,
const GimpCoords *coords,
GError **error);
static gboolean gimp_paint_core_real_pre_paint (GimpPaintCore *core,
GimpDrawable *drawable,
GimpPaintOptions *options,
GimpPaintState paint_state,
guint32 time);
static void gimp_paint_core_real_paint (GimpPaintCore *core,
GimpDrawable *drawable,
GimpPaintOptions *options,
const GimpCoords *coords,
GimpPaintState paint_state,
guint32 time);
static void gimp_paint_core_real_post_paint (GimpPaintCore *core,
GimpDrawable *drawable,
GimpPaintOptions *options,
GimpPaintState paint_state,
guint32 time);
static void gimp_paint_core_real_interpolate (GimpPaintCore *core,
GimpDrawable *drawable,
GimpPaintOptions *options,
guint32 time);
static GeglBuffer *
gimp_paint_core_real_get_paint_buffer (GimpPaintCore *core,
GimpDrawable *drawable,
GimpPaintOptions *options,
const GimpCoords *coords,
gint *paint_buffer_x,
gint *paint_buffer_y);
static GimpUndo* gimp_paint_core_real_push_undo (GimpPaintCore *core,
GimpImage *image,
const gchar *undo_desc);
G_DEFINE_TYPE (GimpPaintCore, gimp_paint_core, GIMP_TYPE_OBJECT)
#define parent_class gimp_paint_core_parent_class
static gint global_core_ID = 1;
static void
gimp_paint_core_class_init (GimpPaintCoreClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->finalize = gimp_paint_core_finalize;
object_class->set_property = gimp_paint_core_set_property;
object_class->get_property = gimp_paint_core_get_property;
klass->start = gimp_paint_core_real_start;
klass->pre_paint = gimp_paint_core_real_pre_paint;
klass->paint = gimp_paint_core_real_paint;
klass->post_paint = gimp_paint_core_real_post_paint;
klass->interpolate = gimp_paint_core_real_interpolate;
klass->get_paint_buffer = gimp_paint_core_real_get_paint_buffer;
klass->push_undo = gimp_paint_core_real_push_undo;
g_object_class_install_property (object_class, PROP_UNDO_DESC,
g_param_spec_string ("undo-desc", NULL, NULL,
_("Paint"),
GIMP_PARAM_READWRITE |
G_PARAM_CONSTRUCT_ONLY));
}
static void
gimp_paint_core_init (GimpPaintCore *core)
{
core->ID = global_core_ID++;
}
static void
gimp_paint_core_finalize (GObject *object)
{
GimpPaintCore *core = GIMP_PAINT_CORE (object);
gimp_paint_core_cleanup (core);
g_free (core->undo_desc);
core->undo_desc = NULL;
if (core->stroke_buffer)
{
g_array_free (core->stroke_buffer, TRUE);
core->stroke_buffer = NULL;
}
G_OBJECT_CLASS (parent_class)->finalize (object);
}
static void
gimp_paint_core_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec)
{
GimpPaintCore *core = GIMP_PAINT_CORE (object);
switch (property_id)
{
case PROP_UNDO_DESC:
g_free (core->undo_desc);
core->undo_desc = g_value_dup_string (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
gimp_paint_core_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec)
{
GimpPaintCore *core = GIMP_PAINT_CORE (object);
switch (property_id)
{
case PROP_UNDO_DESC:
g_value_set_string (value, core->undo_desc);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static gboolean
gimp_paint_core_real_start (GimpPaintCore *core,
GimpDrawable *drawable,
GimpPaintOptions *paint_options,
const GimpCoords *coords,
GError **error)
{
return TRUE;
}
static gboolean
gimp_paint_core_real_pre_paint (GimpPaintCore *core,
GimpDrawable *drawable,
GimpPaintOptions *paint_options,
GimpPaintState paint_state,
guint32 time)
{
return TRUE;
}
static void
gimp_paint_core_real_paint (GimpPaintCore *core,
GimpDrawable *drawable,
GimpPaintOptions *paint_options,
const GimpCoords *coords,
GimpPaintState paint_state,
guint32 time)
{
}
static void
gimp_paint_core_real_post_paint (GimpPaintCore *core,
GimpDrawable *drawable,
GimpPaintOptions *paint_options,
GimpPaintState paint_state,
guint32 time)
{
}
static void
gimp_paint_core_real_interpolate (GimpPaintCore *core,
GimpDrawable *drawable,
GimpPaintOptions *paint_options,
guint32 time)
{
gimp_paint_core_paint (core, drawable, paint_options,
GIMP_PAINT_STATE_MOTION, time);
core->last_coords = core->cur_coords;
}
static GeglBuffer *
gimp_paint_core_real_get_paint_buffer (GimpPaintCore *core,
GimpDrawable *drawable,
GimpPaintOptions *paint_options,
const GimpCoords *coords,
gint *paint_buffer_x,
gint *paint_buffer_y)
{
return NULL;
}
static GimpUndo *
gimp_paint_core_real_push_undo (GimpPaintCore *core,
GimpImage *image,
const gchar *undo_desc)
{
return gimp_image_undo_push (image, GIMP_TYPE_PAINT_CORE_UNDO,
GIMP_UNDO_PAINT, undo_desc,
0,
"paint-core", core,
NULL);
}
/* public functions */
void
gimp_paint_core_paint (GimpPaintCore *core,
GimpDrawable *drawable,
GimpPaintOptions *paint_options,
GimpPaintState paint_state,
guint32 time)
{
GimpPaintCoreClass *core_class;
g_return_if_fail (GIMP_IS_PAINT_CORE (core));
g_return_if_fail (GIMP_IS_DRAWABLE (drawable));
g_return_if_fail (gimp_item_is_attached (GIMP_ITEM (drawable)));
g_return_if_fail (GIMP_IS_PAINT_OPTIONS (paint_options));
core_class = GIMP_PAINT_CORE_GET_CLASS (core);
if (core_class->pre_paint (core, drawable,
paint_options,
paint_state, time))
{
if (paint_state == GIMP_PAINT_STATE_MOTION)
{
/* Save coordinates for gimp_paint_core_interpolate() */
core->last_paint.x = core->cur_coords.x;
core->last_paint.y = core->cur_coords.y;
}
core_class->paint (core, drawable,
paint_options,
&core->cur_coords,
paint_state, time);
core_class->post_paint (core, drawable,
paint_options,
paint_state, time);
}
}
gboolean
gimp_paint_core_start (GimpPaintCore *core,
GimpDrawable *drawable,
GimpPaintOptions *paint_options,
const GimpCoords *coords,
GError **error)
{
GimpImage *image;
GimpItem *item;
GimpChannel *mask;
g_return_val_if_fail (GIMP_IS_PAINT_CORE (core), FALSE);
g_return_val_if_fail (GIMP_IS_DRAWABLE (drawable), FALSE);
g_return_val_if_fail (gimp_item_is_attached (GIMP_ITEM (drawable)), FALSE);
g_return_val_if_fail (GIMP_IS_PAINT_OPTIONS (paint_options), FALSE);
g_return_val_if_fail (coords != NULL, FALSE);
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
item = GIMP_ITEM (drawable);
image = gimp_item_get_image (item);
if (core->stroke_buffer)
{
g_array_free (core->stroke_buffer, TRUE);
core->stroke_buffer = NULL;
}
core->stroke_buffer = g_array_sized_new (TRUE, TRUE,
sizeof (GimpCoords),
STROKE_BUFFER_INIT_SIZE);
core->cur_coords = *coords;
if (! GIMP_PAINT_CORE_GET_CLASS (core)->start (core, drawable,
paint_options,
coords, error))
{
return FALSE;
}
/* Allocate the undo structure */
if (core->undo_buffer)
g_object_unref (core->undo_buffer);
core->undo_buffer = gegl_buffer_dup (gimp_drawable_get_buffer (drawable));
/* Allocate the saved proj structure */
if (core->saved_proj_buffer)
{
g_object_unref (core->saved_proj_buffer);
core->saved_proj_buffer = NULL;
}
if (core->use_saved_proj)
{
GimpPickable *pickable = GIMP_PICKABLE (gimp_image_get_projection (image));
GeglBuffer *buffer = gimp_pickable_get_buffer (pickable);
core->saved_proj_buffer = gegl_buffer_dup (buffer);
}
/* Allocate the canvas blocks structure */
if (core->canvas_buffer)
g_object_unref (core->canvas_buffer);
core->canvas_buffer =
gegl_buffer_new (GEGL_RECTANGLE (0, 0,
gimp_item_get_width (item),
gimp_item_get_height (item)),
babl_format ("Y float"));
/* Get the initial undo extents */
core->x1 = core->x2 = core->cur_coords.x;
core->y1 = core->y2 = core->cur_coords.y;
core->last_paint.x = -1e6;
core->last_paint.y = -1e6;
mask = gimp_image_get_mask (image);
/* don't apply the mask to itself and don't apply an empty mask */
if (GIMP_DRAWABLE (mask) == drawable || gimp_channel_is_empty (mask))
mask = NULL;
core->applicator = gimp_applicator_new (NULL);
if (mask)
{
GeglBuffer *mask_buffer;
gint offset_x;
gint offset_y;
mask_buffer = gimp_drawable_get_buffer (GIMP_DRAWABLE (mask));
gimp_item_get_offset (item, &offset_x, &offset_y);
gimp_applicator_set_mask_buffer (core->applicator, mask_buffer);
gimp_applicator_set_mask_offset (core->applicator,
-offset_x, -offset_y);
}
gimp_applicator_set_affect (core->applicator,
gimp_drawable_get_active_mask (drawable));
gimp_applicator_set_dest_buffer (core->applicator,
gimp_drawable_get_buffer (drawable));
/* Freeze the drawable preview so that it isn't constantly updated. */
gimp_viewable_preview_freeze (GIMP_VIEWABLE (drawable));
return TRUE;
}
void
gimp_paint_core_finish (GimpPaintCore *core,
GimpDrawable *drawable,
gboolean push_undo)
{
GimpImage *image;
g_return_if_fail (GIMP_IS_PAINT_CORE (core));
g_return_if_fail (GIMP_IS_DRAWABLE (drawable));
g_return_if_fail (gimp_item_is_attached (GIMP_ITEM (drawable)));
if (core->applicator)
{
g_object_unref (core->applicator);
core->applicator = NULL;
}
if (core->stroke_buffer)
{
g_array_free (core->stroke_buffer, TRUE);
core->stroke_buffer = NULL;
}
image = gimp_item_get_image (GIMP_ITEM (drawable));
/* Determine if any part of the image has been altered--
* if nothing has, then just return...
*/
if ((core->x2 == core->x1) || (core->y2 == core->y1))
{
gimp_viewable_preview_thaw (GIMP_VIEWABLE (drawable));
return;
}
if (push_undo)
{
GeglBuffer *buffer;
gint x, y, width, height;
gimp_rectangle_intersect (core->x1, core->y1,
core->x2 - core->x1, core->y2 - core->y1,
0, 0,
gimp_item_get_width (GIMP_ITEM (drawable)),
gimp_item_get_height (GIMP_ITEM (drawable)),
&x, &y, &width, &height);
gimp_image_undo_group_start (image, GIMP_UNDO_GROUP_PAINT,
core->undo_desc);
GIMP_PAINT_CORE_GET_CLASS (core)->push_undo (core, image, NULL);
buffer = gegl_buffer_new (GEGL_RECTANGLE (0, 0, width, height),
gimp_drawable_get_format (drawable));
gegl_buffer_copy (core->undo_buffer,
GEGL_RECTANGLE (x, y, width, height),
buffer,
GEGL_RECTANGLE (0, 0, 0, 0));
gimp_drawable_push_undo (drawable, NULL,
buffer, x, y, width, height);
g_object_unref (buffer);
gimp_image_undo_group_end (image);
}
g_object_unref (core->undo_buffer);
core->undo_buffer = NULL;
if (core->saved_proj_buffer)
{
g_object_unref (core->saved_proj_buffer);
core->saved_proj_buffer = NULL;
}
gimp_viewable_preview_thaw (GIMP_VIEWABLE (drawable));
}
void
gimp_paint_core_cancel (GimpPaintCore *core,
GimpDrawable *drawable)
{
gint x, y;
gint width, height;
g_return_if_fail (GIMP_IS_PAINT_CORE (core));
g_return_if_fail (GIMP_IS_DRAWABLE (drawable));
g_return_if_fail (gimp_item_is_attached (GIMP_ITEM (drawable)));
/* Determine if any part of the image has been altered--
* if nothing has, then just return...
*/
if ((core->x2 == core->x1) || (core->y2 == core->y1))
return;
if (gimp_rectangle_intersect (core->x1, core->y1,
core->x2 - core->x1,
core->y2 - core->y1,
0, 0,
gimp_item_get_width (GIMP_ITEM (drawable)),
gimp_item_get_height (GIMP_ITEM (drawable)),
&x, &y, &width, &height))
{
gegl_buffer_copy (core->undo_buffer,
GEGL_RECTANGLE (x, y, width, height),
gimp_drawable_get_buffer (drawable),
GEGL_RECTANGLE (x, y, width, height));
}
g_object_unref (core->undo_buffer);
core->undo_buffer = NULL;
if (core->saved_proj_buffer)
{
g_object_unref (core->saved_proj_buffer);
core->saved_proj_buffer = NULL;
}
gimp_drawable_update (drawable, x, y, width, height);
gimp_viewable_preview_thaw (GIMP_VIEWABLE (drawable));
}
void
gimp_paint_core_cleanup (GimpPaintCore *core)
{
g_return_if_fail (GIMP_IS_PAINT_CORE (core));
if (core->undo_buffer)
{
g_object_unref (core->undo_buffer);
core->undo_buffer = NULL;
}
if (core->saved_proj_buffer)
{
g_object_unref (core->saved_proj_buffer);
core->saved_proj_buffer = NULL;
}
if (core->canvas_buffer)
{
g_object_unref (core->canvas_buffer);
core->canvas_buffer = NULL;
}
if (core->paint_buffer)
{
g_object_unref (core->paint_buffer);
core->paint_buffer = NULL;
}
}
void
gimp_paint_core_interpolate (GimpPaintCore *core,
GimpDrawable *drawable,
GimpPaintOptions *paint_options,
const GimpCoords *coords,
guint32 time)
{
g_return_if_fail (GIMP_IS_PAINT_CORE (core));
g_return_if_fail (GIMP_IS_DRAWABLE (drawable));
g_return_if_fail (gimp_item_is_attached (GIMP_ITEM (drawable)));
g_return_if_fail (GIMP_IS_PAINT_OPTIONS (paint_options));
g_return_if_fail (coords != NULL);
core->cur_coords = *coords;
GIMP_PAINT_CORE_GET_CLASS (core)->interpolate (core, drawable,
paint_options, time);
}
void
gimp_paint_core_set_current_coords (GimpPaintCore *core,
const GimpCoords *coords)
{
g_return_if_fail (GIMP_IS_PAINT_CORE (core));
g_return_if_fail (coords != NULL);
core->cur_coords = *coords;
}
void
gimp_paint_core_get_current_coords (GimpPaintCore *core,
GimpCoords *coords)
{
g_return_if_fail (GIMP_IS_PAINT_CORE (core));
g_return_if_fail (coords != NULL);
*coords = core->cur_coords;
}
void
gimp_paint_core_set_last_coords (GimpPaintCore *core,
const GimpCoords *coords)
{
g_return_if_fail (GIMP_IS_PAINT_CORE (core));
g_return_if_fail (coords != NULL);
core->last_coords = *coords;
}
void
gimp_paint_core_get_last_coords (GimpPaintCore *core,
GimpCoords *coords)
{
g_return_if_fail (GIMP_IS_PAINT_CORE (core));
g_return_if_fail (coords != NULL);
*coords = core->last_coords;
}
/**
* gimp_paint_core_round_line:
* @core: the #GimpPaintCore
* @options: the #GimpPaintOptions to use
* @constrain_15_degrees: the modifier state
*
* Adjusts core->last_coords and core_cur_coords in preparation to
* drawing a straight line. If @center_pixels is TRUE the endpoints
* get pushed to the center of the pixels. This avoids artefacts
* for e.g. the hard mode. The rounding of the slope to 15 degree
* steps if ctrl is pressed happens, as does rounding the start and
* end coordinates (which may be fractional in high zoom modes) to
* the center of pixels.
**/
void
gimp_paint_core_round_line (GimpPaintCore *core,
GimpPaintOptions *paint_options,
gboolean constrain_15_degrees)
{
g_return_if_fail (GIMP_IS_PAINT_CORE (core));
g_return_if_fail (GIMP_IS_PAINT_OPTIONS (paint_options));
if (gimp_paint_options_get_brush_mode (paint_options) == GIMP_BRUSH_HARD)
{
core->last_coords.x = floor (core->last_coords.x) + 0.5;
core->last_coords.y = floor (core->last_coords.y) + 0.5;
core->cur_coords.x = floor (core->cur_coords.x ) + 0.5;
core->cur_coords.y = floor (core->cur_coords.y ) + 0.5;
}
if (constrain_15_degrees)
gimp_constrain_line (core->last_coords.x, core->last_coords.y,
&core->cur_coords.x, &core->cur_coords.y,
GIMP_CONSTRAIN_LINE_15_DEGREES);
}
/* protected functions */
GeglBuffer *
gimp_paint_core_get_paint_buffer (GimpPaintCore *core,
GimpDrawable *drawable,
GimpPaintOptions *paint_options,
const GimpCoords *coords,
gint *paint_buffer_x,
gint *paint_buffer_y)
{
GeglBuffer *paint_buffer;
g_return_val_if_fail (GIMP_IS_PAINT_CORE (core), NULL);
g_return_val_if_fail (GIMP_IS_DRAWABLE (drawable), NULL);
g_return_val_if_fail (gimp_item_is_attached (GIMP_ITEM (drawable)), NULL);
g_return_val_if_fail (GIMP_IS_PAINT_OPTIONS (paint_options), NULL);
g_return_val_if_fail (coords != NULL, NULL);
g_return_val_if_fail (paint_buffer_x != NULL, NULL);
g_return_val_if_fail (paint_buffer_y != NULL, NULL);
paint_buffer =
GIMP_PAINT_CORE_GET_CLASS (core)->get_paint_buffer (core, drawable,
paint_options,
coords,
paint_buffer_x,
paint_buffer_y);
core->paint_buffer_x = *paint_buffer_x;
core->paint_buffer_y = *paint_buffer_y;
return paint_buffer;
}
GeglBuffer *
gimp_paint_core_get_orig_image (GimpPaintCore *core)
{
g_return_val_if_fail (GIMP_IS_PAINT_CORE (core), NULL);
g_return_val_if_fail (core->undo_buffer != NULL, NULL);
return core->undo_buffer;
}
GeglBuffer *
gimp_paint_core_get_orig_proj (GimpPaintCore *core)
{
g_return_val_if_fail (GIMP_IS_PAINT_CORE (core), NULL);
g_return_val_if_fail (core->saved_proj_buffer != NULL, NULL);
return core->saved_proj_buffer;
}
void
gimp_paint_core_paste (GimpPaintCore *core,
GeglBuffer *paint_mask,
const GeglRectangle *paint_mask_rect,
GimpDrawable *drawable,
gdouble paint_opacity,
gdouble image_opacity,
GimpLayerModeEffects paint_mode,
GimpPaintApplicationMode mode)
{
gint width = gegl_buffer_get_width (core->paint_buffer);
gint height = gegl_buffer_get_height (core->paint_buffer);
/* If the mode is CONSTANT:
* combine the canvas buf, the paint mask to the canvas buffer
*/
if (mode == GIMP_PAINT_CONSTANT)
{
/* Some tools (ink) paint the mask to paint_core->canvas_buffer
* directly. Don't need to copy it in this case.
*/
if (paint_mask != core->canvas_buffer)
{
gimp_gegl_combine_mask_weird (paint_mask, paint_mask_rect,
core->canvas_buffer,
GEGL_RECTANGLE (core->paint_buffer_x,
core->paint_buffer_y,
width, height),
paint_opacity,
GIMP_IS_AIRBRUSH (core));
}
gimp_gegl_apply_mask (core->canvas_buffer,
GEGL_RECTANGLE (core->paint_buffer_x,
core->paint_buffer_y,
width, height),
core->paint_buffer,
GEGL_RECTANGLE (0, 0, width, height),
1.0);
gimp_applicator_set_src_buffer (core->applicator,
core->undo_buffer);
}
/* Otherwise:
* combine the canvas buf and the paint mask to the canvas buf
*/
else
{
gimp_gegl_apply_mask (paint_mask, paint_mask_rect,
core->paint_buffer,
GEGL_RECTANGLE (0, 0, width, height),
paint_opacity);
gimp_applicator_set_src_buffer (core->applicator,
gimp_drawable_get_buffer (drawable));
}
gimp_applicator_set_apply_buffer (core->applicator,
core->paint_buffer);
gimp_applicator_set_apply_offset (core->applicator,
core->paint_buffer_x,
core->paint_buffer_y);
gimp_applicator_set_mode (core->applicator,
image_opacity, paint_mode);
/* apply the paint area to the image */
gimp_applicator_blit (core->applicator,
GEGL_RECTANGLE (core->paint_buffer_x,
core->paint_buffer_y,
width, height));
/* Update the undo extents */
core->x1 = MIN (core->x1, core->paint_buffer_x);
core->y1 = MIN (core->y1, core->paint_buffer_y);
core->x2 = MAX (core->x2, core->paint_buffer_x + width);
core->y2 = MAX (core->y2, core->paint_buffer_y + height);
/* Update the drawable */
gimp_drawable_update (drawable,
core->paint_buffer_x,
core->paint_buffer_y,
width, height);
}
/* This works similarly to gimp_paint_core_paste. However, instead of
* combining the canvas to the paint core drawable using one of the
* combination modes, it uses a "replace" mode (i.e. transparent
* pixels in the canvas erase the paint core drawable).
* When not drawing on alpha-enabled images, it just paints using
* NORMAL mode.
*/
void
gimp_paint_core_replace (GimpPaintCore *core,
GeglBuffer *paint_mask,
const GeglRectangle *paint_mask_rect,
GimpDrawable *drawable,
gdouble paint_opacity,
gdouble image_opacity,
GimpPaintApplicationMode mode)
{
GeglRectangle mask_rect;
gint width, height;
if (! gimp_drawable_has_alpha (drawable))
{
gimp_paint_core_paste (core, paint_mask, paint_mask_rect,
drawable,
paint_opacity,
image_opacity, GIMP_NORMAL_MODE,
mode);
return;
}
width = gegl_buffer_get_width (core->paint_buffer);
height = gegl_buffer_get_height (core->paint_buffer);
if (mode == GIMP_PAINT_CONSTANT &&
/* Some tools (ink) paint the mask to paint_core->canvas_buffer
* directly. Don't need to copy it in this case.
*/
paint_mask != core->canvas_buffer)
{
/* combine the paint mask and the canvas buffer */
gimp_gegl_combine_mask_weird (paint_mask, paint_mask_rect,
core->canvas_buffer,
GEGL_RECTANGLE (core->paint_buffer_x,
core->paint_buffer_y,
width, height),
paint_opacity,
GIMP_IS_AIRBRUSH (core));
/* initialize the maskPR from the canvas buffer */
paint_mask = core->canvas_buffer;
mask_rect = *GEGL_RECTANGLE (core->paint_buffer_x,
core->paint_buffer_y,
width, height);
}
else
{
mask_rect = *paint_mask_rect;
}
/* apply the paint area to the image */
gimp_drawable_replace_buffer (drawable, core->paint_buffer,
GEGL_RECTANGLE (0, 0, width, height),
FALSE, NULL,
image_opacity,
paint_mask, &mask_rect,
core->paint_buffer_x,
core->paint_buffer_y);
/* Update the undo extents */
core->x1 = MIN (core->x1, core->paint_buffer_x);
core->y1 = MIN (core->y1, core->paint_buffer_y);
core->x2 = MAX (core->x2, core->paint_buffer_x + width);
core->y2 = MAX (core->y2, core->paint_buffer_y + height);
/* Update the drawable */
gimp_drawable_update (drawable,
core->paint_buffer_x,
core->paint_buffer_y,
width, height);
}
/**
* Smooth and store coords in the stroke buffer
*/
void
gimp_paint_core_smooth_coords (GimpPaintCore *core,
GimpPaintOptions *paint_options,
GimpCoords *coords)
{
GimpSmoothingOptions *smoothing_options = paint_options->smoothing_options;
GArray *history = core->stroke_buffer;
if (core->stroke_buffer == NULL)
return; /* Paint core has not initalized yet */
if (smoothing_options->use_smoothing &&
smoothing_options->smoothing_quality > 0)
{
gint i;
guint length;
gint min_index;
gdouble gaussian_weight = 0.0;
gdouble gaussian_weight2 = SQR (smoothing_options->smoothing_factor);
gdouble velocity_sum = 0.0;
gdouble scale_sum = 0.0;
g_array_append_val (history, *coords);
if (history->len < 2)
return; /* Just dont bother, nothing to do */
coords->x = coords->y = 0.0;
length = MIN (smoothing_options->smoothing_quality, history->len);
min_index = history->len - length;
if (gaussian_weight2 != 0.0)
gaussian_weight = 1 / (sqrt (2 * G_PI) * smoothing_options->smoothing_factor);
for (i = history->len - 1; i >= min_index; i--)
{
gdouble rate = 0.0;
GimpCoords *next_coords = &g_array_index (history,
GimpCoords, i);
if (gaussian_weight2 != 0.0)
{
/* We use gaussian function with velocity as a window function */
velocity_sum += next_coords->velocity * 100;
rate = gaussian_weight * exp (-velocity_sum * velocity_sum /
(2 * gaussian_weight2));
}
scale_sum += rate;
coords->x += rate * next_coords->x;
coords->y += rate * next_coords->y;
}
if (scale_sum != 0.0)
{
coords->x /= scale_sum;
coords->y /= scale_sum;
}
}
}