/* 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., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

/* IWarp  a plug-in for the GIMP
   Version 0.1 
   
   IWarp is a gimp plug-in for interactive image warping. To apply the 
   selected deformation to the image, press the left mouse button and 
   move the mouse pointer in the preview image.
   
   Copyright (C) 1997 Norbert Schmitz
   nobert.schmitz@student.uni-tuebingen.de

   Most of the gimp and gtk specific code is taken from other plug-ins 

   v0.11a
    animation of non-alpha layers (background) creates now layers with 
    alpha channel. (thanks to Adrian Likins for reporting this bug)

*/

#include <stdlib.h>
#include <math.h>
#include <stdio.h>
#include "gtk/gtk.h"
#include "libgimp/gimp.h"

#ifndef M_PI
#define M_PI    3.14159265358979323846
#endif /* M_PI */

#define MAX_PREVIEW_WIDTH 256
#define MAX_PREVIEW_HEIGHT 256
#define MAX_DEFORM_AREA_RADIUS 100

#define SCALE_WIDTH 150
#define MAX_NUM_FRAMES 100
#define CHECK_SIZE 32
#define CHECK_LIGHT 170
#define CHECK_DARK  85

typedef struct 
{
 gfloat x;
 gfloat y;
}vector_2d;
 

typedef struct
{
 gint run;
}iwarp_interface;
 
typedef struct
{
 gint deform_area_radius;
 gfloat deform_amount;
 gint do_grow;
 gint do_shrink;
 gint do_move;
 gint do_remove;
 gint do_swirl_ccw;
 gint do_swirl_cw;
 gint do_bilinear;
 gint do_supersample;
 gfloat supersample_threshold;
 gint max_supersample_depth;
} iwarp_vals_t; 


/* Declare local functions.
 */
static void      query  (void);
static void      run    (char      *name,
			 int        nparams,
			 GParam    *param,
			 int       *nreturn_vals,
			 GParam   **return_vals);

static gint      iwarp_dialog();
static void      iwarp();
static void      iwarp_frame();

static void      iwarp_close_callback  (GtkWidget *widget,
					 gpointer   data);
static void      iwarp_ok_callback     (GtkWidget *widget,
					 gpointer   data);
static gint      iwarp_motion_callback (GtkWidget *widget,
                                          GdkEvent *event);
static void      iwarp_iscale_update (GtkAdjustment *adjustment,
                                      gint* scale_val);
static void      iwarp_fscale_update (GtkAdjustment *adjustment,
                                      gfloat* scale_val);
static void      iwarp_toggle_update (GtkWidget *widget,
                                        int  *data);
static void      iwarp_supersample_toggle (GtkWidget *widget,
                                        int  *data);
static void      iwarp_animate_toggle (GtkWidget *widget,
                                        int  *data);

static void      iwarp_reset_callback (GtkWidget *widget,
		                       gpointer   data);


static void      iwarp_update_preview(int x0, int y0,int x1,int y1);

static gint32    iwarp_layer_copy(gint32 layerID);

static void      iwarp_get_pixel(int x, int y, guchar *pixel);

static void      iwarp_get_deform_vector(gfloat x, gfloat y, 
                                         gfloat* xv, gfloat* yv);
                                         
static void      iwarp_get_point(gfloat x, gfloat y, guchar *color);

static gint      iwarp_supersample_test(vector_2d* v0 , vector_2d* v1, 
                                        vector_2d* v2, vector_2d* v3);
                                        
static void      iwarp_getsample(vector_2d v0, vector_2d  v1,
                                 vector_2d v2, vector_2d v3, 
                                 gfloat x, gfloat y,gint* sample,gint* cc,
                                 gint depth,gfloat scale);
                                 
static void      iwarp_supersample(gint sxl,gint syl ,gint sxr, gint syr,
                                   guchar* dest_data,int stride,
                                   int* progress,int max_progress);
                                   
static guchar    iwarp_transparent_color(int x, int y);

static void      iwarp_cpy_images();

static void      iwarp_animate_dialog(GtkWidget* dlg, GtkWidget* notebook);
                                 
static void      iwarp_settings_dialog(GtkWidget* dlg, GtkWidget* notebook);                                 

static void      iwarp_preview_get_pixel(int x, int y , guchar **color);

static void      iwarp_preview_get_point( gfloat x, gfloat y,guchar* color);

static void      iwarp_deform(int x, int y, gfloat vx, gfloat vy);

static void      iwarp_move(int x, int y,int xx, int yy);


GPlugInInfo PLUG_IN_INFO =
{
  NULL,    /* init_proc */
  NULL,    /* quit_proc */
  query,   /* query_proc */
  run,     /* run_proc */
};

static iwarp_interface wint = 
{
  FALSE
};
static iwarp_vals_t iwarp_vals = 
{
 20,
 0.3,
 FALSE,
 FALSE,
 TRUE,
 FALSE,
 FALSE,
 FALSE,
 TRUE,
 FALSE,
 2.0,
 2
};  


static GDrawable *drawable = NULL;
static GDrawable *destdrawable = NULL;
static GtkWidget *preview = NULL;
static GtkWidget* supersample_frame;
static GtkWidget* animate_frame;
static guchar *srcimage = NULL;
static guchar *dstimage = NULL;
static gint preview_width, preview_height,sel_width,sel_height;
static gint image_bpp;
static vector_2d* deform_vectors = NULL;
static vector_2d* deform_area_vectors = NULL;
static int lastx, lasty;
static gfloat filter[MAX_DEFORM_AREA_RADIUS];
static int do_animate = FALSE;
static int do_animate_reverse = FALSE;
static int do_animate_ping_pong = FALSE;
static gfloat supersample_threshold_2;
static int xl,yl,xh,yh;
static int tile_width, tile_height;
static GTile* tile = NULL;
static gfloat pre2img, img2pre;
static int preview_bpp;
static gfloat animate_deform_value = 1.0;
static gint32 imageID;
static int animate_num_frames = 2;
static int frame_number;
static int layer_alpha;


MAIN ()

static void
query ()
{
  static GParamDef args[] =
  {
    { PARAM_INT32, "run_mode", "Interactive, non-interactive" },
    { PARAM_IMAGE, "image", "Input image (unused)" },
    { PARAM_DRAWABLE, "drawable", "Input drawable" },
  };
  static GParamDef *return_vals = NULL;
  static int nargs = sizeof (args) / sizeof (args[0]);
  static int nreturn_vals = 0;

  gimp_install_procedure ("plug_in_iwarp",
			  "Interactive warping of the specified drawable",
			  "Interactive warping of the specified drawable ",
			  "Norbert Schmitz",
			  "Norbert Schmitz",
			  "1997",
			  "<Image>/Filters/Distorts/IWarp",
			  "RGB*, GRAY*",
			  PROC_PLUG_IN,
			  nargs, nreturn_vals,
			  args, return_vals);
}

