/* 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.
 */
#include <stdlib.h>
#include <math.h>
#include "appenv.h"
#include "boundary.h"
#include "canvas.h"
#include "draw_core.h"
#include "drawable.h"
#include "edit_selection.h"
#include "depth/float16.h"
#include "fuzzy_select.h"
#include "gimage_mask.h"
#include "rc.h"
#include "gdisplay.h"
#include "rect_select.h"
#include "paint_funcs_area.h"
#include "pixelarea.h"
#include "pixelrow.h"


/* ------------------------------------------------------------
  
      prototype of a generalized PixelArea control structure
             with a sample seed-fill application
  
  ------------------------------------------------------------ */

/* a bounding box */

typedef struct BBox BBox;
struct BBox
{
  gint x, y, w, h;
};



/* a range of pixels in a row */

typedef struct Segment Segment;
struct Segment
{
  guint start;
  guint end;
  Segment * next;
};

static Segment * segment_new    (guint start, guint end, Segment * next);
static void      segment_delete (Segment * s);
static void      segment_add    (Segment ** s, guint start, guint end);
static void      segment_del    (Segment ** s, guint start, guint end);



/* a generalized PixelArea control structure */

typedef struct PixelIter PixelIter;

/* an image area operation.  it may be precision neutral or not */
typedef guint (*PIFunc) (PixelIter * p);

/* the data block for the control structure */
struct PixelIter
{
  /* the image on which to operate */
  Canvas * image;

  /* automatic canvas portion ref: none, RO, RW */
  guint reftype;
  
  /* the bounding box of the whole area */
  BBox area; 
  
  /* the bounding box of the current chunk */
  BBox chunk;
  
  /* user callback to invoke */
  PIFunc callback;

  /* user data */
  void * data;

  /* pixel to do list */
  Segment ** todo;  
};

/* public methods */
PixelIter *  pixeliter_new       (void);
void         pixeliter_delete    (PixelIter *);
guint        pixeliter_init      (PixelIter * p, Canvas * c,
                                  PIFunc callback, void * userdata,
                                  guint x, guint y, guint w, guint h,
                                  guint reftype);
guint        pixeliter_process   (PixelIter *);
guint        pixeliter_get_work  (PixelIter *, gint *, gint *);
void         pixeliter_add_work  (PixelIter *, gint, gint, gint, gint);
void         pixeliter_del_work  (PixelIter *, gint, gint, gint, gint);

/* private methods */
static guint pixeliter_choose_chunk (PixelIter *);
static guint pixeliter_first        (PixelIter *, BBox *, guint *, guint *);

/* types of refs for pixeliter_init */
#define REF_NONE 0
#define REF_RO   1
#define REF_RW   2








typedef struct FuzzyOptions FuzzyOptions;
typedef struct SeedFillData SeedFillData;

/* the precision-specific kernel for the seed fill */
typedef guint (*SFFunc) (PixelIter * p, SeedFillData * d);


struct FuzzyOptions
{
  DrawCore *     core;         /*  Core select object                      */

  int            x, y;         /*  Point from which to execute seed fill  */
  int            last_x;       /*                                         */
  int            last_y;       /*  variables to keep track of sensitivity */
  gfloat         threshold;    /*  threshold value for soft seed fill     */

  int            op;           /*  selection operation (ADD, SUB, etc)     */
};


struct SeedFillData
{
  /* the specific kernel to use */
  SFFunc kernel;
  
  /* fill this with the contiguous area */
  Canvas * mask;

  /* the threshold */
  gfloat threshold;

  /* should we antialias */
  guint antialias;

  /* the color to match */
  PixelRow color;
  
  /* size of existing scratch space */
  guint x_w, x_h;

  /* temp copy of mask for this chunk */
  Canvas * x_mask;

  /* the output of absdiff for this chunk */
  Canvas * x_diff;
};


/*  fuzzy select action functions  */

static void   fuzzy_select_button_press   (Tool *, GdkEventButton *, gpointer);
static void   fuzzy_select_button_release (Tool *, GdkEventButton *, gpointer);
static void   fuzzy_select_motion         (Tool *, GdkEventMotion *, gpointer);
static void   fuzzy_select_draw           (Tool *);
static void   fuzzy_select_control        (Tool *, int, gpointer);

/*  fuzzy select action functions  */
static GdkSegment *   fuzzy_select_calculate (Tool *, void *, int *);


/*  XSegments which make up the fuzzy selection boundary  */

static GdkSegment *segs = NULL;
static int         num_segs = 0;
static Channel *   fuzzy_mask = NULL;
static SelectionOptions *fuzzy_selection_options = NULL;

static void fuzzy_select (GImage *, CanvasDrawable *, int, int, double);
static Argument *fuzzy_select_invoker (Argument *);


/* seed fill kernel */

static guint seed_fill        (PixelIter * p);
static guint seed_fill_alloc  (PixelIter * p, SeedFillData * d);
static void  seed_fill_free   (SeedFillData * d);
static guint seed_fill_u8     (PixelIter * p, SeedFillData * d);
static guint seed_fill_u16    (PixelIter * p, SeedFillData * d);
static guint seed_fill_float  (PixelIter * p, SeedFillData * d);
static guint seed_fill_float16  (PixelIter * p, SeedFillData * d);


/*************************************/
/*  Fuzzy selection apparatus  */

