/* The GIMP -- an image manipulation program
 * Copyright (C) 1995-2001 Spencer Kimball, Peter Mattis, and others
 *
 * 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 <gtk/gtk.h>

#include "libgimpmath/gimpmath.h"
#include "libgimpwidgets/gimpwidgets.h"

#include "tools-types.h"
#include "gui/gui-types.h"

#include "base/base-config.h"
#include "base/pixel-region.h"
#include "base/pixel-surround.h"
#include "base/tile-manager.h"
#include "base/tile.h"

#include "paint-funcs/paint-funcs.h"

#include "core/gimp.h"
#include "core/gimpchannel.h"
#include "core/gimpcontext.h"
#include "core/gimpdrawable.h"
#include "core/gimpimage.h"
#include "core/gimpimage-mask.h"
#include "core/gimplayer.h"
#include "core/gimpmarshal.h"
#include "core/gimptoolinfo.h"

#include "gui/info-dialog.h"

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

#include "tool_manager.h"
#include "tool_options.h"
#include "transform_options.h"
#include "gimptransformtool.h"
#include "gimpperspectivetool.h"
#include "gimprotatetool.h"
#include "gimpscaletool.h"
#include "gimpsheartool.h"
#include "gimpfliptool.h"

#include "app_procs.h"
#include "floating_sel.h"
#include "undo.h"
#include "path_transform.h"

#include "libgimp/gimpintl.h"


enum
{
  TRANSFORM,
  LAST_SIGNAL
};

#define BILINEAR(jk,j1k,jk1,j1k1,dx,dy) \
                ((1-dy) * (jk + dx * (j1k - jk)) + \
		    dy  * (jk1 + dx * (j1k1 - jk1)))

/* access interleaved pixels */
#define CUBIC_ROW(dx, row, step) \
  gimp_transform_tool_cubic(dx, (row)[0], (row)[step], (row)[step+step], (row)[step+step+step])
#define CUBIC_SCALED_ROW(dx, row, step, i) \
  gimp_transform_tool_cubic(dx, (row)[0] * (row)[i], \
            (row)[step] * (row)[step + i], \
            (row)[step+step]* (row)[step+step + i], \
            (row)[step+step+step] * (row)[step+step+step + i])

#define REF_TILE(i,x,y) \
     tile[i] = tile_manager_get_tile (float_tiles, x, y, TRUE, FALSE); \
     src[i] = tile_data_pointer (tile[i], (x) % TILE_WIDTH, (y) % TILE_HEIGHT);


/*  forward function declarations  */
static void      gimp_transform_tool_bounds    (GimpTransformTool      *tool,
                                                GimpDisplay            *gdisp);
static void      gimp_transform_tool_recalc    (GimpTransformTool      *tool,
					        GimpDisplay            *gdisp);
static void      gimp_transform_tool_doit      (GimpTransformTool      *tool,
					        GimpDisplay            *gdisp);
static gdouble   gimp_transform_tool_cubic     (gdouble                 dx,
					        gint                    jm1,
					        gint                    j,
					        gint                    jp1,
					        gint                    jp2);
static void    gimp_transform_tool_setup_grid  (GimpTransformTool      *tool);
static void    gimp_transform_tool_grid_recalc (GimpTransformTool      *gimp_transform_tool);
static void    gimp_transform_tool_init        (GimpTransformTool      *tool);
static void    gimp_transform_tool_class_init  (GimpTransformToolClass *tool);

static void    gimp_transform_tool_finalize    (GObject                *object);

static void   gimp_transform_tool_button_press (GimpTool               *tool,
                                                GdkEventButton         *bevent,
			                        GimpDisplay            *gdisp);
			          
static void gimp_transform_tool_button_release (GimpTool               *tool,
			                        GdkEventButton         *bevent,
			                        GimpDisplay            *gdisp);
			                        
static void    gimp_transform_tool_motion      (GimpTool               *tool,
		                                GdkEventMotion         *mevent,
		                                GimpDisplay            *gdisp);
		                                
static void  gimp_transform_tool_cursor_update (GimpTool               *tool,
	                 		        GdkEventMotion         *mevent,
			                        GimpDisplay            *gdisp);
			                        
static void    gimp_transform_tool_control     (GimpTool               *tool,
			                        ToolAction              action,
			                        GimpDisplay            *gdisp);

static void    gimp_transform_tool_draw        (GimpDrawTool           *draw_tool);


/*  variables  */
static TranInfo           old_trans_info;
InfoDialog               *transform_info        = NULL;
static gboolean           transform_info_inited = FALSE;

static GimpDrawToolClass *parent_class = NULL;

static guint   gimp_transform_tool_signals[LAST_SIGNAL] = { 0 };


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

  if (! tool_type)
    {
      static const GTypeInfo tool_info =
      {
        sizeof (GimpTransformToolClass),
	(GBaseInitFunc) NULL,
	(GBaseFinalizeFunc) NULL,
	(GClassInitFunc) gimp_transform_tool_class_init,
	NULL,           /* class_finalize */
	NULL,           /* class_data     */
	sizeof (GimpTransformTool),
	0,              /* n_preallocs    */
	(GInstanceInitFunc) gimp_transform_tool_init,
      };

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

  return tool_type;
}

static void
gimp_transform_tool_class_init (GimpTransformToolClass *klass)
{
  GObjectClass      *object_class;
  GimpToolClass     *tool_class;
  GimpDrawToolClass *draw_class;

  object_class = G_OBJECT_CLASS (klass);
  tool_class   = GIMP_TOOL_CLASS (klass);
  draw_class   = GIMP_DRAW_TOOL_CLASS (klass);

  parent_class = g_type_class_peek_parent (klass);

  gimp_transform_tool_signals[TRANSFORM] =
    g_signal_new ("transform",
		  G_TYPE_FROM_CLASS (klass),
		  G_SIGNAL_RUN_LAST,
		  G_STRUCT_OFFSET (GimpTransformToolClass, transform),
		  NULL, NULL,
		  gimp_cclosure_marshal_POINTER__POINTER_INT,
		  G_TYPE_POINTER, 2,
		  G_TYPE_POINTER,
		  G_TYPE_INT);

  object_class->finalize     = gimp_transform_tool_finalize;

  tool_class->button_press   = gimp_transform_tool_button_press;
  tool_class->button_release = gimp_transform_tool_button_release;
  tool_class->motion         = gimp_transform_tool_motion;
  tool_class->cursor_update  = gimp_transform_tool_cursor_update;
  tool_class->control        = gimp_transform_tool_control;

  draw_class->draw           = gimp_transform_tool_draw;
}

static void
gimp_transform_tool_init (GimpTransformTool *tr_tool)
{
  GimpTool *tool = GIMP_TOOL (tr_tool);
  gint      i;

  tr_tool->function = TRANSFORM_CREATING;
  tr_tool->original = NULL;

  tr_tool->bpressed = FALSE;

  for (i = 0; i < TRAN_INFO_SIZE; i++)
    tr_tool->trans_info[i] = 0;

  tr_tool->grid_coords = tr_tool->tgrid_coords = NULL;

  /* FIXME */
  tr_tool->interactive = TRUE;

  tool->scroll_lock = TRUE;   /*  Disallow scrolling  */
  tool->preserve    = FALSE;  /*  Don't preserve on drawable change  */

}