static void
run (char    *name,
     int      nparams,
     GParam  *param,
     int     *nreturn_vals,
     GParam **return_vals)
{
  static GParam values[1];
  GRunModeType run_mode;
  GStatusType status = STATUS_SUCCESS;

  run_mode = param[0].data.d_int32;

  /*  Get the specified drawable  */
  destdrawable = drawable = gimp_drawable_get (param[2].data.d_drawable);
  imageID = param[1].data.d_int32;
  /*  Make sure that the drawable is gray or RGB color  */
  if (gimp_drawable_color (drawable->id) || gimp_drawable_gray (drawable->id)) { 
      switch ( run_mode) {
        case RUN_INTERACTIVE :
          gimp_get_data("plug_in_iwarp",&iwarp_vals);
          gimp_tile_cache_ntiles(2*(drawable->width +gimp_tile_width()-1) / gimp_tile_width());
          if (iwarp_dialog()) iwarp();
          gimp_set_data("plug_in_iwarp",&iwarp_vals,sizeof(iwarp_vals_t));
 	  gimp_displays_flush ();
	break;
        case RUN_NONINTERACTIVE :
         status = STATUS_CALLING_ERROR;
        break;
        case RUN_WITH_LAST_VALS :
         status = STATUS_CALLING_ERROR;
        break;

        default : 
        break;	
      }
  }
  else {
      status = STATUS_EXECUTION_ERROR;
  }

  *nreturn_vals = 1;
  *return_vals = values;

  values[0].type = PARAM_STATUS;
  values[0].data.d_status = status;

  gimp_drawable_detach (drawable);
  if (srcimage != NULL) g_free(srcimage);
  if (dstimage != NULL) g_free(dstimage);  
  if (deform_vectors != NULL) g_free(deform_vectors);
  if (deform_area_vectors != NULL) g_free(deform_area_vectors);
}




static void
iwarp_get_pixel(int x, int y, guchar *pixel)
{
 static gint old_col = -1 , old_row = -1;
 guchar* data;
 gint col, row;
 int i;
 
 if (x>=xl && x < xh && y >=yl && y < yh) {
  col = x / tile_width;
  row = y / tile_height;
  if ( col != old_col || row != old_row) {
    gimp_tile_unref(tile,FALSE);
    tile = gimp_drawable_get_tile(drawable,FALSE,row,col);
    gimp_tile_ref(tile);
    old_col = col;
    old_row = row;
  }
  data = tile->data + (tile->ewidth * (y % tile_height) + x % tile_width) * image_bpp;
  for (i=0; i<image_bpp; i++) *pixel++ = *data++;
 }
 else {
  pixel[0] = pixel[1] = pixel[2] = pixel[3] = 0;
 }
}
 


static void
iwarp_get_deform_vector(gfloat x, gfloat y, gfloat* xv, gfloat* yv)
{
 int i,xi,yi;
 gfloat dx,dy,my0,my1,mx0,mx1;

 if (x>=0 && x< (preview_width-1) && y>=0  && y<(preview_height-1)) { 
  xi = (int)x;
  yi = (int)y;
  dx = x-xi;
  dy = y-yi;
  i = (yi*preview_width+ xi);
  mx0 = deform_vectors[i].x + (deform_vectors[i+1].x-deform_vectors[i].x)*dx;
  mx1 = deform_vectors[i+preview_width].x +  (deform_vectors[i+preview_width+1].x-deform_vectors[i+preview_width].x)*dx;
  my0 = deform_vectors[i].y +dx * (deform_vectors[i+1].y-deform_vectors[i].y);
  my1 = deform_vectors[i+preview_width].y + dx * (deform_vectors[i+preview_width+1].y-deform_vectors[i+preview_width].y);
  *xv = mx0 + dy *( mx1-mx0);
  *yv = my0 + dy * (my1-my0); 
 }
 else  *xv = *yv = 0.0; 
}



static void
iwarp_get_point(gfloat x, gfloat y, guchar *color)
{
  gfloat dx,dy,m0,m1;
  guchar p0[4],p1[4],p2[4],p3[4]; 
  gint xi,yi,i;
  
  xi = (int)x;
  yi = (int)y;
  dx = x-xi;
  dy = y-yi; 
  iwarp_get_pixel(xi,yi,p0);
  iwarp_get_pixel(xi+1,yi,p1);
  iwarp_get_pixel(xi,yi+1,p2);
  iwarp_get_pixel(xi+1,yi+1,p3); 	         
  for (i = 0; i < image_bpp; i++) {
    m0 = p0[i] + dx*(p1[i]-p0[i]);
    m1 = p2[i] + dx*(p3[i]-p2[i]);
    color[i] = (guchar)(m0 + dy*(m1-m0));
  }
} 


static gint
iwarp_supersample_test(vector_2d* v0 , vector_2d* v1, vector_2d* v2, vector_2d* v3)
{
 gfloat dx,dy;
 
 dx = 1.0+v1->x - v0->x;
 dy = v1->y - v0->y;
 if (dx*dx+dy*dy > supersample_threshold_2 ) 
   return 1;
 dx = 1.0+v2->x - v3->x;
 dy = v2->y - v3->y;
 if (dx*dx+dy*dy > supersample_threshold_2 ) 
   return 1;
 dx = v2->x - v0->x;
 dy = 1.0+v2->y - v0->y;
 if (dx*dx+dy*dy > supersample_threshold_2 ) 
   return 1;
 dx = v3->x - v1->x;
 dy = 1.0+v3->y - v1->y;
 if (dx*dx+dy*dy > supersample_threshold_2 ) 
   return 1; 
 return 0;
}

static void
iwarp_getsample(vector_2d v0, vector_2d  v1,vector_2d v2, vector_2d v3, 
                gfloat x, gfloat y,gint* sample,gint* cc,
                gint depth,gfloat scale)
{
 int i;
 gfloat xv,yv;
 vector_2d v01,v13,v23,v02,vm;
 guchar c[4];

 if ((depth >=  iwarp_vals.max_supersample_depth) 
      || (!iwarp_supersample_test(&v0,&v1,&v2,&v3))) { 
   iwarp_get_deform_vector(img2pre * (x - xl), img2pre * (y-yl),&xv,&yv);
   xv *= animate_deform_value; yv *= animate_deform_value;
   iwarp_get_point(pre2img*xv+x,pre2img*yv+y,c);
   for (i=0;  i< image_bpp; i++) sample[i] += c[i];
   (*cc)++;
 }
 else {
   scale *= 0.5;
   iwarp_get_deform_vector(img2pre * (x - xl), img2pre * (y-yl),&xv,&yv);
   xv *= animate_deform_value; yv *= animate_deform_value;
   iwarp_get_point(pre2img*xv+x,pre2img*yv+y,c);
   for (i=0;  i< image_bpp; i++) sample[i] += c[i];
   (*cc)++;
   vm.x = xv;
   vm.y = yv;

   iwarp_get_deform_vector(img2pre * (x - xl), img2pre * (y-yl-scale),&xv,&yv);
   xv *= animate_deform_value; yv *= animate_deform_value;
   v01.x = xv;
   v01.y = yv;
  
   iwarp_get_deform_vector(img2pre * (x - xl+scale), img2pre * (y-yl),&xv,&yv);
   xv *= animate_deform_value; yv *= animate_deform_value;
   v13.x = xv;
   v13.y = yv;
 
   iwarp_get_deform_vector(img2pre * (x - xl), img2pre * (y-yl+scale),&xv,&yv);
   xv *= animate_deform_value; yv *= animate_deform_value;
   v23.x = xv;
   v23.y = yv;
 
   iwarp_get_deform_vector(img2pre * (x - xl-scale), img2pre * (y-yl),&xv,&yv);
   xv *= animate_deform_value; yv *= animate_deform_value;
   v02.x = xv;
   v02.y = yv;
 
   iwarp_getsample(v0,v01,vm,v02,x-scale, y-scale,sample, cc, depth+1,
                   scale);
   iwarp_getsample(v01,v1,v13,vm,x+scale, y-scale,sample, cc, depth+1,
                   scale);
   iwarp_getsample(v02,vm,v23,v2,x-scale, y+scale,sample, cc, depth+1,
                  scale);
   iwarp_getsample(vm,v13,v3,v23,x+scale, y+scale,sample, cc, depth+1,
                  scale);
  }   
}


