/*
 * Thin Line plug-in for The GIMP
 *
 * Copyright (C) 2002 Iccii <iccii@hotmail.com>
 */

/* The GIMP -- an image manipulation program
 * Copyright (C) 1995 Spencer Kimball and Peter Mattis
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 *
 */

/*
 * - ChangeLog -
 *
 * version 1.0  2002/02/26  Iccii <iccii@hotmail.com>
 *   - Initial relase
 * version 1.1  2002/02/27  Iccii <iccii@hotmail.com>
 *   - Add preview
 * version 1.2  2002/02/28  Iccii <iccii@hotmail.com>
 *   - Small optimizes
 * version 1.3  2002/03/30  Iccii <iccii@hotmail.com>
 *   - Exchange a preview and add a tooltip help widget
 * version 1.4  2002/04/02  Iccii <iccii@hotmail.com>
 *   - Scrollable preview
 * version 1.5  2002/04/07  Iccii <iccii@hotmail.com>
 *   - Add new edge detect algorithm
 *     (Laplace, Sobel, Gradient, Roberts, Liner Differential)
 * version 1.6  2002/04/12  Iccii <iccii@hotmail.com>
 *   - Optimize (speed up!)
 *   - Fixed bug (if selection area is exist...)
 *   - Clean up the code
 * version 1.7  2002/04/13  Iccii <iccii@hotmail.com>
 *   - New event for preview widget (button pressed and released)
 *   - Add amount slider to adjust edge value
 *
 */

/*
 * Reference Book
 *
 * http://www.ohmsha.co.jp/data/books/contents/4-274-94619-3.htm
 */

/*
 * TODO:
 *
 * - use tile cache system and speed up
 * - support INDEXED mode
 *
 */


#ifdef HAVE_CONFIG_H
#include "config.h"
#endif /* HAVE_CONFIG_H */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>

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

#ifdef HAVE_CONFIG_H
# include "libgimp/stdplugins-intl.h"
#else /* HAVE_CONFIG_H */
#include <libgimp/gimpintl.h>
#ifdef HAVE_LC_MESSAGES
#define INIT_I18N()	G_STMT_START{			\
  setlocale(LC_MESSAGES, ""); 				\
  bindtextdomain("gimp-libgimp", LOCALEDIR);            \
  bindtextdomain("gimp-std-plugins", LOCALEDIR);	\
  textdomain("gimp-std-plugins");			\
  			}G_STMT_END
#else /* HAVE_LC_MESSAGES */
#define INIT_I18N()	G_STMT_START{			\
  bindtextdomain("gimp-libgimp", LOCALEDIR);            \
  bindtextdomain("gimp-std-plugins", LOCALEDIR);	\
  textdomain("gimp-std-plugins");			\
  			}G_STMT_END
#endif /* HAVE_LC_MESSAGES */

#define INIT_I18N_UI()	G_STMT_START{	\
  gtk_set_locale();			\
  setlocale (LC_NUMERIC, "C");		\
  INIT_I18N();				\
			}G_STMT_END
#endif /* HAVE_CONFIG_H */


/*********************************
 *
 *  PLUGIN-SPECIFIC CONSTANTS
 *
 ********************************/

/*
 *  progress meter update frequency
 */

#define PLUG_IN_NAME      "plug_in_thin_line"
#define PLUG_IN_VERSION   "v1.6 Apr 2002"

#define PREVIEW_SIZE      128

#define HIGH_LEVEL        255
#define LOW_LEVEL         0

#ifndef BLACK
#define BLACK 0
#endif
#ifndef WHITE
#define WHITE 255
#endif

/*********************************
 *
 *  PLUGIN-SPECIFIC STRUCTURES AND DATA
 *
 ********************************/

enum
{
  BW_COLOR,
  PREWITT,
  GRADIENT,
  ROBERTS,
  DIFFERENTIAL,
  LAPLACE,
  SOBEL,
};

enum
{
  TWO_TONE,
  GRAY,
};

typedef struct
{
  gint    mode;         /* Edge Line */
  gint    threshold;    /* Threshold for color */
  gint    amount;       /* Amount for edge detection */
  gint    steps;        /* Number of Steps */
  gint    desaturate;   /* desaturate color channel */
  gint    invert;       /* Invert Image */
  gint    thinning;     /* Don't thin */
  gint    isolation;    /* Remove isolation points */
  gint    color;        /* Detect Color */
} LineVals;

static LineVals lvals =
{
  PREWITT,
  128,                  /* always 0 <= threshold <= 255 */
  128,
  1024,
  TRUE,
  FALSE,
  TRUE,
  FALSE,
  WHITE,
};

typedef struct
{
  gint run;
} LineInterface;

static LineInterface lint =
{
  FALSE     /*  have we run? */
};

typedef struct
{
  GtkWidget *widget;
  guchar    *dest;
  guchar    *pixel;
  guchar    *even;
  guchar    *odd;
  gint       bpp;
  gint       width;		/* Width of preview widget */
  gint       height;		/* Height of preview widget */
  gint       x1;		/* Upper-left X of preview */
  gint       y1;		/* Upper-left Y of preview */
  gint       x2;		/* Lower-right X of preview */
  gint       y2;		/* Lower-right Y of preview */
  gint       d_is_gray;
  gint       d_is_rgb;
  gint       bytes;
  gint       has_alpha;
} PreviewData;

static PreviewData preview_d =
{
  NULL,      /*  *widget     */
  NULL,      /*  *dest       */
  NULL,      /*  *pixel      */
  NULL,      /*  *even       */
  NULL,      /*  *odd        */
  0,         /*   bpp        */
  0,         /*   width      */
  0,         /*   height     */
  0,         /*   x1         */
  0,         /*   y1         */
  0,         /*   x2         */
  0,         /*   y2         */
  0,         /*   d_is_gray  */
  0,         /*   d_is_rgb   */
  0,         /*   bytes      */
  0,         /*   has_alpha  */
};

/*********************************
 *
 *  LOCAL FUNCTIONS
 *
 ********************************/

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

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

static void apply_filter (guchar dest[],
                          gint   x1,
                          gint   x2,
                          gint   y1,
                          gint   y2,
                          gint   bpp,
                          gint   bytes,
                          gint   d_is_gray,
                          gint   d_is_rgb,
                          gint   preview);
static void thin_line (GimpDrawable *drawable);
static void thinning (guchar dest_channel[],
                      gint   width,
                      gint   height,
                      gint   preview);
static gint cconc (gint ia[]);
static void store_data (guchar dest[],
                        guchar dest_channel[],
                        gint   channel,
                        gint   bpp,
                        gint   x1,
                        gint   x2,
                        gint   y1,
                        gint   y2,
                        gint   type);

static void edge_detect  (guchar dest_channel[],
                          gint   width,
                          gint   height,
                          gint   preview);
static void prewitt      (guchar dest_channel[],
                          gint   width,
                          gint   height,
                          gint   preview);
static void gradient     (guchar dest_channel[],
                          gint   width,
                          gint   height,
                          gint   preview);
static void roberts      (guchar dest_channel[],
                          gint   width,
                          gint   height,
                          gint   preview);
static void differential (guchar dest_channel[],
                          gint   width,
                          gint   height,
                          gint   preview);
static void laplace      (guchar dest_channel[],
                          gint   width,
                          gint   height,
                          gint   preview);
static void sobel        (guchar dest_channel[],
                          gint   width,
                          gint   height,
                          gint   preview);

static gboolean thin_line_dialog  (GimpDrawable *drawable);
static void thin_line_ok_callback (GtkWidget *widget,
                                   gpointer   data);
static void thinning_toggled      (GtkWidget *widget,
                                   gpointer   data);
static void color_toggle_update   (GtkWidget *widget,
                                   gpointer   data);
static void thin_line_radio_button_callback (GtkWidget *widget,
                                             gpointer   data);

static void preview_do_row (gint    row,
                            gint    width,
                            guchar *even,
                            guchar *odd,
                            guchar *src);
static void filter_preview          (void);
static void filter_preview_through  (void);
static void filter_preview_force    (void);
static void preview_init            (void);
static void preview_exit            (void);
static void preview_scroll_callback (void);


/***** Globals *****/

static gboolean   color_white;

static GimpDrawable *drawable;
static gint       sel_x1;		/* Selection bounds */
static gint       sel_y1;		/* Selection bounds */
static gint       sel_x2;		/* Selection bounds */
static gint       sel_y2;		/* Selection bounds */
static gint       sel_width;		/* Selection width  */
static gint       sel_height;		/* Selection height */