Channel * 
find_contiguous_region  (
                         GImage * gimage,
                         CanvasDrawable * drawable,
                         int antialias,
                         gfloat threshold,
                         int x,
                         int y,
                         int sample_merged
                         )
{
  Channel * mask;
  Canvas * s;

  /* use either the current layer or the composite */
  s = (sample_merged)
    ? gimage_projection (gimage)
    : drawable_data (drawable);

  /* create a new mask */
  mask = channel_new_mask (gimage->ID,
                           canvas_width (s), canvas_height (s),
                           tag_precision (canvas_tag (s)));

  /* if the image refs okay, do a seed fill */
  if (canvas_portion_refro (s, x, y) == REFRC_OK)
    {
      SeedFillData data;
      PixelIter * p;
      
      /* choose the right kernel */
      switch (tag_precision (canvas_tag (s)))
        {
        case PRECISION_U8:
          data.kernel = seed_fill_u8;
          break;
        case PRECISION_U16:
          data.kernel = seed_fill_u16;
          break;
        case PRECISION_FLOAT:
          data.kernel = seed_fill_float;
          break;
        case PRECISION_FLOAT16:
          data.kernel = seed_fill_float16;
          break;
        case PRECISION_NONE:
        default:
          data.kernel = NULL;
        }

      /* init the seed fill parms */
      data.mask = drawable_data (GIMP_DRAWABLE(mask));
      data.threshold = threshold ? threshold : 0.000001;
      data.antialias = antialias;
      pixelrow_init (&data.color,
                     canvas_tag (s),
                     canvas_portion_data (s, x, y),
                     1);
        
      /* init the scratch space too */
      data.x_w = 0;
      data.x_h = 0;
      data.x_mask = NULL;
      data.x_diff = NULL;
      
      /* set up a processor for the seed fill */
      p = pixeliter_new ();
      
      if (pixeliter_init (p, s,
                          seed_fill, &data,
                          0, 0,
                          0, 0,
                          REF_RO))
        {
          /* mark the initial click point for processing */
          pixeliter_add_work (p, x, y, 1, 1);

          /* at this point, the seed fill operation could be queued
             for execution.  a smart scheduler could optimize the
             execution of the seed fill.  since no such mechanism
             exists, do the work immediately */
          while (pixeliter_process (p));

        }

      /* clean up */
      pixeliter_delete (p);
      seed_fill_free (&data);

      /* unref the canvas */
      canvas_portion_unref (s, x, y);
    }

  /* we don;t know the bounds anymore */
  channel_invalidate_bounds (mask);
  
  return mask;
}

static guint 
seed_fill  (
            PixelIter * p
            )
{
  PixelArea src, dst;
  SeedFillData * d;
  
  /* abort the entire operation if things get weird */
  g_return_val_if_fail (p != NULL, FALSE);
  d = (SeedFillData *) p->data;
  g_return_val_if_fail (d != NULL, FALSE);
  g_return_val_if_fail (d->kernel != NULL, FALSE);
  
  /* realloc scratch space */
  if (seed_fill_alloc (p, d) != TRUE)
    return FALSE;
  
  /* copy in part of the existing mask into our scratch pad */
  pixelarea_init (&src, d->mask,
                  p->chunk.x, p->chunk.y,
                  p->chunk.w, p->chunk.h,
                  FALSE);    
  pixelarea_init (&dst, d->x_mask,
                  0, 0,
                  0, 0,
                  TRUE);
  copy_area (&src, &dst);

  /* create the sufficiently_different map */
  pixelarea_init (&src, p->image,
                  p->chunk.x, p->chunk.y,
                  p->chunk.w, p->chunk.h,
                  FALSE);    
  pixelarea_init (&dst, d->x_diff,
                  0, 0,
                  0, 0,
                  TRUE);
  absdiff_area (&src, &dst, &d->color, d->threshold, d->antialias);

  /* process the chunk and save any modifications to the real mask */
  if (d->kernel (p, d) == TRUE)
    {
      pixelarea_init (&src, d->x_mask,
                      0, 0,
                      0, 0,
                      FALSE);
      pixelarea_init (&dst, d->mask,
                      p->chunk.x, p->chunk.y,
                      p->chunk.w, p->chunk.h,
                      TRUE);    
      copy_area (&src, &dst);
    }

  /* we want to be called again */
  return TRUE;
}


/* allocate scratch space for use while calculating contiguous mask */
static guint 
seed_fill_alloc  (
                  PixelIter * p,
                  SeedFillData * d
                  )
{
  /* see if the old stuff is the right size */
  if ((p->chunk.w == CAST(int) d->x_w) &&
      (p->chunk.h == CAST(int) d->x_h) &&
      (d->x_mask) && (d->x_diff))
    {
      return TRUE;
    }

  /* blow away the old stuff */
  seed_fill_free (d);

  /* save the new chunk size */
  d->x_w = p->chunk.w;
  d->x_h = p->chunk.h;

  /* temporary copy of existing mask */
  d->x_mask = canvas_new (canvas_tag (d->mask),
                          d->x_w, d->x_h,
                          STORAGE_FLAT);

  if (canvas_portion_refrw (d->x_mask, 0, 0) != REFRC_OK)
    {
      g_warning ("ooooops");
      return FALSE;
    }
  

  /* scratch pad holding abs diff values */
  d->x_diff = canvas_new (canvas_tag (d->mask),
                          d->x_w, d->x_h,
                          STORAGE_FLAT);

  if (canvas_portion_refrw (d->x_diff, 0, 0) != REFRC_OK)
    {
      g_warning ("ooooops2");
      return FALSE;
    }
  
  return TRUE;
}


/* delete any scratch space that's hanging around */
static void 
seed_fill_free  (
                 SeedFillData * d
                 )
{
  if (d->x_mask)
    {
      canvas_portion_unref (d->x_mask, 0, 0);
      canvas_delete (d->x_mask);
    }

  if (d->x_diff)
    {
      canvas_portion_unref (d->x_diff, 0, 0);
      canvas_delete (d->x_diff);
    }
}


