/* 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 <stdlib.h>
#include <string.h>

#include <gtk/gtk.h>

#include "libgimpcolor/gimpcolor.h"
#include "libgimpmath/gimpmath.h"
#include "libgimpbase/gimpbase.h"

#include "tools-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/gimpbrushpipe.h"
#include "core/gimpcontext.h"
#include "core/gimpdrawable.h"
#include "core/gimpgradient.h"
#include "core/gimpimage.h"
#include "core/gimpimage-mask.h"
#include "core/gimpmarshal.h"

#include "display/gimpdisplay.h"
#include "display/gimpdisplay-foreach.h"

#include "gimpdrawtool.h"
#include "gimpdodgeburntool.h"
#include "gimperasertool.h"
#include "gimpconvolvetool.h"
#include "gimppainttool.h"

#include "devices.h"
#include "gimprc.h"
#include "undo.h"

#include "libgimp/gimpintl.h"

#include "gimppainttool_kernels.h"


/*  target size  */
#define  TARGET_HEIGHT  15
#define  TARGET_WIDTH   15

#define  EPSILON  0.00001

#define  STATUSBAR_SIZE 128


enum
{
  PAINT,
  LAST_SIGNAL
};


/*  local function prototypes  */
static void   gimp_paint_tool_class_init      (GimpPaintToolClass  *klass);
static void   gimp_paint_tool_init            (GimpPaintTool       *paint_tool);

static void   gimp_paint_tool_control         (GimpTool	           *tool,
					       ToolAction           action,
					       GDisplay            *gdisp);
static void   gimp_paint_tool_button_press    (GimpTool            *tool,
					       GdkEventButton      *bevent,
					       GDisplay            *gdisp);
static void   gimp_paint_tool_button_release  (GimpTool            *tool,
					       GdkEventButton      *bevent,
					       GDisplay            *gdisp);
static void   gimp_paint_tool_motion          (GimpTool            *tool,
					       GdkEventMotion      *mevent,
					       GDisplay            *gdisp);
static void   gimp_paint_tool_cursor_update   (GimpTool            *tool,
					       GdkEventMotion      *mevent,
					       GDisplay            *gdisp);

static void   gimp_paint_tool_draw            (GimpDrawTool        *draw_tool);


static void      gimp_paint_tool_sample_color         (GimpDrawable *drawable,
						       gint          x,
						       gint          y,
						       gint          state);
static void      gimp_paint_tool_calculate_brush_size (MaskBuf *mask,
						       gdouble  scale,
						       gint    *width,
						       gint    *height);
static MaskBuf * gimp_paint_tool_subsample_mask       (MaskBuf *mask,
						       gdouble  x,
						       gdouble  y);
static MaskBuf * gimp_paint_tool_pressurize_mask      (MaskBuf *brush_mask,
						       gdouble  x,
						       gdouble  y,
						       gdouble  pressure);
static MaskBuf * gimp_paint_tool_solidify_mask        (MaskBuf *brush_mask);
static MaskBuf * gimp_paint_tool_scale_mask           (MaskBuf *brush_mask,
						       gdouble  scale);
static MaskBuf * gimp_paint_tool_scale_pixmap         (MaskBuf *brush_mask,
						       gdouble  scale);

static MaskBuf * gimp_paint_tool_get_brush_mask       (GimpPaintTool            *gimp_paint_tool,
						       BrushApplicationMode  brush_hardness,
						       gdouble               scale);
static void      gimp_paint_tool_paste                (GimpPaintTool	       *gimp_paint_tool,
						       MaskBuf	       *brush_mask,
						       GimpDrawable	       *drawable,
						       gint		        brush_opacity,
						       gint		        image_opacity,
						       LayerModeEffects      paint_mode,
						       PaintApplicationMode  mode);
static void      gimp_paint_tool_replace              (GimpPaintTool            *gimp_paint_tool,
						       MaskBuf              *brush_mask,
						       GimpDrawable	       *drawable,
						       gint		        brush_opacity,
						       gint                  image_opacity,
						       PaintApplicationMode  mode);

static void      brush_to_canvas_tiles           (GimpPaintTool    *gimp_paint_tool,
						  MaskBuf      *brush_mask,
						  gint          brush_opacity);
static void      brush_to_canvas_buf             (GimpPaintTool    *gimp_paint_tool,
						  MaskBuf      *brush_mask,
						  gint          brush_opacity);
static void      canvas_tiles_to_canvas_buf      (GimpPaintTool    *gimp_paint_tool);

static void      set_undo_tiles                  (GimpDrawable *drawable,
						  gint          x,
						  gint          y,
						  gint          w,
						  gint          h);
static void      set_canvas_tiles                (gint          x,
						  gint          y,
						  gint          w,
						  gint          h);
static void      gimp_paint_tool_invalidate_cache     (GimpBrush    *brush,
						       gpointer      data);


/*  paint buffers utility functions  */
static void        free_paint_buffers            (void);

/*  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,
					    BrushApplicationMode  mode);

/***********************************************************************/


/*  undo blocks variables  */
static TileManager  *undo_tiles   = NULL;
static TileManager  *canvas_tiles = NULL;


/***********************************************************************/


/*  paint buffers variables  */
static TempBuf  *orig_buf   = NULL;
static TempBuf  *canvas_buf = NULL;


/*  brush buffers  */
static MaskBuf  *pressure_brush;
static MaskBuf  *solid_brush;
static MaskBuf  *scale_brush     = NULL;
static MaskBuf  *scale_pixmap    = NULL;
static MaskBuf  *kernel_brushes[SUBSAMPLE + 1][SUBSAMPLE + 1];

static MaskBuf  *last_brush_mask = NULL;
static gboolean  cache_invalid   = FALSE;


static guint gimp_paint_tool_signals[LAST_SIGNAL] = { 0 };

static GimpDrawToolClass *parent_class = NULL;


GType
gimp_paint_tool_get_type (void)
{
  static GType tool_type = 0;

  if (! tool_type)
    {
      static const GTypeInfo tool_info =
      {
        sizeof (GimpPaintToolClass),
	(GBaseInitFunc) NULL,
	(GBaseFinalizeFunc) NULL,
	(GClassInitFunc) gimp_paint_tool_class_init,
	NULL,           /* class_finalize */
	NULL,           /* class_data     */
	sizeof (GimpPaintTool),
	0,              /* n_preallocs    */
	(GInstanceInitFunc) gimp_paint_tool_init,
      };

      tool_type = g_type_register_static (GIMP_TYPE_DRAW_TOOL,
					  "GimpPaintTool", 
                                          &tool_info, 0);
    }

  return tool_type;
}

static void
gimp_paint_tool_class_init (GimpPaintToolClass *klass)
{
  GimpToolClass     *tool_class;
  GimpDrawToolClass *draw_tool_class;

  tool_class      = GIMP_TOOL_CLASS (klass);
  draw_tool_class = GIMP_DRAW_TOOL_CLASS (klass);

  parent_class = g_type_class_peek_parent (klass);

  gimp_paint_tool_signals[PAINT] =
    g_signal_new ("paint",
		  G_TYPE_FROM_CLASS (klass),
		  G_SIGNAL_RUN_FIRST,
		  G_STRUCT_OFFSET (GimpPaintToolClass, paint),
		  NULL, NULL,
		  gimp_cclosure_marshal_VOID__POINTER_INT,
		  G_TYPE_NONE, 2,
		  G_TYPE_POINTER,
		  G_TYPE_INT);

  tool_class->button_press   = gimp_paint_tool_button_press;
  tool_class->button_release = gimp_paint_tool_button_release;
  tool_class->motion         = gimp_paint_tool_motion;
  tool_class->cursor_update  = gimp_paint_tool_cursor_update;
  tool_class->control        = gimp_paint_tool_control;

  draw_tool_class->draw      = gimp_paint_tool_draw;

  klass->paint               = NULL;
}

