/* The GIMP -- an image manipulation program
 * Copyright (C) 1995 Spencer Kimball and Peter Mattis
 *
 * Polarize plug-in --- maps a rectangul to a circle or vice-versa
 * Copyright (C) 1997 Daniel Dunbar
 * Email: ddunbar@diads.com
 * WWW:   http://millennium.diads.com/gimp/
 * Copyright (C) 1997 Federico Mena Quintero
 * federico@nuclecu.unam.mx
 * Copyright (C) 1996 Marc Bless
 * E-mail: bless@ai-lab.fh-furtwangen.de
 * WWW:    www.ai-lab.fh-furtwangen.de/~bless
 *
 * 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.
 */


/* Version 1.0:
 * This is the follow-up release.  It contains a few minor changes, the
 * most major being that the first time I released the wrong version of
 * the code, and this time the changes have been fixed.  I also added
 * tooltips to the dialog.
 *
 * Feel free to email me if you have any comments or suggestions on this
 * plugin.
 *               --Daniel Dunbar
 *                 ddunbar@diads.com
 */


/* Version .5:
 * This is the first version publicly released, it will probably be the
 * last also unless i can think of some features i want to add.
 *
 * This plug-in was created with loads of help from quartic (Frederico
 * Mena Quintero), and would surely not have come about without it.
 *
 * The polar algorithms is copied from Marc Bless' polar plug-in for
 * .54, many thanks to him also.
 * 
 * If you can think of a neat addition to this plug-in, or any other
 * info about it, please email me at ddunbar@diads.com.
 *                                     - Daniel Dunbar
 */

#include "config.h"

#include <stdio.h>
#include <signal.h>
#include <stdlib.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif

#ifdef __GNUC__
#warning GTK_DISABLE_DEPRECATED
#endif
#undef GTK_DISABLE_DEPRECATED

#include <gtk/gtk.h>

#include <libgimp/gimp.h>
#include <libgimp/gimpui.h>

#include "libgimp/stdplugins-intl.h"


#define WITHIN(a, b, c) ((((a) <= (b)) && ((b) <= (c))) ? 1 : 0)


/***** Magic numbers *****/

#define PLUG_IN_NAME    "plug_in_polar_coords"
#define PLUG_IN_VERSION "July 1997, 0.5"

#define PREVIEW_SIZE 128
#define SCALE_WIDTH  200
#define ENTRY_WIDTH   60

/***** Types *****/

typedef struct
{
  gdouble circle;
  gdouble angle;
  gint backwards;
  gint inverse;
  gint polrec;
} polarize_vals_t;

typedef struct
{
  GtkWidget *preview;
  guchar    *check_row_0;
  guchar    *check_row_1;
  guchar    *image;
  guchar    *dimage;

  gint       run;
} polarize_interface_t;

/***** Prototypes *****/

static void query (void);
static void run   (gchar      *name,
		   gint        nparams,
		   GimpParam  *param,
		   gint       *nreturn_vals,
		   GimpParam **return_vals);

static void   polarize(void);
static int    calc_undistorted_coords(double wx, double wy,
				      double *x, double *y);

static void build_preview_source_image(void);

static gint polarize_dialog       (void);
static void dialog_update_preview (void);

static void dialog_scale_update (GtkAdjustment *adjustment, gdouble *value);
static void dialog_ok_callback  (GtkWidget *widget, gpointer data);

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

/***** Variables *****/

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

static polarize_vals_t pcvals =
{
  100.0, /* circle */
  0.0,  /* angle */
  0, /* backwards */
  1,  /* inverse */
  1  /* polar to rectangular? */
};

static polarize_interface_t pcint =
{
  NULL,  /* preview */
  NULL,  /* check_row_0 */
  NULL,  /* check_row_1 */
  NULL,  /* image */
  NULL,  /* dimage */
  FALSE  /* run */
};

static GimpDrawable *drawable;

static gint img_width, img_height, img_bpp, img_has_alpha;
static gint sel_x1, sel_y1, sel_x2, sel_y2;
static gint sel_width, sel_height;
static gint preview_width, preview_height;