static guint
seed_fill_u8 (
               PixelIter * p,
               SeedFillData * d
               )
{
  guint changed = FALSE;
  guint x, y;
  
  /* get pixels in this chunk which need attention */
  while (pixeliter_get_work (p, &x, &y))
    {
      guint8 * dd, * md;
      int xmin_c, xmax_c;

      /* see if this pixel should be done */
      dd = (guint8*) canvas_portion_data (d->x_diff, 0, y - p->chunk.y);
      if (dd[x - p->chunk.x] == 0)
        continue;

      /* see if this pixel is already done */
      md = (guint8*) canvas_portion_data (d->x_mask, 0, y - p->chunk.y);
      if (md[x - p->chunk.x] != 0)
        continue;
      
      /* find contig segment endpoints */
      for (xmin_c = x - p->chunk.x;
           (xmin_c >= 0) && (dd[xmin_c] != 0);
           xmin_c--);

      for (xmax_c = x - p->chunk.x;
           (xmax_c < p->chunk.w) && (dd[xmax_c] != 0);
           xmax_c++);
             
      /* add work to adjacent chunks if segment reached the edges */
      if (xmin_c < 0)
        {
          pixeliter_add_work (p,
                              p->chunk.x - 1, y,
                              1, 1);
        }

      if (xmax_c == p->chunk.w)
        {
          pixeliter_add_work (p,
                              p->chunk.x + p->chunk.w, y,
                              1, 1);
        }
      
      xmin_c++;
      xmax_c--;
      
      /* del work for those pixels */
      pixeliter_del_work (p,
                          p->chunk.x + xmin_c, y,
                          xmax_c - xmin_c + 1, 1);

      /* add work to next and prev rows */
      pixeliter_add_work (p,
                          p->chunk.x + xmin_c, y - 1,
                          xmax_c - xmin_c + 1, 1);
      
      pixeliter_add_work (p,
                          p->chunk.x + xmin_c, y + 1,
                          xmax_c - xmin_c + 1, 1);
      
      /* save those pixels on x_mask */
      memcpy (&md[xmin_c], &dd[xmin_c], (xmax_c - xmin_c + 1) * sizeof (guint8));
      changed = TRUE;
    }

  return changed;
}

static guint
seed_fill_u16 (
               PixelIter * p,
               SeedFillData * d
               )
{
  guint changed = FALSE;
  guint x, y;
  
  /* get pixels in this chunk which need attention */
  while (pixeliter_get_work (p, &x, &y))
    {
      guint16 * dd, * md;
      int xmin_c, xmax_c;

      /* see if this pixel should be done */
      dd = (guint16*) canvas_portion_data (d->x_diff, 0, y - p->chunk.y);
      if (dd[x - p->chunk.x] == 0)
        continue;

      /* see if this pixel is already done */
      md = (guint16*) canvas_portion_data (d->x_mask, 0, y - p->chunk.y);
      if (md[x - p->chunk.x] != 0)
        continue;
      
      /* find contig segment endpoints */
      for (xmin_c = x - p->chunk.x;
           (xmin_c >= 0) && (dd[xmin_c] != 0);
           xmin_c--);

      for (xmax_c = x - p->chunk.x;
           (xmax_c < p->chunk.w) && (dd[xmax_c] != 0);
           xmax_c++);
             
      /* add work to adjacent chunks if segment reached the edges */
      if (xmin_c < 0)
        {
          pixeliter_add_work (p,
                              p->chunk.x - 1, y,
                              1, 1);
        }

      if (xmax_c == p->chunk.w)
        {
          pixeliter_add_work (p,
                              p->chunk.x + p->chunk.w, y,
                              1, 1);
        }
      
      xmin_c++;
      xmax_c--;
      
      /* del work for those pixels */
      pixeliter_del_work (p,
                          p->chunk.x + xmin_c, y,
                          xmax_c - xmin_c + 1, 1);

      /* add work to next and prev rows */
      pixeliter_add_work (p,
                          p->chunk.x + xmin_c, y - 1,
                          xmax_c - xmin_c + 1, 1);
      
      pixeliter_add_work (p,
                          p->chunk.x + xmin_c, y + 1,
                          xmax_c - xmin_c + 1, 1);
      
      /* save those pixels on x_mask */
      memcpy (&md[xmin_c], &dd[xmin_c], (xmax_c - xmin_c + 1) * sizeof (guint16));
      changed = TRUE;
    }

  return changed;
}

static guint
seed_fill_float (
                 PixelIter * p,
                 SeedFillData * d
                 )
{
  guint changed = FALSE;
  guint x, y;
  
  /* get pixels in this chunk which need attention */
  while (pixeliter_get_work (p, &x, &y))
    {
      gfloat * dd, * md;
      int xmin_c, xmax_c;

      /* see if this pixel should be done */
      dd = (gfloat*) canvas_portion_data (d->x_diff, 0, y - p->chunk.y);
      if (dd[x - p->chunk.x] == 0)
        continue;

      /* see if this pixel is already done */
      md = (gfloat*) canvas_portion_data (d->x_mask, 0, y - p->chunk.y);
      if (md[x - p->chunk.x] != 0)
        continue;
      
      /* find contig segment endpoints */
      for (xmin_c = x - p->chunk.x;
           (xmin_c >= 0) && (dd[xmin_c] != 0);
           xmin_c--);

      for (xmax_c = x - p->chunk.x;
           (xmax_c < p->chunk.w) && (dd[xmax_c] != 0);
           xmax_c++);
             
      /* add work to adjacent chunks if segment reached the edges */
      if (xmin_c < 0)
        {
          pixeliter_add_work (p,
                              p->chunk.x - 1, y,
                              1, 1);
        }

      if (xmax_c == p->chunk.w)
        {
          pixeliter_add_work (p,
                              p->chunk.x + p->chunk.w, y,
                              1, 1);
        }
      
      xmin_c++;
      xmax_c--;
      
      /* del work for those pixels */
      pixeliter_del_work (p,
                          p->chunk.x + xmin_c, y,
                          xmax_c - xmin_c + 1, 1);

      /* add work to next and prev rows */
      pixeliter_add_work (p,
                          p->chunk.x + xmin_c, y - 1,
                          xmax_c - xmin_c + 1, 1);
      
      pixeliter_add_work (p,
                          p->chunk.x + xmin_c, y + 1,
                          xmax_c - xmin_c + 1, 1);
      
      /* save those pixels on x_mask */
      memcpy (&md[xmin_c], &dd[xmin_c], (xmax_c - xmin_c + 1) * sizeof (gfloat));
      changed = TRUE;
    }

  return changed;
}