static void
gimp_paint_tool_init (GimpPaintTool *tool)
{
  tool->pick_colors = FALSE;
  tool->flags       = 0;
  tool->context_id  = 0;
}

static void
gimp_paint_tool_control (GimpTool   *tool,
			 ToolAction  action,
			 GDisplay   *gdisp)
{
  GimpPaintTool *paint_tool;
  GimpDrawable  *drawable;

  paint_tool = GIMP_PAINT_TOOL (tool);
  drawable   = gimp_image_active_drawable (gdisp->gimage);

  switch (action)
    {
    case PAUSE:
      break;

    case RESUME:
      break;

    case HALT:
      gimp_paint_tool_paint (paint_tool, drawable, FINISH_PAINT);
      gimp_paint_tool_cleanup ();
      break;

    default:
      break;
    }

  if (GIMP_TOOL_CLASS (parent_class)->control)
    GIMP_TOOL_CLASS (parent_class)->control (tool, action, gdisp);
}

static void
gimp_paint_tool_button_press (GimpTool       *tool,
			      GdkEventButton *bevent,
			      GDisplay       *gdisp)
{
  GimpPaintTool *paint_tool;
  GimpBrush    	*current_brush;
  gboolean       draw_line;
  gdouble        x, y;
  GimpDrawable  *drawable;

  g_return_if_fail (gdisp != NULL);
  g_return_if_fail (tool != NULL);

  paint_tool = GIMP_PAINT_TOOL (tool);

  gdisplay_untransform_coords_f (gdisp, (double) bevent->x, (double) bevent->y,
				 &x, &y, TRUE);
  drawable = gimp_image_active_drawable (gdisp->gimage);

  if (! gimp_paint_tool_start (paint_tool, drawable, x, y))
    return;

  draw_line = FALSE;

#ifdef __GNUC__
#warning FIXME: tilt, pressure
#endif

  paint_tool->curpressure = 1.0; /* bevent->pressure; */
  paint_tool->curxtilt    = 0.5; /* bevent->xtilt; */
  paint_tool->curytilt    = 0.5; /* bevent->ytilt; */
  paint_tool->state       = bevent->state;

  if (gdisp != tool->gdisp ||
      paint_tool->context_id < 1)
    {
      /* initialize the statusbar display */
      paint_tool->context_id =
	gtk_statusbar_get_context_id (GTK_STATUSBAR (gdisp->statusbar), "paint");
    }

  /*  if this is a new image, reinit the core vals  */
  if ((gdisp != tool->gdisp) || ! (bevent->state & GDK_SHIFT_MASK))
    {
      /*  initialize some values  */
      paint_tool->startx        = paint_tool->lastx        = paint_tool->curx = x;
      paint_tool->starty        = paint_tool->lasty        = paint_tool->cury = y;
      paint_tool->startpressure = paint_tool->lastpressure = paint_tool->curpressure;
      paint_tool->startytilt    = paint_tool->lastytilt    = paint_tool->curytilt;
      paint_tool->startxtilt    = paint_tool->lastxtilt    = paint_tool->curxtilt;
    }

  /*  If shift is down and this is not the first paint
   *  stroke, then draw a line from the last coords to the pointer
   */
  else if (bevent->state & GDK_SHIFT_MASK)
    {
      draw_line = TRUE;
      paint_tool->startx        = paint_tool->lastx;
      paint_tool->starty        = paint_tool->lasty;
      paint_tool->startpressure = paint_tool->lastpressure;
      paint_tool->startxtilt    = paint_tool->lastxtilt;
      paint_tool->startytilt    = paint_tool->lastytilt;

      /* Restrict to multiples of 15 degrees if ctrl is pressed */
      if (bevent->state & GDK_CONTROL_MASK)
	{
	  gint tangens2[6] = {  34, 106, 196, 334, 618, 1944 };
	  gint cosinus[7]  = { 256, 247, 222, 181, 128, 66, 0 };
	  gint dx, dy, i, radius, frac;

	  dx = paint_tool->curx - paint_tool->lastx;
	  dy = paint_tool->cury - paint_tool->lasty;

	  if (dy)
	    {
	      radius = sqrt (SQR (dx) + SQR (dy));
	      frac = abs ((dx << 8) / dy);
	      for (i = 0; i < 6; i++)
		{
		  if (frac < tangens2[i])
		    break;
		}
	      dx = dx > 0 ? (cosinus[6-i] * radius) >> 8 : - ((cosinus[6-i] * radius) >> 8);
	      dy = dy > 0 ? (cosinus[i] * radius)   >> 8 : - ((cosinus[i] * radius)   >> 8);
	    }
	  paint_tool->curx = paint_tool->lastx + dx;
	  paint_tool->cury = paint_tool->lasty + dy;
	}
    }

  tool->state        = ACTIVE;
  tool->gdisp        = gdisp;
  tool->paused_count = 0;

  /*  pause the current selection and grab the pointer  */
  gimp_image_selection_control (gdisp->gimage, GIMP_SELECTION_PAUSE);

  /* add motion memory if perfectmouse is set */
  if (gimprc.perfectmouse != 0)
    gdk_pointer_grab (gdisp->canvas->window, FALSE,
		      GDK_BUTTON1_MOTION_MASK |
		      GDK_BUTTON_RELEASE_MASK,
		      NULL, NULL, bevent->time);
  else
    gdk_pointer_grab (gdisp->canvas->window, FALSE,
		      GDK_POINTER_MOTION_HINT_MASK |
		      GDK_BUTTON1_MOTION_MASK |
		      GDK_BUTTON_RELEASE_MASK,
		      NULL, NULL, bevent->time);

  /*  Let the specific painting function initialize itself  */
  gimp_paint_tool_paint (paint_tool, drawable, INIT_PAINT);

  if (paint_tool->pick_colors
      && !(bevent->state & GDK_SHIFT_MASK)
      && (bevent->state & (GDK_CONTROL_MASK | GDK_MOD1_MASK)))
    {
      gimp_paint_tool_sample_color (drawable, x, y, bevent->state);
      paint_tool->pick_state = TRUE;
      return;
    }
  else
    paint_tool->pick_state = FALSE;

  /*  store the current brush pointer  */
  current_brush = paint_tool->brush;

  /*  Paint to the image  */
  if (draw_line)
    {
      gimp_draw_tool_pause (GIMP_DRAW_TOOL(paint_tool));
      gimp_paint_tool_interpolate (paint_tool, drawable);

      paint_tool->lastx        = paint_tool->curx;
      paint_tool->lasty        = paint_tool->cury;
      paint_tool->lastpressure = paint_tool->curpressure;
      paint_tool->lastxtilt    = paint_tool->curxtilt;
      paint_tool->lastytilt    = paint_tool->curytilt;
    }
  else
    {
      /* 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 (paint_tool->lastx != paint_tool->curx
	  || paint_tool->lasty != paint_tool->cury
#ifdef __GNUC__
#warning FIXME: GIMP_BRUSH_GET_CLASS (paint_tool->brush)->want_null_motion
#endif
#if 0
	  || GIMP_BRUSH_GET_CLASS (paint_tool->brush)->want_null_motion (paint_tool)
#endif
	  )
	{
	  if (paint_tool->flags & TOOL_CAN_HANDLE_CHANGING_BRUSH)
	    {
#ifdef __GNUC__
#warning FIXME: GIMP_BRUSH_GET_CLASS (paint_tool->brush)->select_brush
#endif
#if 0
	      paint_tool->brush =
		GIMP_BRUSH_GET_CLASS (paint_tool->brush)->select_brush (paint_tool);
#endif
	    }

	  gimp_paint_tool_paint (paint_tool, drawable, MOTION_PAINT);
	}
    }

  if (paint_tool->flags & TOOL_TRACES_ON_WINDOW)
    gimp_paint_tool_paint (paint_tool, drawable, PRETRACE_PAINT);

  gdisplay_flush_now (gdisp);

  if (paint_tool->flags & TOOL_TRACES_ON_WINDOW)
    gimp_paint_tool_paint (paint_tool, drawable, POSTTRACE_PAINT);

  /*  restore the current brush pointer  */
  paint_tool->brush = current_brush;
}

static void
gimp_paint_tool_button_release (GimpTool       *tool,
				GdkEventButton *bevent,
				GDisplay       *gdisp)
{
  GimpImage     *gimage;
  GimpPaintTool *paint_tool;

  gimage     = gdisp->gimage;
  paint_tool = GIMP_PAINT_TOOL (tool);

  /*  resume the current selection and ungrab the pointer  */
  gimp_image_selection_control (gdisp->gimage, GIMP_SELECTION_RESUME);

  gdk_pointer_ungrab (bevent->time);
  gdk_flush ();

  /*  Let the specific painting function finish up  */
  gimp_paint_tool_paint (paint_tool,
			 gimp_image_active_drawable (gdisp->gimage),
			 FINISH_PAINT);

  /*  Set tool state to inactive -- no longer painting */
  gimp_draw_tool_stop (GIMP_DRAW_TOOL (paint_tool));
  tool->state = INACTIVE;

  paint_tool->pick_state = FALSE;

  gimp_paint_tool_finish (paint_tool,
			  gimp_image_active_drawable (gdisp->gimage));

  gdisplays_flush ();
}

static void
gimp_paint_tool_motion (GimpTool       *tool,
			GdkEventMotion *mevent,
			GDisplay       *gdisp)
{
  GimpPaintTool *paint_tool;

  paint_tool = GIMP_PAINT_TOOL (tool);

  gdisplay_untransform_coords_f (gdisp, (double) mevent->x, (double) mevent->y,
				 &paint_tool->curx, &paint_tool->cury, TRUE);

  if (paint_tool->pick_state)
    {
      gimp_paint_tool_sample_color (gimp_image_active_drawable (gdisp->gimage),
				    paint_tool->curx, paint_tool->cury,
				    mevent->state);
      return;
    }

#ifdef __GNUC__
#warning FIXME: tilt, pressure
#endif

  paint_tool->curpressure = 1.0; /* mevent->pressure; */
  paint_tool->curxtilt    = 0.5; /* mevent->xtilt; */
  paint_tool->curytilt    = 0.5; /* mevent->ytilt; */
  paint_tool->state       = mevent->state;

  gimp_paint_tool_interpolate (paint_tool,
			  gimp_image_active_drawable (gdisp->gimage));

  if (paint_tool->flags & TOOL_TRACES_ON_WINDOW)
    gimp_paint_tool_paint(paint_tool,
				gimp_image_active_drawable (gdisp->gimage),
				PRETRACE_PAINT);

  gdisplay_flush_now (gdisp);

  if (paint_tool->flags & TOOL_TRACES_ON_WINDOW)
    gimp_paint_tool_paint(paint_tool,
				gimp_image_active_drawable (gdisp->gimage),
				POSTTRACE_PAINT);

  paint_tool->lastx        = paint_tool->curx;
  paint_tool->lasty        = paint_tool->cury;
  paint_tool->lastpressure = paint_tool->curpressure;
  paint_tool->lastxtilt    = paint_tool->curxtilt;
  paint_tool->lastytilt    = paint_tool->curytilt;
}


/* FIXME: this belongs in the individual tools */
static void
gimp_paint_tool_cursor_update (GimpTool       *tool,
			       GdkEventMotion *mevent,
			       GDisplay       *gdisp)
{
  GimpLayer     *layer;
  GimpPaintTool *paint_tool;
  GimpDrawTool  *draw_tool;
  gint           x, y;
  gchar          status_str[STATUSBAR_SIZE];

  GdkCursorType      ctype     = GIMP_MOUSE_CURSOR;
  GimpCursorModifier cmodifier = GIMP_CURSOR_MODIFIER_NONE;
  gboolean           ctoggle   = FALSE;

  paint_tool = GIMP_PAINT_TOOL (tool);
  draw_tool  = GIMP_DRAW_TOOL (tool);

  /*  undraw the current tool  */
  gimp_draw_tool_pause (draw_tool);

  if (gdisp != tool->gdisp || paint_tool->context_id < 1)
    {
      /* initialize the statusbar display */
      paint_tool->context_id =
	gtk_statusbar_get_context_id (GTK_STATUSBAR (gdisp->statusbar), "paint");
    }

  if (paint_tool->context_id)
    gtk_statusbar_pop (GTK_STATUSBAR (gdisp->statusbar), paint_tool->context_id);

#ifdef __GNUC__
#warning this doesnt belong here
#endif
  /* Set toggle cursors for various paint tools */
  if (tool->toggled)
    {
      GType type;

      type = G_TYPE_FROM_CLASS (G_OBJECT_GET_CLASS (tool));

      if (type == GIMP_TYPE_ERASER_TOOL)
	{
	  ctype     = GIMP_MOUSE_CURSOR;
	  cmodifier = GIMP_CURSOR_MODIFIER_MINUS;
	}
      else if (type == GIMP_TYPE_CONVOLVE_TOOL)
	{
	  ctype     = GIMP_MOUSE_CURSOR;
	  cmodifier = GIMP_CURSOR_MODIFIER_MINUS;
	}
      else if (type == GIMP_TYPE_DODGEBURN_TOOL)
	{
	  ctype   = GIMP_MOUSE_CURSOR;
	  ctoggle = TRUE;
	}
      else
	{
	  ctype = GIMP_MOUSE_CURSOR;
	}
    }

  if ((layer = gimp_image_get_active_layer (gdisp->gimage)))
    {
      /* If shift is down and this is not the first paint stroke, draw a line */
      if (gdisp == tool->gdisp && (mevent->state & GDK_SHIFT_MASK))
	{
	  gdouble dx, dy, d;

	  /*  Get the current coordinates */
	  gdisplay_untransform_coords_f (gdisp,
					 (double) mevent->x,
					 (double) mevent->y,
					 &paint_tool->curx,
					 &paint_tool->cury, TRUE);

	  dx = paint_tool->curx - paint_tool->lastx;
	  dy = paint_tool->cury - paint_tool->lasty;

	  /* Restrict to multiples of 15 degrees if ctrl is pressed */
	  if (mevent->state & GDK_CONTROL_MASK)
	    {
	      gint idx = dx;
	      gint idy = dy;
	      gint tangens2[6] = {  34, 106, 196, 334, 618, 1944 };
	      gint cosinus[7]  = { 256, 247, 222, 181, 128,   66, 0 };
	      gint i, radius, frac;

	      if (idy)
		{
		  radius = sqrt (SQR (idx) + SQR (idy));
		  frac = abs ((idx << 8) / idy);
		  for (i = 0; i < 6; i++)
		    {
		      if (frac < tangens2[i])
			break;
		    }
		  dx = idx > 0 ? (cosinus[6-i] * radius) >> 8 : - ((cosinus[6-i] * radius) >> 8);
		  dy = idy > 0 ? (cosinus[i] * radius) >> 8 : - ((cosinus[i] * radius) >> 8);
		}

	      paint_tool->curx = paint_tool->lastx + dx;
	      paint_tool->cury = paint_tool->lasty + dy;
	    }

	  /*  show distance in statusbar  */
	  if (gdisp->dot_for_dot)
	    {
	      d = sqrt (SQR (dx) + SQR (dy));
	      g_snprintf (status_str, STATUSBAR_SIZE, "%.1f %s", d, _("pixels"));
	    }
	  else
	    {
	      gchar *format_str =
		g_strdup_printf ("%%.%df %s",
				 gimp_unit_get_digits (gdisp->gimage->unit),
				 gimp_unit_get_symbol (gdisp->gimage->unit));
	      d = (gimp_unit_get_factor (gdisp->gimage->unit) *
		   sqrt (SQR (dx / gdisp->gimage->xresolution) +
			 SQR (dy / gdisp->gimage->yresolution)));

	      g_snprintf (status_str, STATUSBAR_SIZE, format_str, d);
	      g_free (format_str);
	    }

	  gtk_statusbar_push (GTK_STATUSBAR (gdisp->statusbar),
			      paint_tool->context_id, status_str);

	  if (draw_tool->gc == NULL)
	    {
	      gimp_draw_tool_start (draw_tool, gdisp->canvas->window);
	    }
	  else
	    {
	      /* is this a bad hack ? */
	      draw_tool->paused_count = 0;
	      gimp_draw_tool_resume (draw_tool);
	    }
	}
      /* If Ctrl or Mod1 is pressed, pick colors */
      else if (paint_tool->pick_colors &&
	       !(mevent->state & GDK_SHIFT_MASK) &&
	       (mevent->state & (GDK_CONTROL_MASK | GDK_MOD1_MASK)))
        {
	  ctype = GIMP_COLOR_PICKER_CURSOR;
	}
      /* Normal operation -- no modifier pressed or first stroke */
      else
	{
	  gint off_x, off_y;

	  gimp_drawable_offsets (GIMP_DRAWABLE (layer), &off_x, &off_y);
	  gdisplay_untransform_coords (gdisp,
				       (double) mevent->x, (double) mevent->y,
				       &x, &y, TRUE, FALSE);

	  if (x >= off_x && y >= off_y &&
	      x < (off_x + gimp_drawable_width (GIMP_DRAWABLE (layer))) &&
	      y < (off_y + gimp_drawable_height (GIMP_DRAWABLE (layer))))
	    {
	      /*  One more test--is there a selected region?
	       *  if so, is cursor inside?
	       */
	      if (gimage_mask_is_empty (gdisp->gimage))
		ctype = GIMP_MOUSE_CURSOR;
	      else if (gimage_mask_value (gdisp->gimage, x, y))
		ctype = GIMP_MOUSE_CURSOR;
	    }
	}

      gdisplay_install_tool_cursor (gdisp,
				    ctype,
				    ctype == GIMP_COLOR_PICKER_CURSOR ?
				    GIMP_COLOR_PICKER_TOOL_CURSOR :
				    ctoggle ?
				    tool->toggle_cursor : tool->tool_cursor,
				    cmodifier);
    }
}

static void
gimp_paint_tool_draw (GimpDrawTool *draw_tool)
{
  GDisplay      *gdisp;
  GimpPaintTool *paint_tool;
  GimpTool      *tool;
  gint           tx1, ty1, tx2, ty2;

  paint_tool = GIMP_PAINT_TOOL (draw_tool);
  tool       = GIMP_TOOL (draw_tool);

  /* if shift was never used, draw_tool->gc is NULL
     and we don't care about a redraw                       */
  if (draw_tool->gc != NULL)
    {
      gdisp = tool->gdisp;

      gdisplay_transform_coords (gdisp, paint_tool->lastx, paint_tool->lasty,
				 &tx1, &ty1, 1);
      gdisplay_transform_coords (gdisp, paint_tool->curx, paint_tool->cury,
				 &tx2, &ty2, 1);

      /*  Only draw line if it's in the visible area
       *  thus preventing from drawing rubbish
       */

      if (tx2 > 0 && ty2 > 0)
        {
	  gint offx, offy;

	  /* Adjust coords to start drawing from center of pixel if zoom > 1 */
	  offx = (int) SCALEFACTOR_X (gdisp) >> 1;
	  offy = (int) SCALEFACTOR_Y (gdisp) >> 1;
	  tx1 += offx;
	  ty1 += offy;
	  tx2 += offx;
	  ty2 += offy;

	  /*  Draw start target  */
	  gdk_draw_line (gdisp->canvas->window, draw_tool->gc,
			tx1 - (TARGET_WIDTH >> 1), ty1,
			tx1 + (TARGET_WIDTH >> 1), ty1);
	  gdk_draw_line (gdisp->canvas->window, draw_tool->gc,
			tx1, ty1 - (TARGET_HEIGHT >> 1),
			tx1, ty1 + (TARGET_HEIGHT >> 1));

	  /*  Draw end target  */
	  gdk_draw_line (gdisp->canvas->window, draw_tool->gc,
			tx2 - (TARGET_WIDTH >> 1), ty2,
			tx2 + (TARGET_WIDTH >> 1), ty2);
	  gdk_draw_line (gdisp->canvas->window, draw_tool->gc,
			tx2, ty2 - (TARGET_HEIGHT >> 1),
			tx2, ty2 + (TARGET_HEIGHT >> 1));

	  /*  Draw the line between the start and end coords  */
	  gdk_draw_line (gdisp->canvas->window, draw_tool->gc,
			tx1, ty1, tx2, ty2);
	}
    }
  return;
}	

/***********************************************************************/

static void
gimp_paint_tool_sample_color (GimpDrawable *drawable,
			      gint          x,
			      gint          y,
			      gint          state)
{
  GimpRGB  color;
  guchar  *col;

  if( x >= 0 && x < gimp_drawable_width (drawable) &&
      y >= 0 && y < gimp_drawable_height (drawable))
    {
      if ((col = gimp_drawable_get_color_at (drawable, x, y)))
	{
	  Gimp *gimp;

	  gimp = gimp_drawable_gimage (drawable)->gimp;

	  gimp_rgba_set_uchar (&color,
			       col[RED_PIX],
			       col[GREEN_PIX],
			       col[BLUE_PIX],
			       255);

	  if ((state & GDK_CONTROL_MASK))
	    gimp_context_set_foreground (gimp_get_user_context (gimp), &color);
	  else
	    gimp_context_set_background (gimp_get_user_context (gimp), &color);

	  g_free (col);
	}
    }
}

void
gimp_paint_tool_paint (GimpPaintTool *tool,
		       GimpDrawable  *drawable,
		       PaintState     state)
{
  g_signal_emit (G_OBJECT(tool), gimp_paint_tool_signals[PAINT], 0,
                 drawable, state);
}

gboolean
gimp_paint_tool_start (GimpPaintTool *paint_tool,
		       GimpDrawable  *drawable,
		       gdouble        x,
		       gdouble        y)
{
  static GimpBrush *brush = NULL;

  GimpContext *context;

  context = gimp_get_current_context (gimp_drawable_gimage (drawable)->gimp);

  paint_tool->curx = x;
  paint_tool->cury = y;

#ifdef __GNUC__
#warning (FIX non-gui paint tools)
#endif
#if 0
  /* Set up some defaults for non-gui use */
  if (paint_tool == &non_gui_paint_tool)
    {
      paint_tool->startpressure = paint_tool->lastpressure = paint_tool->curpressure = 0.5;
      paint_tool->startxtilt = paint_tool->lastxtilt = paint_tool->curxtilt = 0;
      paint_tool->startytilt = paint_tool->lastytilt = paint_tool->curytilt = 0;
    }
#endif

  /*  Each buffer is the same size as the maximum bounds of the active brush... */
  if (brush && brush != gimp_context_get_brush (context))
    {
      g_signal_handlers_disconnect_by_func (G_OBJECT (brush),
                                            G_CALLBACK (gimp_paint_tool_invalidate_cache),
                                            NULL);
      g_object_unref (G_OBJECT (brush));
    }
  if (!(brush = gimp_context_get_brush (context)))
    {
      g_message (_("No brushes available for use with this tool."));
      return FALSE;
    }

  g_object_ref (G_OBJECT (brush));
  g_signal_connect (G_OBJECT (brush), "invalidate_preview",
                    G_CALLBACK (gimp_paint_tool_invalidate_cache),
                    NULL);

  paint_tool->spacing = (double) gimp_brush_get_spacing (brush) / 100.0;

  paint_tool->brush = brush;

  /*  free the block structures  */
  if (undo_tiles)
    tile_manager_destroy (undo_tiles);
  if (canvas_tiles)
    tile_manager_destroy (canvas_tiles);

  /*  Allocate the undo structure  */
  undo_tiles = tile_manager_new (gimp_drawable_width (drawable),
				 gimp_drawable_height (drawable),
				 gimp_drawable_bytes (drawable));

  /*  Allocate the canvas blocks structure  */
  canvas_tiles = tile_manager_new (gimp_drawable_width (drawable),
				   gimp_drawable_height (drawable), 1);

  /*  Get the initial undo extents  */
  paint_tool->x1         = paint_tool->x2 = paint_tool->curx;
  paint_tool->y1         = paint_tool->y2 = paint_tool->cury;
  paint_tool->distance   = 0.0;
  paint_tool->pixel_dist = 0.0;

  return TRUE;
}

void
gimp_paint_tool_interpolate (GimpPaintTool *paint_tool,
			     GimpDrawable  *drawable)
{
  GimpBrush   *current_brush;
  GimpVector2  delta;
  gdouble      dpressure, dxtilt, dytilt;
  /*   double spacing; */
  /*   double lastscale, curscale; */
  gdouble n;
  gdouble left;
  gdouble t;
  gdouble initial;
  gdouble dist;
  gdouble total;
  gdouble pixel_dist;
  gdouble pixel_initial;
  gdouble xd, yd;
  gdouble mag;

  delta.x   = paint_tool->curx        - paint_tool->lastx;
  delta.y   = paint_tool->cury        - paint_tool->lasty;
  dpressure = paint_tool->curpressure - paint_tool->lastpressure;
  dxtilt    = paint_tool->curxtilt    - paint_tool->lastxtilt;
  dytilt    = paint_tool->curytilt    - paint_tool->lastytilt;

  /* return if there has been no motion */
  if (!delta.x && !delta.y && !dpressure && !dxtilt && !dytilt)
    return;

  /* calculate the distance traveled in the coordinate space of the brush */
  mag = gimp_vector2_length (&(paint_tool->brush->x_axis));
  xd  = gimp_vector2_inner_product (&delta,
				    &(paint_tool->brush->x_axis)) / (mag*mag);

  mag = gimp_vector2_length (&(paint_tool->brush->y_axis));
  yd  = gimp_vector2_inner_product (&delta,
				    &(paint_tool->brush->y_axis)) / (mag*mag);

  dist    = 0.5 * sqrt (xd*xd + yd*yd);
  total   = dist + paint_tool->distance;
  initial = paint_tool->distance;

  pixel_dist    = gimp_vector2_length (&delta);
  pixel_initial = paint_tool->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)); */

  while (paint_tool->distance < total)
    {
      n = (gint) (paint_tool->distance / paint_tool->spacing + 1.0 + EPSILON);
      left = n * paint_tool->spacing - paint_tool->distance;

      paint_tool->distance += left;

      if (paint_tool->distance <= (total+EPSILON))
	{
	  t = (paint_tool->distance - initial) / dist;

	  paint_tool->curx        = paint_tool->lastx + delta.x * t;
	  paint_tool->cury        = paint_tool->lasty + delta.y * t;
	  paint_tool->pixel_dist  = pixel_initial + pixel_dist * t;
	  paint_tool->curpressure = paint_tool->lastpressure + dpressure * t;
	  paint_tool->curxtilt    = paint_tool->lastxtilt + dxtilt * t;
	  paint_tool->curytilt    = paint_tool->lastytilt + dytilt * t;

	  /*  save the current brush  */
	  current_brush = paint_tool->brush;

	  if (paint_tool->flags & TOOL_CAN_HANDLE_CHANGING_BRUSH)
	    {
#ifdef __GNUC__
#warning FIXME: GIMP_BRUSH_GET_CLASS (paint_tool->brush)->select_brush
#endif
#if 0
	      paint_tool->brush =
		GIMP_BRUSH_GET_CLASS (paint_tool->brush)->select_brush (paint_tool);
#endif
	    }
	  gimp_paint_tool_paint(paint_tool, drawable, MOTION_PAINT);

	  /*  restore the current brush pointer  */
	  paint_tool->brush = current_brush;
	}
    }

  paint_tool->distance    = total;
  paint_tool->pixel_dist  = pixel_initial + pixel_dist;
  paint_tool->curx        = paint_tool->lastx + delta.x;
  paint_tool->cury        = paint_tool->lasty + delta.y;
  paint_tool->curpressure = paint_tool->lastpressure + dpressure;
  paint_tool->curxtilt    = paint_tool->lastxtilt + dxtilt;
  paint_tool->curytilt    = paint_tool->lastytilt + dytilt;
}

