/* 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 #include #include #include #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-mask.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 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; 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_image_apply_image (gimage, 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_image_replace_image (gimage, 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; } } }