static guint
seed_fill_float16 (
                 PixelIter * p,
                 SeedFillData * d
                 )
{
  guint changed = FALSE;
  guint x, y;
  
  /* get pixels in this chunk which need attention */
  while (pixeliter_get_work (p, &x, &y))
    {
      guint16 * dd, * md;
      int xmin_c, xmax_c;

      /* see if this pixel should be done */
      dd = (guint16*) canvas_portion_data (d->x_diff, 0, y - p->chunk.y);
      if (dd[x - p->chunk.x] == ZERO_FLOAT16)
        continue;

      /* see if this pixel is already done */
      md = (guint16*) canvas_portion_data (d->x_mask, 0, y - p->chunk.y);
      if (md[x - p->chunk.x] != ZERO_FLOAT16)
        continue;
      
      /* find contig segment endpoints */
      for (xmin_c = x - p->chunk.x;
           (xmin_c >= 0) && (dd[xmin_c] != ZERO_FLOAT16);
           xmin_c--);

      for (xmax_c = x - p->chunk.x;
           (xmax_c < p->chunk.w) && (dd[xmax_c] != ZERO_FLOAT16);
           xmax_c++);
             
      /* add work to adjacent chunks if segment reached the edges */
      if (xmin_c < 0)
        {
          pixeliter_add_work (p,
                              p->chunk.x - 1, y,
                              1, 1);
        }

      if (xmax_c == p->chunk.w)
        {
          pixeliter_add_work (p,
                              p->chunk.x + p->chunk.w, y,
                              1, 1);
        }
      
      xmin_c++;
      xmax_c--;
      
      /* del work for those pixels */
      pixeliter_del_work (p,
                          p->chunk.x + xmin_c, y,
                          xmax_c - xmin_c + 1, 1);

      /* add work to next and prev rows */
      pixeliter_add_work (p,
                          p->chunk.x + xmin_c, y - 1,
                          xmax_c - xmin_c + 1, 1);
      
      pixeliter_add_work (p,
                          p->chunk.x + xmin_c, y + 1,
                          xmax_c - xmin_c + 1, 1);
      
      /* save those pixels on x_mask */
      memcpy (&md[xmin_c], &dd[xmin_c], (xmax_c - xmin_c + 1) * sizeof (guint16));
      changed = TRUE;
    }

  return changed;
}


static void
fuzzy_select (GImage *gimage, CanvasDrawable *drawable, int op, int feather,
	      double feather_radius)
{
  int off_x, off_y;

  /*  if applicable, replace the current selection  */
  if (op == REPLACE)
    gimage_mask_clear (gimage);
  else
    gimage_mask_undo (gimage);

  drawable_offsets (drawable, &off_x, &off_y);
  if (feather)
    channel_feather (fuzzy_mask, gimage_get_mask (gimage),
		     feather_radius, op, off_x, off_y);
  else
    channel_combine_mask (gimage_get_mask (gimage),
			  fuzzy_mask, op, off_x, off_y);

  /*  free the fuzzy region struct  */
  channel_delete (fuzzy_mask);
  fuzzy_mask = NULL;
}

/*  fuzzy select action functions  */

static void
fuzzy_select_button_press (Tool *tool, GdkEventButton *bevent,
			   gpointer gdisp_ptr)
{
  GDisplay *gdisp;
  FuzzyOptions *fuzzy_opts;

  gdisp = (GDisplay *) gdisp_ptr;
  fuzzy_opts = (FuzzyOptions *) tool->private;

  fuzzy_opts->x = bevent->x;
  fuzzy_opts->y = bevent->y;
  fuzzy_opts->last_x = fuzzy_opts->x;
  fuzzy_opts->last_y = fuzzy_opts->y;
  /*fuzzy_opts->threshold = default_threshold;*/
  fuzzy_opts->threshold = fuzzy_selection_options->threshold;

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

  tool->state = ACTIVE;
  tool->gdisp_ptr = gdisp_ptr;

  if (bevent->state & GDK_MOD1_MASK)
    {
      init_edit_selection (tool, gdisp_ptr, bevent, MaskTranslate);
      return;
    }
  else if ((bevent->state & GDK_SHIFT_MASK) && !(bevent->state & GDK_CONTROL_MASK))
    fuzzy_opts->op = ADD;
  else if ((bevent->state & GDK_CONTROL_MASK) && !(bevent->state & GDK_SHIFT_MASK))
    fuzzy_opts->op = SUB;
  else if ((bevent->state & GDK_CONTROL_MASK) && (bevent->state & GDK_SHIFT_MASK))
    fuzzy_opts->op = INTERSECT;
  else
    {
      if (! (layer_is_floating_sel (gimage_get_active_layer (gdisp->gimage))) &&
	  gdisplay_mask_value (gdisp,CAST(int)  bevent->x,CAST(int)  bevent->y) > HALF_WAY)
	{
	  init_edit_selection (tool, gdisp_ptr, bevent, MaskToLayerTranslate);
	  return;
	}
      fuzzy_opts->op = REPLACE;
    }

  /*  calculate the region boundary  */
  /*segs = fuzzy_select_calculate (tool, gdisp_ptr, &num_segs);*/

#if 0
  draw_core_start (fuzzy_opts->core,
		   gdisp->canvas->window,
		   tool);
#endif
}

static void
fuzzy_select_button_release (Tool *tool, GdkEventButton *bevent,
			     gpointer gdisp_ptr)
{
  FuzzyOptions * fuzzy_opts;
  GDisplay * gdisp;
  CanvasDrawable *drawable;

  gdisp = (GDisplay *) gdisp_ptr;
  fuzzy_opts = (FuzzyOptions *) tool->private;

  gdk_pointer_ungrab (bevent->time);
  gdk_flush ();

#if 0
  draw_core_stop (fuzzy_opts->core, tool);
#endif
  tool->state = INACTIVE;

  /*  First take care of the case where the user "cancels" the action  */
  if (! (bevent->state & GDK_BUTTON3_MASK))
    {
      segs = fuzzy_select_calculate (tool, gdisp_ptr, &num_segs);
      drawable = (fuzzy_selection_options->sample_merged) ? NULL : gimage_active_drawable (gdisp->gimage);
      fuzzy_select (gdisp->gimage, drawable, fuzzy_opts->op,
		    fuzzy_selection_options->feather, fuzzy_selection_options->feather_radius);

      gdisplays_flush ();

      /*  adapt the threshold based on the final value of this use  */
      /*default_threshold = fuzzy_opts->threshold;*/
    }

  /*  If the segment array is allocated, free it  */
  if (segs)
    g_free (segs);
  segs = NULL;
}

