mirror of https://github.com/GNOME/gimp.git
2135 lines
65 KiB
C
2135 lines
65 KiB
C
/* The GIMP -- an 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 2 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, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include <glib-object.h>
|
|
|
|
#include "libgimpcolor/gimpcolor.h"
|
|
#include "libgimpmath/gimpmath.h"
|
|
#include "libgimpbase/gimpbase.h"
|
|
|
|
#include "paint-types.h"
|
|
|
|
#include "base/brush-scale.h"
|
|
#include "base/pixel-region.h"
|
|
#include "base/temp-buf.h"
|
|
#include "base/tile-manager.h"
|
|
#include "base/tile.h"
|
|
|
|
#include "paint-funcs/paint-funcs.h"
|
|
|
|
#include "core/gimp.h"
|
|
#include "core/gimpbrush.h"
|
|
#include "core/gimpcontainer.h"
|
|
#include "core/gimpdrawable.h"
|
|
#include "core/gimpgradient.h"
|
|
#include "core/gimpimage.h"
|
|
#include "core/gimpimage-undo.h"
|
|
#include "core/gimpmarshal.h"
|
|
#include "core/gimppaintinfo.h"
|
|
|
|
#include "gimppaintcore.h"
|
|
#include "gimppaintcore-kernels.h"
|
|
#include "gimppaintcore-undo.h"
|
|
#include "gimppaintoptions.h"
|
|
|
|
#include "gimp-intl.h"
|
|
|
|
|
|
#define EPSILON 0.00001
|
|
|
|
|
|
/* local function prototypes */
|
|
|
|
static void gimp_paint_core_class_init (GimpPaintCoreClass *klass);
|
|
static void gimp_paint_core_init (GimpPaintCore *core);
|
|
|
|
static void gimp_paint_core_finalize (GObject *object);
|
|
|
|
static void gimp_paint_core_calc_brush_size (GimpPaintCore *core,
|
|
MaskBuf *mask,
|
|
gdouble scale,
|
|
gint *width,
|
|
gint *height);
|
|
static inline void rotate_pointers (gulong **p,
|
|
guint32 n);
|
|
static MaskBuf * gimp_paint_core_subsample_mask (GimpPaintCore *core,
|
|
MaskBuf *mask,
|
|
gdouble x,
|
|
gdouble y);
|
|
static MaskBuf * gimp_paint_core_pressurize_mask (GimpPaintCore *core,
|
|
MaskBuf *brush_mask,
|
|
gdouble x,
|
|
gdouble y,
|
|
gdouble pressure);
|
|
static MaskBuf * gimp_paint_core_solidify_mask (GimpPaintCore *core,
|
|
MaskBuf *brush_mask,
|
|
gdouble x,
|
|
gdouble y);
|
|
static MaskBuf * gimp_paint_core_scale_mask (GimpPaintCore *core,
|
|
MaskBuf *brush_mask,
|
|
gdouble scale);
|
|
static MaskBuf * gimp_paint_core_scale_pixmap (GimpPaintCore *core,
|
|
MaskBuf *brush_mask,
|
|
gdouble scale);
|
|
|
|
static MaskBuf * gimp_paint_core_get_brush_mask (GimpPaintCore *core,
|
|
GimpBrushApplicationMode brush_hardness,
|
|
gdouble scale);
|
|
static void gimp_paint_core_paste (GimpPaintCore *core,
|
|
MaskBuf *brush_mask,
|
|
GimpDrawable *drawable,
|
|
gdouble brush_opacity,
|
|
gdouble image_opacity,
|
|
GimpLayerModeEffects paint_mode,
|
|
GimpPaintApplicationMode mode);
|
|
static void gimp_paint_core_replace (GimpPaintCore *core,
|
|
MaskBuf *brush_mask,
|
|
GimpDrawable *drawable,
|
|
gdouble brush_opacity,
|
|
gdouble image_opacity,
|
|
GimpPaintApplicationMode mode);
|
|
|
|
static void brush_to_canvas_tiles (GimpPaintCore *core,
|
|
MaskBuf *brush_mask,
|
|
gdouble brush_opacity);
|
|
static void brush_to_canvas_buf (GimpPaintCore *core,
|
|
MaskBuf *brush_mask,
|
|
gdouble brush_opacity);
|
|
static void canvas_tiles_to_canvas_buf (GimpPaintCore *core);
|
|
|
|
static void set_undo_tiles (GimpPaintCore *core,
|
|
GimpDrawable *drawable,
|
|
gint x,
|
|
gint y,
|
|
gint w,
|
|
gint h);
|
|
static void set_canvas_tiles (GimpPaintCore *core,
|
|
gint x,
|
|
gint y,
|
|
gint w,
|
|
gint h);
|
|
static void gimp_paint_core_invalidate_cache (GimpBrush *brush,
|
|
GimpPaintCore *core);
|
|
|
|
|
|
/* brush pipe utility functions */
|
|
static void paint_line_pixmap_mask (GimpImage *dest,
|
|
GimpDrawable *drawable,
|
|
TempBuf *pixmap_mask,
|
|
TempBuf *brush_mask,
|
|
guchar *d,
|
|
gint x,
|
|
gint y,
|
|
gint bytes,
|
|
gint width,
|
|
GimpBrushApplicationMode mode);
|
|
|
|
|
|
static GimpObjectClass *parent_class = NULL;
|
|
|
|
static gint global_core_ID = 1;
|
|
|
|
|
|
GType
|
|
gimp_paint_core_get_type (void)
|
|
{
|
|
static GType core_type = 0;
|
|
|
|
if (! core_type)
|
|
{
|
|
static const GTypeInfo core_info =
|
|
{
|
|
sizeof (GimpPaintCoreClass),
|
|
(GBaseInitFunc) NULL,
|
|
(GBaseFinalizeFunc) NULL,
|
|
(GClassInitFunc) gimp_paint_core_class_init,
|
|
NULL, /* class_finalize */
|
|
NULL, /* class_data */
|
|
sizeof (GimpPaintCore),
|
|
0, /* n_preallocs */
|
|
(GInstanceInitFunc) gimp_paint_core_init,
|
|
};
|
|
|
|
core_type = g_type_register_static (GIMP_TYPE_OBJECT,
|
|
"GimpPaintCore",
|
|
&core_info, 0);
|
|
}
|
|
|
|
return core_type;
|
|
}
|
|
|
|
static void
|
|
gimp_paint_core_class_init (GimpPaintCoreClass *klass)
|
|
{
|
|
GObjectClass *object_class;
|
|
|
|
object_class = G_OBJECT_CLASS (klass);
|
|
|
|
parent_class = g_type_class_peek_parent (klass);
|
|
|
|
object_class->finalize = gimp_paint_core_finalize;
|
|
|
|
klass->paint = NULL;
|
|
}
|
|
|
|
static void
|
|
gimp_paint_core_init (GimpPaintCore *core)
|
|
{
|
|
gint i, j;
|
|
|
|
core->ID = global_core_ID++;
|
|
|
|
core->distance = 0.0;
|
|
core->spacing = 0.0;
|
|
core->x1 = 0;
|
|
core->y1 = 0;
|
|
core->x2 = 0;
|
|
core->y2 = 0;
|
|
|
|
core->brush = NULL;
|
|
|
|
core->flags = 0;
|
|
core->use_pressure = FALSE;
|
|
|
|
core->undo_tiles = NULL;
|
|
core->canvas_tiles = NULL;
|
|
|
|
core->orig_buf = NULL;
|
|
core->canvas_buf = NULL;
|
|
|
|
core->pressure_brush = NULL;
|
|
|
|
for (i = 0; i < PAINT_CORE_SOLID_SUBSAMPLE; i++)
|
|
for (j = 0; j < PAINT_CORE_SOLID_SUBSAMPLE; j++)
|
|
core->solid_brushes[i][j] = NULL;
|
|
|
|
core->last_solid_brush = NULL;
|
|
core->solid_cache_invalid = FALSE;
|
|
|
|
core->scale_brush = NULL;
|
|
core->last_scale_brush = NULL;
|
|
core->last_scale_width = 0;
|
|
core->last_scale_height = 0;
|
|
|
|
core->scale_pixmap = NULL;
|
|
core->last_scale_pixmap = NULL;
|
|
core->last_scale_pixmap_width = 0;
|
|
core->last_scale_pixmap_height = 0;
|
|
|
|
g_assert (PAINT_CORE_SUBSAMPLE == KERNEL_SUBSAMPLE);
|
|
|
|
for (i = 0; i < KERNEL_SUBSAMPLE + 1; i++)
|
|
for (j = 0; j < KERNEL_SUBSAMPLE + 1; j++)
|
|
core->kernel_brushes[i][j] = NULL;
|
|
|
|
core->last_brush_mask = NULL;
|
|
core->cache_invalid = FALSE;
|
|
|
|
core->grr_brush = NULL;
|
|
core->brush_bound_segs = NULL;
|
|
core->n_brush_bound_segs = 0;
|
|
}
|
|
|
|
static void
|
|
gimp_paint_core_finalize (GObject *object)
|
|
{
|
|
GimpPaintCore *core;
|
|
gint i, j;
|
|
|
|
core = GIMP_PAINT_CORE (object);
|
|
|
|
gimp_paint_core_cleanup (core);
|
|
|
|
if (core->pressure_brush)
|
|
{
|
|
temp_buf_free (core->pressure_brush);
|
|
core->pressure_brush = NULL;
|
|
}
|
|
|
|
for (i = 0; i < PAINT_CORE_SOLID_SUBSAMPLE; i++)
|
|
for (j = 0; j < PAINT_CORE_SOLID_SUBSAMPLE; j++)
|
|
if (core->solid_brushes[i][j])
|
|
{
|
|
temp_buf_free (core->solid_brushes[i][j]);
|
|
core->solid_brushes[i][j] = NULL;
|
|
}
|
|
|
|
if (core->scale_brush)
|
|
{
|
|
temp_buf_free (core->scale_brush);
|
|
core->scale_brush = NULL;
|
|
}
|
|
|
|
if (core->scale_pixmap)
|
|
{
|
|
temp_buf_free (core->scale_pixmap);
|
|
core->scale_pixmap = NULL;
|
|
}
|
|
|
|
for (i = 0; i < KERNEL_SUBSAMPLE + 1; i++)
|
|
for (j = 0; j < KERNEL_SUBSAMPLE + 1; j++)
|
|
if (core->kernel_brushes[i][j])
|
|
{
|
|
temp_buf_free (core->kernel_brushes[i][j]);
|
|
core->kernel_brushes[i][j] = NULL;
|
|
}
|
|
|
|
if (core->grr_brush)
|
|
{
|
|
g_signal_handlers_disconnect_by_func (core->grr_brush,
|
|
gimp_paint_core_invalidate_cache,
|
|
core);
|
|
g_object_unref (core->grr_brush);
|
|
core->grr_brush = NULL;
|
|
}
|
|
|
|
if (core->brush_bound_segs)
|
|
{
|
|
g_free (core->brush_bound_segs);
|
|
core->brush_bound_segs = NULL;
|
|
core->n_brush_bound_segs = 0;
|
|
}
|
|
|
|
G_OBJECT_CLASS (parent_class)->finalize (object);
|
|
}
|
|
|
|
void
|
|
gimp_paint_core_paint (GimpPaintCore *core,
|
|
GimpDrawable *drawable,
|
|
GimpPaintOptions *paint_options,
|
|
GimpPaintCoreState paint_state)
|
|
{
|
|
g_return_if_fail (GIMP_IS_PAINT_CORE (core));
|
|
g_return_if_fail (GIMP_IS_DRAWABLE (drawable));
|
|
g_return_if_fail (GIMP_IS_PAINT_OPTIONS (paint_options));
|
|
|
|
if (paint_state == MOTION_PAINT)
|
|
{
|
|
/* If we current point == last point, check if the brush
|
|
* wants to be painted in that case. (Direction dependent
|
|
* pixmap brush pipes don't, as they don't know which
|
|
* pixmap to select.)
|
|
*/
|
|
if (core->last_coords.x == core->cur_coords.x &&
|
|
core->last_coords.y == core->cur_coords.y &&
|
|
! gimp_brush_want_null_motion (core->brush,
|
|
&core->last_coords,
|
|
&core->cur_coords))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (core->flags & CORE_HANDLES_CHANGING_BRUSH)
|
|
{
|
|
core->brush = gimp_brush_select_brush (core->brush,
|
|
&core->last_coords,
|
|
&core->cur_coords);
|
|
}
|
|
|
|
/* Save coordinates for gimp_paint_core_interpolate() */
|
|
core->last_paint.x = core->cur_coords.x;
|
|
core->last_paint.y = core->cur_coords.y;
|
|
}
|
|
|
|
GIMP_PAINT_CORE_GET_CLASS (core)->paint (core,
|
|
drawable,
|
|
paint_options,
|
|
paint_state);
|
|
}
|
|
|
|
gboolean
|
|
gimp_paint_core_start (GimpPaintCore *core,
|
|
GimpDrawable *drawable,
|
|
GimpPaintOptions *paint_options,
|
|
GimpCoords *coords)
|
|
{
|
|
GimpItem *item;
|
|
GimpImage *gimage;
|
|
|
|
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_IS_PAINT_OPTIONS (paint_options), FALSE);
|
|
g_return_val_if_fail (coords != NULL, FALSE);
|
|
|
|
item = GIMP_ITEM (drawable);
|
|
gimage = gimp_item_get_image (item);
|
|
|
|
g_return_val_if_fail (GIMP_IS_IMAGE (gimage), FALSE);
|
|
|
|
core->cur_coords = *coords;
|
|
|
|
/* Each buffer is the same size as
|
|
* the maximum bounds of the active brush...
|
|
*/
|
|
|
|
if (core->grr_brush != gimp_context_get_brush (GIMP_CONTEXT (paint_options)))
|
|
{
|
|
if (core->grr_brush)
|
|
{
|
|
g_signal_handlers_disconnect_by_func (core->grr_brush,
|
|
gimp_paint_core_invalidate_cache,
|
|
core);
|
|
g_object_unref (core->grr_brush);
|
|
core->grr_brush = NULL;
|
|
}
|
|
|
|
if (core->brush_bound_segs)
|
|
{
|
|
g_free (core->brush_bound_segs);
|
|
core->brush_bound_segs = NULL;
|
|
core->n_brush_bound_segs = 0;
|
|
}
|
|
}
|
|
|
|
if (! core->grr_brush)
|
|
{
|
|
core->grr_brush = gimp_context_get_brush (GIMP_CONTEXT (paint_options));
|
|
|
|
if (! core->grr_brush)
|
|
{
|
|
g_message (_("No brushes available for use with this tool."));
|
|
return FALSE;
|
|
}
|
|
|
|
g_object_ref (core->grr_brush);
|
|
g_signal_connect (core->grr_brush, "invalidate_preview",
|
|
G_CALLBACK (gimp_paint_core_invalidate_cache),
|
|
core);
|
|
}
|
|
|
|
core->spacing = (gdouble) gimp_brush_get_spacing (core->grr_brush) / 100.0;
|
|
|
|
core->brush = core->grr_brush;
|
|
|
|
/* Allocate the undo structure */
|
|
if (core->undo_tiles)
|
|
tile_manager_unref (core->undo_tiles);
|
|
|
|
core->undo_tiles = tile_manager_new (gimp_item_width (item),
|
|
gimp_item_height (item),
|
|
gimp_drawable_bytes (drawable));
|
|
|
|
/* Allocate the canvas blocks structure */
|
|
if (core->canvas_tiles)
|
|
tile_manager_unref (core->canvas_tiles);
|
|
|
|
core->canvas_tiles = tile_manager_new (gimp_item_width (item),
|
|
gimp_item_height (item),
|
|
1);
|
|
|
|
/* 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;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
void
|
|
gimp_paint_core_finish (GimpPaintCore *core,
|
|
GimpDrawable *drawable)
|
|
{
|
|
GimpPaintInfo *paint_info;
|
|
GimpImage *gimage;
|
|
|
|
g_return_if_fail (GIMP_IS_PAINT_CORE (core));
|
|
g_return_if_fail (GIMP_IS_DRAWABLE (drawable));
|
|
|
|
gimage = gimp_item_get_image (GIMP_ITEM (drawable));
|
|
|
|
g_return_if_fail (GIMP_IS_IMAGE (gimage));
|
|
|
|
/* 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;
|
|
|
|
paint_info = (GimpPaintInfo *)
|
|
gimp_container_get_child_by_name (gimage->gimp->paint_info_list,
|
|
g_type_name (G_TYPE_FROM_INSTANCE (core)));
|
|
|
|
gimp_image_undo_group_start (gimage, GIMP_UNDO_GROUP_PAINT,
|
|
paint_info ? paint_info->blurb : _("Paint"));
|
|
|
|
gimp_paint_core_push_undo (gimage, NULL, core);
|
|
|
|
gimp_drawable_push_undo (drawable, NULL,
|
|
core->x1, core->y1,
|
|
core->x2, core->y2,
|
|
core->undo_tiles,
|
|
TRUE);
|
|
|
|
tile_manager_unref (core->undo_tiles);
|
|
core->undo_tiles = NULL;
|
|
|
|
gimp_image_undo_group_end (gimage);
|
|
|
|
/* invalidate the previews -- have to do it here, because
|
|
* it is not done during the actual painting.
|
|
*/
|
|
gimp_viewable_invalidate_preview (GIMP_VIEWABLE (drawable));
|
|
gimp_viewable_invalidate_preview (GIMP_VIEWABLE (gimage));
|
|
}
|
|
|
|
static void
|
|
gimp_paint_core_copy_valid_tiles (TileManager *src_tiles,
|
|
TileManager *dest_tiles,
|
|
gint x,
|
|
gint y,
|
|
gint w,
|
|
gint h)
|
|
{
|
|
Tile *src_tile;
|
|
gint i, j;
|
|
|
|
for (i = y; i < (y + h); i += (TILE_HEIGHT - (i % TILE_HEIGHT)))
|
|
{
|
|
for (j = x; j < (x + w); j += (TILE_WIDTH - (j % TILE_WIDTH)))
|
|
{
|
|
src_tile = tile_manager_get_tile (src_tiles,
|
|
j, i, FALSE, FALSE);
|
|
|
|
if (tile_is_valid (src_tile) == TRUE)
|
|
{
|
|
src_tile = tile_manager_get_tile (src_tiles,
|
|
j, i, TRUE, FALSE);
|
|
|
|
tile_manager_map_tile (dest_tiles, j, i, src_tile);
|
|
|
|
tile_release (src_tile, FALSE);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
gimp_paint_core_cancel (GimpPaintCore *core,
|
|
GimpDrawable *drawable)
|
|
{
|
|
GimpImage *gimage;
|
|
|
|
g_return_if_fail (GIMP_IS_PAINT_CORE (core));
|
|
g_return_if_fail (GIMP_IS_DRAWABLE (drawable));
|
|
|
|
gimage = gimp_item_get_image (GIMP_ITEM (drawable));
|
|
|
|
g_return_if_fail (GIMP_IS_IMAGE (gimage));
|
|
|
|
/* 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;
|
|
|
|
gimp_paint_core_copy_valid_tiles (core->undo_tiles,
|
|
gimp_drawable_data (drawable),
|
|
core->x1, core->y1,
|
|
core->x2 - core->x1,
|
|
core->y2 - core->y1);
|
|
|
|
tile_manager_unref (core->undo_tiles);
|
|
core->undo_tiles = NULL;
|
|
|
|
gimp_drawable_update (drawable,
|
|
core->x1, core->y1,
|
|
core->x2 - core->x1, core->y2 - core->y1);
|
|
}
|
|
|
|
void
|
|
gimp_paint_core_cleanup (GimpPaintCore *core)
|
|
{
|
|
g_return_if_fail (GIMP_IS_PAINT_CORE (core));
|
|
|
|
if (core->undo_tiles)
|
|
{
|
|
tile_manager_unref (core->undo_tiles);
|
|
core->undo_tiles = NULL;
|
|
}
|
|
|
|
if (core->canvas_tiles)
|
|
{
|
|
tile_manager_unref (core->canvas_tiles);
|
|
core->canvas_tiles = NULL;
|
|
}
|
|
|
|
if (core->orig_buf)
|
|
{
|
|
temp_buf_free (core->orig_buf);
|
|
core->orig_buf = NULL;
|
|
}
|
|
|
|
if (core->canvas_buf)
|
|
{
|
|
temp_buf_free (core->canvas_buf);
|
|
core->canvas_buf = NULL;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* gimp_paint_core_constrain_helper:
|
|
* @dx: the (fixed) delta-x
|
|
* @dy: a suggested delta-y
|
|
*
|
|
* Returns an adjusted dy' near dy such that the slope (dx,dy') is a
|
|
* multiple of 15 degrees.
|
|
**/
|
|
static gdouble
|
|
gimp_paint_core_constrain_helper (gdouble dx,
|
|
gdouble dy)
|
|
{
|
|
static gdouble slope[4] = { 0, 0.26795, 0.57735, 1 };
|
|
static gdouble divider[3] = { 0.13165, 0.41421, 0.76732 };
|
|
gint i;
|
|
|
|
if (dy < 0)
|
|
return - gimp_paint_core_constrain_helper (dx,-dy);
|
|
dx = fabs (dx);
|
|
for (i = 0; i < 3; i ++)
|
|
if (dy < dx * divider[i])
|
|
break;
|
|
dy = dx * slope[i];
|
|
return dy;
|
|
}
|
|
|
|
/**
|
|
* gimp_paint_core_constrain:
|
|
* @core: the #GimpPaintCore.
|
|
*
|
|
* Restricts the (core->last_coords, core->cur_coords) vector to 15
|
|
* degree steps, possibly changing core->cur_coords.
|
|
**/
|
|
void
|
|
gimp_paint_core_constrain (GimpPaintCore *core)
|
|
{
|
|
gdouble dx, dy;
|
|
|
|
g_return_if_fail (GIMP_IS_PAINT_CORE (core));
|
|
|
|
dx = core->cur_coords.x - core->last_coords.x;
|
|
dy = core->cur_coords.y - core->last_coords.y;
|
|
|
|
/* This algorithm changes only one of dx and dy, and does not try
|
|
* to constrain the resulting dx and dy to integers. This gives
|
|
* at least two benefits:
|
|
* 1. gimp_paint_core_constrain is idempotent, even if followed by
|
|
* a rounding operation.
|
|
* 2. For any two lines with the same starting-point and ideal
|
|
* 15-degree direction, the points plotted by
|
|
* gimp_paint_core_interpolate for the shorter line will always
|
|
* be a superset of those plotted for the longer line.
|
|
*/
|
|
if (fabs(dx) > fabs(dy))
|
|
core->cur_coords.y = core->last_coords.y +
|
|
gimp_paint_core_constrain_helper (dx,dy);
|
|
else
|
|
core->cur_coords.x = core->last_coords.x +
|
|
gimp_paint_core_constrain_helper (dy,dx);
|
|
}
|
|
|
|
/**
|
|
* gimp_avoid_exact_integer
|
|
* @x: points to a gdouble
|
|
*
|
|
* Adjusts *x such that it is not too close to an integer. This is used
|
|
* for decision algorithms that would be vulnerable to rounding glitches
|
|
* if exact integers were input.
|
|
*
|
|
* Side effects: Changes the value of *x
|
|
**/
|
|
static void
|
|
gimp_avoid_exact_integer (gdouble *x)
|
|
{
|
|
gdouble integral = floor (*x);
|
|
gdouble fractional = *x - integral;
|
|
|
|
if (fractional < EPSILON)
|
|
*x = integral + EPSILON;
|
|
else if (fractional > (1-EPSILON))
|
|
*x = integral + (1-EPSILON);
|
|
}
|
|
|
|
void
|
|
gimp_paint_core_interpolate (GimpPaintCore *core,
|
|
GimpDrawable *drawable,
|
|
GimpPaintOptions *paint_options)
|
|
{
|
|
GimpCoords delta;
|
|
gint n, num_points;
|
|
gdouble t0, dt, tn;
|
|
gdouble st_factor, st_offset;
|
|
gdouble initial;
|
|
gdouble dist;
|
|
gdouble total;
|
|
gdouble pixel_dist;
|
|
gdouble pixel_initial;
|
|
gdouble xd, yd;
|
|
gdouble mag;
|
|
|
|
g_return_if_fail (GIMP_IS_PAINT_CORE (core));
|
|
g_return_if_fail (GIMP_IS_DRAWABLE (drawable));
|
|
g_return_if_fail (GIMP_IS_PAINT_OPTIONS (paint_options));
|
|
|
|
gimp_avoid_exact_integer (&core->last_coords.x);
|
|
gimp_avoid_exact_integer (&core->last_coords.y);
|
|
gimp_avoid_exact_integer (&core->cur_coords.x);
|
|
gimp_avoid_exact_integer (&core->cur_coords.y);
|
|
|
|
delta.x = core->cur_coords.x - core->last_coords.x;
|
|
delta.y = core->cur_coords.y - core->last_coords.y;
|
|
delta.pressure = core->cur_coords.pressure - core->last_coords.pressure;
|
|
delta.xtilt = core->cur_coords.xtilt - core->last_coords.xtilt;
|
|
delta.ytilt = core->cur_coords.ytilt - core->last_coords.ytilt;
|
|
delta.wheel = core->cur_coords.wheel - core->last_coords.wheel;
|
|
|
|
/* return if there has been no motion */
|
|
if (! delta.x &&
|
|
! delta.y &&
|
|
! delta.pressure &&
|
|
! delta.xtilt &&
|
|
! delta.ytilt &&
|
|
! delta.wheel)
|
|
return;
|
|
|
|
/* calculate the distance traveled in the coordinate space of the brush */
|
|
mag = gimp_vector2_length (&(core->brush->x_axis));
|
|
xd = gimp_vector2_inner_product ((GimpVector2 *) &delta,
|
|
&(core->brush->x_axis)) / (mag * mag);
|
|
|
|
mag = gimp_vector2_length (&(core->brush->y_axis));
|
|
yd = gimp_vector2_inner_product ((GimpVector2 *) &delta,
|
|
&(core->brush->y_axis)) / (mag * mag);
|
|
|
|
dist = 0.5 * sqrt (xd * xd + yd * yd);
|
|
total = dist + core->distance;
|
|
initial = core->distance;
|
|
|
|
pixel_dist = gimp_vector2_length ((GimpVector2 *) &delta);
|
|
pixel_initial = core->pixel_dist;
|
|
|
|
/* FIXME: need to adapt the spacing to the size */
|
|
/* lastscale = MIN (gimp_paint_tool->lastpressure, 1/256); */
|
|
/* curscale = MIN (gimp_paint_tool->curpressure, 1/256); */
|
|
/* spacing = gimp_paint_tool->spacing * sqrt (0.5 * (lastscale + curscale)); */
|
|
|
|
/* Compute spacing parameters such that a brush position will be
|
|
* made each time the line crosses the *center* of a pixel row or
|
|
* column, according to whether the line is mostly horizontal or
|
|
* mostly vertical. The term "stripe" will mean "column" if the
|
|
* line is horizontalish; "row" if the line is verticalish.
|
|
*
|
|
* We start by deriving coefficients for a new parameter 's':
|
|
* s = t * st_factor + st_offset
|
|
* such that the "nice" brush positions are the ones with *integer*
|
|
* s values. (Actually the value of s will be 1/2 less than the nice
|
|
* brush position's x or y coordinate - note that st_factor may
|
|
* be negative!)
|
|
*/
|
|
|
|
if (delta.x*delta.x > delta.y*delta.y)
|
|
{
|
|
st_factor = delta.x;
|
|
st_offset = core->last_coords.x - 0.5;
|
|
}
|
|
else
|
|
{
|
|
st_factor = delta.y;
|
|
st_offset = core->last_coords.y - 0.5;
|
|
}
|
|
|
|
if (fabs (st_factor) > dist / core->spacing)
|
|
{
|
|
/* The stripe principle leads to brush positions that are spaced
|
|
* *closer* than the official brush spacing. Use the official
|
|
* spacing instead. This is the common case when the brush spacing
|
|
* is large.
|
|
* The net effect is then to put a lower bound on the spacing, but
|
|
* one that varies with the slope of the line. This is suppose to
|
|
* make thin lines (say, with a 1x1 brush) prettier while leaving
|
|
* lines with larger brush spacing as they used to look in 1.2.x.
|
|
*/
|
|
|
|
dt = core->spacing / dist;
|
|
n = (gint) (initial / core->spacing + 1.0 + EPSILON);
|
|
t0 = (n * core->spacing - initial) / dist;
|
|
num_points = 1 + (gint) floor ((1 + EPSILON - t0) / dt);
|
|
|
|
/* if we arnt going to paint anything this time and the brush
|
|
* has only moved on one axis return without updating the brush
|
|
* position, distance etc. so that we can more accurately space
|
|
* brush strokes when curves are supplied to us in single pixel
|
|
* chunks.
|
|
*/
|
|
|
|
if (num_points == 0 && (delta.x == 0 || delta.y == 0))
|
|
return;
|
|
}
|
|
else if (fabs (st_factor) < EPSILON)
|
|
{
|
|
/* Hm, we've hardly moved at all. Don't draw anything, but reset the
|
|
* old coordinates and hope we've gone longer the next time.
|
|
*/
|
|
core->cur_coords.x = core->last_coords.x;
|
|
core->cur_coords.y = core->last_coords.y;
|
|
/* ... but go along with the current pressure, tilt and wheel */
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
gint direction = st_factor > 0 ? 1 : -1;
|
|
gint x, y;
|
|
gint s0, sn;
|
|
|
|
/* Choose the first and last stripe to paint.
|
|
* FIRST PRIORITY is to avoid gaps painting with a 1x1 aliasing
|
|
* brush when a horizontalish line segment follows a verticalish
|
|
* one or vice versa - no matter what the angle between the two
|
|
* lines is. This will also limit the local thinning that a 1x1
|
|
* subsampled brush may suffer in the same situation.
|
|
* SECOND PRIORITY is to avoid making free-hand drawings
|
|
* unpleasantly fat by plotting redundant points.
|
|
* These are achieved by the following rules, but it is a little
|
|
* tricky to see just why. Do not change this algorithm unless you
|
|
* are sure you know what you're doing!
|
|
*/
|
|
|
|
/* Basic case: round the beginning and ending point to nearest
|
|
* stripe center.
|
|
*/
|
|
s0 = (gint) floor (st_offset + 0.5);
|
|
sn = (gint) floor (st_offset + st_factor + 0.5);
|
|
|
|
t0 = (s0 - st_offset) / st_factor;
|
|
tn = (sn - st_offset) / st_factor;
|
|
|
|
x = (gint) floor (core->last_coords.x + t0 * delta.x);
|
|
y = (gint) floor (core->last_coords.y + t0 * delta.y);
|
|
if (t0 < 0.0 && !( x == (gint) floor (core->last_coords.x) &&
|
|
y == (gint) floor (core->last_coords.y) ))
|
|
{
|
|
/* Exception A: If the first stripe's brush position is
|
|
* EXTRApolated into a different pixel square than the
|
|
* ideal starting point, dont't plot it.
|
|
*/
|
|
s0 += direction;
|
|
}
|
|
else if (x == (gint) floor (core->last_paint.x) &&
|
|
y == (gint) floor (core->last_paint.y))
|
|
{
|
|
/* Exception B: If first stripe's brush position is within the
|
|
* same pixel square as the last plot of the previous line,
|
|
* don't plot it either.
|
|
*/
|
|
s0 += direction;
|
|
}
|
|
|
|
x = (gint) floor (core->last_coords.x + tn * delta.x);
|
|
y = (gint) floor (core->last_coords.y + tn * delta.y);
|
|
if (tn > 1.0 && !( x == (gint) floor( core->cur_coords.x ) &&
|
|
y == (gint) floor( core->cur_coords.y ) ))
|
|
{
|
|
/* Exception C: If the last stripe's brush position is
|
|
* EXTRApolated into a different pixel square than the
|
|
* ideal ending point, don't plot it.
|
|
*/
|
|
sn -= direction;
|
|
}
|
|
|
|
t0 = (s0 - st_offset) / st_factor;
|
|
tn = (sn - st_offset) / st_factor;
|
|
dt = direction * 1.0 / st_factor;
|
|
num_points = 1 + direction * (sn - s0);
|
|
|
|
if (num_points >= 1)
|
|
{
|
|
/* Hack the reported total distance such that it looks to the
|
|
* next line as if the the last pixel plotted were at an integer
|
|
* multiple of the brush spacing. This helps prevent artifacts
|
|
* for connected lines when the brush spacing is such that some
|
|
* slopes will use the stripe regime and other slopes will use
|
|
* the nominal brush spacing.
|
|
*/
|
|
|
|
if (tn < 1)
|
|
total = initial + tn * dist;
|
|
|
|
total = core->spacing * (gint) (total / core->spacing + 0.5);
|
|
total += (1.0 - tn) * dist;
|
|
}
|
|
}
|
|
|
|
for (n = 0; n < num_points; n++)
|
|
{
|
|
GimpBrush *current_brush;
|
|
gdouble t = t0 + n*dt;
|
|
|
|
core->cur_coords.x = core->last_coords.x + t * delta.x;
|
|
core->cur_coords.y = core->last_coords.y + t * delta.y;
|
|
core->cur_coords.pressure = core->last_coords.pressure + t * delta.pressure;
|
|
core->cur_coords.xtilt = core->last_coords.xtilt + t * delta.xtilt;
|
|
core->cur_coords.ytilt = core->last_coords.ytilt + t * delta.ytilt;
|
|
core->cur_coords.wheel = core->last_coords.wheel + t * delta.wheel;
|
|
|
|
core->distance = initial + t * dist;
|
|
core->pixel_dist = pixel_initial + t * pixel_dist;
|
|
|
|
/* save the current brush */
|
|
current_brush = core->brush;
|
|
|
|
gimp_paint_core_paint (core, drawable, paint_options, MOTION_PAINT);
|
|
|
|
/* restore the current brush pointer */
|
|
core->brush = current_brush;
|
|
}
|
|
|
|
core->cur_coords.x = core->last_coords.x + delta.x;
|
|
core->cur_coords.y = core->last_coords.y + delta.y;
|
|
core->cur_coords.pressure = core->last_coords.pressure + delta.pressure;
|
|
core->cur_coords.xtilt = core->last_coords.xtilt + delta.xtilt;
|
|
core->cur_coords.ytilt = core->last_coords.ytilt + delta.ytilt;
|
|
core->cur_coords.wheel = core->last_coords.wheel + delta.wheel;
|
|
|
|
core->distance = total;
|
|
core->pixel_dist = pixel_initial + pixel_dist;
|
|
|
|
core->last_coords = core->cur_coords;
|
|
|
|
}
|
|
|
|
|
|
/* protected functions */
|
|
|
|
TempBuf *
|
|
gimp_paint_core_get_paint_area (GimpPaintCore *core,
|
|
GimpDrawable *drawable,
|
|
gdouble scale)
|
|
{
|
|
gint x, y;
|
|
gint x1, y1, x2, y2;
|
|
gint bytes;
|
|
gint dwidth, dheight;
|
|
gint bwidth, bheight;
|
|
|
|
bytes = (gimp_drawable_has_alpha (drawable) ?
|
|
gimp_drawable_bytes (drawable) : gimp_drawable_bytes (drawable) + 1);
|
|
|
|
gimp_paint_core_calc_brush_size (core,
|
|
core->brush->mask,
|
|
scale,
|
|
&bwidth, &bheight);
|
|
|
|
/* adjust the x and y coordinates to the upper left corner of the brush */
|
|
x = (gint) floor (core->cur_coords.x) - (bwidth >> 1);
|
|
y = (gint) floor (core->cur_coords.y) - (bheight >> 1);
|
|
|
|
dwidth = gimp_item_width (GIMP_ITEM (drawable));
|
|
dheight = gimp_item_height (GIMP_ITEM (drawable));
|
|
|
|
x1 = CLAMP (x - 1, 0, dwidth);
|
|
y1 = CLAMP (y - 1, 0, dheight);
|
|
x2 = CLAMP (x + bwidth + 1, 0, dwidth);
|
|
y2 = CLAMP (y + bheight + 1, 0, dheight);
|
|
|
|
/* configure the canvas buffer */
|
|
if ((x2 - x1) && (y2 - y1))
|
|
core->canvas_buf = temp_buf_resize (core->canvas_buf, bytes,
|
|
x1, y1,
|
|
(x2 - x1), (y2 - y1));
|
|
else
|
|
return NULL;
|
|
|
|
return core->canvas_buf;
|
|
}
|
|
|
|
TempBuf *
|
|
gimp_paint_core_get_orig_image (GimpPaintCore *core,
|
|
GimpDrawable *drawable,
|
|
gint x1,
|
|
gint y1,
|
|
gint x2,
|
|
gint y2)
|
|
{
|
|
PixelRegion srcPR;
|
|
PixelRegion destPR;
|
|
Tile *undo_tile;
|
|
gint h;
|
|
gint refd;
|
|
gint pixelwidth;
|
|
gint dwidth;
|
|
gint dheight;
|
|
guchar *s;
|
|
guchar *d;
|
|
gpointer pr;
|
|
|
|
core->orig_buf = temp_buf_resize (core->orig_buf,
|
|
gimp_drawable_bytes (drawable),
|
|
x1, y1,
|
|
(x2 - x1), (y2 - y1));
|
|
|
|
dwidth = gimp_item_width (GIMP_ITEM (drawable));
|
|
dheight = gimp_item_height (GIMP_ITEM (drawable));
|
|
|
|
x1 = CLAMP (x1, 0, dwidth);
|
|
y1 = CLAMP (y1, 0, dheight);
|
|
x2 = CLAMP (x2, 0, dwidth);
|
|
y2 = CLAMP (y2, 0, dheight);
|
|
|
|
/* configure the pixel regions */
|
|
pixel_region_init (&srcPR, gimp_drawable_data (drawable),
|
|
x1, y1,
|
|
(x2 - x1), (y2 - y1),
|
|
FALSE);
|
|
|
|
destPR.bytes = core->orig_buf->bytes;
|
|
destPR.x = 0;
|
|
destPR.y = 0;
|
|
destPR.w = (x2 - x1);
|
|
destPR.h = (y2 - y1);
|
|
destPR.rowstride = core->orig_buf->bytes * core->orig_buf->width;
|
|
destPR.data = (temp_buf_data (core->orig_buf) +
|
|
(y1 - core->orig_buf->y) * destPR.rowstride +
|
|
(x1 - core->orig_buf->x) * destPR.bytes);
|
|
|
|
for (pr = pixel_regions_register (2, &srcPR, &destPR);
|
|
pr != NULL;
|
|
pr = pixel_regions_process (pr))
|
|
{
|
|
/* If the undo tile corresponding to this location is valid, use it */
|
|
undo_tile = tile_manager_get_tile (core->undo_tiles,
|
|
srcPR.x, srcPR.y,
|
|
FALSE, FALSE);
|
|
if (tile_is_valid (undo_tile) == TRUE)
|
|
{
|
|
refd = 1;
|
|
undo_tile = tile_manager_get_tile (core->undo_tiles,
|
|
srcPR.x, srcPR.y,
|
|
TRUE, FALSE);
|
|
s = (unsigned char*)tile_data_pointer (undo_tile, 0, 0) +
|
|
srcPR.rowstride * (srcPR.y % TILE_HEIGHT) +
|
|
srcPR.bytes * (srcPR.x % TILE_WIDTH); /* dubious... */
|
|
}
|
|
else
|
|
{
|
|
refd = 0;
|
|
s = srcPR.data;
|
|
}
|
|
|
|
d = destPR.data;
|
|
pixelwidth = srcPR.w * srcPR.bytes;
|
|
h = srcPR.h;
|
|
while (h --)
|
|
{
|
|
memcpy (d, s, pixelwidth);
|
|
s += srcPR.rowstride;
|
|
d += destPR.rowstride;
|
|
}
|
|
|
|
if (refd)
|
|
tile_release (undo_tile, FALSE);
|
|
}
|
|
|
|
return core->orig_buf;
|
|
}
|
|
|
|
void
|
|
gimp_paint_core_paste_canvas (GimpPaintCore *core,
|
|
GimpDrawable *drawable,
|
|
gdouble brush_opacity,
|
|
gdouble image_opacity,
|
|
GimpLayerModeEffects paint_mode,
|
|
GimpBrushApplicationMode brush_hardness,
|
|
gdouble brush_scale,
|
|
GimpPaintApplicationMode mode)
|
|
{
|
|
MaskBuf *brush_mask = gimp_paint_core_get_brush_mask (core,
|
|
brush_hardness,
|
|
brush_scale);
|
|
|
|
if (brush_mask)
|
|
gimp_paint_core_paste (core, brush_mask, drawable,
|
|
brush_opacity,
|
|
image_opacity, paint_mode,
|
|
mode);
|
|
}
|
|
|
|
/* Similar to gimp_paint_core_paste_canvas, but replaces the alpha channel
|
|
* rather than using it to composite (i.e. transparent over opaque
|
|
* becomes transparent rather than opauqe.
|
|
*/
|
|
void
|
|
gimp_paint_core_replace_canvas (GimpPaintCore *core,
|
|
GimpDrawable *drawable,
|
|
gdouble brush_opacity,
|
|
gdouble image_opacity,
|
|
GimpBrushApplicationMode brush_hardness,
|
|
gdouble brush_scale,
|
|
GimpPaintApplicationMode mode)
|
|
{
|
|
MaskBuf *brush_mask = gimp_paint_core_get_brush_mask (core,
|
|
brush_hardness,
|
|
brush_scale);
|
|
|
|
if (brush_mask)
|
|
gimp_paint_core_replace (core, brush_mask, drawable,
|
|
brush_opacity,
|
|
image_opacity, mode);
|
|
}
|
|
|
|
|
|
static void
|
|
gimp_paint_core_invalidate_cache (GimpBrush *brush,
|
|
GimpPaintCore *core)
|
|
{
|
|
/* Make sure we don't cache data for a brush that has changed */
|
|
|
|
core->cache_invalid = TRUE;
|
|
core->solid_cache_invalid = TRUE;
|
|
|
|
if (core->brush_bound_segs)
|
|
{
|
|
g_free (core->brush_bound_segs);
|
|
core->brush_bound_segs = NULL;
|
|
core->n_brush_bound_segs = 0;
|
|
}
|
|
}
|
|
|
|
/************************************************************
|
|
* LOCAL FUNCTION DEFINITIONS *
|
|
************************************************************/
|
|
|
|
static void
|
|
gimp_paint_core_calc_brush_size (GimpPaintCore *core,
|
|
MaskBuf *mask,
|
|
gdouble scale,
|
|
gint *width,
|
|
gint *height)
|
|
{
|
|
scale = CLAMP (scale, 0.0, 1.0);
|
|
|
|
if (! core->use_pressure)
|
|
{
|
|
*width = mask->width;
|
|
*height = mask->height;
|
|
}
|
|
else
|
|
{
|
|
gdouble ratio;
|
|
|
|
if (scale < 1 / 256)
|
|
ratio = 1 / 16;
|
|
else
|
|
ratio = sqrt (scale);
|
|
|
|
*width = MAX ((gint) (mask->width * ratio + 0.5), 1);
|
|
*height = MAX ((gint) (mask->height * ratio + 0.5), 1);
|
|
}
|
|
}
|
|
|
|
static inline void
|
|
rotate_pointers (gulong **p,
|
|
guint32 n)
|
|
{
|
|
guint32 i;
|
|
gulong *tmp;
|
|
|
|
tmp = p[0];
|
|
for (i = 0; i < n-1; i++)
|
|
{
|
|
p[i] = p[i+1];
|
|
}
|
|
p[i] = tmp;
|
|
}
|
|
|
|
static MaskBuf *
|
|
gimp_paint_core_subsample_mask (GimpPaintCore *core,
|
|
MaskBuf *mask,
|
|
gdouble x,
|
|
gdouble y)
|
|
{
|
|
MaskBuf *dest;
|
|
gdouble left;
|
|
guchar *m;
|
|
guchar *d;
|
|
const gint *k;
|
|
gint index1;
|
|
gint index2;
|
|
gint dest_offset_x = 0;
|
|
gint dest_offset_y = 0;
|
|
const gint *kernel;
|
|
gint i, j;
|
|
gint r, s;
|
|
gulong *accum[KERNEL_HEIGHT];
|
|
gint offs;
|
|
gint kernel_sum;
|
|
|
|
while (x < 0) x += mask->width;
|
|
left = x - floor (x);
|
|
index1 = (gint) (left * (gdouble) (KERNEL_SUBSAMPLE + 1));
|
|
|
|
while (y < 0) y += mask->height;
|
|
left = y - floor (y);
|
|
index2 = (gint) (left * (gdouble) (KERNEL_SUBSAMPLE + 1));
|
|
|
|
|
|
if ((mask->width % 2) == 0)
|
|
{
|
|
index1 += KERNEL_SUBSAMPLE >> 1;
|
|
|
|
if (index1 > KERNEL_SUBSAMPLE)
|
|
{
|
|
index1 -= KERNEL_SUBSAMPLE + 1;
|
|
dest_offset_x = 1;
|
|
}
|
|
}
|
|
|
|
if ((mask->height % 2) == 0)
|
|
{
|
|
index2 += KERNEL_SUBSAMPLE >> 1;
|
|
|
|
if (index2 > KERNEL_SUBSAMPLE)
|
|
{
|
|
index2 -= KERNEL_SUBSAMPLE + 1;
|
|
dest_offset_y = 1;
|
|
}
|
|
}
|
|
|
|
|
|
kernel = subsample[index2][index1];
|
|
|
|
if (mask == core->last_brush_mask && ! core->cache_invalid)
|
|
{
|
|
if (core->kernel_brushes[index2][index1])
|
|
return core->kernel_brushes[index2][index1];
|
|
}
|
|
else
|
|
{
|
|
for (i = 0; i < KERNEL_SUBSAMPLE + 1; i++)
|
|
for (j = 0; j < KERNEL_SUBSAMPLE + 1; j++)
|
|
if (core->kernel_brushes[i][j])
|
|
{
|
|
mask_buf_free (core->kernel_brushes[i][j]);
|
|
core->kernel_brushes[i][j] = NULL;
|
|
}
|
|
|
|
core->last_brush_mask = mask;
|
|
core->cache_invalid = FALSE;
|
|
}
|
|
|
|
dest = mask_buf_new (mask->width + 2,
|
|
mask->height + 2);
|
|
|
|
/* Allocate and initialize the accum buffer */
|
|
for (i = 0; i < KERNEL_HEIGHT ; i++)
|
|
accum[i] = g_new0 (gulong, dest->width);
|
|
|
|
/* Investigate modifiying kernelgen to make the sum the same
|
|
* for all kernels. That way kernal_sum becomes a constant
|
|
*/
|
|
kernel_sum = 0;
|
|
for (i = 0; i < KERNEL_HEIGHT * KERNEL_WIDTH; i++)
|
|
{
|
|
kernel_sum += kernel[i];
|
|
}
|
|
|
|
core->kernel_brushes[index2][index1] = dest;
|
|
|
|
m = mask_buf_data (mask);
|
|
for (i = 0; i < mask->height; i++)
|
|
{
|
|
for (j = 0; j < mask->width; j++)
|
|
{
|
|
k = kernel;
|
|
for (r = 0; r < KERNEL_HEIGHT; r++)
|
|
{
|
|
offs = j + dest_offset_x;
|
|
s = KERNEL_WIDTH;
|
|
while (s--)
|
|
{
|
|
accum[r][offs++] += *m * *k++;
|
|
}
|
|
}
|
|
m++;
|
|
}
|
|
|
|
/* store the accum buffer into the destination mask */
|
|
d = mask_buf_data (dest) + (i + dest_offset_y) * dest->width;
|
|
for (j = 0; j < dest->width; j++)
|
|
*d++ = (accum[0][j] + 127) / kernel_sum;
|
|
|
|
rotate_pointers (accum, KERNEL_HEIGHT);
|
|
|
|
memset (accum[KERNEL_HEIGHT - 1], 0, sizeof (gulong) * dest->width);
|
|
}
|
|
|
|
/* store the rest of the accum buffer into the dest mask */
|
|
while (i + dest_offset_y < dest->height)
|
|
{
|
|
d = mask_buf_data (dest) + (i + dest_offset_y) * dest->width;
|
|
for (j = 0; j < dest->width; j++)
|
|
*d++ = (accum[0][j] + (kernel_sum / 2)) / kernel_sum;
|
|
|
|
rotate_pointers (accum, KERNEL_HEIGHT);
|
|
i++;
|
|
}
|
|
|
|
for (i = 0; i < KERNEL_HEIGHT ; i++)
|
|
g_free (accum[i]);
|
|
|
|
return dest;
|
|
}
|
|
|
|
/* #define FANCY_PRESSURE */
|
|
|
|
static MaskBuf *
|
|
gimp_paint_core_pressurize_mask (GimpPaintCore *core,
|
|
MaskBuf *brush_mask,
|
|
gdouble x,
|
|
gdouble y,
|
|
gdouble pressure)
|
|
{
|
|
static guchar mapi[256];
|
|
guchar *source;
|
|
guchar *dest;
|
|
MaskBuf *subsample_mask;
|
|
gint i;
|
|
#ifdef FANCY_PRESSURE
|
|
static gdouble map[256];
|
|
gdouble ds, s, c;
|
|
#endif
|
|
|
|
/* Get the raw subsampled mask */
|
|
subsample_mask = gimp_paint_core_subsample_mask (core,
|
|
brush_mask,
|
|
x, y);
|
|
|
|
/* Special case pressure = 0.5 */
|
|
if ((int)(pressure * 100 + 0.5) == 50)
|
|
return subsample_mask;
|
|
|
|
if (core->pressure_brush)
|
|
mask_buf_free (core->pressure_brush);
|
|
|
|
core->pressure_brush = mask_buf_new (brush_mask->width + 2,
|
|
brush_mask->height + 2);
|
|
|
|
#ifdef FANCY_PRESSURE
|
|
/* Create the pressure profile
|
|
|
|
It is: I'(I) = tanh(20*(pressure-0.5)*I) : pressure > 0.5
|
|
I'(I) = 1 - tanh(20*(0.5-pressure)*(1-I)) : pressure < 0.5
|
|
|
|
It looks like:
|
|
|
|
low pressure medium pressure high pressure
|
|
|
|
| / --
|
|
| / /
|
|
/ / |
|
|
-- / |
|
|
|
|
*/
|
|
|
|
ds = (pressure - 0.5)*(20./256.);
|
|
s = 0;
|
|
c = 1.0;
|
|
|
|
if (ds > 0)
|
|
{
|
|
for (i=0;i<256;i++)
|
|
{
|
|
map[i] = s/c;
|
|
s += c*ds;
|
|
c += s*ds;
|
|
}
|
|
for (i=0;i<256;i++)
|
|
mapi[i] = (int)(255*map[i]/map[255]);
|
|
}
|
|
else
|
|
{
|
|
ds = -ds;
|
|
for (i=255;i>=0;i--)
|
|
{
|
|
map[i] = s/c;
|
|
s += c*ds;
|
|
c += s*ds;
|
|
}
|
|
for (i=0;i<256;i++)
|
|
mapi[i] = (int)(255*(1-map[i]/map[0]));
|
|
}
|
|
#else /* ! FANCY_PRESSURE */
|
|
|
|
for (i = 0; i < 256; i++)
|
|
{
|
|
gint tmp = (pressure / 0.5) * i;
|
|
|
|
if (tmp > 255)
|
|
mapi[i] = 255;
|
|
else
|
|
mapi[i] = tmp;
|
|
}
|
|
|
|
#endif /* FANCY_PRESSURE */
|
|
|
|
/* Now convert the brush */
|
|
|
|
source = mask_buf_data (subsample_mask);
|
|
dest = mask_buf_data (core->pressure_brush);
|
|
|
|
i = subsample_mask->width * subsample_mask->height;
|
|
while (i--)
|
|
{
|
|
*dest++ = mapi[(*source++)];
|
|
}
|
|
|
|
return core->pressure_brush;
|
|
}
|
|
|
|
static MaskBuf *
|
|
gimp_paint_core_solidify_mask (GimpPaintCore *core,
|
|
MaskBuf *brush_mask,
|
|
gdouble x,
|
|
gdouble y)
|
|
{
|
|
MaskBuf *dest;
|
|
guchar *m;
|
|
guchar *d;
|
|
gint dest_offset_x = 0;
|
|
gint dest_offset_y = 0;
|
|
gint i, j;
|
|
|
|
if ((brush_mask->width % 2) == 0)
|
|
{
|
|
while (x < 0) x += brush_mask->width;
|
|
|
|
if ((x - floor (x)) >= 0.5)
|
|
dest_offset_x++;
|
|
}
|
|
|
|
if ((brush_mask->height % 2) == 0)
|
|
{
|
|
while (y < 0) y += brush_mask->height;
|
|
|
|
if ((y - floor (y)) >= 0.5)
|
|
dest_offset_y++;
|
|
}
|
|
|
|
if (brush_mask == core->last_solid_brush && ! core->solid_cache_invalid)
|
|
{
|
|
if (core->solid_brushes[dest_offset_y][dest_offset_x])
|
|
return core->solid_brushes[dest_offset_y][dest_offset_x];
|
|
}
|
|
else
|
|
{
|
|
for (i = 0; i < PAINT_CORE_SOLID_SUBSAMPLE; i++)
|
|
for (j = 0; j < PAINT_CORE_SOLID_SUBSAMPLE; j++)
|
|
if (core->solid_brushes[i][j])
|
|
{
|
|
mask_buf_free (core->solid_brushes[i][j]);
|
|
core->solid_brushes[i][j] = NULL;
|
|
}
|
|
|
|
core->last_solid_brush = brush_mask;
|
|
core->solid_cache_invalid = FALSE;
|
|
}
|
|
|
|
dest = mask_buf_new (brush_mask->width + 2,
|
|
brush_mask->height + 2);
|
|
|
|
core->solid_brushes[dest_offset_y][dest_offset_x] = dest;
|
|
|
|
m = mask_buf_data (brush_mask);
|
|
d = (mask_buf_data (dest) +
|
|
(dest_offset_y + 1) * dest->width +
|
|
(dest_offset_x + 1));
|
|
|
|
for (i = 0; i < brush_mask->height; i++)
|
|
{
|
|
for (j = 0; j < brush_mask->width; j++)
|
|
{
|
|
*d++ = (*m++) ? OPAQUE_OPACITY : TRANSPARENT_OPACITY;
|
|
}
|
|
|
|
d += 2;
|
|
}
|
|
|
|
return dest;
|
|
}
|
|
|
|
static MaskBuf *
|
|
gimp_paint_core_scale_mask (GimpPaintCore *core,
|
|
MaskBuf *brush_mask,
|
|
gdouble scale)
|
|
{
|
|
gint dest_width;
|
|
gint dest_height;
|
|
|
|
scale = CLAMP (scale, 0.0, 1.0);
|
|
|
|
if (scale == 0.0)
|
|
return NULL;
|
|
|
|
if (scale == 1.0)
|
|
return brush_mask;
|
|
|
|
gimp_paint_core_calc_brush_size (core,
|
|
brush_mask,
|
|
scale,
|
|
&dest_width, &dest_height);
|
|
|
|
if (brush_mask == core->last_scale_brush &&
|
|
core->scale_brush &&
|
|
! core->cache_invalid &&
|
|
dest_width == core->last_scale_width &&
|
|
dest_height == core->last_scale_height)
|
|
{
|
|
return core->scale_brush;
|
|
}
|
|
|
|
core->last_scale_brush = brush_mask;
|
|
core->last_scale_width = dest_width;
|
|
core->last_scale_height = dest_height;
|
|
|
|
if (core->scale_brush)
|
|
mask_buf_free (core->scale_brush);
|
|
|
|
core->scale_brush = brush_scale_mask (brush_mask,
|
|
dest_width, dest_height);
|
|
|
|
core->cache_invalid = TRUE;
|
|
core->solid_cache_invalid = TRUE;
|
|
|
|
return core->scale_brush;
|
|
}
|
|
|
|
static MaskBuf *
|
|
gimp_paint_core_scale_pixmap (GimpPaintCore *core,
|
|
MaskBuf *brush_mask,
|
|
gdouble scale)
|
|
{
|
|
gint dest_width;
|
|
gint dest_height;
|
|
|
|
scale = CLAMP (scale, 0.0, 1.0);
|
|
|
|
if (scale == 0.0)
|
|
return NULL;
|
|
|
|
if (scale == 1.0)
|
|
return brush_mask;
|
|
|
|
gimp_paint_core_calc_brush_size (core,
|
|
brush_mask,
|
|
scale,
|
|
&dest_width, &dest_height);
|
|
|
|
if (brush_mask == core->last_scale_pixmap &&
|
|
core->scale_pixmap &&
|
|
! core->cache_invalid &&
|
|
dest_width == core->last_scale_pixmap_width &&
|
|
dest_height == core->last_scale_pixmap_height)
|
|
{
|
|
return core->scale_pixmap;
|
|
}
|
|
|
|
core->last_scale_pixmap = brush_mask;
|
|
core->last_scale_pixmap_width = dest_width;
|
|
core->last_scale_pixmap_height = dest_height;
|
|
|
|
if (core->scale_pixmap)
|
|
mask_buf_free (core->scale_pixmap);
|
|
|
|
core->scale_pixmap = brush_scale_pixmap (brush_mask,
|
|
dest_width, dest_height);
|
|
core->cache_invalid = TRUE;
|
|
|
|
return core->scale_pixmap;
|
|
}
|
|
|
|
static MaskBuf *
|
|
gimp_paint_core_get_brush_mask (GimpPaintCore *core,
|
|
GimpBrushApplicationMode brush_hardness,
|
|
gdouble scale)
|
|
{
|
|
MaskBuf *mask;
|
|
|
|
if (core->use_pressure)
|
|
mask = gimp_paint_core_scale_mask (core, core->brush->mask, scale);
|
|
else
|
|
mask = core->brush->mask;
|
|
|
|
if (!mask)
|
|
return NULL;
|
|
|
|
switch (brush_hardness)
|
|
{
|
|
case GIMP_BRUSH_SOFT:
|
|
mask = gimp_paint_core_subsample_mask (core, mask,
|
|
core->cur_coords.x,
|
|
core->cur_coords.y);
|
|
break;
|
|
|
|
case GIMP_BRUSH_HARD:
|
|
mask = gimp_paint_core_solidify_mask (core, mask,
|
|
core->cur_coords.x,
|
|
core->cur_coords.y);
|
|
break;
|
|
|
|
case GIMP_BRUSH_PRESSURE:
|
|
if (core->use_pressure)
|
|
mask = gimp_paint_core_pressurize_mask (core, mask,
|
|
core->cur_coords.x,
|
|
core->cur_coords.y,
|
|
core->cur_coords.pressure);
|
|
else
|
|
mask = gimp_paint_core_subsample_mask (core, mask,
|
|
core->cur_coords.x,
|
|
core->cur_coords.y);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return mask;
|
|
}
|
|
|
|
static void
|
|
gimp_paint_core_paste (GimpPaintCore *core,
|
|
MaskBuf *brush_mask,
|
|
GimpDrawable *drawable,
|
|
gdouble brush_opacity,
|
|
gdouble image_opacity,
|
|
GimpLayerModeEffects paint_mode,
|
|
GimpPaintApplicationMode mode)
|
|
{
|
|
GimpImage *gimage;
|
|
PixelRegion srcPR;
|
|
TileManager *alt = NULL;
|
|
gint offx;
|
|
gint offy;
|
|
|
|
gimage = gimp_item_get_image (GIMP_ITEM (drawable));
|
|
|
|
/* set undo blocks */
|
|
set_undo_tiles (core,
|
|
drawable,
|
|
core->canvas_buf->x,
|
|
core->canvas_buf->y,
|
|
core->canvas_buf->width,
|
|
core->canvas_buf->height);
|
|
|
|
/* If the mode is CONSTANT:
|
|
* combine the canvas buf, the brush mask to the canvas tiles
|
|
*/
|
|
if (mode == GIMP_PAINT_CONSTANT)
|
|
{
|
|
/* initialize any invalid canvas tiles */
|
|
set_canvas_tiles (core,
|
|
core->canvas_buf->x,
|
|
core->canvas_buf->y,
|
|
core->canvas_buf->width,
|
|
core->canvas_buf->height);
|
|
|
|
brush_to_canvas_tiles (core, brush_mask, brush_opacity);
|
|
canvas_tiles_to_canvas_buf (core);
|
|
alt = core->undo_tiles;
|
|
}
|
|
/* Otherwise:
|
|
* combine the canvas buf and the brush mask to the canvas buf
|
|
*/
|
|
else
|
|
{
|
|
brush_to_canvas_buf (core, brush_mask, brush_opacity);
|
|
}
|
|
|
|
/* intialize canvas buf source pixel regions */
|
|
srcPR.bytes = core->canvas_buf->bytes;
|
|
srcPR.x = 0;
|
|
srcPR.y = 0;
|
|
srcPR.w = core->canvas_buf->width;
|
|
srcPR.h = core->canvas_buf->height;
|
|
srcPR.rowstride = core->canvas_buf->width * core->canvas_buf->bytes;
|
|
srcPR.data = temp_buf_data (core->canvas_buf);
|
|
|
|
/* apply the paint area to the gimage */
|
|
gimp_drawable_apply_region (drawable, &srcPR,
|
|
FALSE, NULL,
|
|
image_opacity, paint_mode,
|
|
alt, /* specify an alternative src1 */
|
|
core->canvas_buf->x,
|
|
core->canvas_buf->y);
|
|
|
|
/* Update the undo extents */
|
|
core->x1 = MIN (core->x1, core->canvas_buf->x);
|
|
core->y1 = MIN (core->y1, core->canvas_buf->y);
|
|
core->x2 = MAX (core->x2, core->canvas_buf->x + core->canvas_buf->width);
|
|
core->y2 = MAX (core->y2, core->canvas_buf->y + core->canvas_buf->height);
|
|
|
|
/* Update the gimage -- It is important to call gimp_image_update()
|
|
* instead of gimp_drawable_update() because we don't want the
|
|
* drawable and image previews to be constantly invalidated
|
|
*/
|
|
gimp_item_offsets (GIMP_ITEM (drawable), &offx, &offy);
|
|
gimp_image_update (gimage,
|
|
core->canvas_buf->x + offx,
|
|
core->canvas_buf->y + offy,
|
|
core->canvas_buf->width,
|
|
core->canvas_buf->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.
|
|
*/
|
|
static void
|
|
gimp_paint_core_replace (GimpPaintCore *core,
|
|
MaskBuf *brush_mask,
|
|
GimpDrawable *drawable,
|
|
gdouble brush_opacity,
|
|
gdouble image_opacity,
|
|
GimpPaintApplicationMode mode)
|
|
{
|
|
GimpImage *gimage;
|
|
PixelRegion srcPR;
|
|
PixelRegion maskPR;
|
|
TileManager *alt = NULL;
|
|
gint offx;
|
|
gint offy;
|
|
|
|
if (! gimp_drawable_has_alpha (drawable))
|
|
{
|
|
gimp_paint_core_paste (core, brush_mask, drawable,
|
|
brush_opacity,
|
|
image_opacity, GIMP_NORMAL_MODE,
|
|
mode);
|
|
return;
|
|
}
|
|
|
|
gimage = gimp_item_get_image (GIMP_ITEM (drawable));
|
|
|
|
/* set undo blocks */
|
|
set_undo_tiles (core,
|
|
drawable,
|
|
core->canvas_buf->x,
|
|
core->canvas_buf->y,
|
|
core->canvas_buf->width,
|
|
core->canvas_buf->height);
|
|
|
|
if (mode == GIMP_PAINT_CONSTANT)
|
|
{
|
|
/* initialize any invalid canvas tiles */
|
|
set_canvas_tiles (core,
|
|
core->canvas_buf->x,
|
|
core->canvas_buf->y,
|
|
core->canvas_buf->width,
|
|
core->canvas_buf->height);
|
|
|
|
/* combine the brush mask and the canvas tiles */
|
|
brush_to_canvas_tiles (core, brush_mask, brush_opacity);
|
|
|
|
/* set the alt source as the unaltered undo_tiles */
|
|
alt = core->undo_tiles;
|
|
|
|
/* initialize the maskPR from the canvas tiles */
|
|
pixel_region_init (&maskPR, core->canvas_tiles,
|
|
core->canvas_buf->x,
|
|
core->canvas_buf->y,
|
|
core->canvas_buf->width,
|
|
core->canvas_buf->height,
|
|
FALSE);
|
|
}
|
|
else
|
|
{
|
|
/* The mask is just the brush mask */
|
|
maskPR.bytes = 1;
|
|
maskPR.x = 0;
|
|
maskPR.y = 0;
|
|
maskPR.w = core->canvas_buf->width;
|
|
maskPR.h = core->canvas_buf->height;
|
|
maskPR.rowstride = maskPR.bytes * brush_mask->width;
|
|
maskPR.data = mask_buf_data (brush_mask);
|
|
}
|
|
|
|
/* intialize canvas buf source pixel regions */
|
|
srcPR.bytes = core->canvas_buf->bytes;
|
|
srcPR.x = 0;
|
|
srcPR.y = 0;
|
|
srcPR.w = core->canvas_buf->width;
|
|
srcPR.h = core->canvas_buf->height;
|
|
srcPR.rowstride = core->canvas_buf->width * core->canvas_buf->bytes;
|
|
srcPR.data = temp_buf_data (core->canvas_buf);
|
|
|
|
/* apply the paint area to the gimage */
|
|
gimp_drawable_replace_region (drawable, &srcPR,
|
|
FALSE, NULL,
|
|
image_opacity,
|
|
&maskPR,
|
|
core->canvas_buf->x,
|
|
core->canvas_buf->y);
|
|
|
|
/* Update the undo extents */
|
|
core->x1 = MIN (core->x1, core->canvas_buf->x);
|
|
core->y1 = MIN (core->y1, core->canvas_buf->y);
|
|
core->x2 = MAX (core->x2, core->canvas_buf->x + core->canvas_buf->width) ;
|
|
core->y2 = MAX (core->y2, core->canvas_buf->y + core->canvas_buf->height) ;
|
|
|
|
/* Update the gimage -- It is important to call gimp_image_update()
|
|
* instead of gimp_drawable_update() because we don't want the
|
|
* drawable and image previews to be constantly invalidated
|
|
*/
|
|
gimp_item_offsets (GIMP_ITEM (drawable), &offx, &offy);
|
|
gimp_image_update (gimage,
|
|
core->canvas_buf->x + offx,
|
|
core->canvas_buf->y + offy,
|
|
core->canvas_buf->width,
|
|
core->canvas_buf->height);
|
|
}
|
|
|
|
static void
|
|
canvas_tiles_to_canvas_buf (GimpPaintCore *core)
|
|
{
|
|
PixelRegion srcPR;
|
|
PixelRegion maskPR;
|
|
|
|
/* combine the canvas tiles and the canvas buf */
|
|
srcPR.bytes = core->canvas_buf->bytes;
|
|
srcPR.x = 0;
|
|
srcPR.y = 0;
|
|
srcPR.w = core->canvas_buf->width;
|
|
srcPR.h = core->canvas_buf->height;
|
|
srcPR.rowstride = core->canvas_buf->width * core->canvas_buf->bytes;
|
|
srcPR.data = temp_buf_data (core->canvas_buf);
|
|
|
|
pixel_region_init (&maskPR, core->canvas_tiles,
|
|
core->canvas_buf->x,
|
|
core->canvas_buf->y,
|
|
core->canvas_buf->width,
|
|
core->canvas_buf->height,
|
|
FALSE);
|
|
|
|
/* apply the canvas tiles to the canvas buf */
|
|
apply_mask_to_region (&srcPR, &maskPR, OPAQUE_OPACITY);
|
|
}
|
|
|
|
static void
|
|
brush_to_canvas_tiles (GimpPaintCore *core,
|
|
MaskBuf *brush_mask,
|
|
gdouble brush_opacity)
|
|
{
|
|
PixelRegion srcPR;
|
|
PixelRegion maskPR;
|
|
gint x;
|
|
gint y;
|
|
gint xoff;
|
|
gint yoff;
|
|
|
|
/* combine the brush mask and the canvas tiles */
|
|
pixel_region_init (&srcPR, core->canvas_tiles,
|
|
core->canvas_buf->x,
|
|
core->canvas_buf->y,
|
|
core->canvas_buf->width,
|
|
core->canvas_buf->height,
|
|
TRUE);
|
|
|
|
x = (gint) floor (core->cur_coords.x) - (brush_mask->width >> 1);
|
|
y = (gint) floor (core->cur_coords.y) - (brush_mask->height >> 1);
|
|
|
|
xoff = (x < 0) ? -x : 0;
|
|
yoff = (y < 0) ? -y : 0;
|
|
|
|
maskPR.bytes = 1;
|
|
maskPR.x = 0;
|
|
maskPR.y = 0;
|
|
maskPR.w = srcPR.w;
|
|
maskPR.h = srcPR.h;
|
|
maskPR.rowstride = maskPR.bytes * brush_mask->width;
|
|
maskPR.data = mask_buf_data (brush_mask) + (yoff * maskPR.rowstride +
|
|
xoff * maskPR.bytes);
|
|
|
|
/* combine the mask to the canvas tiles */
|
|
combine_mask_and_region (&srcPR, &maskPR, brush_opacity * 255.999);
|
|
}
|
|
|
|
static void
|
|
brush_to_canvas_buf (GimpPaintCore *core,
|
|
MaskBuf *brush_mask,
|
|
gdouble brush_opacity)
|
|
{
|
|
PixelRegion srcPR;
|
|
PixelRegion maskPR;
|
|
gint x;
|
|
gint y;
|
|
gint xoff;
|
|
gint yoff;
|
|
|
|
x = (gint) floor (core->cur_coords.x) - (brush_mask->width >> 1);
|
|
y = (gint) floor (core->cur_coords.y) - (brush_mask->height >> 1);
|
|
|
|
xoff = (x < 0) ? -x : 0;
|
|
yoff = (y < 0) ? -y : 0;
|
|
|
|
/* combine the canvas buf and the brush mask to the canvas buf */
|
|
srcPR.bytes = core->canvas_buf->bytes;
|
|
srcPR.x = 0;
|
|
srcPR.y = 0;
|
|
srcPR.w = core->canvas_buf->width;
|
|
srcPR.h = core->canvas_buf->height;
|
|
srcPR.rowstride = core->canvas_buf->width * core->canvas_buf->bytes;
|
|
srcPR.data = temp_buf_data (core->canvas_buf);
|
|
|
|
maskPR.bytes = 1;
|
|
maskPR.x = 0;
|
|
maskPR.y = 0;
|
|
maskPR.w = srcPR.w;
|
|
maskPR.h = srcPR.h;
|
|
maskPR.rowstride = maskPR.bytes * brush_mask->width;
|
|
maskPR.data = mask_buf_data (brush_mask) + yoff * maskPR.rowstride + xoff * maskPR.bytes;
|
|
|
|
/* apply the mask */
|
|
apply_mask_to_region (&srcPR, &maskPR, brush_opacity * 255.999);
|
|
}
|
|
|
|
static void
|
|
set_undo_tiles (GimpPaintCore *core,
|
|
GimpDrawable *drawable,
|
|
gint x,
|
|
gint y,
|
|
gint w,
|
|
gint h)
|
|
{
|
|
gint i;
|
|
gint j;
|
|
Tile *src_tile;
|
|
Tile *dest_tile;
|
|
|
|
if (! core->undo_tiles)
|
|
{
|
|
g_warning ("set_undo_tiles: undo_tiles is null");
|
|
return;
|
|
}
|
|
|
|
for (i = y; i < (y + h); i += (TILE_HEIGHT - (i % TILE_HEIGHT)))
|
|
{
|
|
for (j = x; j < (x + w); j += (TILE_WIDTH - (j % TILE_WIDTH)))
|
|
{
|
|
dest_tile = tile_manager_get_tile (core->undo_tiles, j, i,
|
|
FALSE, FALSE);
|
|
|
|
if (tile_is_valid (dest_tile) == FALSE)
|
|
{
|
|
src_tile = tile_manager_get_tile (gimp_drawable_data (drawable),
|
|
j, i, TRUE, FALSE);
|
|
tile_manager_map_tile (core->undo_tiles, j, i, src_tile);
|
|
tile_release (src_tile, FALSE);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
set_canvas_tiles (GimpPaintCore *core,
|
|
gint x,
|
|
gint y,
|
|
gint w,
|
|
gint h)
|
|
{
|
|
gint i;
|
|
gint j;
|
|
Tile *tile;
|
|
|
|
for (i = y; i < (y + h); i += (TILE_HEIGHT - (i % TILE_HEIGHT)))
|
|
{
|
|
for (j = x; j < (x + w); j += (TILE_WIDTH - (j % TILE_WIDTH)))
|
|
{
|
|
tile = tile_manager_get_tile (core->canvas_tiles, j, i,
|
|
FALSE, FALSE);
|
|
|
|
if (tile_is_valid (tile) == FALSE)
|
|
{
|
|
tile = tile_manager_get_tile (core->canvas_tiles, j, i,
|
|
TRUE, TRUE);
|
|
memset (tile_data_pointer (tile, 0, 0), 0, tile_size (tile));
|
|
tile_release (tile, TRUE);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/**************************************************/
|
|
/* Brush pipe utility functions */
|
|
/**************************************************/
|
|
|
|
void
|
|
gimp_paint_core_color_area_with_pixmap (GimpPaintCore *core,
|
|
GimpImage *dest,
|
|
GimpDrawable *drawable,
|
|
TempBuf *area,
|
|
gdouble scale,
|
|
GimpBrushApplicationMode mode)
|
|
{
|
|
PixelRegion destPR;
|
|
void *pr;
|
|
guchar *d;
|
|
gint ulx;
|
|
gint uly;
|
|
gint offsetx;
|
|
gint offsety;
|
|
gint y;
|
|
TempBuf *pixmap_mask;
|
|
TempBuf *brush_mask;
|
|
|
|
g_return_if_fail (GIMP_IS_BRUSH (core->brush));
|
|
g_return_if_fail (core->brush->pixmap != NULL);
|
|
|
|
/* scale the brushes */
|
|
pixmap_mask = gimp_paint_core_scale_pixmap (core,
|
|
core->brush->pixmap,
|
|
scale);
|
|
|
|
if (!pixmap_mask)
|
|
return;
|
|
|
|
if (mode != GIMP_BRUSH_HARD)
|
|
brush_mask = gimp_paint_core_scale_mask (core,
|
|
core->brush->mask,
|
|
scale);
|
|
else
|
|
brush_mask = NULL;
|
|
|
|
destPR.bytes = area->bytes;
|
|
destPR.x = 0;
|
|
destPR.y = 0;
|
|
destPR.w = area->width;
|
|
destPR.h = area->height;
|
|
destPR.rowstride = destPR.bytes * area->width;
|
|
destPR.data = temp_buf_data (area);
|
|
|
|
pr = pixel_regions_register (1, &destPR);
|
|
|
|
/* Calculate upper left corner of brush as in
|
|
* gimp_paint_core_get_paint_area. Ugly to have to do this here, too.
|
|
*/
|
|
ulx = (gint) floor (core->cur_coords.x) - (pixmap_mask->width >> 1);
|
|
uly = (gint) floor (core->cur_coords.y) - (pixmap_mask->height >> 1);
|
|
|
|
offsetx = area->x - ulx;
|
|
offsety = area->y - uly;
|
|
|
|
for (; pr != NULL; pr = pixel_regions_process (pr))
|
|
{
|
|
d = destPR.data;
|
|
for (y = 0; y < destPR.h; y++)
|
|
{
|
|
paint_line_pixmap_mask (dest, drawable, pixmap_mask, brush_mask,
|
|
d, offsetx, y + offsety,
|
|
destPR.bytes, destPR.w, mode);
|
|
d += destPR.rowstride;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
paint_line_pixmap_mask (GimpImage *dest,
|
|
GimpDrawable *drawable,
|
|
TempBuf *pixmap_mask,
|
|
TempBuf *brush_mask,
|
|
guchar *d,
|
|
gint x,
|
|
gint y,
|
|
gint bytes,
|
|
gint width,
|
|
GimpBrushApplicationMode mode)
|
|
{
|
|
guchar *b;
|
|
guchar *p;
|
|
guchar *mask;
|
|
gdouble alpha;
|
|
gdouble factor = 0.00392156986; /* 1.0 / 255.0 */
|
|
gint x_index;
|
|
gint i,byte_loop;
|
|
|
|
/* Make sure x, y are positive */
|
|
while (x < 0)
|
|
x += pixmap_mask->width;
|
|
while (y < 0)
|
|
y += pixmap_mask->height;
|
|
|
|
/* Point to the approriate scanline */
|
|
b = temp_buf_data (pixmap_mask) +
|
|
(y % pixmap_mask->height) * pixmap_mask->width * pixmap_mask->bytes;
|
|
|
|
if (mode == GIMP_BRUSH_SOFT && brush_mask)
|
|
{
|
|
/* ditto, except for the brush mask,
|
|
so we can pre-multiply the alpha value */
|
|
mask = temp_buf_data (brush_mask) +
|
|
(y % brush_mask->height) * brush_mask->width;
|
|
for (i = 0; i < width; i++)
|
|
{
|
|
/* attempt to avoid doing this calc twice in the loop */
|
|
x_index = ((i + x) % pixmap_mask->width);
|
|
p = b + x_index * pixmap_mask->bytes;
|
|
d[bytes-1] = mask[x_index];
|
|
|
|
/* multiply alpha into the pixmap data
|
|
* maybe we could do this at tool creation or brush switch time?
|
|
* and compute it for the whole brush at once and cache it?
|
|
*/
|
|
alpha = d[bytes-1] * factor;
|
|
if (alpha)
|
|
for (byte_loop = 0; byte_loop < bytes - 1; byte_loop++)
|
|
d[byte_loop] *= alpha;
|
|
|
|
/* printf("i: %i d->r: %i d->g: %i d->b: %i d->a: %i\n",i,(int)d[0], (int)d[1], (int)d[2], (int)d[3]); */
|
|
gimp_image_transform_color (dest, drawable, d, GIMP_RGB, p);
|
|
d += bytes;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (i = 0; i < width; i++)
|
|
{
|
|
/* attempt to avoid doing this calc twice in the loop */
|
|
x_index = ((i + x) % pixmap_mask->width);
|
|
p = b + x_index * pixmap_mask->bytes;
|
|
d[bytes-1] = 255;
|
|
|
|
/* multiply alpha into the pixmap data */
|
|
/* maybe we could do this at tool creation or brush switch time? */
|
|
/* and compute it for the whole brush at once and cache it? */
|
|
gimp_image_transform_color (dest, drawable, d, GIMP_RGB, p);
|
|
d += bytes;
|
|
}
|
|
}
|
|
}
|