void
gimp_paint_tool_finish (GimpPaintTool *paint_tool,
			GimpDrawable  *drawable)
{
  GimpImage *gimage;
  PaintUndo *pu;

  if (! (gimage = gimp_drawable_gimage (drawable)))
    return;

  /*  Determine if any part of the image has been altered--
   *  if nothing has, then just return...
   */

  if ((paint_tool->x2 == paint_tool->x1) ||
      (paint_tool->y2 == paint_tool->y1))
    return;

  undo_push_group_start (gimage, PAINT_CORE_UNDO);

  pu = g_new0 (PaintUndo, 1);
  pu->tool_ID      = GIMP_TOOL (paint_tool)->ID;
  pu->tool_type    = G_TYPE_FROM_CLASS (G_OBJECT_GET_CLASS (paint_tool));
  pu->lastx        = paint_tool->startx;
  pu->lasty        = paint_tool->starty;
  pu->lastpressure = paint_tool->startpressure;
  pu->lastxtilt    = paint_tool->startxtilt;
  pu->lastytilt    = paint_tool->startytilt;

  /*  Push a paint undo  */
  undo_push_paint (gimage, pu);

  /*  push an undo  */
  gimp_drawable_apply_image (drawable, paint_tool->x1, paint_tool->y1,
			     paint_tool->x2, paint_tool->y2, undo_tiles, TRUE);
  undo_tiles = NULL;

  /*  push the group end  */
  undo_push_group_end (gimage);

  /*  invalidate the drawable--have to do it here, because
   *  it is not done during the actual painting.
   */
  gimp_viewable_invalidate_preview (GIMP_VIEWABLE (drawable));
}