static void
fuzzy_select_motion (Tool *tool, GdkEventMotion *mevent, gpointer gdisp_ptr)
{
}

#if 0
static void
fuzzy_select_motion (Tool *tool, GdkEventMotion *mevent, gpointer gdisp_ptr)
{
  FuzzyOptions * fuzzy_opts;
  GdkSegment * new_segs;
  int num_new_segs;
  int diff, diff_x, diff_y;

  if (tool->state != ACTIVE)
    return;

  fuzzy_opts = (FuzzyOptions *) tool->private;

  diff_x = mevent->x - fuzzy_opts->last_x;
  diff_y = mevent->y - fuzzy_opts->last_y;

  diff = ((ABS (diff_x) > ABS (diff_y)) ? diff_x : diff_y) / 2;

  fuzzy_opts->last_x = mevent->x;
  fuzzy_opts->last_y = mevent->y;

  fuzzy_opts->threshold += diff/255.0;
  fuzzy_opts->threshold = BOUNDS (fuzzy_opts->threshold, 0, 1);

  /*  calculate the new fuzzy boundary  */
  new_segs = fuzzy_select_calculate (tool, gdisp_ptr, &num_new_segs);

  /*  stop the current boundary  */
  draw_core_pause (fuzzy_opts->core, tool);

  /*  make sure the XSegment array is freed before we assign the new one  */
  if (segs)
    g_free (segs);
  segs = new_segs;
  num_segs = num_new_segs;

  /*  start the new boundary  */
  draw_core_resume (fuzzy_opts->core, tool);
}
#endif

static GdkSegment *
fuzzy_select_calculate (Tool *tool, void *gdisp_ptr, int *nsegs)
{
  FuzzyOptions *fuzzy_opts;
  GDisplay *gdisp;
  Channel *new;
  GdkSegment *segs;
  BoundSeg *bsegs;
  int i, x, y;
  CanvasDrawable *drawable;
  int use_offsets;

  fuzzy_opts = (FuzzyOptions *) tool->private;
  gdisp = (GDisplay *) gdisp_ptr;
  drawable = gimage_active_drawable (gdisp->gimage);

  use_offsets = (fuzzy_selection_options->sample_merged) ? FALSE : TRUE;

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

  new = find_contiguous_region (gdisp->gimage, drawable, fuzzy_selection_options->antialias,
				fuzzy_opts->threshold, x, y, fuzzy_selection_options->sample_merged);

  if (fuzzy_mask)
    channel_delete (fuzzy_mask);
  fuzzy_mask = channel_ref (new);

  /*  calculate and allocate a new XSegment array which represents the boundary
   *  of the color-contiguous region
   */
  bsegs = find_mask_boundary (drawable_data (GIMP_DRAWABLE(fuzzy_mask)),
                              0, 0,
                              0, 0,
                              nsegs, WithinBounds,
			      0, 0,
			      drawable_width (GIMP_DRAWABLE(fuzzy_mask)),
			      drawable_height (GIMP_DRAWABLE(fuzzy_mask)));

  segs = (GdkSegment *) g_malloc (sizeof (GdkSegment) * *nsegs);

  for (i = 0; i < *nsegs; i++)
    {
      gdisplay_transform_coords (gdisp, bsegs[i].x1, bsegs[i].y1, &x, &y, use_offsets);
      segs[i].x1 = x;  segs[i].y1 = y;
      gdisplay_transform_coords (gdisp, bsegs[i].x2, bsegs[i].y2, &x, &y, use_offsets);
      segs[i].x2 = x;  segs[i].y2 = y;
    }

  /*  free boundary segments  */
  g_free (bsegs);

  return segs;
}

static void
fuzzy_select_draw (Tool *tool)
{
  FuzzyOptions * fuzzy_opts;

  fuzzy_opts = (FuzzyOptions *) tool->private;

  if (segs )//&& fuzzy_opts && fuzzy_opts->core && fuzzy_opts->core->win && fuzzy_opts->core->gc)
    gdk_draw_segments (fuzzy_opts->core->win, fuzzy_opts->core->gc, segs, num_segs);
}

static void
fuzzy_select_control (Tool *tool, int action, gpointer gdisp_ptr)
{
  FuzzyOptions * fuzzy_opts;

  fuzzy_opts = (FuzzyOptions *) tool->private;

  switch (action)
    {
    case PAUSE :
      draw_core_pause (fuzzy_opts->core, tool);
      break;
    case RESUME :
      draw_core_resume (fuzzy_opts->core, tool);
      break;
    case HALT :
      draw_core_stop (fuzzy_opts->core, tool);
      break;
    }
}

Tool *
tools_new_fuzzy_select (void)
{
  Tool * tool;
  FuzzyOptions * private;

  /*  Fuzzy options  */
  tool = (Tool *) g_malloc_zero (sizeof (Tool));
  private = (FuzzyOptions *) g_malloc_zero (sizeof (FuzzyOptions));

  private->core = draw_core_new (fuzzy_select_draw);

  tool->type = FUZZY_SELECT;
  tool->state = INACTIVE;
  tool->scroll_lock = 1;  /*  Disallow scrolling  */
  tool->auto_snap_to = TRUE;
  tool->private = (void *) private;
  tool->button_press_func = fuzzy_select_button_press;
  tool->button_release_func = fuzzy_select_button_release;
  tool->motion_func = fuzzy_select_motion;
  tool->arrow_keys_func = standard_arrow_keys_func;
  tool->cursor_update_func = rect_select_cursor_update;
  tool->control_func = fuzzy_select_control;

  /*  The selection options  */
  if (!fuzzy_selection_options)
    fuzzy_selection_options = create_selection_options (FUZZY_SELECT);

  return tool;
}

void
tools_free_fuzzy_select (Tool *tool)
{
  FuzzyOptions * fuzzy_opts;

  fuzzy_opts = (FuzzyOptions *) tool->private;
  draw_core_free (fuzzy_opts->core);
  g_free (fuzzy_opts);
}

