/* edge filter for the GIMP
 *  -Peter Mattis
 *
 * This filter performs edge detection on the input image.
 *  The code for this filter is based on "pgmedge", a program
 *  that is part of the netpbm package.
 */

/* pgmedge.c - edge-detect a portable graymap
**
** Copyright (C) 1989 by Jef Poskanzer.
**
** Permission to use, copy, modify, and distribute this software and its
** documentation for any purpose and without fee is hereby granted, provided
** that the above copyright notice appear in all copies and that both that
** copyright notice and this permission notice appear in supporting
** documentation.  This software is provided "as is" without express or
** implied warranty.
*/

/*
 *  Ported to GIMP Plug-in API 1.0
 *  version 1.07
 *  This version requires GIMP v0.99.10 or above.
 *
 *  This plug-in performs edge detection. The code is based on edge.c
 *  for GIMP 0.54 by Peter Mattis.
 *
 *	Eiichi Takamori <taka@ma1.seikyou.ne.jp>
 *	http://ha1.seikyou.ne.jp/home/taka/gimp/
 *
 *  Tips: you can enter arbitrary value into entry.
 *	(not bounded between 1.0 and 10.0)
 *
 *  Changes from version 1.06 to version 1.07:
 *  - Added entry
 *  - Cleaned up code a bit
 *
 *  Differences from Peter Mattis's original `edge' plug-in:
 *    - Added Wrapmode. (useful for tilable images)
 *    - Enhanced speed in this version.
 *    - It works with the alpha channel.
 */


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

#ifdef RCSID
static char rcsid[] = "$Id: edge.c,v 1.2 1997/09/27 10:30:28 nobody Exp $";
#endif

/* Some useful macros */

#define TILE_CACHE_SIZE 48
#define SCALE_WIDTH 92
#define ENTRY_WIDTH 40

#define  WRAP	0
#define  SMEAR  1
#define  BLACK  2

typedef struct {
  gdouble  amount;
  gint    wrapmode;
} EdgeVals;

typedef struct {
  gint run;
} EdgeInterface;

typedef struct {
  GTile *tile;
  gint	row, col;	/* tile's row, col */
  gint  bpp;
  gint  tile_width, tile_height;
  GDrawable *drawable;
  gint  drawable_width, drawable_height;
} TileBuf;

/*
 * Function prototypes.
 */

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

static void      edge        (GDrawable *drawable);
static gint      edge_dialog (GDrawable *drawable);
static void      edge_close_callback   (GtkWidget *widget,
					    gpointer   data);
static void      edge_ok_callback      (GtkWidget *widget,
					    gpointer   data);
static void      edge_toggle_update    (GtkWidget *widget,
					    gpointer   data);
static void      edge_scale_update     (GtkAdjustment *ajustment,
					    gpointer   data);
static void	 edge_entry_update	(GtkWidget *entry,
					 gpointer data);
static long      long_sqrt (long n);

static void   init_tile_buf ( TileBuf *buf, GDrawable *drawable );
static void   get_tile_pixel ( TileBuf *buf, gint x, gint y, 
			      guchar *pixel, gint wrapmode );
static void   end_tile_buf ( TileBuf *buf );

/***** Local vars *****/

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

static EdgeVals evals =
{
  2.0,       /* amount */
  SMEAR    /* wrapmode */
};