TileManager *
gimp_transform_tool_transform (GimpTransformTool   *tool,
                               GimpDisplay         *gdisp,
			       TransformState       state)
{
  TileManager *retval;

  g_return_val_if_fail (tool, NULL);
  g_return_val_if_fail (GIMP_IS_TRANSFORM_TOOL (tool), NULL);

  g_signal_emit (G_OBJECT (tool), gimp_transform_tool_signals[TRANSFORM], 0,
                 gdisp, state, &retval);

  return retval;
}

static void
transform_ok_callback (GtkWidget *widget,
		       gpointer   data)
{
  GimpTool *tool;

  tool = GIMP_TOOL(data);
  gimp_transform_tool_doit (GIMP_TRANSFORM_TOOL(tool), tool->gdisp);
}

static void
transform_reset_callback (GtkWidget *widget,
			  gpointer   data)
{
  GimpTransformTool   *tool;
  GimpDrawTool        *dr_tool;
  gint                 i;

  tool    = GIMP_TRANSFORM_TOOL (data);
  dr_tool = GIMP_DRAW_TOOL (data);

  /*  stop the current tool drawing process  */
  gimp_draw_tool_pause (dr_tool);

  /*  Restore the previous transformation info  */
  for (i = 0; i < TRAN_INFO_SIZE; i++)
    tool->trans_info [i] = old_trans_info [i];

  /*  recalculate the tool's transformation matrix  */
  gimp_transform_tool_recalc (tool, GIMP_TOOL(tool)->gdisp);

  /*  resume drawing the current tool  */
  gimp_draw_tool_resume (dr_tool);
}

static void
gimp_transform_tool_finalize (GObject *object)
{
  GimpTransformTool *tr_tool;

  tr_tool = GIMP_TRANSFORM_TOOL (object);

  if (tr_tool->original)
    {
      tile_manager_destroy (tr_tool->original);
      tr_tool->original = NULL;
    }

  if (transform_info)
    {
      info_dialog_free (transform_info);
      transform_info        = NULL;
      transform_info_inited = FALSE;
    }

  if (tr_tool->grid_coords)
    {
      g_free (tr_tool->grid_coords);
      tr_tool->grid_coords = NULL;
    }

  if (tr_tool->tgrid_coords)
    {
      g_free (tr_tool->tgrid_coords);
      tr_tool->tgrid_coords = NULL;
    }

  G_OBJECT_CLASS (parent_class)->finalize (object);
}