static void
iwarp_supersample(gint sxl,gint syl ,gint sxr, gint syr,guchar* dest_data,int stride,
                  int* progress,int max_progress)
{
 int i,wx,wy,col,row,cc;
 vector_2d *srow,*srow_old, *vh;
 gfloat xv,yv;
 gint color[4];
 guchar *dest;
 
 wx = sxr-sxl+1;
 wy = syr-syl+1;
 srow = g_malloc((sxr-sxl+1)*2*sizeof(gfloat));
 srow_old = g_malloc((sxr-sxl+1)*2*sizeof(gfloat));

 for (i=sxl; i< (sxr+1); i++) {
    iwarp_get_deform_vector(img2pre * (-0.5 +i-xl), img2pre * (-0.5+syl-yl),&xv,&yv);
   xv *= animate_deform_value; yv *= animate_deform_value;
   srow_old[i-sxl].x = xv;
    srow_old[i-sxl].y = yv;
 }
 
 for (col = syl; col <syr; col++) {
  iwarp_get_deform_vector(img2pre * (-0.5+sxl-xl), img2pre * (0.5+col-yl),&xv,&yv);
  xv *= animate_deform_value; yv *= animate_deform_value;
  srow[0].x = xv;
  srow[0].y = yv; 
  for (row = sxl; row <sxr; row++) {
    iwarp_get_deform_vector(img2pre * (0.5+row-xl), img2pre * (0.5+col-yl),&xv,&yv);
    xv *= animate_deform_value; yv *= animate_deform_value;
    srow[row-sxl+1].x = xv;
    srow[row-sxl+1].y = yv;
    cc = 0;
    color[0] = color[1] = color[2] = color[3] = 0;
    iwarp_getsample( srow_old[row-sxl] ,srow_old[row-sxl+1],
                     srow[row-sxl] ,srow[row-sxl+1], row,col,color,&cc,0,1.0);
    if (layer_alpha) dest = dest_data+(col-syl)*(stride)+(row-sxl)*(image_bpp+1);
    else  dest = dest_data+(col-syl)*stride+(row-sxl)*image_bpp;
    for (i=0; i<image_bpp; i++) *dest++ = color[i] / cc;
    if (layer_alpha) *dest++ = 255; 
    (*progress)++;
  }      
  gimp_progress_update((double) (*progress) / max_progress);
  vh = srow_old;
  srow_old = srow;
  srow = vh;   
 }    
 g_free(srow);
 g_free(srow_old);
}


static void
iwarp_frame()
{
  GPixelRgn dest_rgn;
  gpointer pr;
  guchar *dest_row, *dest;
  gint row, col;
  gint i;
  gint progress, max_progress;
  guchar color[4];
  gfloat xv,yv;

  progress = 0;
  max_progress = (yh-yl)*(xh-xl);

  gimp_pixel_rgn_init (&dest_rgn, destdrawable, xl, yl, xh-xl, yh-yl, TRUE, TRUE);
  if (!do_animate) gimp_progress_init("Warping ...");
  for (pr = gimp_pixel_rgns_register(1, &dest_rgn);
         pr != NULL; pr = gimp_pixel_rgns_process(pr)) {
         dest_row = dest_rgn.data;
         if (!iwarp_vals.do_supersample) {
	   for (row = dest_rgn.y; row < (dest_rgn.y + dest_rgn.h); row++) {
	     dest = dest_row;
             for (col = dest_rgn.x; col < (dest_rgn.x + dest_rgn.w); col++) {  	  
	        progress++;
	        iwarp_get_deform_vector(img2pre * (col -xl), img2pre * (row -yl), &xv ,&yv);
	        xv *= animate_deform_value; yv *= animate_deform_value;
	        if (fabs(xv) > 0.0 || fabs(yv) > 0.0) { 
	          iwarp_get_point(pre2img * xv +col, pre2img *yv +row,color);
 	          for (i=0; i<image_bpp; i++) *dest++ = color[i];
                  if (layer_alpha) *dest++ = 255;
 	        }
 	        else {
 	         iwarp_get_pixel(col,row,color);
 	         for (i=0; i<image_bpp; i++) *dest++ = color[i];
 	         if (layer_alpha) *dest++ = 255;
 	        }  
	     }
	     dest_row += dest_rgn.rowstride;
             gimp_progress_update((double) (progress) / max_progress);
	   }
	 }
	 else {
            supersample_threshold_2 = iwarp_vals.supersample_threshold * 
                                      iwarp_vals.supersample_threshold; 
	    iwarp_supersample(dest_rgn.x, dest_rgn.y, dest_rgn.x+dest_rgn.w,
	                      dest_rgn.y+dest_rgn.h,dest_rgn.data,
	                      dest_rgn.rowstride,&progress, max_progress);
	     
	 } 
	     
  }
  gimp_drawable_flush (destdrawable);
  gimp_drawable_merge_shadow (destdrawable->id, TRUE);
  gimp_drawable_update (destdrawable->id, xl, yl, (xh - xl), (yh - yl));

}

static gint32 
iwarp_layer_copy(gint32 layerID)
{
 GParam *return_vals;
 int nreturn_vals;
 gint32 nlayer;

 return_vals = gimp_run_procedure ("gimp_layer_copy", 
                                    &nreturn_vals,
                                    PARAM_LAYER, layerID,
                                    PARAM_INT32, TRUE,
                                    PARAM_END);
 
 if (return_vals[0].data.d_status == STATUS_SUCCESS)
   nlayer = return_vals[1].data.d_layer;
 else nlayer = -1;
 gimp_destroy_params(return_vals, nreturn_vals);
 return nlayer;
} 
 
                                  


static void iwarp()
{
 int i;
 gint32 layerID;
 gint32 *animlayers;
 char st[100];
 gfloat delta;

 
 if (animate_num_frames > 1 && do_animate) {
  animlayers = g_malloc(animate_num_frames * sizeof(gint32));
  if (do_animate_reverse) {
   animate_deform_value = 1.0;
   delta = -1.0/(animate_num_frames-1);
  }
  else {
   animate_deform_value = 0.0;
   delta = 1.0/(animate_num_frames-1);
  }
  layerID = gimp_image_get_active_layer(imageID);
  if (image_bpp == 1 || image_bpp == 3) layer_alpha = TRUE; else layer_alpha = FALSE;
  frame_number = 0;
  for (i=0; i< animate_num_frames; i++) {
   sprintf(st,"Frame %d",i);
   animlayers[i] = iwarp_layer_copy(layerID);
   gimp_layer_set_name(animlayers[i],st);
   destdrawable = gimp_drawable_get(animlayers[i]);
   sprintf(st,"Warping Frame Nr %d ...",frame_number);
   gimp_progress_init(st);
   if (animate_deform_value >0.0) iwarp_frame();
   gimp_image_add_layer(imageID,animlayers[i],0);
   animate_deform_value = animate_deform_value + delta;
   frame_number++;
  }
  if (do_animate_ping_pong) {
   sprintf(st,"Warping Frame Nr %d ...",frame_number);
   gimp_progress_init("Ping Pong");
   for (i=0; i < animate_num_frames; i++) {
     gimp_progress_update((double)i / (animate_num_frames-1));
     layerID = iwarp_layer_copy(animlayers[animate_num_frames-i-1]); 
     sprintf(st,"Frame %d",i+animate_num_frames);
     gimp_layer_set_name(layerID,st);
     gimp_image_add_layer(imageID,layerID,0); 
   }
  }
  g_free(animlayers);
 } 
 else {
  animate_deform_value = 1.0;
  iwarp_frame();
 } 
 if (tile != NULL) {
   gimp_tile_unref(tile, FALSE);
   tile = NULL;
 }
} 