/*  The fuzzy_select procedure definition  */
ProcArg fuzzy_select_args[] =
{
  { PDB_IMAGE,
    "image",
    "the image"
  },
  { PDB_DRAWABLE,
    "drawable",
    "the drawable"
  },
  { PDB_FLOAT,
    "x",
    "x coordinate of initial seed fill point: (image coordinates)"
  },
  { PDB_FLOAT,
    "y",
    "y coordinate of initial seed fill point: (image coordinates)"
  },
  { PDB_INT32,
    "threshold",
    "threshold in intensity levels: 0 <= threshold <= 255"
  },
  { PDB_INT32,
    "operation",
    "the selection operation: { ADD (0), SUB (1), REPLACE (2), INTERSECT (3) }"
  },
  { PDB_INT32,
    "antialias",
    "antialiasing On/Off"
  },
  { PDB_INT32,
    "feather",
    "feather option for selections"
  },
  { PDB_FLOAT,
    "feather_radius",
    "radius for feather operation"
  },
  { PDB_INT32,
    "sample_merged",
    "use the composite image, not the drawable"
  }
};

ProcRecord fuzzy_select_proc =
{
  "gimp_fuzzy_select",
  "Create a fuzzy selection starting at the specified coordinates on the specified drawable",
  "This tool creates a fuzzy selection over the specified image.  A fuzzy selection is determined by a seed fill under the constraints of the specified threshold.  Essentially, the color at the specified coordinates (in the drawable) is measured and the selection expands outwards from that point to any adjacent pixels which are not significantly different (as determined by the threshold value).  This process continues until no more expansion is possible.  The antialiasing parameter allows the final selection mask to contain intermediate values based on close misses to the threshold bar at pixels along the seed fill boundary.  Feathering can be enabled optionally and is controlled with the \"feather_radius\" paramter.  If the sample_merged parameter is non-zero, the data of the composite image will be used instead of that for the specified drawable.  This is equivalent to sampling for colors after merging all visible layers.  In the case of a merged sampling, the supplied drawable is ignored.  If the sample is merged, the specified coordinates are relative to the image origin; otherwise, they are relative to the drawable's origin.",
  "Spencer Kimball & Peter Mattis",
  "Spencer Kimball & Peter Mattis",
  "1995-1996",
  PDB_INTERNAL,

  /*  Input arguments  */
  10,
  fuzzy_select_args,

  /*  Output arguments  */
  0,
  NULL,

  /*  Exec method  */
  { { fuzzy_select_invoker } },
};


static Argument *
fuzzy_select_invoker (Argument *args)
{
  int success = TRUE;
  GImage *gimage;
  CanvasDrawable *drawable;
  int op;
  gfloat threshold;
  int antialias;
  int feather;
  int sample_merged;
  double x, y;
  double feather_radius;
  int int_value;

  drawable    = NULL;
  op          = REPLACE;
  threshold   = 0;

  /*  the gimage  */
  if (success)
    {
      int_value = args[0].value.pdb_int;
      if (! (gimage = gimage_get_ID (int_value)))
	success = FALSE;
    }
  /*  the drawable  */
  if (success)
    {
      int_value = args[1].value.pdb_int;
      drawable = drawable_get_ID (int_value);
      if (drawable == NULL || gimage != drawable_gimage (drawable))
	success = FALSE;
    }
  /*  x, y  */
  if (success)
    {
      x = args[2].value.pdb_float;
      y = args[3].value.pdb_float;
    }
  /*  threshold  */
  if (success)
    {
      int_value = args[4].value.pdb_int;
      if (int_value >= 0 && int_value <= 255)
	threshold = int_value / 255.0;
      else
	success = FALSE;
    }
  /*  operation  */
  if (success)
    {
      int_value = args[5].value.pdb_int;
      switch (int_value)
	{
	case 0: op = ADD; break;
	case 1: op = SUB; break;
	case 2: op = REPLACE; break;
	case 3: op = INTERSECT; break;
	default: success = FALSE;
	}
    }
  /*  antialiasing?  */
  if (success)
    {
      int_value = args[6].value.pdb_int;
      antialias = (int_value) ? TRUE : FALSE;
    }
  /*  feathering  */
  if (success)
    {
      int_value = args[7].value.pdb_int;
      feather = (int_value) ? TRUE : FALSE;
    }
  /*  feather radius  */
  if (success)
    {
      feather_radius = args[8].value.pdb_float;
    }
  /*  sample merged  */
  if (success)
    {
      int_value = args[9].value.pdb_int;
      sample_merged = (int_value) ? TRUE : FALSE;
    }

  /*  call the fuzzy_select procedure  */
  if (success)
    {
      Channel *new;
      Channel *old_fuzzy_mask;

      new = find_contiguous_region (gimage, drawable, antialias, threshold,CAST(int) x,CAST(int) y, sample_merged);
      old_fuzzy_mask = fuzzy_mask;
      fuzzy_mask = new;

      drawable = (sample_merged) ? NULL : drawable;
      fuzzy_select (gimage, drawable, op, feather, feather_radius);

      fuzzy_mask = old_fuzzy_mask;
    }

  return procedural_db_return_args (&fuzzy_select_proc, success);
}











/* the rest of this stuff goes elsewhere eventually */

static Segment *
segment_new (
             guint start,
             guint end,
             Segment * next
             )
{
  Segment * s = (Segment *) g_malloc (sizeof (Segment));

  s->start = start;
  s->end = end;
  s->next = next;

  return s;
}


static void
segment_delete (
                Segment * s
                )
{
  g_free (s);
}


/* add (x1 <= x < x2) to the segment */
static void 
segment_add  (
              Segment ** s,
              guint start,
              guint end
              )
{
  g_return_if_fail (s != NULL);

  if (start >= end)
    return;
  
  /* insert a new segment or extend an existing one */
  while (1)
    {
      /* did we hit the end of the list */
      if (*s == NULL)
        {
          /* insert at the end of the (possibly empty) list */
          *s = segment_new (start, end, NULL);
          return;
        }

      /* do we start before this guy */
      if (start < (*s)->start)
        {
          /* insert a new segment */
          *s = segment_new (start, end, *s);
          break;
        }
      
      /* do we start in or immediately after this guy */
      if (start <= (*s)->end)
        {
          /* do we end before this guy */
          if (end <= (*s)->end)
            return;

          /* extend the current segment */
          (*s)->end = end;
          break;
        }

      /* try next segment */
      s = &((*s)->next);
    }

  /* at this point, *s points to a segment with the correct start and
     end points.  however, it might overlap the following segments */
  while (1)
    {
      /* are we at the end of the list */
      if ((*s)->next == NULL)
        return;

      /* is the next guy correct */
      if ((*s)->next->start > (*s)->end)
        return;

      {
        /* pull the next guy out of the list */
        Segment * n = (*s)->next;
        (*s)->next = n->next;

        /* does the next guy have a partial segment we need */
        if (n->end > (*s)->end)
          (*s)->end = n->end;

        segment_delete (n);
      }
    }
}