static gint       preview_cache_rowstride;
static GtkObject *hscroll_data;		/* Horizontal scrollbar data */
static GtkObject *vscroll_data;		/* Vertical scrollbar data */
static gint       auto_preview = TRUE;


/************************************ Guts ***********************************/

MAIN ()

/*********************************
 *
 *  query() - query_proc
 *
 *      called by the GIMP to learn about this plug-in
 *
 ********************************/

static void
query (void)
{
  static GimpParamDef args[] =
  {
    { GIMP_PDB_INT32,    "run_mode",   "Interactive, non-interactive"      },
    { GIMP_PDB_IMAGE,    "image",      "Input image (unused)"              },
    { GIMP_PDB_DRAWABLE, "drawable",   "Input drawable"                    },
    { GIMP_PDB_INT32,    "mode"        "Edge detect mode"                  },
    { GIMP_PDB_INT32,    "threshold",  "Threshold (0 <= threshold <= 255)" },
    { GIMP_PDB_INT32,    "amount"      "Amount fot Edge detect"            },
    { GIMP_PDB_INT32,    "steps",      "Number of Steps for thinning"      },
    { GIMP_PDB_INT32,    "desaturate", "Desaturete all color channel"      },
    { GIMP_PDB_INT32,    "invert",     "Invert Image"                      },
    { GIMP_PDB_INT32,    "thinning"    "Apply Thinning filter"             },
    { GIMP_PDB_INT32,    "isolation"   "Remove isolation points"           },
    { GIMP_PDB_INT32,    "color",  "Color mode for line detection (0 or 255)" }
  };
  static gint nargs = sizeof(args) / sizeof (args[0]);

  gimp_install_procedure (PLUG_IN_NAME,
                          "Convert image into thin line image",
                          "Convert image into thin line image",
                          "Iccii <iccii@hotmail.com>",
                          "Iccii",
                          PLUG_IN_VERSION,
                          N_("<Image>/Filters/Edge-Detect/Thin Line..."),
                          "RGB*, GRAY*",
                          GIMP_PLUGIN,
                          nargs, 0,
                          args, NULL);
}

/*********************************
 *
 *  run() - main routine
 *
 *  This handles the main interaction with the GIMP itself,
 *  and invokes the routine that actually does the work.
 *
 ********************************/

static void
run (gchar      *name, 
     gint        nparams, 
     GimpParam  *param, 
     gint       *nreturn_vals,
     GimpParam **return_vals)
{
  GimpRunModeType run_mode;
  GimpPDBStatusType status = GIMP_PDB_SUCCESS;        /* assume the best! */
  static GimpParam values[1];

  INIT_I18N_UI();

  /*
   *  Get the specified drawable, do standard initialization.
   */
  run_mode = param[0].data.d_int32;
  drawable = gimp_drawable_get(param[2].data.d_drawable);

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

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

  values[0].type = GIMP_PDB_STATUS;
  values[0].data.d_status = status;
  *nreturn_vals = 1;
  *return_vals = values;
  /*
   *  Make sure the drawable type is appropriate.
   */
  if (gimp_drawable_is_rgb (drawable->id) ||
      gimp_drawable_is_gray (drawable->id))
    {
      switch (run_mode)
        {
          /*
           *  If we're running interactively, pop up the dialog box.
           */
        case GIMP_RUN_INTERACTIVE:
          gimp_get_data (PLUG_IN_NAME, &lvals);
          if (!thin_line_dialog (drawable))        /* return on Cancel */
            return;
          break;

          /*
           *  If we're not interactive (probably scripting), we
           *  get the parameters from the param[] array, since
           *  we don't use the dialog box.  Make sure all
           *  parameters have legitimate values.
           */
        case GIMP_RUN_NONINTERACTIVE:
          if (nparams == 12)
            {
              lvals.mode       = param[3].data.d_int32;
              lvals.threshold  = CLAMP(param[4].data.d_int32, 0, 255);
              lvals.amount     = param[5].data.d_int32;
              lvals.steps      = param[6].data.d_int32;
              lvals.desaturate = (param[7].data.d_int32) ? TRUE : FALSE;
              lvals.invert     = (param[8].data.d_int32) ? TRUE : FALSE;
              lvals.thinning   = (param[9].data.d_int32) ? TRUE : FALSE;
              lvals.isolation  = (param[10].data.d_int32) ? TRUE : FALSE;
              lvals.color      = (param[11].data.d_int32) ? WHITE : BLACK;
            }
          else
            {
              status = GIMP_PDB_CALLING_ERROR;
            }
          break;

          /*
           *  If we're running with the last set of values, get those values.
           */
	case GIMP_RUN_WITH_LAST_VALS:
	  gimp_get_data (PLUG_IN_NAME, &lvals);
	  break;

	  /*
	   *  Hopefully we never get here!
	   */
	default:
	  break;
        }

      if (status == GIMP_PDB_SUCCESS)
	{
	  /*
	   *  JUST DO IT!
	   */
	  gimp_tile_cache_ntiles (2 * (drawable->width / gimp_tile_width() + 1));

	  thin_line (drawable);
	  /*
	   *  If we ran interactively (even repeating) update the display.
	   */
	  if (run_mode != GIMP_RUN_NONINTERACTIVE)
	    {
	      gimp_displays_flush ();
            }
	  /*
	   *  If we use the dialog popup, set the data for future use.
	   */
	  if (run_mode == GIMP_RUN_INTERACTIVE)
	    {
	      gimp_set_data (PLUG_IN_NAME, &lvals, sizeof (LineVals));
            }
        }
    }
  else
    {
      /*
       *  If we got the wrong drawable type, we need to complain.
       */
      status = GIMP_PDB_EXECUTION_ERROR;
    }
  /*
   *  DONE!
   *  Set the status where the GIMP can see it, and let go
   *  of the drawable.
   */
  values[0].data.d_status = status;
  gimp_drawable_detach (drawable);
}

/*********************************
 *
 *  thin_line()
 *
 *  Actually mess with the image.
 *
 ********************************/

static void
thin_line (GimpDrawable *drawable)
{
  GimpPixelRgn srcPR, destPR;
  gint      bytes, bpp;
  gint      has_alpha;
  gint      d_is_rgb, d_is_gray;
  guchar   *dest;

  has_alpha = gimp_drawable_has_alpha(drawable->id);
  d_is_rgb = gimp_drawable_is_rgb(drawable->id);
  d_is_gray = gimp_drawable_is_gray(drawable->id);
  bpp = drawable->bpp;
  bytes = bpp - has_alpha;

  /*
   *  allocate image buffers
   */
  dest = g_new (guchar, sel_width * sel_height * bpp);

  /*
   *  initialize the pixel regions
   */
  gimp_pixel_rgn_init (&srcPR, drawable, sel_x1, sel_y1, sel_width, sel_height, FALSE, FALSE);
  gimp_pixel_rgn_init (&destPR, drawable, sel_x1, sel_y1, sel_width, sel_height, TRUE, TRUE);

  gimp_pixel_rgn_get_rect (&srcPR, dest, sel_x1, sel_y1, sel_width, sel_height);

  apply_filter (dest, 0, sel_width, 0, sel_height, bpp, bytes,
                d_is_gray, d_is_rgb, FALSE);

  gimp_pixel_rgn_set_rect (&destPR, dest, sel_x1, sel_y1, sel_width, sel_height);

  /*
   *  update the thin linered region
   */
  gimp_drawable_flush(drawable);
  gimp_drawable_merge_shadow(drawable->id, TRUE);
  gimp_drawable_update(drawable->id, sel_x1, sel_y1, sel_width, sel_height);
  /*
   *  clean up after ourselves.
   */
  g_free (dest);
}


/*
 * This base function called by both main filter and preview
 */