static guchar
iwarp_transparent_color(int x, int y)
{

 if ((y % CHECK_SIZE) > (CHECK_SIZE / 2)) {
  if ((x % CHECK_SIZE) > (CHECK_SIZE / 2))  return CHECK_DARK; 
   else return CHECK_LIGHT; 
 }
 else
  if ((x % CHECK_SIZE) < (CHECK_SIZE / 2))  return CHECK_DARK; 
   else return CHECK_LIGHT;
}



static void
iwarp_cpy_images()
{
 int i,j,k,p;
 gfloat alpha;
 guchar *srccolor, *dstcolor;
 
 if (image_bpp == 1 || image_bpp ==3) 
  memcpy(dstimage,srcimage,preview_width *preview_height*preview_bpp);
 else {
  for (i=0; i< preview_width; i++) 
   for (j=0; j< preview_height; j++) {
    p = (j*preview_width+i) ;
    srccolor = srcimage + p*image_bpp;
    alpha = (gfloat)srccolor[image_bpp-1]/255;
    dstcolor = dstimage + p*preview_bpp;
    for (k=0; k <preview_bpp; k++) {
     *dstcolor++ = (guchar)(alpha *srccolor[k]+(1.0-alpha)*iwarp_transparent_color(i,j));
    }
   }  
 }
}


static void 
iwarp_init()
{
 int y,x,xi,i;
 GPixelRgn srcrgn;
 guchar *pts;
 guchar *linebuffer = NULL;
 gfloat dx,dy;
 
 gimp_drawable_mask_bounds (drawable->id, &xl, &yl, &xh, &yh);
 sel_width = xh-xl;
 sel_height = yh-yl;
 image_bpp = gimp_drawable_bpp(drawable->id);
 if (image_bpp <3) preview_bpp = 1; else preview_bpp = 3;
 dx = (gfloat) sel_width / MAX_PREVIEW_WIDTH;
 dy = (gfloat) sel_height / MAX_PREVIEW_HEIGHT;
 if (dx >dy) pre2img = dx; else pre2img = dy;
 if (dx <=1.0 && dy <= 1.0) pre2img = 1.0;  
 img2pre = 1.0 / pre2img;
 preview_width = (int)(sel_width / pre2img);
 preview_height = (int)(sel_height / pre2img);
 tile_width = gimp_tile_width();
 tile_height = gimp_tile_height();

 srcimage = g_malloc(preview_width * preview_height * image_bpp * sizeof(guchar));
 dstimage = g_malloc(preview_width * preview_height * preview_bpp * sizeof(guchar));
 deform_vectors = g_malloc(preview_width * preview_height*sizeof(gfloat)*2);
 deform_area_vectors = g_malloc((MAX_DEFORM_AREA_RADIUS*2+1)*(MAX_DEFORM_AREA_RADIUS*2+1)*sizeof(gfloat)*2);
 linebuffer = g_malloc(sel_width* image_bpp *sizeof(guchar));

 for (i=0; i<preview_width*preview_height; i++) 
        deform_vectors[i].x = deform_vectors[i].y = 0.0;
  
 gimp_pixel_rgn_init(&srcrgn,drawable,xl,yl,sel_width,sel_height,FALSE,FALSE);
  
 for (y=0; y<preview_height; y++) {
  gimp_pixel_rgn_get_row(&srcrgn,linebuffer,xl,(int)(pre2img*y)+yl,sel_width);
  for (x = 0; x<preview_width; x++) {
   pts = srcimage + (y * preview_width +x) * image_bpp;
   xi = (int)(pre2img*x);
   for (i=0; i<image_bpp; i++) {
     *pts++ = linebuffer[xi*image_bpp+i];
   }
  }
 }
 iwarp_cpy_images();
 for (i=0; i <MAX_DEFORM_AREA_RADIUS; i++) { 
   filter[i] = 
    pow((cos(sqrt((gfloat)i / MAX_DEFORM_AREA_RADIUS)*M_PI)+1)*0.5,0.7); /*0.7*/
 }
 g_free(linebuffer);

}     
 
static void
iwarp_animate_dialog(GtkWidget* dlg, GtkWidget* notebook)
{ 
 GtkWidget *table;
 GtkWidget *animate_table;
 GtkWidget *button;
 GtkWidget *label;
 GtkWidget *scale;
 GtkObject *scale_data;
 

 table = gtk_table_new (2, 2, FALSE);

 gtk_container_border_width (GTK_CONTAINER (table), 5);
 gtk_table_set_row_spacings(GTK_TABLE(table), 10);
 gtk_table_set_col_spacings(GTK_TABLE(table), 3);

 button = gtk_check_button_new_with_label ("Animate");
 gtk_table_attach (GTK_TABLE (table), button, 0, 1, 0, 1, GTK_FILL , GTK_FILL , 0, 0); 
 gtk_signal_connect (GTK_OBJECT (button), "clicked",
                      (GtkSignalFunc) iwarp_animate_toggle,
                      &do_animate);
 gtk_widget_show(button);

 animate_table = gtk_table_new (3, 5, FALSE);
 gtk_container_border_width (GTK_CONTAINER (animate_table), 5);
 gtk_table_set_row_spacings(GTK_TABLE(animate_table), 5);
 gtk_table_set_col_spacings(GTK_TABLE(animate_table), 3);

 
 animate_frame = gtk_frame_new ("");
 gtk_frame_set_shadow_type (GTK_FRAME (animate_frame), GTK_SHADOW_ETCHED_IN);
 gtk_container_add (GTK_CONTAINER (animate_frame), animate_table);
 

 label = gtk_label_new ("Number of Frames");
 gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
 gtk_table_attach (GTK_TABLE (animate_table), label, 0, 1, 0, 1, GTK_FILL, 0, 5, 0);
 scale_data = gtk_adjustment_new (animate_num_frames, 2, MAX_NUM_FRAMES, 1.0, 1.0, 0.0);
 scale = gtk_hscale_new (GTK_ADJUSTMENT (scale_data));
 gtk_widget_set_usize (scale, SCALE_WIDTH, 0);
 gtk_table_attach (GTK_TABLE (animate_table), scale, 1, 2, 0, 1, GTK_FILL, 0, 0, 0);
 gtk_scale_set_value_pos (GTK_SCALE (scale), GTK_POS_TOP);
 gtk_scale_set_digits(GTK_SCALE(scale),0);
 gtk_range_set_update_policy (GTK_RANGE (scale), GTK_UPDATE_DELAYED);
 gtk_signal_connect (GTK_OBJECT (scale_data), "value_changed",
		      (GtkSignalFunc) iwarp_iscale_update,
		      &animate_num_frames);
 gtk_widget_show (label);
 gtk_widget_show (scale);
   

 button = gtk_check_button_new_with_label ("Reverse");
 gtk_table_attach (GTK_TABLE (animate_table), button, 0, 1, 1, 2, GTK_FILL | GTK_EXPAND, GTK_FILL | GTK_EXPAND, 0, 0); 
 gtk_signal_connect (GTK_OBJECT (button), "clicked",
                      (GtkSignalFunc) iwarp_toggle_update,
                      &do_animate_reverse);
 gtk_widget_show(button);
 
 button = gtk_check_button_new_with_label ("Ping Pong");
 gtk_table_attach (GTK_TABLE (animate_table), button, 0, 1, 2, 3, GTK_FILL | GTK_EXPAND, GTK_FILL | GTK_EXPAND, 0, 0); 
 gtk_signal_connect (GTK_OBJECT (button), "clicked",
                      (GtkSignalFunc) iwarp_toggle_update,
                      &do_animate_ping_pong);
 gtk_widget_show(button);
 



  gtk_widget_show (animate_table);

  gtk_widget_show (animate_frame);
  gtk_widget_set_sensitive(animate_frame,do_animate);
  gtk_table_attach (GTK_TABLE (table), animate_frame, 0, 2, 1, 2, GTK_FILL, 0, 0, 0);
 
  label = gtk_label_new("Animate");
  gtk_misc_set_alignment(GTK_MISC(label),0.5,0.5);
  gtk_notebook_append_page(GTK_NOTEBOOK(notebook),table,label);
  gtk_widget_show(label);
  gtk_widget_show(table);
} 
   