static double cen_x, cen_y;
static double scale_x, scale_y;

/***** Functions *****/

MAIN ()

static void
query (void)
{
  static GimpParamDef args[] =
  {
    { GIMP_PDB_INT32,    "run_mode",  "Interactive, non-interactive" },
    { GIMP_PDB_IMAGE,    "image",     "Input image" },
    { GIMP_PDB_DRAWABLE, "drawable",  "Input drawable" },
    { GIMP_PDB_FLOAT,    "circle",    "Circle depth in %" },
    { GIMP_PDB_FLOAT,    "angle",     "Offset angle" },
    { GIMP_PDB_INT32,    "backwards",    "Map backwards?" },
    { GIMP_PDB_INT32,    "inverse",     "Map from top?" },
    { GIMP_PDB_INT32,    "polrec",     "Polar to rectangular?" }
  };

  gimp_install_procedure (PLUG_IN_NAME,
			  "Converts and image to and from polar coords",
			  "Remaps and image from rectangular coordinates to polar coordinates "
			  "or vice versa",
			  "Daniel Dunbar and Federico Mena Quintero",
			  "Daniel Dunbar and Federico Mena Quintero",
			  PLUG_IN_VERSION,
			  N_("<Image>/Filters/Distorts/Polar Coords..."),
			  "RGB*, GRAY*",
			  GIMP_PLUGIN,
			  G_N_ELEMENTS (args), 0,
			  args, NULL);
}

static void
run (gchar      *name,
     gint        nparams,
     GimpParam  *param,
     gint       *nreturn_vals,
     GimpParam **return_vals)
{
  static GimpParam values[1];

  GimpRunMode run_mode;
  GimpPDBStatusType  status;
  double       xhsiz, yhsiz;
  int          pwidth, pheight;

  status   = GIMP_PDB_SUCCESS;
  run_mode = param[0].data.d_int32;

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

  *nreturn_vals = 1;
  *return_vals  = values;

  /* Get the active drawable info */

  drawable = gimp_drawable_get (param[2].data.d_drawable);

  img_width     = gimp_drawable_width (drawable->drawable_id);
  img_height    = gimp_drawable_height (drawable->drawable_id);
  img_bpp       = gimp_drawable_bpp (drawable->drawable_id);
  img_has_alpha = gimp_drawable_has_alpha (drawable->drawable_id);

  gimp_drawable_mask_bounds (drawable->drawable_id, &sel_x1, &sel_y1, &sel_x2, &sel_y2);

  /* Calculate scaling parameters */

  sel_width  = sel_x2 - sel_x1;
  sel_height = sel_y2 - sel_y1;

  cen_x = (double) (sel_x1 + sel_x2 - 1) / 2.0;
  cen_y = (double) (sel_y1 + sel_y2 - 1) / 2.0;

  xhsiz = (double) (sel_width - 1) / 2.0;
  yhsiz = (double) (sel_height - 1) / 2.0;

  if (xhsiz < yhsiz)
    {
      scale_x = yhsiz / xhsiz;
      scale_y = 1.0;
    }
  else if (xhsiz > yhsiz)
    {
      scale_x = 1.0;
      scale_y = xhsiz / yhsiz;
    }
  else
    {
      scale_x = 1.0;
      scale_y = 1.0;
    }

  /* Calculate preview size */

  if (sel_width > sel_height)
    {
      pwidth  = MIN (sel_width, PREVIEW_SIZE);
      pheight = sel_height * pwidth / sel_width;
    }
  else
    {
      pheight = MIN (sel_height, PREVIEW_SIZE);
      pwidth  = sel_width * pheight / sel_height;
    }

  preview_width  = MAX (pwidth, 2); /* Min size is 2 */
  preview_height = MAX (pheight, 2);

  /* See how we will run */

  switch (run_mode)
    {
    case GIMP_RUN_INTERACTIVE:
      INIT_I18N_UI();

      /* Possibly retrieve data */
      gimp_get_data (PLUG_IN_NAME, &pcvals);

      /* Get information from the dialog */
      if (!polarize_dialog ())
	return;

      break;

    case GIMP_RUN_NONINTERACTIVE:
      INIT_I18N();

      /* Make sure all the arguments are present */
      if (nparams != 8)
	{
	  status = GIMP_PDB_CALLING_ERROR;
	}
      else
	{
	  pcvals.circle  = param[3].data.d_float;
	  pcvals.angle  = param[4].data.d_float;
	  pcvals.backwards  = param[5].data.d_int32;
	  pcvals.inverse  = param[6].data.d_int32;
	  pcvals.polrec  = param[7].data.d_int32;
	}
      break;

    case GIMP_RUN_WITH_LAST_VALS:
      INIT_I18N();

      /* Possibly retrieve data */
      gimp_get_data (PLUG_IN_NAME, &pcvals);
      break;

    default:
      break;
    }

  /* Distort the image */
  if ((status == GIMP_PDB_SUCCESS) &&
      (gimp_drawable_is_rgb (drawable->drawable_id) ||
       gimp_drawable_is_gray (drawable->drawable_id)))
    {
      /* Set the tile cache size */
      gimp_tile_cache_ntiles (2 * (drawable->width + gimp_tile_width() - 1) /
			      gimp_tile_width ());

      /* Run! */
      polarize ();

      /* If run mode is interactive, flush displays */
      if (run_mode != GIMP_RUN_NONINTERACTIVE)
	gimp_displays_flush ();

      /* Store data */
      if (run_mode == GIMP_RUN_INTERACTIVE)
	gimp_set_data (PLUG_IN_NAME, &pcvals, sizeof (polarize_vals_t));
    }
  else if (status == GIMP_PDB_SUCCESS)
    status = GIMP_PDB_EXECUTION_ERROR;

  values[0].data.d_status = status;

  gimp_drawable_detach (drawable);
}