static void
apply_filter (guchar dest[],
              gint   x1,
              gint   x2,
              gint   y1,
              gint   y2,
              gint   bpp,
              gint   bytes,
              gint   d_is_gray,
              gint   d_is_rgb,
              gint   preview)
{
  gint    x, y, j;
  gint    dix;
  gfloat  gray;

  gint    width;
  gint    height;
  guchar *dest_channel;

  gint    edge_detecting = (lvals.mode == BW_COLOR) ? FALSE : TRUE;

  width  = x2 - x1;
  height = y2 - y1;

  dest_channel = g_new (guchar, (width + 2) * (height + 2));


  if (lvals.invert)
    for (y = y1; y < y2; y++)
      for (x = x1; x < x2; x++)
        {
          dix = bpp * (width * y + x);
          for (j = 0; j < bytes; j++)
            dest[dix + j] = 255 - dest[dix + j];
        }

  /* Prewitt edge detect */

  if (edge_detecting)
    {
      /* apply to gray image */
      if (d_is_gray)
        {
          if (!preview)
            gimp_progress_init (_("Edge Detecting (Gray)..."));
          store_data (dest, dest_channel, 0, bpp, x1, x2, y1, y2, 0);
          edge_detect (dest_channel, width + 2, height + 2, preview);
          store_data (dest, dest_channel, 0, bpp, x1, x2, y1, y2, 1);
        }
      /* apply to color channel with desaturate */
      else if (d_is_rgb && lvals.desaturate)
        {
          if (!preview)
            gimp_progress_init (_("Edge Detecting (Desaturated)..."));
          store_data (dest, dest_channel, 0, bpp, x1, x2, y1, y2, 0);
          edge_detect (dest_channel, width + 2, height + 2, preview);
          for (j = 0; j < bytes; j++)
            store_data (dest, dest_channel, j, bpp, x1, x2, y1, y2, 1);
        }
      /* apply to each color channel */
      else if (d_is_rgb && !lvals.desaturate)
        {
          for (j = 0; j < bytes; j++)
            {
              if (!preview)
                {
                  switch (j)
                    {
                    case 0:
                      gimp_progress_init (_("Edge Detecting (Red)..."));
                      break;
                    case 1:
                      gimp_progress_init (_("Edge Detecting (Green)..."));
                      break;
                    case 2:
                      gimp_progress_init (_("Edge Detecting (Blue)..."));
                      break;
                    default:
                      break;
                    }
                }
              store_data (dest, dest_channel, j, bpp, x1, x2, y1, y2, 0);
              edge_detect (dest_channel, width + 2, height + 2, preview);
              store_data (dest, dest_channel, j, bpp, x1, x2, y1, y2, 1);
            }
        }
      else
        {
          g_message (_("Error!"));
        }
    }

  /* Tow tone (b/w) color using threshold value */

  if (lvals.mode == BW_COLOR || lvals.thinning)
    {
      for (y = y1; y < y2; y++)
        for (x = x1; x < x2; x++)
          {
            dix = bpp * (width * y + x);

            if (lvals.desaturate && d_is_rgb)
              {
                gray = 0.299 * dest[dix] + 0.587 * dest[dix + 1] + 0.114 * dest[dix + 2];
                for (j = 0; j < bytes; j++)
                  dest[dix + j] = (guchar)CLAMP(gray, 0, 255);
              }

            for (j = 0; j < bytes; j++)
              {
                if (dest[dix + j] < lvals.threshold)
                  dest[dix + j] = WHITE;
                else
                  dest[dix + j] = BLACK;
              }
          }
    }

  if ((lvals.color == WHITE && edge_detecting) ||
      (lvals.color == WHITE && lvals.mode == BW_COLOR))
      for (y = y1; y < y2; y++)
        for (x = x1; x < x2; x++)
            {
              dix = bpp * (width * y + x);
              for (j = 0; j < bytes; j++)
                dest[dix + j] = 255 - dest[dix + j];
            }

  /*
   * Apply!
   */

  if (lvals.thinning)
    {
      if (d_is_gray)
        {
          if (!preview)
            {
              gimp_progress_init (_("Line Detecting (Gray)..."));
            }
          store_data (dest, dest_channel, 0, bpp, x1, x2, y1, y2, 0);
          thinning(dest_channel, width + 2, height + 2, preview);
          store_data (dest, dest_channel, 0, bpp, x1, x2, y1, y2, 1);
        }
      else if (d_is_rgb && lvals.desaturate)
        {
          if (!preview)
            {
              gimp_progress_init (_("Line Detecting (Desaturated)..."));
            }
          store_data (dest, dest_channel, 0, bpp, x1, x2, y1, y2, 0);
          thinning(dest_channel, width + 2, height + 2, preview);
          for (j = 0; j < bytes; j++)
            store_data (dest, dest_channel, j, bpp, x1, x2, y1, y2, 1);
        }
      /* apply to each color channel */
      else if (d_is_rgb && ! lvals.desaturate)
        {
          for (j = 0; j < bytes; j++)
            {
              if (!preview)
                switch (j)
                  {
                  case 0:
                    gimp_progress_init (_("Line Detecting (Red)..."));
                    break;
                  case 1:
                    gimp_progress_init (_("Line Detecting (Green)..."));
                    break;
                  case 2:
                    gimp_progress_init (_("Line Detecting (Blue)..."));
                    break;
                  default:
                    break;
                  }
              store_data (dest, dest_channel, j, bpp, x1, x2, y1, y2, 0);
              thinning(dest_channel, width + 2, height + 2, preview);
              store_data (dest, dest_channel, j, bpp, x1, x2, y1, y2, 1);
            }
        }
    }

  {
    gboolean A, B;
    A = ((lvals.color == BLACK && lvals.mode == BW_COLOR) ||
         (lvals.color == WHITE && edge_detecting && lvals.thinning == FALSE) ||
         (lvals.color == BLACK && edge_detecting && lvals.thinning == TRUE));
    B = (edge_detecting && lvals.invert);

    if ((A || B) && !(A && B))
      for (y = y1; y < y2; y++)
        for (x = x1; x < x2; x++)
          {
            dix = bpp * (width * y + x);
            for (j = 0; j < bytes; j++)
              dest[dix + j] = 255 - dest[dix + j];
          }
  }

  g_free (dest_channel);
}

/*
 * Hilditch thinning
 */

static void
thinning (guchar dest_channel[],
          gint   width,
          gint   height,
          gint   preview)
{
  gint   ia[9];
  gint   ic[9];
  gint   dix;
  gint   overflow, overflow_max;
  gint   i, ix, iy, m, ir, iv, iw;

  m = 100; /* why 100? */
  ir = 1;
  overflow = 0;
  overflow_max = lvals.steps;

  while ((ir != 0) && (overflow++ < overflow_max))
    {
      ir = 0;
      for (iy = 1; iy < height - 1; iy++)
        {
          for (ix = 1; ix < width - 1; ix++)
            {
              dix = iy * width + ix;
              if (dest_channel[dix] != HIGH_LEVEL)
                continue;

              /* ia[x] = { [3] [2] [1]
               *           [4] [8] [0]
               *           [5] [6] [7] }
               */
              ia[0] = dest_channel[dix         + 1];
              ia[1] = dest_channel[dix - width + 1];
              ia[2] = dest_channel[dix - width    ];
              ia[3] = dest_channel[dix - width - 1];
              ia[4] = dest_channel[dix         - 1];
              ia[5] = dest_channel[dix + width - 1];
              ia[6] = dest_channel[dix + width    ];
              ia[7] = dest_channel[dix + width + 1];

              for (i = 0; i < 8; i++)
                if (ia[i] == m)
                  {
                    ia[i] = HIGH_LEVEL;
                    ic[i] = LOW_LEVEL;
                  }
                else
                  {
                    if (ia[i] < HIGH_LEVEL)
                      ia[i] = LOW_LEVEL;
                    ic[i] = ia[i];
                  }

              ia[8] = ia[0];
              ic[8] = ic[0];
              if (ia[0] + ia[2] + ia[4] + ia[6] == HIGH_LEVEL * 4)
                continue;
              for (i = 0, iv = 0, iw = 0; i < 8; i++)
                {
                  if (ia[i] == HIGH_LEVEL) iv++;
                  if (ic[i] == HIGH_LEVEL) iw++;
                }

              if (iv <= 1) continue;
              if (iw == 0) continue;
              if (cconc (ia) != 1) continue;

              if (dest_channel[dix - width] == m)
                {
                  ia[2] = LOW_LEVEL;
                  if (cconc (ia) != 1) continue;
                  ia[2] = HIGH_LEVEL;
                }

              if (dest_channel[dix - 1] == m)
                {
                  ia[4] = LOW_LEVEL;
                  if (cconc (ia) != 1) continue;
                  ia[4] = HIGH_LEVEL;
                }

              dest_channel[dix] = m;
              ir++;
            }
        }

      if (!preview)
        if ((overflow % 10) == 0)
          gimp_progress_update ( (gdouble) overflow / (gdouble) overflow_max );

      m++;
    }

  for (iy = 0; iy < height; iy++)
    for (ix = 0; ix < width; ix++)
      dest_channel[iy * width + ix] =
        (dest_channel[iy * width + ix] < HIGH_LEVEL) ? LOW_LEVEL : HIGH_LEVEL;
}