static void
iwarp_settings_dialog(GtkWidget* dlg, GtkWidget* notebook)
{
 GtkWidget *button;
 GtkWidget *table;
 GtkWidget *label;
 GtkWidget *scale;
 GtkWidget *toggle;
 GtkWidget *separator;
 GtkObject *scale_data;
 GtkWidget *supersample_table;  
 GSList *group = NULL;
 
 table = gtk_table_new (12, 2, FALSE);
 gtk_container_border_width (GTK_CONTAINER (table), 5);
 gtk_table_set_row_spacings(GTK_TABLE(table), 5);
 gtk_table_set_col_spacings(GTK_TABLE(table), 3);
  

 label = gtk_label_new ("Deform Radius");
 gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
 gtk_table_attach (GTK_TABLE (table), label, 0, 1, 0, 1, GTK_FILL, 0, 5, 0);
 scale_data = gtk_adjustment_new (iwarp_vals.deform_area_radius, 5.0, MAX_DEFORM_AREA_RADIUS, 1.0, 1.0, 0.0);
 scale = gtk_hscale_new (GTK_ADJUSTMENT (scale_data));
 gtk_widget_set_usize (scale, SCALE_WIDTH, 0);
 gtk_table_attach (GTK_TABLE (table), scale, 1, 2, 0, 1, GTK_FILL, 0, 0, 0);
 gtk_scale_set_value_pos (GTK_SCALE (scale), GTK_POS_TOP);
 gtk_scale_set_digits(GTK_SCALE(scale),0);
 gtk_range_set_update_policy (GTK_RANGE (scale), GTK_UPDATE_DELAYED);
 gtk_signal_connect (GTK_OBJECT (scale_data), "value_changed",
	      (GtkSignalFunc) iwarp_iscale_update,
		      &iwarp_vals.deform_area_radius);
 gtk_widget_show (label);
 gtk_widget_show (scale);
 
 label = gtk_label_new ("Deform Amount");
 gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
 gtk_table_attach (GTK_TABLE (table), label, 0, 1, 1, 2, GTK_FILL, 0, 5, 0);
 scale_data = gtk_adjustment_new (iwarp_vals.deform_amount, 0.0, 1.0, 0.01,0.01, 0.0);
 scale = gtk_hscale_new (GTK_ADJUSTMENT (scale_data));
 gtk_widget_set_usize (scale, SCALE_WIDTH, 0);
 gtk_table_attach (GTK_TABLE (table), scale, 1, 2, 1, 2, GTK_FILL, 0, 0, 0);
 gtk_scale_set_value_pos (GTK_SCALE (scale), GTK_POS_TOP);
 gtk_scale_set_digits(GTK_SCALE(scale),2);
 gtk_range_set_update_policy (GTK_RANGE (scale), GTK_UPDATE_DELAYED);
 gtk_signal_connect (GTK_OBJECT (scale_data), "value_changed",
	      (GtkSignalFunc) iwarp_fscale_update,
		      &iwarp_vals.deform_amount);
 gtk_widget_show (label);
 gtk_widget_show (scale);

 separator = gtk_hseparator_new();
 gtk_table_attach (GTK_TABLE(table), separator, 0, 2 ,2,3,GTK_FILL,0,0,0); 
 gtk_widget_show(separator);
    
 toggle = gtk_radio_button_new_with_label(group,"Move");
 group = gtk_radio_button_group(GTK_RADIO_BUTTON(toggle));
 gtk_table_attach (GTK_TABLE (table), toggle, 0, 1, 3, 4, GTK_FILL, 0, 0, 0);
 gtk_signal_connect (GTK_OBJECT (toggle), "toggled",
		      (GtkSignalFunc) iwarp_toggle_update,
		      &iwarp_vals.do_move);
 gtk_toggle_button_set_state(GTK_TOGGLE_BUTTON(toggle), iwarp_vals.do_move);
 gtk_widget_show(toggle);  
 
 toggle = gtk_radio_button_new_with_label(group,"Shrink");
 group = gtk_radio_button_group(GTK_RADIO_BUTTON(toggle));
 gtk_table_attach (GTK_TABLE (table), toggle, 1, 2, 4, 5, GTK_FILL, 0, 0, 0);
 gtk_signal_connect (GTK_OBJECT (toggle), "toggled",
		      (GtkSignalFunc) iwarp_toggle_update,
		      &iwarp_vals.do_shrink);
 gtk_toggle_button_set_state(GTK_TOGGLE_BUTTON(toggle), iwarp_vals.do_shrink);
 gtk_widget_show(toggle);  
 
 toggle = gtk_radio_button_new_with_label(group,"Grow");
 group = gtk_radio_button_group(GTK_RADIO_BUTTON(toggle));
 gtk_table_attach (GTK_TABLE (table), toggle, 0, 1, 4, 5, GTK_FILL, 0, 0, 0);
 gtk_signal_connect (GTK_OBJECT (toggle), "toggled",
	      (GtkSignalFunc) iwarp_toggle_update,
		      &iwarp_vals.do_grow);
 gtk_toggle_button_set_state(GTK_TOGGLE_BUTTON(toggle), iwarp_vals.do_grow);
 gtk_widget_show(toggle);  

 toggle = gtk_radio_button_new_with_label(group,"Remove");
 group = gtk_radio_button_group(GTK_RADIO_BUTTON(toggle));
 gtk_table_attach (GTK_TABLE (table), toggle, 1, 2, 3, 4, GTK_FILL, 0, 0, 0);
 gtk_signal_connect (GTK_OBJECT (toggle), "toggled",
		      (GtkSignalFunc) iwarp_toggle_update,
		      &iwarp_vals.do_remove);
 gtk_toggle_button_set_state(GTK_TOGGLE_BUTTON(toggle), iwarp_vals.do_remove);
 gtk_widget_show(toggle);  

 toggle = gtk_radio_button_new_with_label(group,"Swirl CW");
 group = gtk_radio_button_group(GTK_RADIO_BUTTON(toggle));
 gtk_table_attach (GTK_TABLE (table), toggle, 1, 2, 5, 6, GTK_FILL, 0, 0, 0);
 gtk_signal_connect (GTK_OBJECT (toggle), "toggled",
		      (GtkSignalFunc) iwarp_toggle_update,
		      &iwarp_vals.do_swirl_cw);
 gtk_toggle_button_set_state(GTK_TOGGLE_BUTTON(toggle), iwarp_vals.do_swirl_cw);
 gtk_widget_show(toggle);  
 
 toggle = gtk_radio_button_new_with_label(group,"Swirl CCW");
 group = gtk_radio_button_group(GTK_RADIO_BUTTON(toggle));
 gtk_table_attach (GTK_TABLE (table), toggle, 0, 1, 5, 6, GTK_FILL, 0, 0, 0);
 gtk_signal_connect (GTK_OBJECT (toggle), "toggled",
		      (GtkSignalFunc) iwarp_toggle_update,
		      &iwarp_vals.do_swirl_ccw);
 gtk_toggle_button_set_state(GTK_TOGGLE_BUTTON(toggle), iwarp_vals.do_swirl_ccw);
 gtk_widget_show(toggle);  


 separator = gtk_hseparator_new();
 gtk_table_attach (GTK_TABLE(table), separator, 0, 2 ,6,7,GTK_FILL,0,0,0); 
 gtk_widget_show(separator);
  

 button = gtk_check_button_new_with_label ("Bilinear");
 gtk_toggle_button_set_state(GTK_TOGGLE_BUTTON(button), iwarp_vals.do_bilinear);
 gtk_table_attach (GTK_TABLE (table), button, 1, 2, 7, 8, GTK_FILL | GTK_EXPAND, GTK_FILL | GTK_EXPAND, 0, 0); 
 gtk_signal_connect (GTK_OBJECT (button), "clicked",
                      (GtkSignalFunc) iwarp_toggle_update,
                      &iwarp_vals.do_bilinear);
 gtk_widget_show(button);


 button = gtk_button_new_with_label ("Reset");
 gtk_table_attach (GTK_TABLE (table), button, 0, 1, 7, 8, GTK_FILL | GTK_EXPAND, GTK_FILL | GTK_EXPAND, 0, 0); 
 gtk_signal_connect (GTK_OBJECT (button), "clicked",
                      (GtkSignalFunc) iwarp_reset_callback,
                      NULL);
 gtk_widget_show(button);

 separator = gtk_hseparator_new();
 gtk_table_attach (GTK_TABLE(table), separator, 0, 2 ,8,9,GTK_FILL,0,0,0); 
 gtk_widget_show(separator);
  

 button = gtk_check_button_new_with_label ("Adaptive Supersample");
 gtk_toggle_button_set_state(GTK_TOGGLE_BUTTON(button), iwarp_vals.do_supersample);
 gtk_table_attach (GTK_TABLE (table), button, 0, 1, 9, 10, GTK_FILL | GTK_EXPAND, GTK_FILL | GTK_EXPAND, 0, 0); 
 gtk_signal_connect (GTK_OBJECT (button), "clicked",
                      (GtkSignalFunc) iwarp_supersample_toggle,
                      &iwarp_vals.do_supersample);
 gtk_widget_show(button);

 supersample_frame = gtk_frame_new ("");
 gtk_frame_set_shadow_type (GTK_FRAME (supersample_frame), GTK_SHADOW_ETCHED_IN);
 supersample_table = gtk_table_new (2, 2, FALSE);
 gtk_container_border_width (GTK_CONTAINER (supersample_table), 5);
 gtk_table_set_row_spacings(GTK_TABLE(supersample_table), 5);
 gtk_table_set_col_spacings(GTK_TABLE(supersample_table), 3);
  
 gtk_container_add (GTK_CONTAINER (supersample_frame), supersample_table);
 
 label = gtk_label_new ("Max Depth");
 gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
 gtk_table_attach (GTK_TABLE (supersample_table), label, 0, 1, 0, 1, GTK_FILL, 0, 5, 0);
 scale_data = gtk_adjustment_new (iwarp_vals.max_supersample_depth, 1.0, 5.0, 1.0, 1.0, 0.0);
 scale = gtk_hscale_new (GTK_ADJUSTMENT (scale_data));
 gtk_widget_set_usize (scale, SCALE_WIDTH, 0);
 gtk_table_attach (GTK_TABLE (supersample_table), scale, 1, 2, 0, 1, GTK_FILL, 0, 0, 0);
 gtk_scale_set_value_pos (GTK_SCALE (scale), GTK_POS_TOP);
 gtk_scale_set_digits(GTK_SCALE(scale),0);
 gtk_range_set_update_policy (GTK_RANGE (scale), GTK_UPDATE_DELAYED);
 gtk_signal_connect (GTK_OBJECT (scale_data), "value_changed",
		      (GtkSignalFunc) iwarp_iscale_update,
		      &iwarp_vals.max_supersample_depth);
 gtk_widget_show (label);
 gtk_widget_show (scale);
   
 label = gtk_label_new ("Threshold");
 gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
 gtk_table_attach (GTK_TABLE (supersample_table), label, 0, 1, 1, 2, GTK_FILL, 0, 5, 0);
 scale_data = gtk_adjustment_new (iwarp_vals.supersample_threshold, 1.0, 10.0, 0.01,0.01, 0.0);
 scale = gtk_hscale_new (GTK_ADJUSTMENT (scale_data));
 gtk_widget_set_usize (scale, SCALE_WIDTH, 0);
 gtk_table_attach (GTK_TABLE (supersample_table), scale, 1, 2, 1, 2, GTK_FILL, 0, 0, 0);
 gtk_scale_set_value_pos (GTK_SCALE (scale), GTK_POS_TOP);
 gtk_scale_set_digits(GTK_SCALE(scale),2);
 gtk_range_set_update_policy (GTK_RANGE (scale), GTK_UPDATE_DELAYED);
 gtk_signal_connect (GTK_OBJECT (scale_data), "value_changed",
		      (GtkSignalFunc) iwarp_fscale_update,
		      &iwarp_vals.supersample_threshold);
 gtk_widget_show (label);
 gtk_widget_show (scale);


 gtk_widget_show (supersample_table);
 gtk_widget_show (supersample_frame);

 gtk_widget_set_sensitive(supersample_frame,iwarp_vals.do_supersample);

 gtk_table_attach (GTK_TABLE (table), supersample_frame, 0, 2, 11, 12, GTK_FILL, 0, 0, 0);

    
 gtk_widget_show (table);

 label = gtk_label_new("Settings");
 gtk_misc_set_alignment(GTK_MISC(label),0.5,0.5);
 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),table,label);
 gtk_widget_show(label);
 gtk_widget_show(table);
}