static EdgeInterface eint =
{
  FALSE   /*  run  */
};

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

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" },
    { PARAM_FLOAT, "amount", "Edge detection amount" },
    { PARAM_INT32, "wrapmode", "Edge detection behavior: { WRAP (0), SMEAR (1), BLACK (2) }" }
  };
  static GParamDef *return_vals = NULL;
  static gint nargs = sizeof (args) / sizeof (args[0]);
  static gint nreturn_vals = 0;
  gchar *help_string =
    " Perform edge detection on the contents of the specified"
    " drawable. It applies, I think, convolusion with 3x3 kernel. AMOUNT"
    " is an arbitrary constant, WRAPMODE is like displace plug-in"
    " (useful for tilable image).";


  gimp_install_procedure ("plug_in_edge",
			  "Perform edge detection on the contents of the specified drawable",
			  help_string,
			  "Peter Mattis & (ported to 1.0 by) Eiichi Takamori",
			  "Peter Mattis",
			  "1996",
			  "<Image>/Filters/Edge-Detect/Edge",
			  "RGB*, GRAY*",
			  PROC_PLUG_IN,
			  nargs, nreturn_vals,
			  args, return_vals);
}

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

  run_mode = param[0].data.d_int32;

  /*  Get the specified drawable  */
  drawable = gimp_drawable_get (param[2].data.d_drawable);

  *nreturn_vals = 1;
  *return_vals = values;

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

  switch (run_mode)
    {
    case RUN_INTERACTIVE:
      /*  Possibly retrieve data  */
      gimp_get_data ("plug_in_edge", &evals);

      /*  First acquire information with a dialog  */
      if (! edge_dialog (drawable))
	return;
      break;

    case RUN_NONINTERACTIVE:
      /*  Make sure all the arguments are there!  */
      if (nparams != 5)
	status = STATUS_CALLING_ERROR;
      if (status == STATUS_SUCCESS)
	{
	  evals.amount = param[3].data.d_float;
	  evals.wrapmode = param[4].data.d_int32;
	}
      break;

    case RUN_WITH_LAST_VALS:
      /*  Possibly retrieve data  */
      gimp_get_data ("plug_in_edge", &evals);
      break;

    default:
      break;
    }

  if (status == STATUS_SUCCESS)
    {
      gimp_progress_init ("Edge detection...");

      /*  set the tile cache size  */
      gimp_tile_cache_ntiles (TILE_CACHE_SIZE);

      /*  run the edge effect  */
      edge (drawable);

      if (run_mode != RUN_NONINTERACTIVE)
	gimp_displays_flush ();

      /*  Store data  */
      if (run_mode == RUN_INTERACTIVE)
	gimp_set_data ("plug_in_edge", &evals, sizeof (EdgeVals));
    }

  values[0].data.d_status = status;

  gimp_drawable_detach (drawable);
}

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

   TileBuf Util Routines:   Util routines for getting arbitrary pixel
   CAUTION -- the tile is read only !!

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

static void
init_tile_buf( TileBuf *buf, GDrawable *drawable )
{
  buf->tile = NULL;
  buf->col = 0;
  buf->row = 0;
  if ( gimp_drawable_color( drawable->id ) )
    buf->bpp = 3;
  else
    buf->bpp = 1;
  buf->tile_width = gimp_tile_width();
  buf->tile_height = gimp_tile_height();
  buf->drawable = drawable;
  buf->drawable_width = gimp_drawable_width( drawable->id );
  buf->drawable_height = gimp_drawable_height( drawable->id );
}