void
gimp_paint_tool_cleanup (void)
{
  /*  CLEANUP  */
  /*  If the undo tiles exist, nuke them  */
  if (undo_tiles)
    {
      tile_manager_destroy (undo_tiles);
      undo_tiles = NULL;
    }

  /*  If the canvas blocks exist, nuke them  */
  if (canvas_tiles)
    {
      tile_manager_destroy (canvas_tiles);
      canvas_tiles = NULL;
    }

  /*  Free the temporary buffers if they exist  */
  free_paint_buffers ();
}

void
gimp_paint_tool_get_color_from_gradient (GimpPaintTool     *paint_tool,
                                         GimpGradient      *gradient,
					 gdouble            gradient_length,
					 GimpRGB           *color,
					 GradientPaintMode  mode)
{
  gdouble distance;  /* distance in current brush stroke */
  gdouble y;

  distance = paint_tool->pixel_dist;
  y        = (gdouble) distance / gradient_length;

  /* for the once modes, set y close to 1.0 after the first chunk */
  if ((mode == ONCE_FORWARD || mode == ONCE_BACKWARDS) && y >= 1.0)
    y = 0.9999999;

  if ((((gint) y & 1) && mode != LOOP_SAWTOOTH) || mode == ONCE_BACKWARDS )
    y = 1.0 - (y - (gint) y);
  else
    y = y - (gint) y;

  gimp_gradient_get_color_at (gradient, y, color);
}