static gint
iwarp_dialog()
{
 GtkWidget *dlg;
 GtkWidget *pframe;
 GtkWidget *top_table;
 GtkWidget *notebook;
 GtkWidget *button;
 guchar *color_cube; 
 gint argc;
 gchar** argv;
  
 
 argc = 1;
 argv = g_new(gchar *, 1);
 argv[0] = g_strdup("iwarp");

 gtk_init(&argc,&argv);
 gtk_rc_parse(gimp_gtkrc());
 gtk_preview_set_gamma (gimp_gamma ());
 gtk_preview_set_install_cmap (gimp_install_cmap ());
 color_cube = gimp_color_cube ();
 gtk_preview_set_color_cube (color_cube[0], color_cube[1],
			      color_cube[2], color_cube[3]);

 gtk_widget_set_default_visual (gtk_preview_get_visual ());
 gtk_widget_set_default_colormap (gtk_preview_get_cmap ());
 
 iwarp_init();
 
 dlg = gtk_dialog_new ();
 gtk_window_set_title (GTK_WINDOW (dlg), "IWarp");
 gtk_window_position (GTK_WINDOW (dlg), GTK_WIN_POS_MOUSE);
 gtk_signal_connect (GTK_OBJECT (dlg), "destroy",
		      (GtkSignalFunc) iwarp_close_callback,
		      NULL);
 
 button = gtk_button_new_with_label ("OK");
 GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT);
 gtk_signal_connect (GTK_OBJECT (button), "clicked",
                      (GtkSignalFunc) iwarp_ok_callback,
                      dlg);
 gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dlg)->action_area), button, TRUE, TRUE, 0);
 gtk_widget_grab_default (button);
 gtk_widget_show (button);

 button = gtk_button_new_with_label ("Cancel");
 GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT);
 gtk_signal_connect_object (GTK_OBJECT (button), "clicked",
			     (GtkSignalFunc) gtk_widget_destroy,
			     GTK_OBJECT (dlg));

 gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dlg)->action_area), button, TRUE, TRUE, 0);
 gtk_widget_show (button);

  
 pframe = gtk_frame_new (NULL);
 gtk_frame_set_shadow_type (GTK_FRAME (pframe), GTK_SHADOW_IN);
 gtk_widget_show (pframe); 

 if (preview_bpp == 3) preview = gtk_preview_new (GTK_PREVIEW_COLOR);
 else preview = gtk_preview_new (GTK_PREVIEW_GRAYSCALE);
 gtk_preview_size (GTK_PREVIEW (preview), preview_width, preview_height);
 iwarp_update_preview(0,0,preview_width,preview_height);
 gtk_container_add (GTK_CONTAINER (pframe), preview);
 gtk_widget_show (preview);
 
 gtk_widget_set_events(preview,GDK_BUTTON_PRESS_MASK |
   GDK_BUTTON_RELEASE_MASK | GDK_BUTTON1_MOTION_MASK | 
   GDK_POINTER_MOTION_HINT_MASK);
 gtk_signal_connect(GTK_OBJECT(preview), "event",
                     (GtkSignalFunc)iwarp_motion_callback,NULL);

 notebook = gtk_notebook_new();
 gtk_notebook_set_tab_pos(GTK_NOTEBOOK (notebook), GTK_POS_TOP);
 
 iwarp_settings_dialog(dlg,notebook);
 iwarp_animate_dialog(dlg,notebook);
 
 gtk_widget_show(notebook);
  
 top_table = gtk_table_new(1, 2, FALSE);
 gtk_container_border_width(GTK_CONTAINER(top_table), 6);
 gtk_table_set_row_spacings(GTK_TABLE(top_table), 5);
 gtk_table_set_col_spacings(GTK_TABLE(top_table), 3);
 gtk_table_attach(GTK_TABLE(top_table), pframe, 0,1,0,1,0,0,0,0); 
 gtk_table_attach(GTK_TABLE(top_table), notebook, 1,2,0,1,0,0,0,0);
 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dlg)->vbox), top_table, FALSE, FALSE, 0);
 gtk_widget_show(top_table);
 gtk_widget_show(dlg);
 gtk_main ();
 gdk_flush ();

 return wint.run;   
}