static void
get_tile_pixel( TileBuf *buf, gint x, gint y, guchar *pixel, gint wrapmode )
{
  gint b;
  gint offx, offy;
  gint row, col;
  guchar *ptr;

  if ( x < 0 || x >= buf->drawable_width ||
       y < 0 || y >= buf->drawable_height )
    switch( wrapmode )
      {
      case WRAP:
	if ( x < 0 || x >= buf->drawable_width )
	  {
	    x %= buf->drawable_width;
	    if ( x < 0 )
	      x += buf->drawable_width;
	  }
	if ( y < 0 || y >= buf->drawable_height )
	  {
	    y %= buf->drawable_height;
	    if ( y < 0 )
	      y += buf->drawable_height;
	  }
	break;
      case SMEAR:
	if ( x < 0 )
	  x = 0;
	if ( x >= buf->drawable_width )
	  x = buf->drawable_width - 1;
	if ( y < 0 )
	  y = 0;
	if ( y >= buf->drawable_height )
	  y = buf->drawable_height - 1;
	break;
      case BLACK:
	if ( x < 0 || x >= buf->drawable_width || 
	     y < 0 || y >= buf->drawable_height )
	  {
	    for ( b = 0; b < buf->bpp; b++ )
	      pixel[b]=0;
	    return;
	  }
	break;
      default:
	return;
      }

  
  col = x / buf->tile_width;
  offx = x % buf->tile_width;
  row = y / buf->tile_height;
  offy = y % buf->tile_height;

  /* retrieve tile */
  if ( !buf->tile || col != buf->col || row != buf->row )
    {
      if( buf->tile )
	gimp_tile_unref( buf->tile, FALSE );
      buf->col = col;
      buf->row = row;
      buf->tile = gimp_drawable_get_tile( buf->drawable, FALSE, row, col );
      gimp_tile_ref( buf->tile );
    }

  /* retrieve target pixel */
  ptr = buf->tile->data + ( offy * buf->tile->ewidth + offx ) * buf->tile->bpp;
  for( b = 0; b < buf->bpp; b++ )
    pixel[b] = ptr[b];
}

static void
end_tile_buf( TileBuf *buf )
{
  if( buf->tile )
    gimp_tile_unref( buf->tile, FALSE );
}

/**********************************************************************
   TileBuf Util Routines End
 **********************************************************************/



static long
long_sqrt (n)
     long n;
{
#define lsqrt_max4pow (1UL << 30)
  /* lsqrt_max4pow is the (machine-specific) largest power of 4 that can
   * be represented in an unsigned long.
   *
   * Compute the integer square root of the integer argument n
   * Method is to divide n by x computing the quotient x and remainder r
   * Notice that the divisor x is changing as the quotient x changes
   * 
   * Instead of shifting the dividend/remainder left, we shift the
   * quotient/divisor right. The binary point starts at the extreme
   * left, and shifts two bits at a time to the extreme right.
   * 
   * The residue contains n-x^2. (Within these comments, the ^ operator
   * signifies exponentiation rather than exclusive or. Also, the /
   * operator returns fractions, rather than truncating, so 1/4 means
   * one fourth, not zero.)
   * 
   * Since (x + 1/2)^2 == x^2 + x + 1/4,
   * n - (x + 1/2)^2 == (n - x^2) - (x + 1/4)
   * Thus, we can increase x by 1/2 if we decrease (n-x^2) by (x+1/4)
   */

  unsigned long residue;        /* n - x^2  */
  unsigned long root;           /* x + 1/4  */
  unsigned long half;           /* 1/2      */

  residue = n;                  /* n - (x = 0)^2, with suitable alignment */

  /*
   * if the correct answer fits in two bits, pull it out of a magic hat
   */
  if (residue <= 12)
    return (0x03FFEA94 >> (residue *= 2)) & 3;

  root = lsqrt_max4pow;         /* x + 1/4, shifted all the way left */
  /* half = root + root; 1/2, shifted likewise */

  /* 
   * Unwind iterations corresponding to leading zero bits 
   */
  while (root > residue)
    root >>= 2;

  /*
   * Unwind the iteration corresponding to the first one bit
   * Operations have been rearranged and combined for efficiency
   * Initialization of half is folded into this iteration
   */
  residue -= root;              /* Decrease (n-x^2) by (0+1/4)             */
  half = root >> 2;             /* 1/4, with binary point shifted right 2  */
  root += half;                 /* x=1. (root is now (x=1)+1/4.)           */
  half += half;                 /* 1/2, properly aligned                   */

  /*
   * Normal loop (there is at least one iteration remaining)
   */
  do
    {
      if (root <= residue)      /* Whenever we can,                          */
        {
          residue -= root;      /* decrease (n-x^2) by (x+1/4)               */
          root += half;         /* increase x by 1/2                         */
        }
      half >>= 2;               /* Shift binary point 2 places right          */
      root -= half;             /* x{ +1/2 } +1/4 - 1/8 == x { +1/2 } 1/8     */
      root >>= 1;               /* 2x{ +1 } +1/4, shifted right 2 places      */
    }
  while (half);                 /* When 1/2 == 0, bin. point is at far right  */

  /* 
   * round up if (x+1/2)^2 < n
   */
  if (root < residue)
    ++root;

  /* 
   * Guaranteed to be correctly rounded (or truncated)
   */
  return root;
}