static void
polarize (void)
{
  GimpPixelRgn  dest_rgn;
  guchar    *dest, *d;
  guchar     pixel[4][4];
  guchar     pixel2[4];
  gdouble    values[4];
  gint       progress, max_progress;
  double     cx, cy;
  gint       x1, y1, x2, y2;
  gint       x, y, b;
  gpointer   pr;
  
  GimpPixelFetcher *pft;

  /* Get selection area */
  gimp_drawable_mask_bounds (drawable->drawable_id, &x1, &y1, &x2, &y2);

  /* Initialize pixel region */
  gimp_pixel_rgn_init (&dest_rgn, drawable,
		       x1, y1, (x2 - x1), (y2 - y1), TRUE, TRUE);
  
  pft = gimp_pixel_fetcher_new (drawable);

  gimp_pixel_fetcher_set_bg_color (pft);

  progress     = 0;
  max_progress = img_width * img_height;

  gimp_progress_init (_("Polarizing..."));

  for (pr = gimp_pixel_rgns_register (1, &dest_rgn);
       pr != NULL;
       pr = gimp_pixel_rgns_process (pr))
    {
      dest = dest_rgn.data;

      for (y = dest_rgn.y; y < (dest_rgn.y + dest_rgn.h); y++)
	{
	  d = dest;

	  for (x = dest_rgn.x; x < (dest_rgn.x + dest_rgn.w); x++)
	    {
	      if (calc_undistorted_coords (x, y, &cx, &cy))
		{
		  gimp_pixel_fetcher_get_pixel (pft, cx, cy, pixel[0]);
		  gimp_pixel_fetcher_get_pixel (pft, cx + 1, cy, pixel[1]);
		  gimp_pixel_fetcher_get_pixel (pft, cx, cy + 1, pixel[2]);
		  gimp_pixel_fetcher_get_pixel (pft, cx + 1, cy + 1, pixel[3]);

                  if (img_has_alpha)
                    {
                      gdouble alpha;
                      values[0] = pixel[0][img_bpp-1];
                      values[1] = pixel[1][img_bpp-1];
                      values[2] = pixel[2][img_bpp-1];
                      values[3] = pixel[3][img_bpp-1];
                      alpha = gimp_bilinear (cx, cy, values);
                      d[img_bpp-1] = alpha;

                      if (d[img_bpp-1]) 
                        {
                          for (b = 0; b < img_bpp-1; b++)
                            {
                              values[0] = pixel[0][img_bpp-1] * pixel[0][b];
                              values[1] = pixel[1][img_bpp-1] * pixel[1][b];
                              values[2] = pixel[2][img_bpp-1] * pixel[2][b];
                              values[3] = pixel[3][img_bpp-1] * pixel[3][b];

                              d[b] = gimp_bilinear (cx, cy, values)/alpha;
                            }
                        }
                    }
                  else
                    {
                      for (b = 0; b < img_bpp; b++)
                        {
                          values[0] = pixel[0][b];
                          values[1] = pixel[1][b];
                          values[2] = pixel[2][b];
                          values[3] = pixel[3][b];
              
                          d[b] = gimp_bilinear (cx, cy, values);
                        }
                    }
		}
	      else
		{
		  gimp_pixel_fetcher_get_pixel (pft, x, y, pixel2);
		  for (b = 0; b < img_bpp; b++)
		    {
		      d[b] = 255;
		    }	  
		}

	      d += dest_rgn.bpp;
	    }
      
	  dest += dest_rgn.rowstride;
	}
      progress += dest_rgn.w *dest_rgn.h;

      gimp_progress_update ((double) progress / max_progress);
    }
  
  gimp_drawable_flush (drawable);
  gimp_drawable_merge_shadow (drawable->drawable_id, TRUE);
  gimp_drawable_update (drawable->drawable_id, x1, y1, (x2 - x1), (y2 - y1));
}