static void 
segment_del  (
              Segment ** s,
              guint start,
              guint end
              )
{
  g_return_if_fail (s != NULL);

  if (start >= end)
    return;
  
  while (1)
    {
      /* we hit the end of the list */
      if (*s == NULL)
        return;

      /* see if we overlap segment head */
      if ((*s)->end > end)
        {
          if ((*s)->start >= end)
            {
              /* first segment is completely after deletion */
              return;
            }
          else if ((*s)->start >= start)
            {
              /* first segment head overlaps us */
              (*s)->start = end;
              return;
            }
          else
            {
              /* we split first segment */
              *s = segment_new ((*s)->start, start, *s);
              (*s)->next->start = end;
              return;
            }
        }
      
      /* see if we overlap segment tail */
      else if ((*s)->end > start)
        {
          if ((*s)->start >= start)
            {
              Segment * n = *s;
              *s = (*s)->next;
              segment_delete (n);
              continue;
            }
          else
            {
              (*s)->end = start;
            }
        }

      /* next segment */
      s = &((*s)->next);
    }
}


PixelIter * 
pixeliter_new  (
                void
                )
{
  PixelIter * p;
  
  p = (PixelIter *) g_malloc (sizeof (PixelIter));
  
  p->image = NULL;
  p->reftype = REF_NONE;
  p->area.x = 0;
  p->area.y = 0;
  p->area.w = 0;
  p->area.h = 0;
  p->chunk.x = 0;
  p->chunk.y = 0;
  p->chunk.w = 0;
  p->chunk.h = 0;

  p->callback = NULL;
  p->data = NULL;
  p->todo = NULL;

  return p;
}


void 
pixeliter_delete  (
                   PixelIter * p
                   )
{
  guint h;
  
  g_return_if_fail (p != NULL);
  g_return_if_fail (p->todo != NULL);

  h = p->area.h;

  while (h-- > 0)
    {
      Segment * s = p->todo[h];
      while (s)
        {
          Segment * n = s->next;
          segment_delete (s);
          s = n;
        }
    }

  g_free (p->todo);
}


/* set up p so that it will work within the xywh bounding box on image
  calling callback with the pixeliter p as an arg.

  data is stored inside p so callback has access to it
*/
guint 
pixeliter_init  (
                 PixelIter * p,
                 Canvas * image,
                 PIFunc callback,
                 void * data,
                 guint x,
                 guint y,
                 guint w,
                 guint h,
                 guint reftype
                 )
{
  g_return_val_if_fail (p != NULL, FALSE);
  g_return_val_if_fail (image != NULL, FALSE);
  g_return_val_if_fail (w >= 0, FALSE);
  g_return_val_if_fail (h >= 0, FALSE);
  g_return_val_if_fail (callback != NULL, FALSE);
  g_return_val_if_fail (CAST(int) x < canvas_width (image), FALSE);
  g_return_val_if_fail (CAST(int) y < canvas_height (image), FALSE);

  p->image = image;
  p->reftype = reftype;
  p->area.x = x;
  p->area.y = y;
  p->area.w = canvas_width (image);
  if ((w > 0) && (CAST(int) w <= p->area.w))
    p->area.w = w;
  p->area.h = canvas_height (image);
  if ((h > 0) && (CAST(int) h <= p->area.h))
    p->area.h = h;
  p->chunk.x = 0;
  p->chunk.y = 0;
  p->chunk.w = 0;
  p->chunk.h = 0;
  
  p->callback = callback;
  p->data = data;
  p->todo = (Segment **) g_malloc (sizeof (Segment*) * p->area.h);
  memset (p->todo, 0, (sizeof (Segment*) * p->area.h));
  
  return TRUE;
}


/* choose a chunk of the image to process and invoke the user callback
  on that chunk.

  from the perspective of the user callback, the exact area chosen is
  random.  however, the entire area is guaranteed to be contained
  somewhere within a single portion of the canvas and inside the
  bounding box specified in the init call.

  the user callback may cause the entire operation to cease simply by
  returning FALSE.  this is useful for things like "is_area_empty"
  which may only need to see a single data point to return an answer.

  the user callback may add or delete work from the todo list.  this
  is useful for operations like seed fill that need to go exploring.

  it is trivial to emulate the current pixelarea_process behaviour.
  simply schedule the top left corner of the bounding box as the
  initial work, have the callback process the entire chunk, then add
  work for the strip of pixels directly to the right and below the
  chunk.  if pixeliter_choose_chunk() always prefers upper left chunks
  over lower right chunks, the chunks will be processed in exactly the
  same order.
  
  return true if more work remains, false if todo list is empty or if
  the operation was aborted by the user callback */
guint 
pixeliter_process  (
                    PixelIter * p
                    )
{
  guint rc = FALSE;
  
  g_return_val_if_fail (p != NULL, FALSE);

  /* see if work remains on this image */
  if (pixeliter_choose_chunk (p) != TRUE)
    return FALSE;
  
  /* if requested, ref the underlying image data */
  if (p->reftype == REF_RO)
    if (canvas_portion_refro (p->image, p->chunk.x, p->chunk.y) != REFRC_OK)
      return FALSE;

  if (p->reftype == REF_RW)
    if (canvas_portion_refrw (p->image, p->chunk.x, p->chunk.y) != REFRC_OK)
      return FALSE;

  /* ready ike?  kick the callback! */
  rc = p->callback (p);

  /* unref the image data if required */
  if ((p->reftype == REF_RO) || (p->reftype == REF_RW))
    {
      (void) canvas_portion_unref (p->image, p->chunk.x, p->chunk.y);
    }

  return rc;
}