/********************************************************/
/*                                                      */
/*              Edge Detection main                     */
/*                                                      */
/********************************************************/

static void
edge( GDrawable *drawable )
{
  /*
   * this function is too long, so I must split this into a few
   * functions later ...  -- taka
   */

  GPixelRgn src_rgn, dest_rgn;
  gpointer pr;
  TileBuf buf;
  guchar *srcrow, *src;
  guchar *destrow, *dest;
  guchar pix00[3], pix01[3], pix02[3];
  guchar pix10[3],/*pix11[3],*/ pix12[3];
  guchar pix20[3], pix21[3], pix22[3];
  glong width, height;
  gint alpha, has_alpha, chan;
  gint x, y;
  gint x1, y1, x2, y2;
  glong sum1, sum2;
  glong sum, scale;
  gint maxval;
  gint cur_progress;
  gint max_progress;
  gint wrapmode;

  if (evals.amount < 1.0 ) evals.amount = 1.0;

  init_tile_buf( &buf, drawable );

  gimp_drawable_mask_bounds ( drawable->id, &x1, &y1, &x2, &y2);

  width = gimp_drawable_width ( drawable->id );
  height = gimp_drawable_height ( drawable->id );
  alpha = gimp_drawable_bpp ( drawable->id );
  has_alpha = gimp_drawable_has_alpha ( drawable->id );
  if( has_alpha ) alpha--;

  maxval = 255;
  scale = (10 << 16) / evals.amount;
  wrapmode = evals.wrapmode;

  cur_progress = 0;
  max_progress = (x2 - x1) * (y2 - y1);

  gimp_pixel_rgn_init( &src_rgn, drawable, x1, y1, x2-x1, y2-y1, FALSE, FALSE );
  gimp_pixel_rgn_init( &dest_rgn, drawable, x1, y1, x2-x1, y2-y1, TRUE, TRUE );

  for( pr = gimp_pixel_rgns_register( 2, &src_rgn, &dest_rgn ); 
       pr != NULL;
       pr = gimp_pixel_rgns_process(pr) )
    {
      srcrow = src_rgn.data;
      destrow = dest_rgn.data;
      for ( y = dest_rgn.y;
	    y < (dest_rgn.y + dest_rgn.h);
	    y++, srcrow += src_rgn.rowstride, destrow += dest_rgn.rowstride )
	{
	  src = srcrow;
	  dest = destrow;
	  for( x = dest_rgn.x;
	       x < (dest_rgn.x + dest_rgn.w);
	       x++,  src += src_rgn.bpp, dest += dest_rgn.bpp )
	    {
	      if( dest_rgn.x < x &&  x < dest_rgn.x + dest_rgn.w - 1 &&
		  dest_rgn.y < y &&  y < dest_rgn.y + dest_rgn.h - 1 )
		{
		  /*
		  ** 3x3 kernel is inside of the tile -- do fast
		  ** version
		  */
		  for( chan = 0; chan < alpha; chan++ )
		    {
		      /*
		       * PIX(1,1) is the current pixel, so
		       * e.g. PIX(0,0) means 1 above and 1 left pixel.
		       *
		       * There were casting to `long' in GIMP 0.54
		       * edge code, but I think `guchar' should be
		       * extended to `int' with minus operators, so
		       * there's no need to cast to `long'. Both sum1
		       * and sum2 will be between -4*255 to 4*255
		       *
		       *    -- taka
		       */
#define PIX(X,Y)  src[ (Y-1)*src_rgn.rowstride + (X-1)*src_rgn.bpp + chan ]
		      /* make convolusion */
		      sum1 = ( PIX(2,0) - PIX(0,0) ) +
			 2 * ( PIX(2,1) - PIX(0,1) ) +
			     ( PIX(2,2) - PIX(2,0) );
		      sum2 = ( PIX(0,2) - PIX(0,0) ) +
			 2 * ( PIX(1,2) - PIX(1,0) ) +
			     ( PIX(2,2) - PIX(2,0) );
#undef  PIX
		      /* common job ... */
		      sum = long_sqrt ( (long) sum1 * sum1 + (long) sum2 * sum2);
		      sum = (sum * scale) >> 16;    /* arbitrary scaling factor */
		      if (sum > maxval) sum = maxval;
		      dest[chan] = sum;
		    }
		} else {
		  /*
		  ** The kernel is not inside of the tile -- do slow
		  ** version
		  */
		  /*
		   * When the kernel intersects the boundary of the
		   * image, get_tile_pixel() will (should) do the
		   * right work with `wrapmode'.
		   */
		  get_tile_pixel( &buf, x-1, y-1, pix00, wrapmode );
		  get_tile_pixel( &buf, x  , y-1, pix10, wrapmode );
		  get_tile_pixel( &buf, x+1, y-1, pix20, wrapmode );
		  get_tile_pixel( &buf, x-1, y  , pix01, wrapmode );
		  get_tile_pixel( &buf, x+1, y  , pix21, wrapmode );
		  get_tile_pixel( &buf, x-1, y+1, pix02, wrapmode );
		  get_tile_pixel( &buf, x  , y+1, pix12, wrapmode );
		  get_tile_pixel( &buf, x+1, y+1, pix22, wrapmode );
	      
		  for( chan = 0; chan < alpha; chan++ )
		    {
		      /* make convolusion */
		      sum1 = (pix20[chan] - pix00[chan] ) +
			 2 * (pix21[chan] - pix01[chan] ) +
			     (pix22[chan] - pix20[chan] );
		      sum2 = (pix02[chan] - pix00[chan] ) +
			 2 * (pix12[chan] - pix10[chan] ) +
			     (pix22[chan] - pix20[chan] );
		      /* common job ... */
		      sum = long_sqrt ( (long) sum1 * sum1 + (long) sum2 * sum2);
		      sum = (sum * scale) >> 16;    /* arbitrary scaling factor */
		      if (sum > maxval) sum = maxval;
		      dest[chan] = sum;
		    }
		}
	      if(has_alpha)
		dest[alpha]=src[alpha];
	    }
        }
      cur_progress += dest_rgn.w * dest_rgn.h;
      gimp_progress_update ( (double) cur_progress / (double) max_progress);
    }

  end_tile_buf( &buf );
  gimp_drawable_flush (drawable);
  gimp_drawable_merge_shadow (drawable->id, TRUE);
  gimp_drawable_update (drawable->id, x1, y1, (x2 - x1), (y2 - y1));
}