static gint
calc_undistorted_coords (gdouble  wx,
			 gdouble  wy,
			 gdouble *x,
			 gdouble *y)
{
  gint    inside;
  gdouble phi, phi2;
  gdouble xx, xm, ym, yy;
  gint    xdiff, ydiff;
  gdouble r;
  gdouble m;
  gdouble xmax, ymax, rmax;
  gdouble x_calc, y_calc;
  gdouble xi, yi;
  gdouble circle, angl, t, angle;
  gint    x1, x2, y1, y2;

  /* initialize */

  phi = 0.0;
  r = 0.0;

  x1 = 0;
  y1 = 0;
  x2 = img_width;
  y2 = img_height;
  xdiff = x2 - x1;
  ydiff = y2 - y1;
  xm = xdiff / 2.0;
  ym = ydiff / 2.0;
  circle = pcvals.circle;
  angle = pcvals.angle;
  angl = (double)angle / 180.0 * G_PI;

  if (pcvals.polrec)
    {
      if (wx >= cen_x)
	{
	  if (wy > cen_y)
	    {
	      phi = G_PI - atan (((double)(wx - cen_x))/((double)(wy - cen_y)));
	      r   = sqrt (SQR (wx - cen_x) + SQR (wy - cen_y));
	    }
	  else if (wy < cen_y)
	    {
	      phi = atan (((double)(wx - cen_x))/((double)(cen_y - wy)));
	      r   = sqrt (SQR (wx - cen_x) + SQR (cen_y - wy));
	    }
	  else
	    {
	      phi = G_PI / 2;
	      r   = wx - cen_x; /* cen_x - x1; */
	    }
	}
      else if (wx < cen_x)
	{
	  if (wy < cen_y)
	    {
	      phi = 2 * G_PI - atan (((double)(cen_x -wx)) /
				     ((double)(cen_y - wy)));
	      r   = sqrt (SQR (cen_x - wx) + SQR (cen_y - wy));
	    }
	  else if (wy > cen_y)
	    {
	      phi = G_PI + atan (((double)(cen_x - wx))/((double)(wy - cen_y)));
	      r   = sqrt (SQR (cen_x - wx) + SQR (wy - cen_y));
	    }
	  else
	    {
	      phi = 1.5 * G_PI;
	      r   = cen_x - wx; /* cen_x - x1; */
	    }
	}
      if (wx != cen_x)
	{
	  m = fabs (((double)(wy - cen_y)) / ((double)(wx - cen_x)));
	}
      else
	{
	  m = 0;
	}
    
      if (m <= ((double)(y2 - y1) / (double)(x2 - x1)))
	{
	  if (wx == cen_x)
	    {
	      xmax = 0;
	      ymax = cen_y - y1;
	    }
	  else
	    {
	      xmax = cen_x - x1;
	      ymax = m * xmax;
	    }
	}
      else
	{
	  ymax = cen_y - y1;
	  xmax = ymax / m;
	}
    
      rmax = sqrt ( (double)(SQR (xmax) + SQR (ymax)) );
    
      t = ((cen_y - y1) < (cen_x - x1)) ? (cen_y - y1) : (cen_x - x1);
      rmax = (rmax - t) / 100 * (100 - circle) + t;
    
      phi = fmod (phi + angl, 2*G_PI);
    
      if (pcvals.backwards)
	x_calc = x2 - 1 - (x2 - x1 - 1)/(2*G_PI) * phi;
      else
	x_calc = (x2 - x1 - 1)/(2*G_PI) * phi + x1;
    
      if (pcvals.inverse)
	y_calc = (y2 - y1)/rmax   * r   + y1;
      else
	y_calc = y2 - (y2 - y1)/rmax * r;
    
      xi = (int) (x_calc+0.5);
      yi = (int) (y_calc+0.5);
    
      if (WITHIN(0, xi, img_width - 1) && WITHIN(0, yi, img_height - 1))
	{
	  *x = x_calc;
	  *y = y_calc;
      
	  inside = TRUE;
	}
      else
	{
	  inside = FALSE;
	}
    }
  else
    {
      if (pcvals.backwards)
	phi = (2 * G_PI) * (x2 - wx) / xdiff;
      else
	phi = (2 * G_PI) * (wx - x1) / xdiff;
    
      phi = fmod (phi + angl, 2 * G_PI);
    
      if (phi >= 1.5 * G_PI)
	phi2 = 2 * G_PI - phi;
      else
	if (phi >= G_PI)
	  phi2 = phi - G_PI;
	else
	  if (phi >= 0.5 * G_PI)
	    phi2 = G_PI - phi;
	  else
	    phi2 = phi;
    
      xx = tan (phi2);
      if (xx != 0)
	m = (double) 1.0 / xx;
      else
	m = 0;
    
      if (m <= ((double)(ydiff) / (double)(xdiff)))
	{
	  if (phi2 == 0)
	    {
	      xmax = 0;
	      ymax = ym - y1;
	    }
	  else
	    {
	      xmax = xm - x1;
	      ymax = m * xmax;
	    }
	}
      else
	{
	  ymax = ym - y1;
	  xmax = ymax / m;
	}
    
      rmax = sqrt ((double)(SQR (xmax) + SQR (ymax)));
    
      t = ((ym - y1) < (xm - x1)) ? (ym - y1) : (xm - x1);
    
      rmax = (rmax - t) / 100.0 * (100 - circle) + t;
    
      if (pcvals.inverse)
	r = rmax * (double)((wy - y1) / (double)(ydiff));
      else
	r = rmax * (double)((y2 - wy) / (double)(ydiff));
    
      xx = r * sin (phi2);
      yy = r * cos (phi2);
    
      if (phi >= 1.5 * G_PI)
	{
	  x_calc = (double)xm - xx;
	  y_calc = (double)ym - yy;
	}
      else
	if (phi >= G_PI)
	  {
	    x_calc = (double)xm - xx;
	    y_calc = (double)ym + yy;
	  }
	else
	  if (phi >= 0.5 * G_PI)
	    {
	      x_calc = (double)xm + xx;
	      y_calc = (double)ym + yy;
	    }
	  else
	    {
	      x_calc = (double)xm + xx;
	      y_calc = (double)ym - yy;
	    }
    
      xi = (int)(x_calc + 0.5);
      yi = (int)(y_calc + 0.5);
  
      if (WITHIN(0, xi, img_width - 1) && WITHIN(0, yi, img_height - 1)) {
	*x = x_calc;
	*y = y_calc;
      
	inside = TRUE;
      }
      else
	{
	  inside = FALSE;
	}
    }
  
  return inside;
}