/*
 * Check joined node point (thinning function call)
 */
static gint
cconc (gint ia[])
{
  gint i, icn;

  icn = 0;

  /* 4 points */
  for (i = 0; i < 8; i += 2)
    if (ia[i] == LOW_LEVEL)
      if (ia[i + 1] == HIGH_LEVEL || ia[i + 2] == HIGH_LEVEL)
        icn++;

  return icn;
}

/*
 * extend 1 pixel
 *
 * store or restor image data
 *   type 0: store , type 1: restore
 */
#warning *** OPTIMZE ME! ***
#warning we should use prepare_row function in *blur.c instead
static void
store_data (guchar dest[],
            guchar dest_channel[],
            gint   channel,
            gint   bpp,
            gint   x1,
            gint   x2,
            gint   y1,
            gint   y2,
            gint   type)
{
  gint i;
  gint x, y;
  gint width, height;
  gint dix;

  width  = x2 - x1;
  height = y2 - y1;

  switch (type)
    {
    /* store data */
    case 0:
      i = 0;
      for (y = y1 - 1; y < y2 + 1; y++)
        for (x = x1 - 1; x < x2 + 1; x++)
          {
            if (x1 <= x && x < x2 && y1 <= y && y < y2)
              {
                dix  = bpp * (width * y + x);
                dest_channel[i] = dest[dix + channel];
              }
            i++;
          }

      /* copy pixel data from neighbor to above or down */
      for (x = 1; x < width + 1; x++)
        {
          y = 0;
          dix  = (width + 2) * y + x;
          dest_channel[dix] = dest_channel[dix + (width + 2)];
          y = height + 1;
          dix  = (width + 2) * y + x;
          dest_channel[dix] = dest_channel[dix - (width + 2)];
        }

      /* copy pixel data from neighbor to left or right side */
      for (y = 0; y < height + 2; y++)
        {
          x = 0;
          dix  = (width + 2) * y + x;
          dest_channel[dix] = dest_channel[dix + 1];
          x = width + 1;
          dix  = (width + 2) * y + x;
          dest_channel[dix] = dest_channel[dix - 1];
        }
      break;

    /* restore data expect the next 1 pixel data */
    case 1:
      i = 0;
      for (y = y1 - 1; y < y2 + 1; y++)
        for (x = x1 - 1; x < x2 + 1; x++)
          {
            if (x1 <= x && x < x2 && y1 <= y && y < y2)
              {
                dix  = bpp * (width * y + x);
                dest[dix + channel] = dest_channel[i];
              }
            i++;
          }
      break;
    default:
      break;
    }
}


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


/*
 * Edge detect switcher function
 */

static void
edge_detect (guchar dest_channel[],
             gint   width,
             gint   height,
             gint   preview)
{
  switch (lvals.mode)
    {
       case BW_COLOR:
         g_message ("Oh?");
         break;
       case PREWITT:
         prewitt (dest_channel, width, height, preview);
         break;
       case GRADIENT:
         gradient (dest_channel, width, height, preview);
         break;
       case ROBERTS:
         roberts (dest_channel, width, height, preview);
         break;
       case DIFFERENTIAL:
         differential (dest_channel, width, height, preview);
         break;
       case LAPLACE:
         laplace (dest_channel, width, height, preview);
         break;
       case SOBEL:
         sobel (dest_channel, width, height, preview);
         break;
       default:
         break;
    }
}


/*
 * Edge detector via template matting
 *   -- Prewitt
 */
static void
prewitt (guchar dest_channel[],
         gint   width,
         gint   height,
         gint   preview)
{
  gint    d0, d1, d2, d3, d4, d5, d6, d7, d8;
  gint    x, y, k, max;
  gint    dix;
  gint    m[8];
  gfloat  amp;
  guchar *src_channel;

  src_channel = g_new(guchar, width * height);

  memcpy (src_channel, dest_channel, (width * height));

  amp = 0.5 + lvals.amount / 128.0;

  for (y = 1; y < height - 1; y++)
    {
      for (x = 1; x < width - 1; x++)
        {
          dix = y * width + x;
          d0 = src_channel[dix - width - 1];
          d1 = src_channel[dix - width    ];
          d2 = src_channel[dix - width + 1];
          d3 = src_channel[dix         - 1];
          d4 = src_channel[dix            ];
          d5 = src_channel[dix         + 1];
          d6 = src_channel[dix + width - 1];
          d7 = src_channel[dix + width    ];
          d8 = src_channel[dix + width + 1];
          m[0] =  d0 + d1 + d2 + d3 -2*d4 + d5 - d6 - d7 - d8;
          m[1] =  d0 + d1 + d2 + d3 -2*d4 - d5 + d6 - d7 - d8;
          m[2] =  d0 + d1 - d2 + d3 -2*d4 - d5 + d6 + d7 - d8;
          m[3] =  d0 - d1 - d2 + d3 -2*d4 - d5 + d6 + d7 + d8;
          m[4] = -d0 - d1 - d2 + d3 -2*d4 + d5 + d6 + d7 + d8;
          m[5] = -d0 - d1 + d2 - d3 -2*d4 + d5 + d6 + d7 + d8;
          m[6] = -d0 + d1 + d2 - d3 -2*d4 + d5 - d6 + d7 + d8;
          m[7] =  d0 + d1 + d2 - d3 -2*d4 + d5 - d6 - d7 + d8;

          max = 0;
          for (k = 0; k < 8; k++)
            if (max < m[k])
              max = m[k];
          dest_channel[dix] = CLAMP(amp * max, 0, 255);
        }

      if (!preview)
        if ((y % 10) == 0)
          gimp_progress_update ((gdouble) (y - 1) / (gdouble) (height - 2));
    }

  g_free (src_channel);
}


/*
 * Gradient Edge detector
 */
static void
gradient (guchar dest_channel[],
          gint   width,
          gint   height,
          gint   preview)
{
  gint   v_kernel[9] = { 0,  0,  0,
                         0,  4, -4,
                         0,  0,  0};
  gint   h_kernel[9] = { 0,  0,  0,
                         0, -4,  0,
                         0,  4,  0};

  gint   x, y;
  gint   dix;
  gint   v_grad, h_grad;
  gfloat amp;
  guchar *src_channel;

  src_channel = g_new(guchar, width * height);

  memcpy (src_channel, dest_channel, (width * height));

  amp = lvals.amount / 128.0;

  for (y = 1; y < height - 1; y++)
    {
      for (x = 1; x < width - 1; x++)
        {
          dix = y * width + x;
          v_grad =
//            v_kernel[0] * src_channel[dix - width - 1] +
//            v_kernel[1] * src_channel[dix - width    ] +
//            v_kernel[2] * src_channel[dix - width + 1] +
//            v_kernel[3] * src_channel[dix         - 1] +
            v_kernel[4] * src_channel[dix            ] +
            v_kernel[5] * src_channel[dix         + 1] +
//            v_kernel[6] * src_channel[dix + width - 1] +
//            v_kernel[7] * src_channel[dix + width    ] +
//            v_kernel[8] * src_channel[dix + width + 1];
            0.0;
          h_grad =
//            h_kernel[0] * src_channel[dix - width - 1] +
//            h_kernel[1] * src_channel[dix - width    ] +
//            h_kernel[2] * src_channel[dix - width + 1] +
//            h_kernel[3] * src_channel[dix         - 1] +
            h_kernel[4] * src_channel[dix            ] +
//            h_kernel[5] * src_channel[dix         + 1] +
//            h_kernel[6] * src_channel[dix + width - 1] +
            h_kernel[7] * src_channel[dix + width    ] +
//            h_kernel[8] * src_channel[dix + width + 1];
            0.0;
          dest_channel[dix] = CLAMP(sqrt (v_grad * v_grad * amp +
                                          h_grad * h_grad * amp), 0, 255);
        }

      if (!preview)
        if ((y % 10) == 0)
          gimp_progress_update ((gdouble) (y - 1) / (gdouble) (height - 2));
    }

  g_free (src_channel);
}



/*
 * Roberts Edge detector
 */