/*******************************************************/
/*                                                     */
/*                    Dialog                           */
/*                                                     */
/*******************************************************/

static gint
edge_dialog(GDrawable *drawable)
{
  GtkWidget *dlg;
  GtkWidget *frame;
  GtkWidget *table;
  GtkWidget *hbox;
  GtkWidget *button;
  GtkWidget *toggle;
  GtkWidget *label;
  GtkWidget *scale;
  GtkObject *scale_data;
  GtkWidget *entry;
  gchar	    buffer[256];
  GSList    *group = NULL;
  gchar	    **argv;
  gint	    argc;
  gint	    use_wrap = (evals.wrapmode == WRAP);
  gint	    use_smear = (evals.wrapmode == SMEAR);
  gint	    use_black = (evals.wrapmode == BLACK);
  gdouble   init_val;

  argc = 1;
  argv = g_new (gchar *, 1);
  argv[0] = g_strdup ("edge");

  gtk_init (&argc, &argv);
  gtk_rc_parse (gimp_gtkrc ());

  dlg = gtk_dialog_new ();
  gtk_window_set_title (GTK_WINDOW (dlg), "Edge Detection");
  gtk_window_position (GTK_WINDOW (dlg), GTK_WIN_POS_MOUSE);
  gtk_signal_connect (GTK_OBJECT (dlg), "destroy",
		      (GtkSignalFunc) edge_close_callback,
		      NULL);

  /*  Action area  */
  button = gtk_button_new_with_label ("OK");
  GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT);
  gtk_signal_connect (GTK_OBJECT (button), "clicked",
                      (GtkSignalFunc) edge_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);

  /*  parameter settings  */
  frame = gtk_frame_new ("Edge Detection Options");
  gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_ETCHED_IN);
  gtk_container_border_width (GTK_CONTAINER (frame), 10);
  gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dlg)->vbox), frame, TRUE, TRUE, 0);

  table = gtk_table_new (2, 3, FALSE);
  gtk_container_border_width (GTK_CONTAINER (table), 5);
  gtk_container_add (GTK_CONTAINER (frame), table);

  /*
    Label, scale, entry for	evals.amount
   */

  label = gtk_label_new ("Amount:");
  gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
  gtk_widget_show (label);

  /* This prevents annoying change of adjustment */
  init_val = evals.amount < 1.0 ? 1.0 :
	     evals.amount > 10.0 ? 10.0 : evals.amount;
  scale_data = gtk_adjustment_new (init_val, 1.0, 10.0, 0.1, 0.1, 0.0);

  scale = gtk_hscale_new (GTK_ADJUSTMENT (scale_data));
  gtk_widget_set_usize (scale, SCALE_WIDTH, 0);
  gtk_scale_set_draw_value (GTK_SCALE (scale), FALSE);