static void
build_preview_source_image (void)
{
  gdouble          left, right, bottom, top;
  gdouble          px, py;
  gdouble          dx, dy;
  gint             x, y;
  guchar          *p;
  guchar           pixel[4];
  GimpPixelFetcher *pf;

  pcint.check_row_0 = g_new (guchar, preview_width);
  pcint.check_row_1 = g_new (guchar, preview_width);
  pcint.image       = g_new (guchar, preview_width * preview_height * 4);
  pcint.dimage      = g_new (guchar, preview_width * preview_height * 3);

  left   = sel_x1;
  right  = sel_x2 - 1;
  bottom = sel_y2 - 1;
  top    = sel_y1;

  dx = (right - left) / (preview_width - 1);
  dy = (bottom - top) / (preview_height - 1);

  py = top;

  pf = gimp_pixel_fetcher_new(drawable);

  p = pcint.image;

  for (y = 0; y < preview_height; y++)
    {
      px = left;

      for (x = 0; x < preview_width; x++)
	{
	  /* Checks */

	  if ((x / GIMP_CHECK_SIZE) & 1)
	    {
	      pcint.check_row_0[x] = GIMP_CHECK_DARK * 255;
	      pcint.check_row_1[x] = GIMP_CHECK_LIGHT * 255;
	    }
	  else
	    {
	      pcint.check_row_0[x] = GIMP_CHECK_LIGHT * 255;
	      pcint.check_row_1[x] = GIMP_CHECK_DARK * 255;
	    }

	  /* Thumbnail image */

	  gimp_pixel_fetcher_get_pixel (pf, (int) px, (int) py, pixel);

	  if (img_bpp < 3)
	    {
	      if (img_has_alpha)
		pixel[3] = pixel[1];
	      else
		pixel[3] = 255;

	      pixel[1] = pixel[0];
	      pixel[2] = pixel[0];
	    }
	  else if (!img_has_alpha)
	    pixel[3] = 255;

	  *p++ = pixel[0];
	  *p++ = pixel[1];
	  *p++ = pixel[2];
	  *p++ = pixel[3];

	  px += dx;
	}

      py += dy;
    }

  gimp_pixel_fetcher_destroy (pf);
}