static void
roberts (guchar dest_channel[],
         gint   width,
         gint   height,
         gint   preview)
{
  gint   v_kernel[9] = {0,  0,  0,
                        0,  4,  0,
                        0,  0, -4};
  gint   h_kernel[9] = {0,  0,  0,
                        0,  0,  4,
                        0, -4,  0};

  gint   x, y;
  gint   dix;
  gint   v_grad, h_grad;
  gfloat amp;
  guchar *src_channel;

  src_channel = g_new(guchar, width * height);

  memcpy (src_channel, dest_channel, (width * height));

  amp = lvals.amount / 128.0;

  for (y = 1; y < height - 1; y++)
    {
      for (x = 1; x < width - 1; x++)
        {
          dix = y * width + x;
          v_grad =
//            v_kernel[0] * src_channel[dix - width - 1] +
//            v_kernel[1] * src_channel[dix - width    ] +
//            v_kernel[2] * src_channel[dix - width + 1] +
//            v_kernel[3] * src_channel[dix         - 1] +
            v_kernel[4] * src_channel[dix            ] +
//            v_kernel[5] * src_channel[dix         + 1] +
//            v_kernel[6] * src_channel[dix + width - 1] +
//            v_kernel[7] * src_channel[dix + width    ] +
            v_kernel[8] * src_channel[dix + width + 1];
          h_grad =
//            h_kernel[0] * src_channel[dix - width - 1] +
//            h_kernel[1] * src_channel[dix - width    ] +
//            h_kernel[2] * src_channel[dix - width + 1] +
//            h_kernel[3] * src_channel[dix         - 1] +
//            h_kernel[4] * src_channel[dix            ] +
            h_kernel[5] * src_channel[dix         + 1] +
//            h_kernel[6] * src_channel[dix + width - 1] +
            h_kernel[7] * src_channel[dix + width    ] +
//            h_kernel[8] * src_channel[dix + width + 1];
            0.0;
          dest_channel[dix] = CLAMP(sqrt (v_grad * v_grad * amp +
                                          h_grad * h_grad * amp), 0, 255);
        }

      if (!preview)
        if ((y % 10) == 0)
          gimp_progress_update ((gdouble) (y - 1) / (gdouble) (height - 2));
    }

  g_free (src_channel);
}


/*
 * Differential Edge detector
 */
static void
differential (guchar dest_channel[],
              gint   width,
              gint   height,
              gint   preview)
{
  gint   v_kernel[9] = { 0,  0,  0,
                         0,  2, -2,
                         0,  2, -2};
  gint   h_kernel[9] = { 0,  0,  0,
                         0, -2, -2,
                         0,  2,  2};

  gint   x, y;
  gint   dix;
  gint   v_grad, h_grad;
  gfloat amp;
  guchar *src_channel;

  src_channel = g_new(guchar, width * height);

  memcpy (src_channel, dest_channel, (width * height));

  amp = lvals.amount / 128.0;

  for (y = 1; y < height - 1; y++)
    {
      for (x = 1; x < width - 1; x++)
        {
          dix = y * width + x;
          v_grad =
//            v_kernel[0] * src_channel[dix - width - 1] +
//            v_kernel[1] * src_channel[dix - width    ] +
//            v_kernel[2] * src_channel[dix - width + 1] +
//            v_kernel[3] * src_channel[dix         - 1] +
            v_kernel[4] * src_channel[dix            ] +
            v_kernel[5] * src_channel[dix         + 1] +
//            v_kernel[6] * src_channel[dix + width - 1] +
            v_kernel[7] * src_channel[dix + width    ] +
            v_kernel[8] * src_channel[dix + width + 1];
          h_grad =
//            h_kernel[0] * src_channel[dix - width - 1] +
//            h_kernel[1] * src_channel[dix - width    ] +
//            h_kernel[2] * src_channel[dix - width + 1] +
//            h_kernel[3] * src_channel[dix         - 1] +
            h_kernel[4] * src_channel[dix            ] +
            h_kernel[5] * src_channel[dix         + 1] +
//            h_kernel[6] * src_channel[dix + width - 1] +
            h_kernel[7] * src_channel[dix + width    ] +
            h_kernel[8] * src_channel[dix + width + 1];
          dest_channel[dix] = CLAMP(sqrt (v_grad * v_grad * amp +
                                          h_grad * h_grad * amp), 0, 255);
        }

      if (!preview)
        if ((y % 10) == 0)
          gimp_progress_update ((gdouble) (y - 1) / (gdouble) (height - 2));
    }

  g_free (src_channel);
}


/*
 * Laplace Edge detector
 */
static void
laplace (guchar dest_channel[],
         gint   width,
         gint   height,
         gint   preview)
{
  gint   kernel[9] = { 1,  1,  1,
                       1, -8,  1,
                       1,  1,  1};

  gint   x, y;
  gint   dix;
  gint   grad;
  gfloat amp;
  guchar *src_channel;

  src_channel = g_new(guchar, width * height);

  memcpy (src_channel, dest_channel, (width * height));

  amp = 0.5 + lvals.amount / 128.0;

  for (y = 1; y < height - 1; y++)
    {
      for (x = 1; x < width - 1; x++)
        {
          dix = y * width + x;
          grad =
            kernel[0] * src_channel[dix - width - 1] +
            kernel[1] * src_channel[dix - width    ] +
            kernel[2] * src_channel[dix - width + 1] +
            kernel[3] * src_channel[dix         - 1] +
            kernel[4] * src_channel[dix            ] +
            kernel[5] * src_channel[dix         + 1] +
            kernel[6] * src_channel[dix + width - 1] +
            kernel[7] * src_channel[dix + width    ] +
            kernel[8] * src_channel[dix + width + 1];
          grad *= amp;
          dest_channel[dix] = CLAMP(grad, 0, 255);
        }

      if (!preview)
        if ((y % 10) == 0)
          gimp_progress_update ((gdouble) (y - 1) / (gdouble) (height - 2));
    }

  g_free (src_channel);

}


/*
 * Sobel Edge detector
 */
static void
sobel (guchar dest_channel[],
       gint   width,
       gint   height,
       gint   preview)
{
  gint   v_kernel[9] = {-1,  0,  1,
                        -2,  0,  2,
                        -1,  0,  1};
  gint   h_kernel[9] = {-1, -2, -1,
                         0,  0,  0,
                         1,  2,  1};

  gint   x, y;
  gint   dix;
  gint   v_grad, h_grad;
  gfloat amp;
  guchar *src_channel;

  src_channel = g_new(guchar, width * height);

  memcpy (src_channel, dest_channel, (width * height));

  amp = lvals.amount / 128.0;

  for (y = 1; y < height - 1; y++)
    {
      for (x = 1; x < width - 1; x++)
        {
          dix = y * width + x;
          v_grad =
            v_kernel[0] * src_channel[dix - width - 1] +
//            v_kernel[1] * src_channel[dix - width    ] +
            v_kernel[2] * src_channel[dix - width + 1] +
            v_kernel[3] * src_channel[dix         - 1] +
//            v_kernel[4] * src_channel[dix            ] +
            v_kernel[5] * src_channel[dix         + 1] +
            v_kernel[6] * src_channel[dix + width - 1] +
//            v_kernel[7] * src_channel[dix + width    ] +
            v_kernel[8] * src_channel[dix + width + 1];
          h_grad =
            h_kernel[0] * src_channel[dix - width - 1] +
            h_kernel[1] * src_channel[dix - width    ] +
            h_kernel[2] * src_channel[dix - width + 1] +
//            h_kernel[3] * src_channel[dix         - 1] +
//            h_kernel[4] * src_channel[dix            ] +
//            h_kernel[5] * src_channel[dix         + 1] +
            h_kernel[6] * src_channel[dix + width - 1] +
            h_kernel[7] * src_channel[dix + width    ] +
            h_kernel[8] * src_channel[dix + width + 1];
          dest_channel[dix] = CLAMP(sqrt (v_grad * v_grad * amp +
                                          h_grad * h_grad * amp), 0, 255);
        }

      if (!preview)
        if ((y % 10) == 0)
          gimp_progress_update ((gdouble) (y - 1) / (gdouble) (height - 2));
    }

  g_free (src_channel);
}


/* ***********************   Preview!   ******************** */