#if 0
  gtk_scale_set_value_pos (GTK_SCALE (scale), GTK_POS_TOP);
  gtk_scale_set_digits (GTK_SCALE (scale), 1);
  gtk_range_set_update_policy (GTK_RANGE (scale), GTK_UPDATE_DELAYED);
#endif

  entry = gtk_entry_new ();
  gtk_widget_set_usize (entry, ENTRY_WIDTH, 0);

  gtk_widget_show (scale);
  gtk_widget_show (entry);

  gtk_table_attach (GTK_TABLE (table), label, 0, 1, 0, 1,
		    GTK_FILL, 0, 5, 0);
  gtk_table_attach (GTK_TABLE (table), scale, 2, 3, 0, 1,
		    GTK_EXPAND | GTK_FILL, 0, 5, 0);
  gtk_table_attach (GTK_TABLE (table), entry, 1, 2, 0, 1,
		    GTK_FILL, 0, 0, 0);

  gtk_signal_connect (GTK_OBJECT (scale_data), "value_changed",
		      (GtkSignalFunc) edge_scale_update,
		      &evals.amount);
  sprintf (buffer, "%0.1f", evals.amount);
  gtk_entry_set_text (GTK_ENTRY (entry), buffer);
  gtk_signal_connect (GTK_OBJECT (entry), "changed",
		      (GtkSignalFunc) edge_entry_update,
		      &evals.amount);

  gtk_object_set_user_data (GTK_OBJECT (entry), scale_data);
  gtk_object_set_user_data (scale_data, entry);
  

  /*
    Radio buttons WRAP, SMEAR, BLACK ...
   */

  hbox = gtk_hbox_new (FALSE, 5);
  gtk_container_border_width (GTK_CONTAINER (hbox), 5);
  gtk_table_attach (GTK_TABLE (table), hbox, 0, 3, 1, 2,
		    GTK_FILL, GTK_FILL, 0, 0);

  toggle = gtk_radio_button_new_with_label (group, "Wrap");
  group = gtk_radio_button_group (GTK_RADIO_BUTTON (toggle));
  gtk_box_pack_start (GTK_BOX (hbox), toggle, TRUE, TRUE, 0);
  gtk_signal_connect (GTK_OBJECT (toggle), "toggled",
		      (GtkSignalFunc) edge_toggle_update,
		      &use_wrap);
  gtk_toggle_button_set_state (GTK_TOGGLE_BUTTON (toggle), use_wrap);
  gtk_widget_show (toggle);

  toggle = gtk_radio_button_new_with_label (group, "Smear");
  group = gtk_radio_button_group (GTK_RADIO_BUTTON (toggle));
  gtk_box_pack_start (GTK_BOX (hbox), toggle, TRUE, TRUE, 0);
  gtk_signal_connect (GTK_OBJECT (toggle), "toggled",
		      (GtkSignalFunc) edge_toggle_update,
		      &use_smear);
  gtk_toggle_button_set_state (GTK_TOGGLE_BUTTON (toggle), use_smear);
  gtk_widget_show (toggle);

  toggle = gtk_radio_button_new_with_label (group, "Black");
  group = gtk_radio_button_group (GTK_RADIO_BUTTON (toggle));
  gtk_box_pack_start (GTK_BOX (hbox), toggle, TRUE, TRUE, 0);
  gtk_signal_connect (GTK_OBJECT (toggle), "toggled",
		      (GtkSignalFunc) edge_toggle_update,
		      &use_black);
  gtk_toggle_button_set_state (GTK_TOGGLE_BUTTON (toggle), use_black);
  gtk_widget_show (toggle);
  gtk_widget_show (hbox);

  gtk_widget_show (table);
  gtk_widget_show (frame);
  gtk_widget_show (dlg);

  gtk_main ();
  gdk_flush ();

  if (use_wrap)
    evals.wrapmode = WRAP;
  else if (use_smear)
    evals.wrapmode = SMEAR;
  else if (use_black)
    evals.wrapmode = BLACK;

  return eint.run;

}