static gint
polarize_dialog (void)
{
  GtkWidget *dialog;
  GtkWidget *main_vbox;
  GtkWidget *vbox;
  GtkWidget *frame;
  GtkWidget *table;
  GtkWidget *abox;
  GtkWidget *pframe;
  GtkWidget *toggle;
  GtkWidget *hbox;
  GtkObject *adj;

  gimp_ui_init ("polar", TRUE);

  build_preview_source_image ();

  dialog = gimp_dialog_new (_("Polarize"), "polar",
			    gimp_standard_help_func, "filters/polar.html",
			    GTK_WIN_POS_MOUSE,
			    FALSE, TRUE, FALSE,

			    GTK_STOCK_CANCEL, gtk_widget_destroy,
			    NULL, 1, NULL, FALSE, TRUE,
			    GTK_STOCK_OK, dialog_ok_callback,
			    NULL, NULL, NULL, TRUE, FALSE,

			    NULL);

  g_signal_connect (dialog, "destroy",
                    G_CALLBACK (gtk_main_quit),
                    NULL);

  main_vbox = gtk_vbox_new (FALSE, 4);
  gtk_container_set_border_width (GTK_CONTAINER (main_vbox), 6);
  gtk_box_pack_start (GTK_BOX (GTK_DIALOG(dialog)->vbox), main_vbox,
		      FALSE, FALSE, 0);
  gtk_widget_show (main_vbox);

  /* Preview */
  frame = gtk_frame_new (_("Preview"));
  gtk_box_pack_start (GTK_BOX (main_vbox), frame, FALSE, FALSE, 0);
  gtk_widget_show (frame);

  abox = gtk_alignment_new (0.5, 0.5, 0.0, 0.0);
  gtk_container_add (GTK_CONTAINER (frame), abox);
  gtk_widget_show (abox);

  pframe = gtk_frame_new (NULL);
  gtk_frame_set_shadow_type (GTK_FRAME (pframe), GTK_SHADOW_IN);
  gtk_container_set_border_width (GTK_CONTAINER (pframe), 4);
  gtk_container_add (GTK_CONTAINER (abox), pframe);
  gtk_widget_show (pframe);

  pcint.preview = gtk_preview_new (GTK_PREVIEW_COLOR);
  gtk_preview_size (GTK_PREVIEW (pcint.preview), preview_width, preview_height);
  gtk_container_add (GTK_CONTAINER (pframe), pcint.preview);
  gtk_widget_show (pcint.preview);

  /* Controls */
  frame = gtk_frame_new (_("Parameter Settings"));
  gtk_box_pack_start (GTK_BOX (main_vbox), frame, FALSE, FALSE, 0);
  gtk_widget_show (frame);

  vbox = gtk_vbox_new (FALSE, 4);
  gtk_container_set_border_width (GTK_CONTAINER (vbox), 4);
  gtk_container_add (GTK_CONTAINER (frame), vbox);
  gtk_widget_show (vbox);

  table = gtk_table_new (2, 3, FALSE);
  gtk_table_set_row_spacings (GTK_TABLE (table), 2);
  gtk_table_set_col_spacings (GTK_TABLE (table), 4);
  gtk_box_pack_start (GTK_BOX (vbox), table, FALSE, FALSE, 0);
  gtk_widget_show (table);

  adj = gimp_scale_entry_new (GTK_TABLE (table), 0, 0,
			      _("Circle _Depth in Percent:"), SCALE_WIDTH, 0,
			      pcvals.circle, 0.0, 100.0, 1.0, 10.0, 2,
			      TRUE, 0, 0,
			      NULL, NULL);
  g_signal_connect (adj, "value_changed",
                    G_CALLBACK (dialog_scale_update),
                    &pcvals.circle);

  adj = gimp_scale_entry_new (GTK_TABLE (table), 0, 1,
			      _("Offset _Angle:"), SCALE_WIDTH, 0,
			      pcvals.angle, 0.0, 359.0, 1.0, 15.0, 2,
			      TRUE, 0, 0,
			      NULL, NULL);
  g_signal_connect (adj, "value_changed",
                    G_CALLBACK (dialog_scale_update),
                    &pcvals.angle);

  /* togglebuttons for backwards, top, polar->rectangular */
  hbox = gtk_hbox_new (TRUE, 4);
  gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);

  toggle = gtk_check_button_new_with_mnemonic (_("_Map Backwards"));
  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), pcvals.backwards);
  gtk_box_pack_start (GTK_BOX (hbox), toggle, TRUE, TRUE, 0);
  gtk_widget_show (toggle);

  gimp_help_set_help_data (toggle,
			   _("If checked the mapping will begin at the right "
			     "side, as opposed to beginning at the left."),
			   NULL);

  g_signal_connect (toggle, "toggled", 
                    G_CALLBACK (polar_toggle_callback),
                    &pcvals.backwards);

  toggle = gtk_check_button_new_with_mnemonic (_("Map from _Top"));
  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), pcvals.inverse);
  gtk_box_pack_start (GTK_BOX (hbox), toggle, TRUE, TRUE, 0);
  gtk_widget_show (toggle);

  gimp_help_set_help_data (toggle,
			   _("If unchecked the mapping will put the bottom "
			     "row in the middle and the top row on the "
			     "outside.  If checked it will be the opposite."),
			   NULL);

  g_signal_connect (toggle, "toggled", 
                    G_CALLBACK (polar_toggle_callback),
                    &pcvals.inverse);

  toggle = gtk_check_button_new_with_mnemonic (_("To _Polar"));
  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), pcvals.polrec);
  gtk_box_pack_start (GTK_BOX (hbox), toggle, TRUE, TRUE, 0);
  gtk_widget_show (toggle);

  gimp_help_set_help_data (toggle,
			   _("If unchecked the image will be circularly "
			     "mapped onto a rectangle.  If checked the image "
			     "will be mapped onto a circle."),
			   NULL);

  g_signal_connect (toggle, "toggled", 
                    G_CALLBACK (polar_toggle_callback),
                    &pcvals.polrec);

  gtk_widget_show (hbox);

  /* Done */

  gtk_widget_show (dialog);
  dialog_update_preview ();

  gtk_main ();
  gdk_flush ();

  g_free (pcint.check_row_0);
  g_free (pcint.check_row_1);
  g_free (pcint.image);
  g_free (pcint.dimage);

  return pcint.run;
}