static void
preview_do_row (gint    row,
                gint    width,
                guchar *even,
                guchar *odd,
                guchar *src)
{
  gint    x;

  guchar *p0 = even;
  guchar *p1 = odd;
  
  gdouble    r, g, b, a;
  gdouble    c0, c1;
  
  for (x = 0; x < width; x++) 
    {
      if (preview_d.bpp == 4)
        {
          r = ((gdouble)src[x*4+0]) / 255.0;
          g = ((gdouble)src[x*4+1]) / 255.0;
          b = ((gdouble)src[x*4+2]) / 255.0;
          a = ((gdouble)src[x*4+3]) / 255.0;
        }
      else if (preview_d.bpp == 3)
        {
          r = ((gdouble)src[x*3+0]) / 255.0;
          g = ((gdouble)src[x*3+1]) / 255.0;
          b = ((gdouble)src[x*3+2]) / 255.0;
          a = 1.0;
        }
      else
        {
          r = ((gdouble)src[x*preview_d.bpp+0]) / 255.0;
          g = b = r;
          if (preview_d.bpp == 2)
            a = ((gdouble)src[x*preview_d.bpp+1]) / 255.0;
          else
            a = 1.0;
        }
      
      if ((x / GIMP_CHECK_SIZE) & 1) 
        {
          c0 = GIMP_CHECK_LIGHT;
          c1 = GIMP_CHECK_DARK;
        } 
      else 
        {
          c0 = GIMP_CHECK_DARK;
          c1 = GIMP_CHECK_LIGHT;
        }
      
      *p0++ = (c0 + (r - c0) * a) * 255.0;
      *p0++ = (c0 + (g - c0) * a) * 255.0;
      *p0++ = (c0 + (b - c0) * a) * 255.0;
      
      *p1++ = (c1 + (r - c1) * a) * 255.0;
      *p1++ = (c1 + (g - c1) * a) * 255.0;
      *p1++ = (c1 + (b - c1) * a) * 255.0;
      
    } /* for */
  
  if ((row / GIMP_CHECK_SIZE) & 1)
    {
      gtk_preview_draw_row (GTK_PREVIEW (preview_d.widget),
                            (guchar *)odd,  0, row, width); 
    }
  else
    {
      gtk_preview_draw_row (GTK_PREVIEW (preview_d.widget),
                            (guchar *)even, 0, row, width); 
    }
}

static void
filter_preview (void)
{
  GimpPixelRgn  srcPR;

  gint     y;
  guchar  *src;
  guchar  *dest;

  if (!auto_preview)
    return;

  gimp_pixel_rgn_init (&srcPR, drawable, preview_d.x1, preview_d.y1,
                       preview_d.width, preview_d.height, FALSE, FALSE);

  gimp_pixel_rgn_get_rect (&srcPR, preview_d.dest, preview_d.x1, preview_d.y1,
                           preview_d.width, preview_d.height);

  apply_filter (preview_d.dest, 0, preview_d.width, 0,
                preview_d.height, preview_d.bpp,
                preview_d.bytes, preview_d.d_is_gray, preview_d.d_is_rgb, TRUE);

  dest = &preview_d.pixel[0];
  for (y = 0; y < preview_d.height; y++)
    {
      src = &preview_d.dest[preview_d.bpp * preview_d.width * y];
      memcpy (dest, src, preview_d.bpp * preview_d.width);
      preview_do_row (y, preview_d.width, preview_d.even, preview_d.odd,
                      preview_d.pixel);
    }

  gtk_widget_queue_draw (preview_d.widget);
}

static void
filter_preview_through (void)
{
  GimpPixelRgn  srcPR;

  gint     y;
  guchar  *src;
  guchar  *dest;

  gimp_pixel_rgn_init (&srcPR, drawable, preview_d.x1, preview_d.y1,
                       preview_d.width, preview_d.height, FALSE, FALSE);

  gimp_pixel_rgn_get_rect (&srcPR, preview_d.dest, preview_d.x1, preview_d.y1,
                           preview_d.width, preview_d.height);

  dest = &preview_d.pixel[0];
  for (y = 0; y < preview_d.height; y++)
    {
      src = &preview_d.dest[preview_d.bpp * preview_d.width * y];
      memcpy (dest, src, preview_d.bpp * preview_d.width);
      preview_do_row (y, preview_d.width, preview_d.even, preview_d.odd,
                      preview_d.pixel);
    }

  gtk_widget_queue_draw (preview_d.widget);
}

static void
filter_preview_force (void)
{
  gint auto_preview_org = auto_preview;

  auto_preview = TRUE;
  filter_preview ();
  auto_preview = auto_preview_org;
}



/*********************************
 *  GUI ROUTINES
 ********************************/