/* pick a pixel that needs attention and decide how big of a chunk to
   work on.  set the chunk boundaries.

   any policy decisions can go in here, but we must at a minimum
   respect the users bounding box and the underlying canvas portion
   boundaries */
static guint
pixeliter_choose_chunk (
                        PixelIter * p
                        )
{
  gint x, y, w, h;

  /* pick a pixel from the todo list */
  if (pixeliter_first (p, &p->area, &x, &y) != TRUE)
    return FALSE;
  
  /* size the chunk around the pixel, respecting user bounding box and
     canvas portion boundaries.  other criteria, such as an absolute
     maximum chunk size/aspect ratio/whatever can be added */
  x = MAX (p->area.x,
           CAST(int) canvas_portion_x (p->image, x, y));
  y = MAX (p->area.y,
           CAST(int) canvas_portion_y (p->image, x, y));
  w = MIN (p->area.w - (x - p->area.x),
           CAST(int) canvas_portion_width (p->image, x, y));
  h = MIN (p->area.h - (y - p->area.y),
           CAST(int) canvas_portion_height (p->image, x, y));

  /* save it so the callback knows where to work */
  p->chunk.x = x;
  p->chunk.y = y;
  p->chunk.w = w;
  p->chunk.h = h;

  return TRUE;
}         


/* find the first pixel in the todo list for p that lies within box b.
   return coords in x,y */
static guint 
pixeliter_first  (
                  PixelIter * p,
                  BBox * b,
                  guint * xx,
                  guint * yy
                  )
{
  guint x, y, w, h;
  
  g_return_val_if_fail (p != NULL, FALSE);
  g_return_val_if_fail (b != NULL, FALSE);
  g_return_val_if_fail (xx != NULL, FALSE);
  g_return_val_if_fail (yy != NULL, FALSE);

  x = b->x;
  y = b->y;
  w = b->w;
  h = b->h;
  
  /* is the work right or below area */
  if (CAST(int) x >= p->area.x + p->area.w)
    return FALSE;
  
  if (CAST(int) y >= p->area.y + p->area.h)
    return FALSE;

  /* does work start left or above area */
  if (CAST(int) x < p->area.x)
    {
      w = w - (p->area.x - x);
      x = p->area.x;
    }
  
  if (CAST(int) y < p->area.y)
    {
      h = h - (p->area.y - y);
      y = p->area.y;
    }

  w = CLAMP (CAST(int) w, 0, p->area.w);
  h = CLAMP (CAST(int) h, 0, p->area.h);

  if ((w == 0) || (h == 0))
    return FALSE;

  while (h--)
    {
      Segment * s = p->todo[y - p->area.y];
      while (s)
        {
          /* is segment completely to right of our range */
          if (s->start >= x+w)
            break;
          
          /* is segment completely to left of our range */
          if (s->end <= x)
            {
              s = s->next;
              continue;
            }
          
          *xx = MAX (s->start, x);
          *yy = y;
          
          return TRUE;
        }

      /* try next row */
      y++;
    }

  return FALSE;
}


/* add the specified rectangle to the PixelIter's todo list.  only the
  part that overlaps the PixelIters area will be added.

  no return code */
void 
pixeliter_add_work  (
                     PixelIter * p,
                     gint x,
                     gint y,
                     gint w,
                     gint h
                     )
{
  g_return_if_fail (p != NULL);
  g_return_if_fail (p->todo != NULL);

  /* is the work right or below area */
  if (x >= p->area.x + p->area.w)
    return;
  
  if (y >= p->area.y + p->area.h)
    return;

  /* does work start left or above area */
  if (x < p->area.x)
    {
      w = w - (p->area.x - x);
      x = p->area.x;
    }
  
  if (y < p->area.y)
    {
      h = h - (p->area.y - y);
      y = p->area.y;
    }

  w = CLAMP (w, 0, p->area.w);
  h = CLAMP (h, 0, p->area.h);

  if ((w == 0) || (h == 0))
    return;

  /* add to each segment list */
  while (h--)
    {
      Segment ** s = &(p->todo[y - p->area.y]);
      segment_add (s, x, x + w);
      y++;
    }
}


/* remove the specified rectangle to the PixelIter's todo list.  only
  the part that overlaps the PixelIters area will be cleared.

  no return code */
void
pixeliter_del_work  (
                     PixelIter * p,
                     gint x,
                     gint y,
                     gint w,
                     gint h
                     )
{
  g_return_if_fail (p != NULL);
  g_return_if_fail (p->todo != NULL);
  
  /* is the work right or below area */
  if (x >= p->area.x + p->area.w)
    return;
  
  if (y >= p->area.y + p->area.h)
    return;

  /* does work start left or above area */
  if (x < p->area.x)
    {
      w = w - (p->area.x - x);
      x = p->area.x;
    }
  
  if (y < p->area.y)
    {
      h = h - (p->area.y - y);
      y = p->area.y;
    }

  w = CLAMP (w, 0, p->area.w);
  h = CLAMP (h, 0, p->area.h);

  if ((w == 0) || (h == 0))
    return;

  /* add to each segment list */
  while (h--)
    {
      Segment ** s = &(p->todo[y - p->area.y]);
      segment_del (s, x, x + w);
      y++;
    }
}


/* see if there are any pixels in the todo list that fall within the
  chunk we are processing. if so, pick one, remove from todo list, and
  store in x,y.

  return true if a point was returned, false if not */
guint 
pixeliter_get_work  (
                     PixelIter * p,
                     gint * x,
                     gint * y
                     )
{
  g_return_val_if_fail (p != NULL, FALSE);
  g_return_val_if_fail (p->todo != NULL, FALSE);
  g_return_val_if_fail (x != NULL, FALSE);
  g_return_val_if_fail (y != NULL, FALSE);

  /* get a pixel */
  if (pixeliter_first (p, &p->chunk, x, y) != TRUE)
    return FALSE;

  /* mark it as done */
  pixeliter_del_work (p, *x, *y, 1, 1);
  
  return TRUE;
}