/*  Edge detection interface functions  */

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

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

static void
edge_toggle_update (GtkWidget *widget,
			gpointer   data)
{
  int *toggle_val;

  toggle_val = (int *) data;

  if (GTK_TOGGLE_BUTTON (widget)->active)
    *toggle_val = TRUE;
  else
    *toggle_val = FALSE;
}

static void
edge_scale_update (GtkAdjustment *adjustment,
		       gpointer      data)
{
  GtkWidget	*entry;
  gdouble *ptr = (gdouble *) data;

  *ptr = adjustment->value;

  entry = gtk_object_get_user_data (GTK_OBJECT (adjustment));
  if (entry)
    {
      gchar	buffer[256];

      sprintf (buffer, "%0.1f", *ptr);
      gtk_signal_handler_block_by_data (GTK_OBJECT (entry), data);
      gtk_entry_set_text (GTK_ENTRY (entry), buffer);
      gtk_signal_handler_unblock_by_data (GTK_OBJECT (entry), data);
    }
}

static void
edge_entry_update (GtkWidget *entry,
		   gpointer data)
{
  GtkAdjustment		*adjustment;
  gdouble		*ptr = (gdouble *) data;
  gdouble		new_val;

  *ptr = atof (gtk_entry_get_text (GTK_ENTRY (entry)));
  
  adjustment = gtk_object_get_user_data (GTK_OBJECT (entry));
  if (adjustment)
    {
      new_val = *ptr < adjustment->lower ? adjustment->lower :
		*ptr > adjustment->upper ? adjustment->upper : *ptr;
      adjustment->value = new_val;
      gtk_signal_handler_block_by_data (GTK_OBJECT (adjustment), data);
      gtk_signal_emit_by_name (GTK_OBJECT (adjustment), "value_changed");
      gtk_signal_handler_unblock_by_data (GTK_OBJECT (adjustment), data);
    }
}