static gint
thin_line_dialog (GimpDrawable *drawable)
{
  GtkWidget *dlg;
  GtkWidget *main_vbox;
  GtkWidget *main_hbox;
  GtkWidget *abox;
  GtkWidget *hbox;
  GtkWidget *vbox;
  GtkWidget *frame;
  GtkWidget *scrollbar;
  GtkWidget *table;
  GtkWidget *toggle;
  GtkWidget *button;
  GtkObject *adj;
  GSList    *group = NULL;
  GtkWidget *helps[10];

  GtkWidget *steps1;
  GtkWidget *steps3;
  GtkWidget *steps2;
  GtkWidget *toggle_c;
  GtkWidget *label_c;
  GtkWidget *toggle0;
  GtkWidget *toggle1;
  GtkWidget *toggle2;

  gint       max_steps;

  color_white = (lvals.color == WHITE) ? TRUE : FALSE;

  gimp_ui_init (PLUG_IN_NAME, TRUE);

  dlg = gimp_dialog_new (_("Thin Line"), PLUG_IN_NAME,
                         gimp_standard_help_func, "filters/thin_line.html",
                         GTK_WIN_POS_MOUSE,
                         FALSE, TRUE, FALSE,

                         _("OK"), thin_line_ok_callback,
                         NULL, NULL, NULL, TRUE, FALSE,
                         _("Cancel"), gtk_widget_destroy,
                         NULL, 1, NULL, FALSE, TRUE,

                         NULL);

  gtk_signal_connect (GTK_OBJECT (dlg), "destroy",
                      GTK_SIGNAL_FUNC (gtk_main_quit),
                      NULL);

  gimp_help_init ();

  /*
   *  Parameter settings
   *
   *  First set up the basic containers, label them, etc.
   */

  main_vbox = gtk_vbox_new (FALSE, 4);
  gtk_container_set_border_width (GTK_CONTAINER (main_vbox), 6);
  gtk_container_add (GTK_CONTAINER (GTK_DIALOG (dlg)->vbox), main_vbox);
  gtk_widget_show (main_vbox);

  main_hbox = gtk_hbox_new (FALSE, 4);
  gtk_box_pack_start (GTK_BOX (main_vbox), main_hbox, FALSE, FALSE, 0);
  gtk_widget_show (main_hbox);

  /* Preview widget */

  frame = gtk_frame_new (_("Preview"));
  gtk_container_set_border_width (GTK_CONTAINER (frame), 0);
  gtk_box_pack_start (GTK_BOX (main_hbox), frame, FALSE, FALSE, 0);
  gtk_widget_show (frame);

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

  abox = gtk_alignment_new (0.5, 0.5, 0.0, 0.0);
  gtk_container_set_border_width (GTK_CONTAINER (abox), 0);
  gtk_box_pack_start (GTK_BOX (vbox), abox, FALSE, FALSE, 0);
  gtk_widget_show (abox);

  table = gtk_table_new (2, 2, FALSE);
  gtk_container_add (GTK_CONTAINER (abox), table);
  gtk_widget_show (table);

  button = gtk_button_new ();
  gtk_signal_connect (GTK_OBJECT (button), "released",
		      GTK_SIGNAL_FUNC (filter_preview_force),
		      NULL);
  gtk_signal_connect (GTK_OBJECT (button), "pressed",
		      GTK_SIGNAL_FUNC (filter_preview_through),
		      NULL);
  gtk_table_attach (GTK_TABLE (table), button, 0, 1, 0, 1,
                    GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 0);
  gtk_widget_show (button);
  gimp_help_set_help_data (button, _("Refresh the Preview window"), NULL);

  preview_d.width  = MIN (sel_width, PREVIEW_SIZE);
  preview_d.height = MIN (sel_height, PREVIEW_SIZE);

  preview_d.widget = gtk_preview_new (GTK_PREVIEW_COLOR);
  gtk_preview_size (GTK_PREVIEW (preview_d.widget),
                    preview_d.width, preview_d.height);
  gtk_container_add (GTK_CONTAINER (button), preview_d.widget);
  gtk_widget_show (preview_d.widget);

  hscroll_data = gtk_adjustment_new (0, 0, sel_width - 1, 1.0,
                                     MIN (preview_d.width, sel_width),
                                     MIN (preview_d.width, sel_width));

  gtk_signal_connect (hscroll_data, "value_changed",
                      (GtkSignalFunc) preview_scroll_callback,
                      NULL);

  scrollbar = gtk_hscrollbar_new (GTK_ADJUSTMENT (hscroll_data));
  gtk_range_set_update_policy (GTK_RANGE (scrollbar), GTK_UPDATE_CONTINUOUS);
  gtk_table_attach (GTK_TABLE (table), scrollbar, 0, 1, 1, 2,
                    GTK_FILL, 0, 0, 0);
  gtk_widget_show (scrollbar);

  vscroll_data = gtk_adjustment_new (0, 0, sel_height - 1, 1.0,
                                     MIN (preview_d.height, sel_height),
                                     MIN (preview_d.height, sel_height));

  gtk_signal_connect (vscroll_data, "value_changed",
                      GTK_SIGNAL_FUNC (preview_scroll_callback),
                      NULL);

  scrollbar = gtk_vscrollbar_new (GTK_ADJUSTMENT (vscroll_data));
  gtk_range_set_update_policy (GTK_RANGE (scrollbar), GTK_UPDATE_CONTINUOUS);
  gtk_table_attach (GTK_TABLE (table), scrollbar, 1, 2, 0, 1,
                    0, GTK_FILL, 0, 0);
  gtk_widget_show (scrollbar);

  preview_init ();
  filter_preview ();

  toggle = gtk_check_button_new_with_label (_("Do Preview"));
  gtk_box_pack_start (GTK_BOX (vbox), toggle, FALSE, FALSE, 0);
  gtk_signal_connect (GTK_OBJECT (toggle), "toggled",
                      GTK_SIGNAL_FUNC (gimp_toggle_button_update),
                      &auto_preview);
  gtk_signal_connect (GTK_OBJECT (toggle), "toggled",
                      GTK_SIGNAL_FUNC (filter_preview),
                      NULL);
  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), lvals.desaturate);
  gtk_widget_show (toggle);
  gimp_help_set_help_data (toggle,
                           _("If you enable this option the preview"
                             " will be redrawn automatically"),
                           NULL);


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

  frame = gimp_radio_group_new2 (TRUE, _("Edge Detect Algorithm"),
                                 thin_line_radio_button_callback,
                                 &lvals.mode, (gpointer) lvals.mode,

                                 _("None (B/W Color)"),
                                 (gpointer) BW_COLOR, &helps[0],
                                 _("Prewitt"),
                                 (gpointer) PREWITT, &helps[1],
                                 _("Gradient"),
                                 (gpointer) GRADIENT, &helps[2],
                                 _("Roberts"),
                                 (gpointer) ROBERTS, &helps[3],
                                 _("Liner Differential"),
                                 (gpointer) DIFFERENTIAL, &helps[4],
                                 _("Laplace"),
                                 (gpointer) LAPLACE, &helps[5],
                                 _("Sobel"),
                                 (gpointer) SOBEL, &helps[6],

                                 NULL);

  gtk_box_pack_start (GTK_BOX (main_hbox), frame, TRUE, TRUE, 0);

  gimp_help_set_help_data (helps[0],
                           _("Black/White value is formed using threshold"),
                           NULL);
  gimp_help_set_help_data (helps[1],
                           _("Apply Prewitt edge detection algorithm"),
                           NULL);
  gimp_help_set_help_data (helps[2], _("Apply Gradient edge "
                                       "detection algorithm"), NULL);
  gimp_help_set_help_data (helps[3],
                           _("Apply Roberts edge detection algorithm"),
                           NULL);
  gimp_help_set_help_data (helps[4], _("Apply Liner Differential edge "
                                       "detection algorithm"), NULL);
  gimp_help_set_help_data (helps[5],
                           _("Apply Laplace edge detection algorithm"),
                           NULL);
  gimp_help_set_help_data (helps[6],
                           _("Apply Sobel edge detection algorithm"),
                           NULL);
  gtk_widget_show (frame);


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

  main_hbox = gtk_vbox_new (FALSE, 0);
  gtk_box_pack_start (GTK_BOX (main_vbox), main_hbox, TRUE, TRUE, 0);
  gtk_widget_show (main_hbox);

  frame = gtk_frame_new (_("Parameter Settings"));
  gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_ETCHED_IN);
  gtk_box_pack_start (GTK_BOX (main_vbox), frame, TRUE, TRUE, 0);

  table = gtk_table_new (9, 3, FALSE);
  gtk_table_set_col_spacings (GTK_TABLE (table), 4);
  gtk_table_set_row_spacings (GTK_TABLE (table), 2);
  gtk_container_set_border_width (GTK_CONTAINER (table), 4);
  gtk_container_add (GTK_CONTAINER (frame), table);
  gtk_widget_show (table);


  toggle_c = gtk_check_button_new_with_label (_("Thinning"));
  gimp_table_attach_aligned (GTK_TABLE (table), 0, 0,
                             NULL, 1.0, 0.5,
                             toggle_c, 1, TRUE);
  gtk_signal_connect (GTK_OBJECT (toggle_c), "toggled",
                      GTK_SIGNAL_FUNC (gimp_toggle_button_update),
                      &lvals.thinning);
  gtk_signal_connect (GTK_OBJECT (toggle_c), "toggled",
                      GTK_SIGNAL_FUNC (filter_preview),
                      NULL);
  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle_c), lvals.thinning);
  gtk_widget_show (toggle_c);
  gimp_help_set_help_data (toggle_c,
                           _("If you disable this option the filter"
                             " will not apply thinning effect"),
                           NULL);

  adj = gimp_scale_entry_new (GTK_TABLE (table), 0, 1,
                              _("Threshold:"), 150, 0,
                              lvals.threshold, 0, 255, 1, 10, 0,
                              TRUE, 0, 0,
                              _("Threshold to convert the image into b/w color"),
                              NULL);
  gtk_signal_connect (GTK_OBJECT (adj), "value_changed",
                      GTK_SIGNAL_FUNC (gimp_int_adjustment_update),
                      &lvals.threshold);
  gtk_signal_connect (GTK_OBJECT (adj), "value_changed",
                      GTK_SIGNAL_FUNC (filter_preview),
                      NULL);

  adj = gimp_scale_entry_new (GTK_TABLE (table), 0, 2,
                              _("Amount:"), 150, 0,
                              lvals.amount, 0, 255, 1, 10, 0,
                              TRUE, 0, 0,
                              _("Amount for edge detection"),
                              NULL);
  gtk_signal_connect (GTK_OBJECT (adj), "value_changed",
                      GTK_SIGNAL_FUNC (gimp_int_adjustment_update),
                      &lvals.amount);
  gtk_signal_connect (GTK_OBJECT (adj), "value_changed",
                      GTK_SIGNAL_FUNC (filter_preview),
                      NULL);

  max_steps = MIN (drawable->width, drawable->height) * 1.2;

  adj = gimp_scale_entry_new (GTK_TABLE (table), 0, 3,
                              _("Steps:"), 150, 0,
                              lvals.steps, 0, max_steps, 1, 1, 0,
                              TRUE, 0, 0,
                              _("Iteration steps for thinning"), NULL);
  gtk_signal_connect (GTK_OBJECT (adj), "value_changed",
                      GTK_SIGNAL_FUNC (gimp_int_adjustment_update),
                      &lvals.steps);
  gtk_signal_connect (GTK_OBJECT (adj), "value_changed",
                      GTK_SIGNAL_FUNC (filter_preview),
                      NULL);

  hbox = gtk_hbox_new (FALSE, 4);
  gimp_table_attach_aligned (GTK_TABLE (table), 0, 4,
                             NULL,
                             1.0, 0.5, hbox, 2, FALSE);

  toggle = gtk_check_button_new_with_label (_("Desaturate"));
  gtk_box_pack_start (GTK_BOX (hbox), toggle, TRUE, TRUE, 0);
  gtk_signal_connect (GTK_OBJECT (toggle), "toggled",
                      GTK_SIGNAL_FUNC (gimp_toggle_button_update),
                      &lvals.desaturate);
  gtk_signal_connect (GTK_OBJECT (toggle), "toggled",
                      GTK_SIGNAL_FUNC (filter_preview),
                      NULL);
  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), lvals.desaturate);
  gtk_widget_show (toggle);
  if (gimp_drawable_is_gray(drawable->id))
    gtk_widget_set_sensitive (toggle, FALSE);
  gimp_help_set_help_data (toggle,
                           _("If you disable this option this filter"
                             " will be applied to each color"),
                           NULL);

  toggle = gtk_check_button_new_with_label (_("Inverted Image"));
  gtk_box_pack_start (GTK_BOX (hbox), toggle, TRUE, TRUE, 0);
  gtk_signal_connect (GTK_OBJECT (toggle), "toggled",
                      GTK_SIGNAL_FUNC (gimp_toggle_button_update),
                      &lvals.invert);
  gtk_signal_connect (GTK_OBJECT (toggle), "toggled",
                      GTK_SIGNAL_FUNC (filter_preview),
                      NULL);
  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), lvals.invert);
  gtk_widget_show (toggle);
  gimp_help_set_help_data (toggle,
                           _("If you enable this option the image"
                             " will invert the color before thinning"),
                           NULL);

  toggle0 = gtk_check_button_new_with_label (_("Remove Isolation Points "
                                               "(yet work)"));