static void
gimp_transform_tool_control (GimpTool    *tool,
			     ToolAction   action,
			     GimpDisplay *gdisp)
{
  GimpDrawTool      *dr_tool;
  GimpTransformTool *tr_tool;

  dr_tool = GIMP_DRAW_TOOL (tool);
  tr_tool = GIMP_TRANSFORM_TOOL (tool);

  switch (action)
    {
    case PAUSE:
      break;

    case RESUME:
      gimp_transform_tool_recalc (tr_tool, gdisp);
      break;

    case HALT:
      gimp_transform_tool_reset (tr_tool, gdisp);
      break;

    default:
      break;
    }

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

static void
gimp_transform_tool_button_press (GimpTool       *tool,
                                  GdkEventButton *bevent,
			          GimpDisplay    *gdisp)
{
  GimpTransformTool  *gt_tool;
  GimpDrawable       *drawable;
  gint                dist;
  gint                closest_dist;
  gint                x, y;
  gint                i;
  gint                off_x, off_y;

  gt_tool = GIMP_TRANSFORM_TOOL (tool);

  gt_tool->bpressed = TRUE; /* ALT */

  drawable = gimp_image_active_drawable (gdisp->gimage);

  if (gt_tool->function == TRANSFORM_CREATING && tool->state == ACTIVE)
    {
      /*  Save the current transformation info  */
      for (i = 0; i < TRAN_INFO_SIZE; i++)
	old_trans_info [i] = gt_tool->trans_info [i];
    }

  /*  if we have already displayed the bounding box and handles,
   *  check to make sure that the display which currently owns the
   *  tool is the one which just received the button pressed event
   */
  if ((gdisp == tool->gdisp) && gt_tool->interactive)
    {
      /*  start drawing the bounding box and handles...  */
      gimp_draw_tool_start (GIMP_DRAW_TOOL (gt_tool), gdisp->canvas->window);

      x = bevent->x;
      y = bevent->y;

      closest_dist = SQR (x - gt_tool->sx1) + SQR (y - gt_tool->sy1);
      gt_tool->function = TRANSFORM_HANDLE_1;

      dist = SQR (x - gt_tool->sx2) + SQR (y - gt_tool->sy2);
      if (dist < closest_dist)
	{
	  closest_dist = dist;
	  gt_tool->function = TRANSFORM_HANDLE_2;
	}

      dist = SQR (x - gt_tool->sx3) + SQR (y - gt_tool->sy3);
      if (dist < closest_dist)
	{
	  closest_dist = dist;
	  gt_tool->function = TRANSFORM_HANDLE_3;
	}

      dist = SQR (x - gt_tool->sx4) + SQR (y - gt_tool->sy4);
      if (dist < closest_dist)
	{
	  closest_dist = dist;
	  gt_tool->function = TRANSFORM_HANDLE_4;
	}

      if ((SQR (x - gt_tool->scx) +
	   SQR (y - gt_tool->scy)) <= 100)
	{
	  gt_tool->function = TRANSFORM_HANDLE_CENTER;
	}

      /*  Save the current pointer position  */
      gdisplay_untransform_coords (gdisp, bevent->x, bevent->y,
				   &gt_tool->startx,
				   &gt_tool->starty, TRUE, 0);
      gt_tool->lastx = gt_tool->startx;
      gt_tool->lasty = gt_tool->starty;

      gdk_pointer_grab (gdisp->canvas->window, FALSE,
			GDK_POINTER_MOTION_HINT_MASK |
			GDK_BUTTON1_MOTION_MASK |
			GDK_BUTTON_RELEASE_MASK,
			NULL, NULL, bevent->time);

      tool->state = ACTIVE;
      return;
    }


  /*  Initialisation stuff: if the cursor is clicked inside the current
   *  selection, show the bounding box and handles...
   */
  gdisplay_untransform_coords (gdisp, bevent->x, bevent->y, &x, &y,
			       FALSE, FALSE);

  gimp_drawable_offsets (drawable, &off_x, &off_y);
  if (x >= off_x && y >= off_y &&
      x < (off_x + gimp_drawable_width (drawable)) &&
      y < (off_y + gimp_drawable_height (drawable)))
    {
      if (gimage_mask_is_empty (gdisp->gimage) ||
	  gimage_mask_value (gdisp->gimage, x, y))
	{
	  if (GIMP_IS_LAYER (drawable) &&
	      gimp_layer_get_mask (GIMP_LAYER (drawable)))
	    {
	      g_message (_("Transformations do not work on\n"
			   "layers that contain layer masks."));
	      tool->state = INACTIVE;
	      return;
	    }

	  /*  If the tool is already active, clear the current state
	   *  and reset
	   */
	  if (tool->state == ACTIVE)
	    gimp_transform_tool_reset (gt_tool, gdisp);

	  /*  Set the pointer to the active display  */
	  tool->gdisp    = gdisp;
	  tool->drawable = drawable;
	  tool->state    = ACTIVE;

	  /*  Grab the pointer if we're in non-interactive mode  */
	  if (!gt_tool->interactive)
	    gdk_pointer_grab (gdisp->canvas->window, FALSE,
			      (GDK_POINTER_MOTION_HINT_MASK |
			       GDK_BUTTON1_MOTION_MASK |
			       GDK_BUTTON_RELEASE_MASK),
			      NULL, NULL, bevent->time);

	  /*  Find the transform bounds for some tools (like scale,
	   *  perspective) that actually need the bounds for
	   *  initializing
	   */
	  gimp_transform_tool_bounds (gt_tool, gdisp);

	  /*  Calculate the grid line endpoints  */
	  if (gimp_transform_tool_show_grid ())
	    gimp_transform_tool_setup_grid (gt_tool);

	  /*  Initialize the transform tool */
	  gimp_transform_tool_transform (gt_tool, gdisp, TRANSFORM_INIT);

	  if (transform_info && !transform_info_inited)
	    {
	      GType tool_type;

	      tool_type =
		gimp_context_get_tool (gimp_get_user_context (gdisp->gimage->gimp))->tool_type;

	      gimp_dialog_create_action_area
		(GIMP_DIALOG (transform_info->shell),

		 /* FIXME: this does not belong here */
		 (tool_type == GIMP_TYPE_ROTATE_TOOL)      ? _("Rotate")    :
		 (tool_type == GIMP_TYPE_SCALE_TOOL)       ? _("Scale")     :
		 (tool_type == GIMP_TYPE_SHEAR_TOOL)       ? _("Shear")     :
		 (tool_type == GIMP_TYPE_PERSPECTIVE_TOOL) ? _("Transform") :
		 "EEK",
		 transform_ok_callback,
		 tool, NULL, NULL, TRUE, FALSE,

		 _("Reset"), transform_reset_callback,
		 tool, NULL, NULL, FALSE, FALSE,

		 NULL);

	      transform_info_inited = TRUE;
	    }

	  /*  Recalculate the transform tool  */
	  gimp_transform_tool_recalc (gt_tool, gdisp);

	  /*  recall this function to find which handle we're dragging  */
	  if (gt_tool->interactive)
	    gimp_transform_tool_button_press (tool, bevent, gdisp);
	}
    }
}

static void
gimp_transform_tool_button_release (GimpTool       *tool,
			            GdkEventButton *bevent,
			            GimpDisplay    *gdisp)
{
  GimpTransformTool *gt_tool;
  gint               i;

  gt_tool = GIMP_TRANSFORM_TOOL (tool);

  gt_tool->bpressed = FALSE; /* ALT */

  /*  if we are creating, there is nothing to be done...exit  */
  if (gt_tool->function == TRANSFORM_CREATING && gt_tool->interactive)
    return;

  /*  release of the pointer grab  */
  gdk_pointer_ungrab (bevent->time);
  gdk_flush ();

  /*  if the 3rd button isn't pressed, transform the selected mask  */
  if (! (bevent->state & GDK_BUTTON3_MASK))
    {
      /* Shift-clicking is another way to approve the transform  */
      if ((bevent->state & GDK_SHIFT_MASK) || GIMP_IS_FLIP_TOOL (tool))
	{
	  gimp_transform_tool_doit (gt_tool, gdisp);
	}
      else
	{
	  /*  Only update the paths preview */
	  path_transform_current_path (gdisp->gimage,
				       gt_tool->transform, TRUE);
	}
    }
  else
    {
      /*  stop the current tool drawing process  */
      gimp_draw_tool_pause (GIMP_DRAW_TOOL (tool));

      /*  Restore the previous transformation info  */
      for (i = 0; i < TRAN_INFO_SIZE; i++)
	gt_tool->trans_info [i] = old_trans_info [i];

      /*  recalculate the tool's transformation matrix  */
      gimp_transform_tool_recalc (gt_tool, gdisp);

      /*  resume drawing the current tool  */
      gimp_draw_tool_resume (GIMP_DRAW_TOOL (tool));

      /* Update the paths preview */
      path_transform_current_path (gdisp->gimage,
				   gt_tool->transform, TRUE);
    }

  /*  if this tool is non-interactive, make it inactive after use  */
  if (!gt_tool->interactive)
    tool->state = INACTIVE;
}

static void
gimp_transform_tool_doit (GimpTransformTool  *gt_tool,
		          GimpDisplay        *gdisp)
{
  GimpTool      *tool;
  TileManager   *new_tiles;
  TransformUndo *tu;
  PathUndo      *pundo;
  gboolean       new_layer;
  gint           i, x, y;

  gimp_set_busy (gdisp->gimage->gimp);

  tool = GIMP_TOOL (gt_tool);

  /* undraw the tool before we muck around with the transform matrix */
  gimp_draw_tool_pause (GIMP_DRAW_TOOL (gt_tool));

  /*  We're going to dirty this image, but we want to keep the tool
   *  around
   */
  tool->preserve = TRUE;

  /*  Start a transform undo group  */
  undo_push_group_start (gdisp->gimage, TRANSFORM_CORE_UNDO);

  /* With the old UI, if original is NULL, then this is the
   * first transformation. In the new UI, it is always so, right?
   */
  g_assert (gt_tool->original == NULL);

  /* If we're in interactive mode, we need to copy the current
   *  selection to the transform tool's private selection pointer, so
   *  that the original source can be repeatedly modified.
   */
  tool->drawable = gimp_image_active_drawable (gdisp->gimage);

  gt_tool->original = gimp_transform_tool_cut (gdisp->gimage,
					       tool->drawable,
					       &new_layer);

  pundo = path_transform_start_undo (gdisp->gimage);

  /*  Send the request for the transformation to the tool...
   */
  new_tiles = gimp_transform_tool_transform (gt_tool, gdisp,
					     TRANSFORM_FINISH);

  gimp_transform_tool_transform (gt_tool, gdisp, TRANSFORM_INIT);

  gimp_transform_tool_recalc (gt_tool, gdisp);

  if (new_tiles)
    {
      /*  paste the new transformed image to the gimage...also implement
       *  undo...
       */
      /*  FIXME: we should check if the drawable is still valid  */
      gimp_transform_tool_paste (gdisp->gimage, tool->drawable,
				 new_tiles, new_layer);

      /*  create and initialize the transform_undo structure  */
      tu = g_new0 (TransformUndo, 1);
      tu->tool_ID   = tool->ID;
      tu->tool_type = G_TYPE_FROM_INSTANCE (tool);

      for (i = 0; i < TRAN_INFO_SIZE; i++)
	tu->trans_info[i] = old_trans_info[i];

      tu->original  = NULL;
      tu->path_undo = pundo;

      /*  Make a note of the new current drawable (since we may have
       *  a floating selection, etc now.
       */
      tool->drawable = gimp_image_active_drawable (gdisp->gimage);

      undo_push_transform (gdisp->gimage, (void *) tu);
    }

  /*  push the undo group end  */
  undo_push_group_end (gdisp->gimage);

  /*  We're done dirtying the image, and would like to be restarted
   *  if the image gets dirty while the tool exists
   */
  tool->preserve = FALSE;

  /*  Flush the gdisplays  */
  if (gdisp->disp_xoffset || gdisp->disp_yoffset)
    {
      x = gdisp->canvas->allocation.width;
      y = gdisp->canvas->allocation.height;

      if (gdisp->disp_yoffset)
	{
	  gdisplay_expose_area (gdisp, 0, 0, gdisp->disp_width,
				gdisp->disp_yoffset);
	  gdisplay_expose_area (gdisp, 0, gdisp->disp_yoffset + y,
				gdisp->disp_width, gdisp->disp_height);
	}
      if (gdisp->disp_xoffset)
	{
	  gdisplay_expose_area (gdisp, 0, 0, gdisp->disp_xoffset,
				gdisp->disp_height);
	  gdisplay_expose_area (gdisp, gdisp->disp_xoffset + x, 0,
				gdisp->disp_width, gdisp->disp_height);
	}
    }

  gimp_unset_busy (gdisp->gimage->gimp);

  gdisplays_flush ();

  gimp_transform_tool_reset (gt_tool, gdisp);

  /*  if this tool is non-interactive, make it inactive after use  */
  if (!gt_tool->interactive)
    tool->state = INACTIVE;
}

static void
gimp_transform_tool_motion (GimpTool       *tool,
		            GdkEventMotion *mevent,
		            GimpDisplay    *gdisp)
{
  GimpTransformTool *tr_tool;

  tr_tool = GIMP_TRANSFORM_TOOL (tool);

  if (! tr_tool->bpressed)
    return;

  /*  if we are creating or this tool is non-interactive, there is
   *  nothing to be done so exit.
   */
  if (tr_tool->function == TRANSFORM_CREATING ||
      !tr_tool->interactive)
    return;

  /*  stop the current tool drawing process  */
  gimp_draw_tool_pause (GIMP_DRAW_TOOL (tool));

  gdisplay_untransform_coords (gdisp, mevent->x, mevent->y,
			       &tr_tool->curx,
			       &tr_tool->cury, TRUE, 0);
  tr_tool->state = mevent->state;

  /*  recalculate the tool's transformation matrix  */
  gimp_transform_tool_transform (tr_tool, gdisp, TRANSFORM_MOTION);

  tr_tool->lastx = tr_tool->curx;
  tr_tool->lasty = tr_tool->cury;

  /*  resume drawing the current tool  */
  gimp_draw_tool_resume (GIMP_DRAW_TOOL (tool));
}

static void
gimp_transform_tool_cursor_update (GimpTool       *tool,
			           GdkEventMotion *mevent,
			           GimpDisplay    *gdisp)
{
  GimpTransformTool  *tr_tool;
  GimpDrawable       *drawable;
  GdkCursorType       ctype = GDK_TOP_LEFT_ARROW;
  gint                x, y;

  tr_tool = GIMP_TRANSFORM_TOOL (tool);

  gdisplay_untransform_coords (gdisp, mevent->x, mevent->y, &x, &y,
			       FALSE, FALSE);

  if ((drawable = gimp_image_active_drawable (gdisp->gimage)))
    {
      if (GIMP_IS_LAYER (drawable) &&
	  gimp_layer_get_mask (GIMP_LAYER (drawable)))
	{
	  ctype = GIMP_BAD_CURSOR;
	}
      else if (x >= drawable->offset_x &&
	       y >= drawable->offset_y &&
	       x < (drawable->offset_x + drawable->width) &&
	       y < (drawable->offset_y + drawable->height))
	{
	  if (gimage_mask_is_empty (gdisp->gimage) ||
	      gimage_mask_value (gdisp->gimage, x, y))
	    {
	      ctype = GIMP_MOUSE_CURSOR;
	    }
	}
    }

  gdisplay_install_tool_cursor (gdisp,
				ctype,
				tool->tool_cursor,
				GIMP_CURSOR_MODIFIER_NONE);
}

static void
gimp_transform_tool_draw (GimpDrawTool *dr_tool)
{
  GimpDisplay        *gdisp;
  GimpTransformTool  *tr_tool;
  GimpTool           *tool;
  gint                x1, y1, x2, y2, x3, y3, x4, y4;
  gint                srw, srh;
  gint                i, k, gci;
  gint                xa, ya, xb, yb;

  tr_tool = GIMP_TRANSFORM_TOOL (dr_tool);
  tool    = GIMP_TOOL (dr_tool);

  gdisp   = tool->gdisp;

  gdisplay_transform_coords (gdisp, tr_tool->tx1, tr_tool->ty1,
			     &tr_tool->sx1, &tr_tool->sy1, FALSE);
  gdisplay_transform_coords (gdisp, tr_tool->tx2, tr_tool->ty2,
			     &tr_tool->sx2, &tr_tool->sy2, FALSE);
  gdisplay_transform_coords (gdisp, tr_tool->tx3, tr_tool->ty3,
			     &tr_tool->sx3, &tr_tool->sy3, FALSE);
  gdisplay_transform_coords (gdisp, tr_tool->tx4, tr_tool->ty4,
			     &tr_tool->sx4, &tr_tool->sy4, FALSE);

  x1 = tr_tool->sx1;  y1 = tr_tool->sy1;
  x2 = tr_tool->sx2;  y2 = tr_tool->sy2;
  x3 = tr_tool->sx3;  y3 = tr_tool->sy3;
  x4 = tr_tool->sx4;  y4 = tr_tool->sy4;

  /*  find the handles' width and height  */
  srw = 10;
  srh = 10;

  /*  draw the bounding box  */
  gdk_draw_line (dr_tool->win, dr_tool->gc,
		 x1, y1, x2, y2);
  gdk_draw_line (dr_tool->win, dr_tool->gc,
		 x2, y2, x4, y4);
  gdk_draw_line (dr_tool->win, dr_tool->gc,
		 x3, y3, x4, y4);
  gdk_draw_line (dr_tool->win, dr_tool->gc,
		 x3, y3, x1, y1);

  /*  Draw the grid */

  if ((tr_tool->grid_coords != NULL) &&
      (tr_tool->tgrid_coords != NULL) /* FIXME!!! this doesn't belong here &&
      ((tool->type != PERSPECTIVE)  ||
       ((tr_tool->transform[0][0] >=0.0) &&
	(tr_tool->transform[1][1] >=0.0)) */ ) 
    {
      gci = 0;
      k = tr_tool->ngx + tr_tool->ngy;

      for (i = 0; i < k; i++)
	{
	  gdisplay_transform_coords (gdisp, tr_tool->tgrid_coords[gci],
				     tr_tool->tgrid_coords[gci+1],
				     &xa, &ya, FALSE);
	  gdisplay_transform_coords (gdisp, tr_tool->tgrid_coords[gci+2],
				     tr_tool->tgrid_coords[gci+3],
				     &xb, &yb, FALSE);

	  gdk_draw_line (dr_tool->win, dr_tool->gc,
			 xa, ya, xb, yb);
	  gci += 4;
	}
    }

  /*  draw the tool handles  */
  gdk_draw_rectangle (dr_tool->win, dr_tool->gc, 0,
		      x1 - (srw >> 1), y1 - (srh >> 1), srw, srh);
  gdk_draw_rectangle (dr_tool->win, dr_tool->gc, 0,
		      x2 - (srw >> 1), y2 - (srh >> 1), srw, srh);
  gdk_draw_rectangle (dr_tool->win, dr_tool->gc, 0,
		      x3 - (srw >> 1), y3 - (srh >> 1), srw, srh);
  gdk_draw_rectangle (dr_tool->win, dr_tool->gc, 0,
		      x4 - (srw >> 1), y4 - (srh >> 1), srw, srh);

  /*  draw the center  */
  gdisplay_transform_coords (gdisp,
			     tr_tool->tcx, tr_tool->tcy,
			     &tr_tool->scx, &tr_tool->scy, FALSE);

  gdk_draw_arc (dr_tool->win,
		dr_tool->gc,
		1,
		tr_tool->scx - (srw >> 1),
		tr_tool->scy - (srh >> 1),
		srw, srh, 0, 23040);

  if (gimp_transform_tool_showpath ())
    {
      GimpMatrix3 tmp_matrix;

      if (gimp_transform_tool_direction () == TRANSFORM_CORRECTIVE)
	{
	  gimp_matrix3_invert (tr_tool->transform, tmp_matrix);
	}
      else
	{
	  gimp_matrix3_duplicate (tr_tool->transform, tmp_matrix);
	}

      path_transform_draw_current (gdisp, dr_tool, tmp_matrix);
    }
}

void
gimp_transform_tool_transform_bounding_box (GimpTransformTool *tr_tool)
{
  GimpTool  *tool;
  gint       i, k;
  gint       gci;

  tool = GIMP_TOOL (tr_tool);

  gimp_matrix3_transform_point (tr_tool->transform,
				tr_tool->x1, tr_tool->y1,
				&tr_tool->tx1, &tr_tool->ty1);
  gimp_matrix3_transform_point (tr_tool->transform,
				tr_tool->x2, tr_tool->y1,
				&tr_tool->tx2, &tr_tool->ty2);
  gimp_matrix3_transform_point (tr_tool->transform,
				tr_tool->x1, tr_tool->y2,
				&tr_tool->tx3, &tr_tool->ty3);
  gimp_matrix3_transform_point (tr_tool->transform,
				tr_tool->x2, tr_tool->y2,
				&tr_tool->tx4, &tr_tool->ty4);

  gimp_matrix3_transform_point (tr_tool->transform,
				tr_tool->cx, tr_tool->cy,
				&tr_tool->tcx, &tr_tool->tcy);

  if (tr_tool->grid_coords != NULL &&
      tr_tool->tgrid_coords != NULL)
    {
      gci = 0;
      k  = (tr_tool->ngx + tr_tool->ngy) * 2;

      for (i = 0; i < k; i++)
	{
	  gimp_matrix3_transform_point (tr_tool->transform,
					tr_tool->grid_coords[gci],
					tr_tool->grid_coords[gci+1],
					&(tr_tool->tgrid_coords[gci]),
					&(tr_tool->tgrid_coords[gci+1]));
	  gci += 2;
	}
    }
}

void
gimp_transform_tool_reset (GimpTransformTool *tr_tool,
		           GimpDisplay       *gdisp)
{
  GimpTool *tool;

  tool = GIMP_TOOL (tr_tool);

  if (tr_tool->original)
    {
      tile_manager_destroy (tr_tool->original);
      tr_tool->original = NULL;
    }

  /*  inactivate the tool  */
  tr_tool->function = TRANSFORM_CREATING;
  gimp_draw_tool_stop (GIMP_DRAW_TOOL (tr_tool));
  info_dialog_popdown (transform_info);

  tool->state    = INACTIVE;
  tool->gdisp    = NULL;
  tool->drawable = NULL;
}

static void
gimp_transform_tool_bounds (GimpTransformTool *tr_tool,
		            GimpDisplay       *gdisp)
{
  TileManager  *tiles;
  GimpDrawable *drawable;
  gint          offset_x, offset_y;

  tiles    = tr_tool->original;
  drawable = gimp_image_active_drawable (gdisp->gimage);

  /*  find the boundaries  */
  if (tiles)
    {
      tile_manager_get_offsets (tiles,
				&tr_tool->x1, &tr_tool->y1);
				
      tr_tool->x2 = tr_tool->x1 + tile_manager_width (tiles);
      tr_tool->y2 = tr_tool->y1 + tile_manager_height (tiles);
    }
  else
    {
      gimp_drawable_offsets (drawable, &offset_x, &offset_y);
      gimp_drawable_mask_bounds (drawable,
				 &tr_tool->x1, &tr_tool->y1,
				 &tr_tool->x2, &tr_tool->y2);
      tr_tool->x1 += offset_x;
      tr_tool->y1 += offset_y;
      tr_tool->x2 += offset_x;
      tr_tool->y2 += offset_y;
    }

  tr_tool->cx = (tr_tool->x1 + tr_tool->x2) / 2;
  tr_tool->cy = (tr_tool->y1 + tr_tool->y2) / 2;

  /*  changing the bounds invalidates any grid we may have  */
  gimp_transform_tool_grid_recalc (tr_tool);
}

void
gimp_transform_tool_grid_density_changed (void)
{
  GimpTransformTool *tr_tool;
  GimpDrawTool      *dr_tool;

  /* EEEK!!! */ 
  tr_tool = GIMP_TRANSFORM_TOOL (tool_manager_get_active (the_gimp));

  dr_tool = GIMP_DRAW_TOOL (tr_tool);

  if (tr_tool->function == TRANSFORM_CREATING)
    return;

  gimp_draw_tool_pause (dr_tool);

  gimp_transform_tool_grid_recalc (tr_tool);
  gimp_transform_tool_transform_bounding_box (tr_tool);

  gimp_draw_tool_resume (dr_tool);
}

void
gimp_transform_tool_showpath_changed (gint type /* a truly undescriptive name */)
{
  GimpTransformTool *tr_tool;

  /* EEEEEEEK!!! */ 
  tr_tool = GIMP_TRANSFORM_TOOL (tool_manager_get_active (the_gimp));

  if (tr_tool->function == TRANSFORM_CREATING)
    return;

  if (type)
    gimp_draw_tool_pause (GIMP_DRAW_TOOL (tr_tool));
  else
    gimp_draw_tool_resume (GIMP_DRAW_TOOL (tr_tool));
}

static void
gimp_transform_tool_grid_recalc (GimpTransformTool *tr_tool)
{
  if (tr_tool->grid_coords != NULL)
    {
      g_free (tr_tool->grid_coords);
      tr_tool->grid_coords = NULL;
    }

  if (tr_tool->tgrid_coords != NULL)
    {
      g_free (tr_tool->tgrid_coords);
      tr_tool->tgrid_coords = NULL;
    }

  if (gimp_transform_tool_show_grid ())
    gimp_transform_tool_setup_grid (tr_tool);
}

static void
gimp_transform_tool_setup_grid (GimpTransformTool *tr_tool)
{
  GimpTool *tool;
  gint      i, gci;
  gdouble  *coords;

  tool = GIMP_TOOL (tr_tool);

  /*  We use the gimp_transform_tool_grid_size function only here, even
   *  if the user changes the grid size in the middle of an
   *  operation, nothing happens.
   */
  tr_tool->ngx = (tr_tool->x2 - tr_tool->x1) / gimp_transform_tool_grid_size ();
  if (tr_tool->ngx > 0)
    tr_tool->ngx--;

  tr_tool->ngy = (tr_tool->y2 - tr_tool->y1) / gimp_transform_tool_grid_size ();
  if (tr_tool->ngy > 0)
    tr_tool->ngy--;

  tr_tool->grid_coords = coords =
    g_new (gdouble, (tr_tool->ngx + tr_tool->ngy) * 4);

  tr_tool->tgrid_coords =
    g_new (gdouble, (tr_tool->ngx + tr_tool->ngy) * 4);

  gci = 0;

  for (i = 1; i <= tr_tool->ngx; i++)
    {
      coords[gci] = tr_tool->x1 +
	((gdouble) i) / (tr_tool->ngx + 1) *
	(tr_tool->x2 - tr_tool->x1);
      coords[gci+1] = tr_tool->y1;
      coords[gci+2] = coords[gci];
      coords[gci+3] = tr_tool->y2;
      gci += 4;
    }

  for (i = 1; i <= tr_tool->ngy; i++)
    {
      coords[gci] = tr_tool->x1;
      coords[gci+1] = tr_tool->y1 +
	((gdouble) i) / (tr_tool->ngy + 1) *
	(tr_tool->y2 - tr_tool->y1);
      coords[gci+2] = tr_tool->x2;
      coords[gci+3] = coords[gci+1];
      gci += 4;
    }
}

static void
gimp_transform_tool_recalc (GimpTransformTool *tr_tool,
		            GimpDisplay       *gdisp)
{
  gimp_transform_tool_bounds (tr_tool, gdisp);

  gimp_transform_tool_transform (tr_tool, gdisp, TRANSFORM_RECALC);
}

/*  Actually carry out a transformation  */
TileManager *
gimp_transform_tool_do (GimpImage        *gimage,
                        GimpDrawable     *drawable,
                        TileManager      *float_tiles,
                        gboolean          interpolation,
                        GimpMatrix3       matrix,
                        GimpProgressFunc  progress_callback,
                        gpointer          progress_data)
{
  PixelRegion  destPR;
  TileManager *tiles;
  GimpMatrix3  m;
  GimpMatrix3  im;
  gint         itx, ity;
  gint         tx1, ty1, tx2, ty2;
  gint         width, height;
  gint         alpha;
  gint         bytes, b;
  gint         x, y;
  gint         sx, sy;
  gint         x1, y1, x2, y2;
  gdouble      xinc, yinc, winc;
  gdouble      tx, ty, tw;
  gdouble      ttx = 0.0, tty = 0.0;
  guchar      *dest;
  guchar      *d;
  guchar      *src[16];
  Tile        *tile[16];
  guchar       bg_col[MAX_CHANNELS];
  gint         i;
  gdouble      a_val, a_recip;
  gint         newval;

  PixelSurround surround;

  alpha = 0;

  /*  turn interpolation off for simple transformations (e.g. rot90)  */
  if (gimp_matrix3_is_simple (matrix) ||
      base_config->interpolation_type == NEAREST_NEIGHBOR_INTERPOLATION)
    interpolation = FALSE;

  /*  Get the background color  */
  gimp_image_get_background (gimage, drawable, bg_col);

  switch (gimp_drawable_type (drawable))
    {
    case RGB_GIMAGE: case RGBA_GIMAGE:
      bg_col[ALPHA_PIX] = TRANSPARENT_OPACITY;
      alpha = ALPHA_PIX;
      break;
    case GRAY_GIMAGE: case GRAYA_GIMAGE:
      bg_col[ALPHA_G_PIX] = TRANSPARENT_OPACITY;
      alpha = ALPHA_G_PIX;
      break;
    case INDEXED_GIMAGE: case INDEXEDA_GIMAGE:
      bg_col[ALPHA_I_PIX] = TRANSPARENT_OPACITY;
      alpha = ALPHA_I_PIX;
      /*  If the gimage is indexed color, ignore smoothing value  */
      interpolation = FALSE;
      break;
    default:
      g_assert_not_reached ();
      break;
    }

  /*  enable rotating un-floated non-layers  */
  if (tile_manager_bpp (float_tiles) == 1)
    {
      bg_col[0] = OPAQUE_OPACITY;

      /*  setting alpha = 0 will cause the channel's value to be treated
       *  as alpha and the color channel loops never to be entered
       */
      alpha = 0;
    }

  if (gimp_transform_tool_direction () == TRANSFORM_CORRECTIVE)
    {
      /*  keep the original matrix here, so we dont need to recalculate
	  the inverse later  */
      gimp_matrix3_duplicate (matrix, m);
      gimp_matrix3_invert (matrix, im);
      matrix = im;
    }
  else
    {
      /*  Find the inverse of the transformation matrix  */
      gimp_matrix3_invert (matrix, m);
    }

  path_transform_current_path (gimage, matrix, FALSE);

  tile_manager_get_offsets (float_tiles, &x1, &y1);
  x2 = x1 + tile_manager_width (float_tiles);
  y2 = y1 + tile_manager_height (float_tiles);

  /*  Find the bounding coordinates  */
  if (alpha == 0 || (tool_manager_get_active (gimage->gimp) &&
		     gimp_transform_tool_clip ()))
    {
      tx1 = x1;
      ty1 = y1;
      tx2 = x2;
      ty2 = y2;
    }
  else
    {
      gdouble dx1, dy1;
      gdouble dx2, dy2;
      gdouble dx3, dy3;
      gdouble dx4, dy4;

      gimp_matrix3_transform_point (matrix, x1, y1, &dx1, &dy1);
      gimp_matrix3_transform_point (matrix, x2, y1, &dx2, &dy2);
      gimp_matrix3_transform_point (matrix, x1, y2, &dx3, &dy3);
      gimp_matrix3_transform_point (matrix, x2, y2, &dx4, &dy4);

      tx1 = MIN (dx1, dx2);
      tx1 = MIN (tx1, dx3);
      tx1 = MIN (tx1, dx4);
      ty1 = MIN (dy1, dy2);
      ty1 = MIN (ty1, dy3);
      ty1 = MIN (ty1, dy4);

      tx2 = MAX (dx1, dx2);
      tx2 = MAX (tx2, dx3);
      tx2 = MAX (tx2, dx4);
      ty2 = MAX (dy1, dy2);
      ty2 = MAX (ty2, dy3);
      ty2 = MAX (ty2, dy4);
    }

  /*  Get the new temporary buffer for the transformed result  */
  tiles = tile_manager_new ((tx2 - tx1), (ty2 - ty1),
			    tile_manager_bpp (float_tiles));
  pixel_region_init (&destPR, tiles, 0, 0, (tx2 - tx1), (ty2 - ty1), TRUE);
  tile_manager_set_offsets (tiles, tx1, ty1);

  /* initialise the pixel_surround accessor */
  if (interpolation)
    {
      if (base_config->interpolation_type == CUBIC_INTERPOLATION)
	{
	  pixel_surround_init (&surround, float_tiles, 4, 4, bg_col);
	}
      else
	{
	  pixel_surround_init (&surround, float_tiles, 2, 2, bg_col);
	}
    }
  else
    {
      /* not actually useful, keeps the code cleaner */
      pixel_surround_init (&surround, float_tiles, 1, 1, bg_col);
    }

  width  = tile_manager_width (tiles);
  height = tile_manager_height (tiles);
  bytes  = tile_manager_bpp (tiles);

  dest = g_new (guchar, width * bytes);

  xinc = m[0][0];
  yinc = m[1][0];
  winc = m[2][0];

  /* these loops could be rearranged, depending on which bit of code
   * you'd most like to write more than once.
   */

  for (y = ty1; y < ty2; y++)
    {
      if (progress_callback && !(y & 0xf))
	(* progress_callback) (ty1, ty2, y, progress_data);

      /* set up inverse transform steps */
      tx = xinc * tx1 + m[0][1] * y + m[0][2];
      ty = yinc * tx1 + m[1][1] * y + m[1][2];
      tw = winc * tx1 + m[2][1] * y + m[2][2];

      d = dest;
      for (x = tx1; x < tx2; x++)
	{
	  /*  normalize homogeneous coords  */
	  if (tw == 0.0)
	    {
	      g_warning ("homogeneous coordinate = 0...\n");
	    }
	  else if (tw != 1.0)
	    {
	      ttx = tx / tw;
	      tty = ty / tw;
	    }
	  else
	    {
	      ttx = tx;
	      tty = ty;
	    }

          /*  Set the destination pixels  */

          if (interpolation)
       	    {
              if (base_config->interpolation_type == CUBIC_INTERPOLATION)
       	        {
                  /*  ttx & tty are the subpixel coordinates of the point in
		   *  the original selection's floating buffer.
		   *  We need the four integer pixel coords around them:
		   *  itx to itx + 3, ity to ity + 3
                   */
                  itx = floor (ttx);
                  ity = floor (tty);

		  /* check if any part of our region overlaps the buffer */

                  if ((itx + 2) >= x1 && (itx - 1) < x2 &&
                      (ity + 2) >= y1 && (ity - 1) < y2 )
                    {
                      guchar  *data;
                      gint     row;
                      gdouble  dx, dy;
                      guchar  *start;

		      /* lock the pixel surround */
                      data = pixel_surround_lock (&surround,
						  itx - 1 - x1, ity - 1 - y1);

                      row = pixel_surround_rowstride (&surround);

                      /* the fractional error */
                      dx = ttx - itx;
                      dy = tty - ity;

		      /* calculate alpha of result */
		      start = &data[alpha];
		      a_val = gimp_transform_tool_cubic
			(dy,
			 CUBIC_ROW (dx, start, bytes),
			 CUBIC_ROW (dx, start + row, bytes),
			 CUBIC_ROW (dx, start + row + row, bytes),
			 CUBIC_ROW (dx, start + row + row + row, bytes));

		      if (a_val <= 0.0)
			{
			  a_recip = 0.0;
			  d[alpha] = 0;
			}
		      else if (a_val > 255.0)
			{
			  a_recip = 1.0 / a_val;
			  d[alpha] = 255;
			}
		      else
			{
			  a_recip = 1.0 / a_val;
			  d[alpha] = RINT(a_val);
			}

		      /*  for colour channels c,
		       *  result = bicubic (c * alpha) / bicubic (alpha)
		       *
		       *  never entered for alpha == 0
		       */
		      for (i = -alpha; i < 0; ++i)
			{
			  start = &data[alpha];
			  newval =
			    RINT (a_recip *
				  gimp_transform_tool_cubic
				  (dy,
				   CUBIC_SCALED_ROW (dx, start, bytes, i),
				   CUBIC_SCALED_ROW (dx, start + row, bytes, i),
				   CUBIC_SCALED_ROW (dx, start + row + row, bytes, i),
				   CUBIC_SCALED_ROW (dx, start + row + row + row, bytes, i)));
			  if (newval <= 0)
			    {
			      *d++ = 0;
			    }
			  else if (newval > 255)
			    {
			      *d++ = 255;
			    }
			  else
			    {
			      *d++ = newval;
			    }
			}

		      /*  alpha already done  */
		      d++;

		      pixel_surround_release (&surround);
		    }
                  else /* not in source range */
                    {
                      /*  increment the destination pointers  */
                      for (b = 0; b < bytes; b++)
                        *d++ = bg_col[b];
                    }
                }

       	      else  /*  linear  */
                {
                  itx = floor (ttx);
                  ity = floor (tty);

		  /*  expand source area to cover interpolation region
		   *  (which runs from itx to itx + 1, same in y)
		   */
                  if ((itx + 1) >= x1 && itx < x2 &&
                      (ity + 1) >= y1 && ity < y2 )
                    {
                      guchar  *data;
                      gint     row;
                      double   dx, dy;
                      guchar  *chan;

		      /* lock the pixel surround */
                      data = pixel_surround_lock (&surround, itx - x1, ity - y1);

                      row = pixel_surround_rowstride (&surround);

                      /* the fractional error */
                      dx = ttx - itx;
                      dy = tty - ity;

		      /* calculate alpha value of result pixel */
		      chan = &data[alpha];
		      a_val = BILINEAR (chan[0], chan[bytes], chan[row],
					chan[row+bytes], dx, dy);
		      if (a_val <= 0.0)
			{
			  a_recip = 0.0;
			  d[alpha] = 0.0;
			}
		      else if (a_val >= 255.0)
			{
			  a_recip = 1.0 / a_val;
			  d[alpha] = 255;
			}
		      else
			{
			  a_recip = 1.0 / a_val;
			  d[alpha] = RINT (a_val);
			}

		      /*  for colour channels c,
		       *  result = bilinear (c * alpha) / bilinear (alpha)
		       *
		       *  never entered for alpha == 0
		       */
		      for (i = -alpha; i < 0; ++i)
			{
			  chan = &data[alpha];
			  newval =
			    RINT (a_recip *
				  BILINEAR (chan[0] * chan[i],
					    chan[bytes] * chan[bytes+i],
					    chan[row] * chan[row+i],
					    chan[row+bytes] * chan[row+bytes+i],
					    dx, dy));
			  if (newval <= 0)
			    {
			      *d++ = 0;
			    }
			  else if (newval > 255)
			    {
			      *d++ = 255;
			    }
			  else
			    {
			      *d++ = newval;
			    }
			}

		      /*  alpha already done  */
		      d++;

                      pixel_surround_release (&surround);
		    }
                  else /* not in source range */
                    {
                      /*  increment the destination pointers  */
                      for (b = 0; b < bytes; b++)
                        *d++ = bg_col[b];
                    }
		}
	    }
          else  /*  no interpolation  */
            {
              itx = floor (ttx);
              ity = floor (tty);

              if (itx >= x1 && itx < x2 &&
                  ity >= y1 && ity < y2 )
                {
                  /*  x, y coordinates into source tiles  */
                  sx = itx - x1;
                  sy = ity - y1;

                  REF_TILE (0, sx, sy);

                  for (b = 0; b < bytes; b++)
                    *d++ = src[0][b];

                  tile_release (tile[0], FALSE);
		}
              else /* not in source range */
                {
                  /*  increment the destination pointers  */
                  for (b = 0; b < bytes; b++)
                    *d++ = bg_col[b];
                }
	    }

	  /*  increment the transformed coordinates  */
	  tx += xinc;
	  ty += yinc;
	  tw += winc;
	}

      /*  set the pixel region row  */
      pixel_region_set_row (&destPR, 0, (y - ty1), width, dest);
    }

  pixel_surround_clear (&surround);

  g_free (dest);

  return tiles;
}

TileManager *
gimp_transform_tool_cut (GimpImage    *gimage,
		         GimpDrawable *drawable,
		         gboolean     *new_layer)
{
  TileManager *tiles;

  /*  extract the selected mask if there is a selection  */
  if (! gimage_mask_is_empty (gimage))
    {
      /* set the keep_indexed flag to FALSE here, since we use
	 gimp_layer_new_from_tiles() later which assumes that the tiles
	 are either RGB or GRAY.  Eeek!!!              (Sven)
       */
      tiles = gimage_mask_extract (gimage, drawable, TRUE, FALSE, TRUE);
      *new_layer = TRUE;
    }
  /*  otherwise, just copy the layer  */
  else
    {
      if (GIMP_IS_LAYER (drawable))
	tiles = gimage_mask_extract (gimage, drawable, FALSE, TRUE, TRUE);
      else
	tiles = gimage_mask_extract (gimage, drawable, FALSE, TRUE, FALSE);
      *new_layer = FALSE;
    }

  return tiles;
}


/*  Paste a transform to the gdisplay  */
gboolean
gimp_transform_tool_paste (GimpImage    *gimage,
		           GimpDrawable *drawable,
		           TileManager  *tiles,
		           gboolean      new_layer)
{
  GimpLayer   *layer   = NULL;
  GimpChannel *channel = NULL;
  GimpLayer   *floating_layer;

  if (new_layer)
    {
      layer =
	gimp_layer_new_from_tiles (gimage,
				   gimp_drawable_type_with_alpha (drawable),
				   tiles,
				   _("Transformation"),
				   OPAQUE_OPACITY, NORMAL_MODE);
      if (! layer)
        {
          g_warning ("%s: gimp_layer_new_frome_tiles() failed",
		     G_GNUC_FUNCTION);
          return FALSE;
        }

      tile_manager_get_offsets (tiles, 
				&(GIMP_DRAWABLE (layer)->offset_x),
				&(GIMP_DRAWABLE (layer)->offset_y));

      /*  Start a group undo  */
      undo_push_group_start (gimage, EDIT_PASTE_UNDO);

      floating_sel_attach (layer, drawable);

      /*  End the group undo  */
      undo_push_group_end (gimage);

      /*  Free the tiles  */
      tile_manager_destroy (tiles);

      return TRUE;
    }
  else
    {
      if (GIMP_IS_LAYER (drawable))
	layer = GIMP_LAYER (drawable);
      else if (GIMP_IS_CHANNEL (drawable))
	channel = GIMP_CHANNEL (drawable);
      else
	return FALSE;

      if (layer)
	gimp_layer_add_alpha (layer);

      floating_layer = gimp_image_floating_sel (gimage);

      if (floating_layer)
	floating_sel_relax (floating_layer, TRUE);

      gdisplays_update_area (gimage,
			     drawable->offset_x,
			     drawable->offset_y,
			     drawable->width,
			     drawable->height);

      /*  Push an undo  */
      if (layer)
	undo_push_layer_mod (gimage, layer);
      else if (channel)
	undo_push_channel_mod (gimage, channel);

      /*  set the current layer's data  */
      drawable->tiles = tiles;

      /*  Fill in the new layer's attributes  */
      drawable->width    = tile_manager_width (tiles);
      drawable->height   = tile_manager_height (tiles);
      drawable->bytes    = tile_manager_bpp (tiles);
      tile_manager_get_offsets (tiles, 
				&drawable->offset_x, &drawable->offset_y);

      if (floating_layer)
	floating_sel_rigor (floating_layer, TRUE);

      gimp_drawable_update (drawable,
			    0, 0,
			    gimp_drawable_width (drawable),
			    gimp_drawable_height (drawable));

      /*  if we were operating on the floating selection, then it's boundary 
       *  and previews need invalidating
       */
      if (drawable == (GimpDrawable *) floating_layer)
	floating_sel_invalidate (floating_layer);

      return TRUE;
    }
}

/* Note: cubic function no longer clips result */
static gdouble
gimp_transform_tool_cubic (gdouble dx,
			   gint    jm1,
			   gint    j,
			   gint    jp1,
			   gint    jp2)
{
  gdouble result;

#if 0
  /* Equivalent to Gimp 1.1.1 and earlier - some ringing */
  result = ((( ( - jm1 + j - jp1 + jp2 ) * dx +
               ( jm1 + jm1 - j - j + jp1 - jp2 ) ) * dx +
               ( - jm1 + jp1 ) ) * dx + j );
  /* Recommended by Mitchell and Netravali - too blurred? */
  result = ((( ( - 7 * jm1 + 21 * j - 21 * jp1 + 7 * jp2 ) * dx +
               ( 15 * jm1 - 36 * j + 27 * jp1 - 6 * jp2 ) ) * dx +
               ( - 9 * jm1 + 9 * jp1 ) ) * dx + (jm1 + 16 * j + jp1) ) / 18.0;
#endif

  /* Catmull-Rom - not bad */
  result = ((( ( - jm1 + 3 * j - 3 * jp1 + jp2 ) * dx +
               ( 2 * jm1 - 5 * j + 4 * jp1 - jp2 ) ) * dx +
               ( - jm1 + jp1 ) ) * dx + (j + j) ) / 2.0;

  return result;
}