/************************/
/*  Painting functions  */
/************************/

TempBuf *
gimp_paint_tool_get_paint_area (GimpPaintTool *paint_tool,
				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_tool_calculate_brush_size (paint_tool->brush->mask, scale,
				   &bwidth, &bheight);

  /*  adjust the x and y coordinates to the upper left corner of the brush  */
  x = (gint) floor (paint_tool->curx) - (bwidth  >> 1);
  y = (gint) floor (paint_tool->cury) - (bheight >> 1);

  dwidth  = gimp_drawable_width  (drawable);
  dheight = gimp_drawable_height (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))
    canvas_buf = temp_buf_resize (canvas_buf, bytes, x1, y1,
				  (x2 - x1), (y2 - y1));
  else
    return NULL;

  return canvas_buf;
}

TempBuf *
gimp_paint_tool_get_orig_image (GimpPaintTool *paint_tool,
				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;

  orig_buf = temp_buf_resize (orig_buf, gimp_drawable_bytes (drawable),
			      x1, y1, (x2 - x1), (y2 - y1));

  dwidth  = gimp_drawable_width  (drawable);
  dheight = gimp_drawable_height (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 = orig_buf->bytes;
  destPR.x = 0; destPR.y = 0;
  destPR.w = (x2 - x1); destPR.h = (y2 - y1);
  destPR.rowstride = orig_buf->bytes * orig_buf->width;
  destPR.data = temp_buf_data (orig_buf) +
    (y1 - orig_buf->y) * destPR.rowstride + (x1 - 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 (undo_tiles, srcPR.x, srcPR.y,
					 FALSE, FALSE);
      if (tile_is_valid (undo_tile) == TRUE)
	{
	  refd = 1;
	  undo_tile = tile_manager_get_tile (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 orig_buf;
}

void
gimp_paint_tool_paste_canvas (GimpPaintTool	   *paint_tool,
			      GimpDrawable	   *drawable,
			      gint		    brush_opacity,
			      gint		    image_opacity,
			      LayerModeEffects      paint_mode,
			      BrushApplicationMode  brush_hardness,
			      gdouble               brush_scale,
			      PaintApplicationMode  mode)
{
  MaskBuf *brush_mask;

  /*  get the brush mask  */
  brush_mask = gimp_paint_tool_get_brush_mask (paint_tool,
					       brush_hardness, brush_scale);

  /*  paste the canvas buf  */
  gimp_paint_tool_paste (paint_tool, brush_mask, drawable,
			 brush_opacity, image_opacity, paint_mode, mode);
}

/* Similar to gimp_paint_tool_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_tool_replace_canvas (GimpPaintTool        *paint_tool,
				GimpDrawable	     *drawable,
				gint                  brush_opacity,
				gint                  image_opacity,
				BrushApplicationMode  brush_hardness,
				gdouble               brush_scale,
				PaintApplicationMode  mode)
{
  MaskBuf *brush_mask;

  /*  get the brush mask  */
  brush_mask =
    gimp_paint_tool_get_brush_mask (paint_tool, brush_hardness, brush_scale);

  /*  paste the canvas buf  */
  gimp_paint_tool_replace (paint_tool, brush_mask, drawable,
			   brush_opacity, image_opacity, mode);
}


static void
gimp_paint_tool_invalidate_cache (GimpBrush *brush,
				  gpointer   data)
{
  /* Make sure we don't cache data for a brush that has changed */
  if (last_brush_mask == brush->mask)
    cache_invalid = TRUE;
}

/************************************************************
 *             LOCAL FUNCTION DEFINITIONS                   *
 ************************************************************/

static void
gimp_paint_tool_calculate_brush_size (MaskBuf *mask,
				      gdouble  scale,
				      gint    *width,
				      gint    *height)
{
  scale = CLAMP (scale, 0.0, 1.0);

  if (current_device == gdk_device_get_core_pointer ())
    {
      *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 MaskBuf *
gimp_paint_tool_subsample_mask (MaskBuf *mask,
				gdouble  x,
				gdouble  y)
{
  MaskBuf    *dest;
  gdouble     left;
  guchar     *m;
  guchar     *d;
  const gint *k;
  gint        index1;
  gint        index2;
  const gint *kernel;
  gint        new_val;
  gint        i, j;
  gint        r, s;

  x += (x < 0) ? mask->width : 0;
  left = x - floor (x);
  index1 = (gint) (left * (gdouble) (SUBSAMPLE + 1));

  y += (y < 0) ? mask->height : 0;
  left = y - floor (y);
  index2 = (gint) (left * (gdouble) (SUBSAMPLE + 1));

  kernel = subsample[index2][index1];

  if (mask == last_brush_mask && !cache_invalid)
    {
      if (kernel_brushes[index2][index1])
	return kernel_brushes[index2][index1];
    }
  else
    {
      for (i = 0; i <= SUBSAMPLE; i++)
	for (j = 0; j <= SUBSAMPLE; j++)
	  {
	    if (kernel_brushes[i][j])
	      mask_buf_free (kernel_brushes[i][j]);

	    kernel_brushes[i][j] = NULL;
	  }

      last_brush_mask = mask;
      cache_invalid = FALSE;
    }

  dest = kernel_brushes[index2][index1] = mask_buf_new (mask->width  + 2,
							mask->height + 2);

  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++)
	    {
	      d = mask_buf_data (dest) + (i+r) * dest->width + j;
	      s = KERNEL_WIDTH;
	      while (s--)
		{
		  new_val = *d + ((*m * *k++ + 128) >> 8);
		  *d++ = MIN (new_val, 255);
		}
	    }
	  m++;
	}
    }

  return dest;
}

/* #define FANCY_PRESSURE */

static MaskBuf *
gimp_paint_tool_pressurize_mask (MaskBuf *brush_mask,
				 gdouble  x,
				 gdouble  y,
				 gdouble  pressure)
{
  static MaskBuf *last_brush = NULL;
  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_tool_subsample_mask (brush_mask, x, y);

  /* Special case pressure = 0.5 */
  if ((int)(pressure * 100 + 0.5) == 50)
    return subsample_mask;

  /* Make sure we have the right sized buffer */
  if (brush_mask != last_brush)
    {
      if (pressure_brush)
	mask_buf_free (pressure_brush);

      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 (pressure_brush);

  i = subsample_mask->width * subsample_mask->height;
  while (i--)
    {
      *dest++ = mapi[(*source++)];
    }

  return pressure_brush;
}

static MaskBuf *
gimp_paint_tool_solidify_mask (MaskBuf *brush_mask)
{
  static MaskBuf *last_brush = NULL;

  gint    i;
  gint    j;
  guchar *data;
  guchar *src;

  if (brush_mask == last_brush && !cache_invalid)
    return solid_brush;

  last_brush = brush_mask;

  if (solid_brush)
    mask_buf_free (solid_brush);

  solid_brush = mask_buf_new (brush_mask->width + 2, brush_mask->height + 2);

  /*  get the data and advance one line into it  */
  data = mask_buf_data (solid_brush) + solid_brush->width;
  src  = mask_buf_data (brush_mask);

  for (i = 0; i < brush_mask->height; i++)
    {
      data++;
      for (j = 0; j < brush_mask->width; j++)
	{
	  *data++ = (*src++) ? OPAQUE_OPACITY : TRANSPARENT_OPACITY;
	}
      data++;
    }

  return solid_brush;
}

static MaskBuf *
gimp_paint_tool_scale_mask (MaskBuf *brush_mask,
			    gdouble  scale)
{
  static MaskBuf *last_brush  = NULL;
  static gint     last_width  = 0.0;
  static gint     last_height = 0.0;
  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_tool_calculate_brush_size (brush_mask, scale,
				   &dest_width, &dest_height);

  if (brush_mask == last_brush && !cache_invalid &&
      dest_width == last_width && dest_height == last_height)
    return scale_brush;

  if (scale_brush)
    mask_buf_free (scale_brush);

  last_brush  = brush_mask;
  last_width  = dest_width;
  last_height = dest_height;

  scale_brush = brush_scale_mask (brush_mask, dest_width, dest_height);
  cache_invalid = TRUE;

  return scale_brush;
}

static MaskBuf *
gimp_paint_tool_scale_pixmap (MaskBuf *brush_mask,
			      gdouble  scale)
{
  static MaskBuf *last_brush  = NULL;
  static gint     last_width  = 0.0;
  static gint     last_height = 0.0;
  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_tool_calculate_brush_size (brush_mask, scale,
				   &dest_width, &dest_height);

  if (brush_mask == last_brush && !cache_invalid &&
      dest_width == last_width && dest_height == last_height)
    return scale_pixmap;

  if (scale_pixmap)
    mask_buf_free (scale_pixmap);

  last_brush  = brush_mask;
  last_width  = dest_width;
  last_height = dest_height;

  scale_pixmap = brush_scale_pixmap (brush_mask, dest_width, dest_height);
  cache_invalid = TRUE;

  return scale_pixmap;
}

static MaskBuf *
gimp_paint_tool_get_brush_mask (GimpPaintTool	     *paint_tool,
				BrushApplicationMode  brush_hardness,
				gdouble               scale)
{
  MaskBuf *mask;

  if (current_device == gdk_device_get_core_pointer ())
    mask = paint_tool->brush->mask;
  else
    mask = gimp_paint_tool_scale_mask (paint_tool->brush->mask, scale);

  switch (brush_hardness)
    {
    case SOFT:
      mask = gimp_paint_tool_subsample_mask (mask,
					     paint_tool->curx, paint_tool->cury);
      break;
    case HARD:
      mask = gimp_paint_tool_solidify_mask (mask);
      break;
    case PRESSURE:
      mask = gimp_paint_tool_pressurize_mask (mask,
					      paint_tool->curx, paint_tool->cury,
					      paint_tool->curpressure);
      break;
    default:
      break;
    }

  return mask;
}

static void
gimp_paint_tool_paste (GimpPaintTool        *paint_tool,
		       MaskBuf              *brush_mask,
		       GimpDrawable         *drawable,
		       gint                  brush_opacity,
		       gint                  image_opacity,
		       LayerModeEffects      paint_mode,
		       PaintApplicationMode  mode)
{
  GimpImage   *gimage;
  PixelRegion  srcPR;
  TileManager *alt = NULL;
  gint         offx;
  gint         offy;

  if (! (gimage = gimp_drawable_gimage (drawable)))
    return;

  /*  set undo blocks  */
  set_undo_tiles (drawable,
		  canvas_buf->x, canvas_buf->y,
		  canvas_buf->width, canvas_buf->height);

  /*  If the mode is CONSTANT:
   *   combine the canvas buf, the brush mask to the canvas tiles
   */
  if (mode == CONSTANT)
    {
      /*  initialize any invalid canvas tiles  */
      set_canvas_tiles (canvas_buf->x, canvas_buf->y,
			canvas_buf->width, canvas_buf->height);

      brush_to_canvas_tiles (paint_tool, brush_mask, brush_opacity);
      canvas_tiles_to_canvas_buf (paint_tool);
      alt = undo_tiles;
    }
  /*  Otherwise:
   *   combine the canvas buf and the brush mask to the canvas buf
   */
  else
    {
      brush_to_canvas_buf (paint_tool, brush_mask, brush_opacity);
    }

  /*  intialize canvas buf source pixel regions  */
  srcPR.bytes = canvas_buf->bytes;
  srcPR.x = 0; srcPR.y = 0;
  srcPR.w = canvas_buf->width;
  srcPR.h = canvas_buf->height;
  srcPR.rowstride = canvas_buf->width * canvas_buf->bytes;
  srcPR.data = temp_buf_data (canvas_buf);

  /*  apply the paint area to the gimage  */
  gimp_image_apply_image (gimage, drawable, &srcPR,
			  FALSE, image_opacity, paint_mode,
			  alt,  /*  specify an alternative src1  */
			  canvas_buf->x, canvas_buf->y);

  /*  Update the undo extents  */
  paint_tool->x1 = MIN (paint_tool->x1, canvas_buf->x);
  paint_tool->y1 = MIN (paint_tool->y1, canvas_buf->y);
  paint_tool->x2 = MAX (paint_tool->x2, (canvas_buf->x + canvas_buf->width));
  paint_tool->y2 = MAX (paint_tool->y2, (canvas_buf->y + canvas_buf->height));

  /*  Update the gimage--it is important to call gdisplays_update_area
   *  instead of drawable_update because we don't want the drawable
   *  preview to be constantly invalidated
   */
  gimp_drawable_offsets (drawable, &offx, &offy);
  gdisplays_update_area (gimage, canvas_buf->x + offx, canvas_buf->y + offy,
			 canvas_buf->width, canvas_buf->height);
}

/* This works similarly to gimp_paint_tool_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_tool_replace (GimpPaintTool        *paint_tool,
			 MaskBuf              *brush_mask,
			 GimpDrawable         *drawable,
			 gint                  brush_opacity,
			 gint                  image_opacity,
			 PaintApplicationMode  mode)
{
  GimpImage   *gimage;
  PixelRegion  srcPR;
  PixelRegion  maskPR;
  TileManager *alt = NULL;
  gint         offx;
  gint         offy;

  if (! gimp_drawable_has_alpha (drawable))
    {
      gimp_paint_tool_paste (paint_tool, brush_mask, drawable,
			     brush_opacity, image_opacity, NORMAL_MODE,
			     mode);
      return;
    }

  if (! (gimage = gimp_drawable_gimage (drawable)))
    return;

  /*  set undo blocks  */
  set_undo_tiles (drawable,
		  canvas_buf->x, canvas_buf->y,
		  canvas_buf->width, canvas_buf->height);

  if (mode == CONSTANT)
    {
      /*  initialize any invalid canvas tiles  */
      set_canvas_tiles (canvas_buf->x, canvas_buf->y,
			canvas_buf->width, canvas_buf->height);

      /* combine the brush mask and the canvas tiles */
      brush_to_canvas_tiles (paint_tool, brush_mask, brush_opacity);

      /* set the alt source as the unaltered undo_tiles */
      alt = undo_tiles;

      /* initialize the maskPR from the canvas tiles */
      pixel_region_init (&maskPR, canvas_tiles,
			 canvas_buf->x, canvas_buf->y,
			 canvas_buf->width, canvas_buf->height, FALSE);
    }
  else
    {
      /* The mask is just the brush mask */
      maskPR.bytes     = 1;
      maskPR.x         = 0;
      maskPR.y         = 0;
      maskPR.w         = canvas_buf->width;
      maskPR.h         = 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     = canvas_buf->bytes;
  srcPR.x         = 0;
  srcPR.y         = 0;
  srcPR.w         = canvas_buf->width;
  srcPR.h         = canvas_buf->height;
  srcPR.rowstride = canvas_buf->width * canvas_buf->bytes;
  srcPR.data      = temp_buf_data (canvas_buf);

  /*  apply the paint area to the gimage  */
  gimp_image_replace_image (gimage, drawable, &srcPR,
			    FALSE, image_opacity,
			    &maskPR,
			    canvas_buf->x, canvas_buf->y);

  /*  Update the undo extents  */
  paint_tool->x1 = MIN (paint_tool->x1, canvas_buf->x);
  paint_tool->y1 = MIN (paint_tool->y1, canvas_buf->y);
  paint_tool->x2 = MAX (paint_tool->x2, (canvas_buf->x + canvas_buf->width));
  paint_tool->y2 = MAX (paint_tool->y2, (canvas_buf->y + canvas_buf->height));

  /*  Update the gimage--it is important to call gdisplays_update_area
   *  instead of drawable_update because we don't want the drawable
   *  preview to be constantly invalidated
   */
  gimp_drawable_offsets (drawable, &offx, &offy);
  gdisplays_update_area (gimage, canvas_buf->x + offx, canvas_buf->y + offy,
			 canvas_buf->width, canvas_buf->height);
}

static void
canvas_tiles_to_canvas_buf (GimpPaintTool *gimp_paint_tool)
{
  PixelRegion srcPR;
  PixelRegion maskPR;

  /*  combine the canvas tiles and the canvas buf  */
  srcPR.bytes     = canvas_buf->bytes;
  srcPR.x         = 0;
  srcPR.y         = 0;
  srcPR.w         = canvas_buf->width;
  srcPR.h         = canvas_buf->height;
  srcPR.rowstride = canvas_buf->width * canvas_buf->bytes;
  srcPR.data      = temp_buf_data (canvas_buf);

  pixel_region_init (&maskPR, canvas_tiles,
		     canvas_buf->x, canvas_buf->y,
		     canvas_buf->width, 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 (GimpPaintTool *paint_tool,
		       MaskBuf       *brush_mask,
		       gint          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, canvas_tiles,
		     canvas_buf->x, canvas_buf->y,
		     canvas_buf->width, canvas_buf->height, TRUE);

  x = (gint) floor (paint_tool->curx) - (brush_mask->width  >> 1);
  y = (gint) floor (paint_tool->cury) - (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);
}

static void
brush_to_canvas_buf (GimpPaintTool *paint_tool,
		     MaskBuf       *brush_mask,
		     gint           brush_opacity)
{
  PixelRegion srcPR;
  PixelRegion maskPR;
  gint        x;
  gint        y;
  gint        xoff;
  gint        yoff;

  x = (gint) floor (paint_tool->curx) - (brush_mask->width  >> 1);
  y = (gint) floor (paint_tool->cury) - (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     = canvas_buf->bytes;
  srcPR.x         = 0;
  srcPR.y         = 0;
  srcPR.w         = canvas_buf->width;
  srcPR.h         = canvas_buf->height;
  srcPR.rowstride = canvas_buf->width * canvas_buf->bytes;
  srcPR.data      = temp_buf_data (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);
}

#if 0
static void
paint_to_canvas_tiles (GimpPaintTool *paint_tool,
		       MaskBuf       *brush_mask,
		       gint           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, canvas_tiles,
		     canvas_buf->x, canvas_buf->y,
		     canvas_buf->width, canvas_buf->height, TRUE);

  x = (gint) floor (paint_tool->curx) - (brush_mask->width  >> 1);
  y = (gint) floor (paint_tool->cury) - (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 and canvas tiles  */
  combine_mask_and_region (&srcPR, &maskPR, brush_opacity);

  /*  combine the canvas tiles and the canvas buf  */
  srcPR.bytes     = canvas_buf->bytes;
  srcPR.x         = 0;
  srcPR.y         = 0;
  srcPR.w         = canvas_buf->width;
  srcPR.h         = canvas_buf->height;
  srcPR.rowstride = canvas_buf->width * canvas_buf->bytes;
  srcPR.data      = temp_buf_data (canvas_buf);

  pixel_region_init (&maskPR, canvas_tiles,
		     canvas_buf->x, canvas_buf->y,
		     canvas_buf->width, canvas_buf->height, FALSE);

  /*  apply the canvas tiles to the canvas buf  */
  apply_mask_to_region (&srcPR, &maskPR, OPAQUE_OPACITY);
}

static void
paint_to_canvas_buf (GimpPaintTool *paint_tool,
		     MaskBuf       *brush_mask,
		     gint           brush_opacity)
{
  PixelRegion srcPR;
  PixelRegion maskPR;
  gint x;
  gint y;
  gint xoff;
  gint yoff;

  x = (gint) floor (paint_tool->curx) - (brush_mask->width  >> 1);
  y = (gint) floor (paint_tool->cury) - (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     = canvas_buf->bytes;
  srcPR.x         = 0;
  srcPR.y         = 0;
  srcPR.w         = canvas_buf->width;
  srcPR.h         = canvas_buf->height;
  srcPR.rowstride = canvas_buf->width * canvas_buf->bytes;
  srcPR.data      = temp_buf_data (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);
}
#endif

static void
set_undo_tiles (GimpDrawable *drawable,
		gint          x,
		gint          y,
		gint          w,
		gint          h)
{
  gint  i;
  gint  j;
  Tile *src_tile;
  Tile *dest_tile;

  if (undo_tiles == NULL)
    {
      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 (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 (undo_tiles, j, i, src_tile);
	      tile_release (src_tile, FALSE);
	    }
	}
    }
}

static void
set_canvas_tiles (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 (canvas_tiles, j, i, FALSE, FALSE);
	  if (tile_is_valid (tile) == FALSE)
	    {
	      tile = tile_manager_get_tile (canvas_tiles, j, i, TRUE, TRUE);
	      memset (tile_data_pointer (tile, 0, 0), 0, tile_size (tile));
	      tile_release (tile, TRUE);
	    }
	}
    }
}


/*****************************************************/
/*  Paint buffers utility functions                  */
/*****************************************************/


static void
free_paint_buffers (void)
{
  if (orig_buf)
    temp_buf_free (orig_buf);
  orig_buf = NULL;

  if (canvas_buf)
    temp_buf_free (canvas_buf);
  canvas_buf = NULL;
}


/**************************************************/
/*  Brush pipe utility functions                  */
/**************************************************/

void
gimp_paint_tool_color_area_with_pixmap (GimpPaintTool        *paint_tool,
					GimpImage            *dest,
					GimpDrawable         *drawable,
					TempBuf              *area,
					gdouble               scale,
					BrushApplicationMode  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 (paint_tool->brush));
  g_return_if_fail (paint_tool->brush->pixmap != NULL);

  /*  scale the brushes  */
  pixmap_mask = gimp_paint_tool_scale_pixmap (paint_tool->brush->pixmap, scale);

  if (mode == SOFT)
    brush_mask = gimp_paint_tool_scale_mask (paint_tool->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_tool_get_paint_area.  Ugly to have to do this here, too.
   */

  ulx = (gint) floor (paint_tool->curx) - (pixmap_mask->width  >> 1);
  uly = (gint) floor (paint_tool->cury) - (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,
			BrushApplicationMode  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 == 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, p, d, RGB);
	  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, p, d, RGB);
	  d += bytes;
	}
    }
}