static void 
iwarp_update_preview(int x0, int y0,int x1,int y1)
{
 int i;
 GdkRectangle rect;
 
 if (x0<0) x0=0;
 if (y0<0) y0=0;
 if (x1>=preview_width) x1=preview_width;
 if (y1>=preview_height) y1=preview_height;
 for (i = y0; i < y1; i++)
    gtk_preview_draw_row (GTK_PREVIEW (preview),
			  dstimage + (i * preview_width + x0) * preview_bpp,
			  x0, i,x1-x0);
 rect.x = x0;
 rect.y = y0;
 rect.width = x1-x0;
 rect.height = y1-y0;  
 gtk_widget_draw(preview,&rect);
 gdk_flush();
}


static void
iwarp_preview_get_pixel(int x, int y , guchar **color)
{
 static guchar black[4] = {0,0,0,0};

 if (x < 0 || x >= preview_width || y<0 || y >= preview_height) { 
  *color = black;
  return;
 }
 *color = srcimage +(y*preview_width+ x)*image_bpp; 
} 

 

static void
iwarp_preview_get_point( gfloat x, gfloat y,guchar* color)
{
 int xi,yi,j;
 gfloat dx,dy,m0,m1;
 guchar *p0,*p1,*p2,*p3;

  xi = (int)x;
  yi = (int)y;
  if (iwarp_vals.do_bilinear) {
   dx = x-xi;
   dy = y-yi; 
   iwarp_preview_get_pixel(xi,yi,&p0);
   iwarp_preview_get_pixel(xi+1,yi,&p1);
   iwarp_preview_get_pixel(xi,yi+1,&p2);
   iwarp_preview_get_pixel(xi+1,yi+1,&p3);
   for (j=0; j< image_bpp; j++) {
      m0 = p0[j] +dx * (p1[j]-p0[j]);
      m1 = p2[j]+ dx * (p3[j]-p2[j]);
     color[j] = (guchar)(m0 + dy * (m1-m0));  
   }
  }
  else {
   iwarp_preview_get_pixel(xi,yi,&p0);
   for (j=0; j < image_bpp; j++) color[j] = p0[j];
  }
  
}