//  gimp_table_attach_aligned (GTK_TABLE (table), 0, 6,
//			     NULL, 1.0, 0.5,
//			     toggle0, 1, TRUE);
  gtk_signal_connect (GTK_OBJECT (toggle0), "toggled",
                      GTK_SIGNAL_FUNC (gimp_toggle_button_update),
                      &lvals.isolation);
  gtk_signal_connect (GTK_OBJECT (toggle0), "toggled",
                      GTK_SIGNAL_FUNC (filter_preview),
                      NULL);
  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle0), lvals.isolation);
  gimp_help_set_help_data (toggle0,
                           _("If you disable this option the filter"
                             " will not apply thinning effect"),
                           NULL);

  hbox = gtk_hbox_new (FALSE, 4);
  gimp_table_attach_aligned (GTK_TABLE (table), 0, 7,
                             NULL,
                             1.0, 0.5, hbox, 2, FALSE);

  label_c = gtk_label_new (_("Target Color:"));
  gtk_misc_set_alignment (GTK_MISC (label_c), 1.0, 0.5);
  gtk_label_set_justify (GTK_LABEL (label_c), GTK_JUSTIFY_RIGHT);
  gtk_table_attach (GTK_TABLE(table), label_c,
		    0, 1, 7, 8,
                    GTK_FILL, GTK_FILL, 0, 0);
  gtk_widget_show (label_c);

  toggle1 = gtk_radio_button_new_with_label (group, _("White"));
  group = gtk_radio_button_group (GTK_RADIO_BUTTON (toggle1));
  gtk_box_pack_start (GTK_BOX (hbox), toggle1, TRUE, TRUE, 0);
  gtk_signal_connect (GTK_OBJECT (toggle1), "toggled",
                      GTK_SIGNAL_FUNC (color_toggle_update),
                      &color_white);
  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle1), color_white);
  gimp_help_set_help_data (toggle1, _("White is used for thinning"), NULL);
  gtk_widget_show (toggle1);

  toggle2 = gtk_radio_button_new_with_label (group, _("Black"));
  group = gtk_radio_button_group (GTK_RADIO_BUTTON (toggle2));
  gtk_box_pack_start (GTK_BOX (hbox), toggle2, TRUE, TRUE, 0);
  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle2), !color_white);

  gimp_help_set_help_data (toggle2, _("Black is used for thinning"), NULL);
  gtk_widget_show (toggle2);

  gtk_widget_show (frame);


  steps1 = GTK_WIDGET (GIMP_SCALE_ENTRY_LABEL (adj));
  steps2 = GTK_WIDGET (GIMP_SCALE_ENTRY_SCALE (adj));
  steps3 = GTK_WIDGET (GIMP_SCALE_ENTRY_SPINBUTTON (adj));
  gtk_object_set_data (GTK_OBJECT (toggle_c), "steps1", steps1);
  gtk_object_set_data (GTK_OBJECT (toggle_c), "steps2", steps2);
  gtk_object_set_data (GTK_OBJECT (toggle_c), "steps3", steps3);
  gtk_object_set_data (GTK_OBJECT (toggle_c), "label_c", label_c);
  gtk_object_set_data (GTK_OBJECT (toggle_c), "toggle0", toggle0);
  gtk_object_set_data (GTK_OBJECT (toggle_c), "toggle1", toggle1);
  gtk_object_set_data (GTK_OBJECT (toggle_c), "toggle2", toggle2);
  gtk_signal_connect (GTK_OBJECT (toggle_c), "toggled",
                      GTK_SIGNAL_FUNC (thinning_toggled),
                      NULL);
  thinning_toggled (toggle_c, NULL);

  gtk_widget_show (dlg);

  gtk_main ();
  gimp_help_free ();
  gdk_flush ();

  lvals.color = (color_white) ? WHITE : BLACK;

  preview_exit ();

  return lint.run;
}


/* ***********************   Preview   ******************** */

static void
preview_init (void)
{
  preview_d.bpp   = gimp_drawable_bpp (drawable->id);
  preview_cache_rowstride = preview_d.width * preview_d.bpp;

  preview_d.even  = g_malloc (preview_d.width * 3);
  preview_d.odd   = g_malloc (preview_d.width * 3);
  preview_d.dest  = g_new (guchar, preview_d.width * preview_d.height * preview_d.bpp);
  preview_d.pixel = g_new (guchar, preview_cache_rowstride);

  preview_d.x1 = sel_x1;
  preview_d.y1 = sel_y1;
  preview_d.x2 = preview_d.x1 + preview_d.width;
  preview_d.y2 = preview_d.y1 + preview_d.height;

  preview_d.d_is_rgb  = (preview_d.bpp == 3 || preview_d.bpp == 4) ? TRUE : FALSE;
  preview_d.d_is_gray = (preview_d.bpp == 1 || preview_d.bpp == 2) ? TRUE : FALSE;
  preview_d.has_alpha = (preview_d.bpp == 2 || preview_d.bpp == 4) ? TRUE : FALSE;
  preview_d.bytes = preview_d.bpp - ((preview_d.has_alpha) ? 1 : 0);
}

static void
preview_exit (void)
{
  g_free (preview_d.even);
  g_free (preview_d.odd);
  g_free (preview_d.dest);
  g_free (preview_d.pixel);
}

static void
preview_scroll_callback (void)
{
  preview_d.x1 = sel_x1 + GTK_ADJUSTMENT (hscroll_data)->value;
  preview_d.y1 = sel_y1 + GTK_ADJUSTMENT (vscroll_data)->value;
  preview_d.x2 = preview_d.x1 + MIN (preview_d.width, sel_width);
  preview_d.y2 = preview_d.y1 + MIN (preview_d.height, sel_height);

  filter_preview ();
}

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

  gtk_widget_destroy (GTK_WIDGET (data));
}

static void
thinning_toggled (GtkWidget *widget,
                  gpointer   data)
{
    GtkWidget *steps1;
    GtkWidget *steps2;
    GtkWidget *steps3;
    GtkWidget *label_c;
    GtkWidget *toggle0;
    GtkWidget *toggle1;
    GtkWidget *toggle2;
    gboolean   active;

    active = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget));
    steps1 = gtk_object_get_data (GTK_OBJECT (widget), "steps1");
    steps2 = gtk_object_get_data (GTK_OBJECT (widget), "steps2");
    steps3 = gtk_object_get_data (GTK_OBJECT (widget), "steps3");
    label_c  = gtk_object_get_data (GTK_OBJECT (widget), "label_c");
    toggle0  = gtk_object_get_data (GTK_OBJECT (widget), "toggle0");
    toggle1  = gtk_object_get_data (GTK_OBJECT (widget), "toggle1");
    toggle2  = gtk_object_get_data (GTK_OBJECT (widget), "toggle2");

    gtk_widget_set_sensitive (steps1, active);
    gtk_widget_set_sensitive (steps2, active);
    gtk_widget_set_sensitive (steps3, active);
    gtk_widget_set_sensitive (label_c, active);
    gtk_widget_set_sensitive (toggle0, active);
    gtk_widget_set_sensitive (toggle1, active);
    gtk_widget_set_sensitive (toggle2, active);
}


static void
color_toggle_update (GtkWidget *widget,
                     gpointer   data)
{
  gimp_toggle_button_update (widget, data);
  lvals.color = (color_white) ? WHITE : BLACK;
  filter_preview ();
}

static void
thin_line_radio_button_callback (GtkWidget *widget,
			         gpointer   data)
{
  gimp_radio_button_update (widget, data);

  filter_preview ();
}