static void
dialog_update_preview (void)
{
  gdouble  left, right, bottom, top;
  gdouble  dx, dy;
  gdouble  px, py;
  gdouble  cx = 0.0, cy = 0.0;
  gint     ix, iy;
  gint     x, y;
  gdouble  scale_x, scale_y;
  guchar  *p_ul, *i, *p;
  guchar  *check_ul;
  gint     check;
  guchar   outside[4];

  gimp_get_bg_guchar (drawable, TRUE, outside);

  left   = sel_x1;
  right  = sel_x2 - 1;
  bottom = sel_y2 - 1;
  top    = sel_y1;

  dx = (right - left) / (preview_width - 1);
  dy = (bottom - top) / (preview_height - 1);

  scale_x = (double) preview_width / (right - left + 1);
  scale_y = (double) preview_height / (bottom - top + 1);

  py = top;

  p_ul = pcint.dimage;
/* p_lr = pcint.dimage + 3 * (preview_width * preview_height - 1);*/

  for (y = 0; y < preview_height; y++)
    {
      px = left;

      if ((y / GIMP_CHECK_SIZE) & 1)
	check_ul = pcint.check_row_0;
      else
	check_ul = pcint.check_row_1;

      for (x = 0; x < preview_width; x++)
	{
	  calc_undistorted_coords (px, py, &cx, &cy);

	  cx = (cx - left) * scale_x;
	  cy = (cy - top) * scale_y;

	  ix = (int) (cx + 0.5);
	  iy = (int) (cy + 0.5);

	  check = check_ul[x];

	  if ((ix >= 0) && (ix < preview_width) &&
	      (iy >= 0) && (iy < preview_height))
	    i = pcint.image + 4 * (preview_width * iy + ix);
	  else
	    i = outside;

	  p_ul[0] = check + ((i[0] - check) * i[3]) / 255;
	  p_ul[1] = check + ((i[1] - check) * i[3]) / 255;
	  p_ul[2] = check + ((i[2] - check) * i[3]) / 255;

	  p_ul += 3;

	  px += dx;
	}

      py += dy;
    }

  p = pcint.dimage;

  for (y = 0; y < img_height; y++)
    {
      gtk_preview_draw_row (GTK_PREVIEW (pcint.preview), p, 0, y, preview_width);

      p += preview_width * 3;
    }

  gtk_widget_queue_draw (pcint.preview);
}

static void
dialog_scale_update (GtkAdjustment *adjustment,
		     gdouble       *value)
{
  gimp_double_adjustment_update (adjustment, value);

  dialog_update_preview ();
}

static void
dialog_ok_callback (GtkWidget *widget,
		    gpointer   data)
{
  pcint.run = TRUE;

  gtk_widget_destroy (GTK_WIDGET (data));
}

static void
polar_toggle_callback (GtkWidget *widget,
		       gpointer   data)
{
  gimp_toggle_button_update (widget, data);

  dialog_update_preview ();
}