static void 
iwarp_deform(int x, int y, gfloat vx, gfloat vy)
{
 int xi,yi,ptr,fptr,x0,x1,y0,y1,radius2,length2;
 gfloat deform_value,xn,yn,nvx=0,nvy=0,emh,em,edge_width,xv,yv,alpha;
 guchar color[3];
 
 if (x - iwarp_vals.deform_area_radius <0) x0 = -x; else x0 = -iwarp_vals.deform_area_radius;
 if (x + iwarp_vals.deform_area_radius >= preview_width) x1 = preview_width-x-1; else x1 = iwarp_vals.deform_area_radius;
 if (y - iwarp_vals.deform_area_radius <0) y0 = -y; else y0 = -iwarp_vals.deform_area_radius;
 if (y + iwarp_vals.deform_area_radius >= preview_height) y1 = preview_height-y-1; else y1 = iwarp_vals.deform_area_radius;

 radius2 = iwarp_vals.deform_area_radius*iwarp_vals.deform_area_radius;
 for (yi= y0; yi <= y1; yi++)
  for (xi = x0; xi <= x1; xi++) {
   length2 = (xi*xi+yi*yi)*MAX_DEFORM_AREA_RADIUS / radius2;
   if (length2 < MAX_DEFORM_AREA_RADIUS) {
    ptr = (y + yi) * preview_width + x + xi;   
    fptr = (yi+iwarp_vals.deform_area_radius) * (iwarp_vals.deform_area_radius*2+1) + xi+iwarp_vals.deform_area_radius;
   
    if (iwarp_vals.do_grow) { 
      deform_value = filter[length2] *  0.1* iwarp_vals.deform_amount; 
      nvx = -deform_value * xi; 
      nvy = -deform_value * yi; 
    }  
    else if (iwarp_vals.do_shrink) { 
      deform_value = filter[length2] * 0.1* iwarp_vals.deform_amount; 
      nvx = deform_value * xi; 
      nvy = deform_value * yi; 
    }
    else if (iwarp_vals.do_swirl_cw) { 
      deform_value = filter[length2] * iwarp_vals.deform_amount * 0.5; 
      nvx =  deform_value * yi; 
      nvy = -deform_value * xi; 
    }
    else if (iwarp_vals.do_swirl_ccw) { 
      deform_value = filter[length2] *iwarp_vals.deform_amount * 0.5; 
      nvx = -deform_value * yi; 
      nvy =  deform_value  * xi; 
    }
    else if (iwarp_vals.do_move) { 
      deform_value = filter[length2] * iwarp_vals.deform_amount; 
      nvx = deform_value * vx; 
      nvy = deform_value * vy; 
    } 
    if (iwarp_vals.do_remove) {
     deform_value = 1.0-0.5*iwarp_vals.deform_amount*filter[length2];
     deform_area_vectors[fptr].x = deform_value * deform_vectors[ptr].x ;
     deform_area_vectors[fptr].y = deform_value * deform_vectors[ptr].y ;
    } 
    else {
     edge_width = 0.2 *  iwarp_vals.deform_area_radius;
     emh = em = 1.0;
     if (x+xi < edge_width) em = (gfloat)(x+xi) / (edge_width);
     if (y+yi < edge_width) emh = (gfloat)(y+yi) / (edge_width);
     if (emh <em) em = emh;
     if (preview_width-x-xi-1 < edge_width ) emh = (gfloat)(preview_width-x-xi-1)/ (edge_width);
     if (emh <em) em = emh;
     if (preview_height-y-yi-1 < edge_width ) emh = (gfloat)(preview_height-y-yi-1)/ (edge_width);
     if (emh <em) em = emh;        
     nvx = nvx*em;
     nvy = nvy*em;

     iwarp_get_deform_vector(nvx + x+ xi,nvy + y +yi, &xv , &yv);
     xv = nvx +xv;
     if (xv +x+xi <0.0) xv = -x-xi;
     else if (xv + x +xi > (preview_width-1)) xv = preview_width - x -xi-1;
     yv = nvy +yv;   
     if (yv +y+yi <0.0) yv = -y-yi;
     else if (yv + y +yi > (preview_height-1)) yv = preview_height - y -yi-1;
     deform_area_vectors[fptr].x =xv;
     deform_area_vectors[fptr].y = yv; 
    } 

    xn = deform_area_vectors[fptr].x + x + xi;
    yn = deform_area_vectors[fptr].y + y + yi;
    iwarp_preview_get_point(xn,yn,color);

    if (preview_bpp == 3) { 
     if (image_bpp == 4) {
       alpha = (gfloat)color[3] / 255;
       dstimage[ptr*3] = (guchar)(alpha*color[0]+ (1.0-alpha)*iwarp_transparent_color(x+xi,y+yi));
       dstimage[ptr*3+1] = (guchar)(alpha*color[1]+ (1.0-alpha)*iwarp_transparent_color(x+xi,y+yi));
       dstimage[ptr*3+2] = (guchar)(alpha*color[2]+ (1.0-alpha)*iwarp_transparent_color(x+xi,y+yi));
     }
     else {
       dstimage[ptr*3] = color[0];
       dstimage[ptr*3+1] = color[1];
       dstimage[ptr*3+2] = color[2];
     }
    }
    else {
     if (image_bpp == 2) { 
      alpha = (gfloat)color[1] / 255;
      dstimage[ptr] = (guchar)(alpha *color[0]+ (1.0-alpha)*iwarp_transparent_color(x+xi,y+yi)) ;
     }
     else
      dstimage[ptr] = color[0];
    }
   }
  } 
   
 for (yi= y0; yi <= y1; yi++)
  for (xi = x0; xi <= x1; xi++) {
   length2 = (xi*xi+yi*yi)*MAX_DEFORM_AREA_RADIUS / radius2;
   if (length2 <MAX_DEFORM_AREA_RADIUS) {
    ptr = (yi +y) * preview_width + xi +x;
    fptr = (yi+iwarp_vals.deform_area_radius) * (iwarp_vals.deform_area_radius*2+1) + xi + iwarp_vals.deform_area_radius;
    deform_vectors[ptr].x = deform_area_vectors[fptr].x;
    deform_vectors[ptr].y = deform_area_vectors[fptr].y;
   }
  }  
 iwarp_update_preview(x+x0,y+y0,x+x1+1,y+y1+1);   
}  
    

static void
iwarp_move(int x, int y,int xx, int yy)
{
 gfloat l,dx,dy,xf,yf;
 int num,i,x0,y0; 
 
 dx = x-xx;
 dy = y-yy;
 l= sqrt(dx*dx+dy*dy);
 num = (int)(l * 2 / iwarp_vals.deform_area_radius)+1;
 dx /= num;
 dy /= num;
 xf = xx+dx; yf = yy+dy; 
 for (i=0; i< num; i++) {
  x0 = (int)(xf);
  y0 = (int)(yf);
  iwarp_deform(x0,y0,-dx,-dy);
  xf += dx;
  yf += dy;
 }
}
  


static void
iwarp_close_callback (GtkWidget *widget,
		       gpointer   data)
{
  gtk_main_quit ();
}

static void
iwarp_ok_callback (GtkWidget *widget,
		    gpointer   data)
{
  wint.run = TRUE;
  gtk_widget_destroy (GTK_WIDGET (data));
}


static gint
iwarp_motion_callback(GtkWidget *widget,
                      GdkEvent *event)
{
 GdkEventButton* mb;
 int x,y;
 
 mb = (GdkEventButton*) event; 
 switch (event->type) {
   case GDK_BUTTON_PRESS:
      lastx = mb->x;
      lasty = mb->y;
   break;
   case GDK_BUTTON_RELEASE:
      if (mb->state & GDK_BUTTON1_MASK) {
       x = mb->x;
       y = mb->y;
       if (iwarp_vals.do_move) iwarp_move(x,y,lastx,lasty);
       else iwarp_deform(x, y,0.0,0.0);
      }
   break; 
   case GDK_MOTION_NOTIFY :
     if (mb->state & GDK_BUTTON1_MASK) { 
      x = mb->x;
      y = mb->y;
      if (iwarp_vals.do_move) iwarp_move(x,y,lastx,lasty);
      else iwarp_deform(x, y,0.0,0.0);
      lastx = x;
      lasty = y;
      gtk_widget_get_pointer(widget,NULL,NULL); 
     }
   break;
   default:
   break;
 }
 
return FALSE;
}


static void    
iwarp_iscale_update (GtkAdjustment *adjustment, gint* scale_val)
{
 *scale_val = (gint)adjustment->value;
}

static void    
iwarp_fscale_update (GtkAdjustment *adjustment, gfloat* scale_val)
{
 *scale_val = adjustment->value;
}
                                      
                                      

static void    
iwarp_toggle_update (GtkWidget *widget,
                     int  *data)
{
 if ( GTK_TOGGLE_BUTTON (widget)->active) *data = TRUE;
 else *data = FALSE;
}

static void    
iwarp_supersample_toggle (GtkWidget *widget,
                     int  *data)
{
 if ( GTK_TOGGLE_BUTTON (widget)->active) *data = TRUE;
 else *data = FALSE;
 gtk_widget_set_sensitive(supersample_frame,iwarp_vals.do_supersample);
}

static void    
iwarp_animate_toggle (GtkWidget *widget,
                     int  *data)
{
 if ( GTK_TOGGLE_BUTTON (widget)->active) *data = TRUE;
 else *data = FALSE;
 gtk_widget_set_sensitive(animate_frame,do_animate);
}


static void
iwarp_reset_callback (GtkWidget *widget,
		    gpointer   data)
{
 int i;

 iwarp_cpy_images();
 for (i=0; i<preview_width* preview_height; i++)  deform_vectors[i].x = deform_vectors[i].y = 0.0;
 iwarp_update_preview(0,0,preview_width, preview_height);   
}
