/*
 * "$Id: despeckle.c,v 1.57 2004/01/20 17:07:33 mitch Exp $"
 *
 *   Despeckle (adaptive median) filter for The GIMP -- an image manipulation
 *   program
 *
 *   Copyright 1997-1998 Michael Sweet (mike@easysw.com)
 *
 *   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.
 *
 * Contents:
 *
 *   main()                      - Main entry - just call gimp_main()...
 *   query()                     - Respond to a plug-in query...
 *   run()                       - Run the filter...
 *   despeckle()                 - Despeckle an image using a median filter.
 *   despeckle_dialog()          -  Popup a dialog window for the filter box size...
 *   preview_init()              - Initialize the preview window...
 *   preview_srcoll_callback()   - Update the preview when a scrollbar is moved.
 *   preview_update()            - Update the preview window.
 *   preview_exit()              - Free all memory used by the preview window...
 *   dialog_iscale_update()      - Update the value field using the scale.
 *   dialog_adaptive_callback()  - Update the filter type...
 *   dialog_recursive_callback() - Update the filter type...
 *
 *
 * Revision History:
 *
 *   See ChangeLog
 */

#include "config.h"

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

#ifdef __GNUC__
#warning GTK_DISABLE_DEPRECATED
#endif
#undef GTK_DISABLE_DEPRECATED

#include <gtk/gtk.h>

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

#include "libgimp/stdplugins-intl.h"


/*
 * Constants...
 */

#define PLUG_IN_NAME	 "plug_in_despeckle"
#define PLUG_IN_VERSION	 "1.3.2 - 17 May 1998"
#define HELP_ID          "plug-in-despeckle"
#define PREVIEW_SIZE	 128
#define SCALE_WIDTH	 100
#define ENTRY_WIDTH	   3
#define MAX_RADIUS	  20
#define SORT_PIVOT	  20
#define FILTER_ADAPTIVE	 0x01
#define FILTER_RECURSIVE 0x02

#define despeckle_radius (despeckle_vals[0])	/* Radius of filter */
#define filter_type	 (despeckle_vals[1])	/* Type of filter */
#define black_level	 (despeckle_vals[2])	/* Black level */
#define white_level	 (despeckle_vals[3])	/* White level */

/*
 * Local functions...
 */

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

static void	despeckle (GimpDrawable *drawable);

static gint	despeckle_dialog          (void);

static void	dialog_iscale_update      (GtkAdjustment *, gint *);
static void	dialog_adaptive_callback  (GtkWidget *, gpointer);
static void	dialog_recursive_callback (GtkWidget *, gpointer);

static void	preview_init              (void);
static void	preview_exit              (void);
static void     preview_update_new (void);
static void	preview_srcoll_callback   (void);
static void     despeckle_new  (guchar *dst_buffer, gint width, gint height, gint bpp, gint initialradius); 
static void     fillMask   (guchar *mask, gint width);
static void     quickSort  (guchar *buffer, gint size);
static void     q_sort     (guchar *buffer, gint left, gint right);
static void     bubbleSort (guchar *buffer, gint size);


/*
 * Globals...
 */

GimpPlugInInfo PLUG_IN_INFO =
{
  NULL,  /* init  */
  NULL,  /* quit  */
  query, /* query */
  run    /* run   */
};
GimpDrawable   *drawable;
GtkWidget      *preview;		/* Preview widget */
gint		preview_width,		/* Width of preview widget */
		preview_height,		/* Height of preview widget */
		preview_x1,		/* Upper-left X of preview */
		preview_y1,		/* Upper-left Y of preview */
		preview_x2,		/* Lower-right X of preview */
		preview_y2;		/* Lower-right Y of preview */
GtkObject      *hscroll_data,		/* Horizontal scrollbar data */
	       *vscroll_data;		/* Vertical scrollbar data */
gint		sel_x1,		/* Selection bounds */
		sel_y1,
		sel_x2,
		sel_y2;
gint		sel_width,	/* Selection width */
         	sel_height;	/* Selection height */

gint despeckle_vals[4] =
{
  3,
  FILTER_ADAPTIVE,
  7,
  248
};


/*
 * 'main()' - Main entry - just call gimp_main()...
 */

MAIN ()

/*
 * 'query()' - Respond to a plug-in query...
 */

static void
query (void)
{
  static GimpParamDef	args[] =
  {
    { GIMP_PDB_INT32,	"run_mode",	"Interactive, non-interactive" },
    { GIMP_PDB_IMAGE,	"image",	"Input image" },
    { GIMP_PDB_DRAWABLE,	"drawable",	"Input drawable" },
    { GIMP_PDB_INT32,	"radius",	"Filter box radius (default = 3)" },
    { GIMP_PDB_INT32,	"type",		"Filter type (0 = median, 1 = adaptive, 2 = recursive-median, 3 = recursive-adaptive)" },
    { GIMP_PDB_INT32,	"black",	"Black level (-1 to 255)" },
    { GIMP_PDB_INT32,	"white",	"White level (0 to 256)" }
  };

  gimp_install_procedure (PLUG_IN_NAME,
			  "Despeckle filter, typically used to \'despeckle\' "
			  "a photographic image.",
			  "This plug-in selectively performs a median or "
			  "adaptive box filter on an image.",
			  "Michael Sweet <mike@easysw.com>",
			  "Copyright 1997-1998 by Michael Sweet",
			  PLUG_IN_VERSION,
			  N_("<Image>/Filters/Enhance/Des_peckle..."),
			  "RGB*, GRAY*",
			  GIMP_PLUGIN,
			  G_N_ELEMENTS (args), 0,
			  args, NULL);
}


/*
 * 'run()' - Run the filter...
 */

static void
run (const gchar      *name,
     gint              nparams,
     const GimpParam  *param,
     gint             *nreturn_vals,
     GimpParam       **return_vals)
{
  GimpRunMode	     run_mode;
  GimpPDBStatusType  status;
  GimpParam	     *values;

  INIT_I18N ();

  /*
   * Initialize parameter data...
   */

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

  values = g_new (GimpParam, 1);

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

  *nreturn_vals = 1;
  *return_vals  = values;

  /*
   * Get drawable information...
   */

  drawable = gimp_drawable_get (param[2].data.d_drawable);
  gimp_drawable_mask_bounds (drawable->drawable_id, &sel_x1, &sel_y1, &sel_x2, &sel_y2);

  sel_width  = sel_x2 - sel_x1;
  sel_height = sel_y2 - sel_y1;
  /*
   * See how we will run
   */

  switch (run_mode)
    {
    case GIMP_RUN_INTERACTIVE :
      /*
       * Possibly retrieve data...
       */

      gimp_get_data (PLUG_IN_NAME, &despeckle_radius);

      /*
       * Get information from the dialog...
       */

      if (!despeckle_dialog())
	return;
      break;

    case GIMP_RUN_NONINTERACTIVE:
      /*
       * Make sure all the arguments are present...
       */

      if (nparams < 4 || nparams > 7)
	status = GIMP_PDB_CALLING_ERROR;
      else if (nparams == 4)
	{
	  despeckle_radius = param[3].data.d_int32;
	  filter_type      = FILTER_ADAPTIVE;
	  black_level      = 7;
	  white_level      = 248;
	}
      else if (nparams == 5)
	{
	  despeckle_radius = param[3].data.d_int32;
	  filter_type      = param[4].data.d_int32;
	  black_level      = 7;
	  white_level      = 248;
	}
      else if (nparams == 6)
	{
	  despeckle_radius = param[3].data.d_int32;
	  filter_type      = param[4].data.d_int32;
	  black_level      = param[5].data.d_int32;
	  white_level      = 248;
	}
      else
	{
	  despeckle_radius = param[3].data.d_int32;
	  filter_type      = param[4].data.d_int32;
	  black_level      = param[5].data.d_int32;
	  white_level      = param[6].data.d_int32;
	};
      break;

    case GIMP_RUN_WITH_LAST_VALS:
      /*
       * Possibly retrieve data...
       */

      INIT_I18N();
      gimp_get_data (PLUG_IN_NAME, despeckle_vals);
	break;
	
    default:
      status = GIMP_PDB_CALLING_ERROR;
      break;
    };

  /*
   * Despeckle the image...
   */

  if (status == GIMP_PDB_SUCCESS)
    {
      if ((gimp_drawable_is_rgb(drawable->drawable_id) ||
	   gimp_drawable_is_gray(drawable->drawable_id)))
	{
	  /*
	   * Set the tile cache size...
	   */

	  gimp_tile_cache_ntiles (2 * (drawable->width + gimp_tile_width() - 1) /
				  gimp_tile_width() + 1);

	  /*
	   * Run!
	   */

	  despeckle (drawable);

	  /*
	   * If run mode is interactive, flush displays...
	   */

	  if (run_mode != GIMP_RUN_NONINTERACTIVE)
	    gimp_displays_flush ();

	  /*
	   * Store data...
	   */

	  if (run_mode == GIMP_RUN_INTERACTIVE)
	    gimp_set_data (PLUG_IN_NAME,
			   despeckle_vals, sizeof (despeckle_vals));
	}
      else
	status = GIMP_PDB_EXECUTION_ERROR;
    };

  /*
   * Reset the current run status...
   */

  values[0].data.d_status = status;

  /*
   * Detach from the drawable...
   */

  gimp_drawable_detach (drawable);
}


/*
 * 'despeckle()' - Despeckle an image using a median filter.
 *
 * A median filter basically collects pixel values in a region around the
 * target pixel, sorts them, and uses the median value. This code uses a
 * circular row buffer to improve performance.
 *
 * The adaptive filter is based on the median filter but analizes the histogram
 * of the region around the target pixel and adjusts the despeckle radius
 * accordingly.
 */

static void
despeckle (GimpDrawable *drawable)
{
  GimpPixelRgn	src_rgn,	/* Source image region */
		dst_rgn;	/* Destination image region */
  guchar       *buffer=NULL; 	/* Source pixel rows */
  gint		bpp;		/* bytes per pixel */  
  /*
   * Let the user know what we're doing...
   */

  gimp_progress_init (_("Despeckling..."));

  /*
   * Setup for filter...
   */

  bpp        = gimp_drawable_bpp (drawable->drawable_id);

  gimp_pixel_rgn_init (&src_rgn, drawable, sel_x1, sel_y1, sel_width, sel_height,
		       FALSE, FALSE);
  gimp_pixel_rgn_init (&dst_rgn, drawable, sel_x1, sel_y1, sel_width, sel_height,
		       TRUE, TRUE);

  g_free(buffer);
  buffer  = g_new (guchar, sel_width * sel_height * bpp);

  gimp_pixel_rgn_get_rect (&src_rgn, buffer, sel_x1, sel_y1, sel_width, sel_height);
  
  despeckle_new (buffer, sel_width,  sel_height, bpp, despeckle_radius); 
  
  gimp_pixel_rgn_set_rect(&dst_rgn, buffer, sel_x1, sel_y1, sel_width, sel_height);

  g_free(buffer);        


  gimp_drawable_flush (drawable);
  gimp_drawable_merge_shadow (drawable->drawable_id, TRUE);
  gimp_drawable_update (drawable->drawable_id,
			sel_x1, sel_y1, sel_width, sel_height);
}


/*
 * 'despeckle_dialog()' - Popup a dialog window for the filter box size...
 */

static gint
despeckle_dialog (void)
{
  GtkWidget *dialog;
  GtkWidget *main_vbox;
  GtkWidget *hbox;
  GtkWidget *vbox;
  GtkWidget *table;
  GtkWidget *ptable;
  GtkWidget *frame;
  GtkWidget *scrollbar;
  GtkWidget *button;
  GtkObject *adj;
  gboolean   run;

  gimp_ui_init ("despeckle", TRUE);

  dialog = gimp_dialog_new (_("Despeckle"), "despeckle",
                            NULL, 0,
			    gimp_standard_help_func, HELP_ID,

			    GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
			    GTK_STOCK_OK,     GTK_RESPONSE_OK,

			    NULL);

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

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

  /*
   * Preview window...
   */

  ptable = gtk_table_new (2, 2, FALSE);
  gtk_box_pack_start (GTK_BOX (hbox), ptable, FALSE, FALSE, 0);
  gtk_widget_show(ptable);

  frame = gtk_frame_new (NULL);
  gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN);
  gtk_table_attach (GTK_TABLE (ptable), frame, 0, 1, 0, 1, 0, 0, 0, 0);
  gtk_widget_show (frame);

  preview_width  = MIN (sel_width, PREVIEW_SIZE);
  preview_height = MIN (sel_height, PREVIEW_SIZE);

  preview = gtk_preview_new (GTK_PREVIEW_COLOR);
  gtk_preview_size (GTK_PREVIEW (preview), preview_width, preview_height);
  gtk_container_add (GTK_CONTAINER (frame), preview);
  gtk_widget_show (preview);

  hscroll_data = gtk_adjustment_new (0, 0, sel_width - 1, 1.0,
				     MIN (preview_width, sel_width),
				     MIN (preview_width, sel_width));

  g_signal_connect (hscroll_data, "value_changed",
                    G_CALLBACK (preview_srcoll_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(ptable), 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_height, sel_height),
				     MIN (preview_height, sel_height));

  g_signal_connect (vscroll_data, "value_changed",
                    G_CALLBACK (preview_srcoll_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 (ptable), scrollbar, 1, 2, 0, 1, 0,
		    GTK_FILL, 0, 0);
  gtk_widget_show (scrollbar);

  preview_init ();

  preview_x1 = sel_x1;
  preview_y1 = sel_y1;
  preview_x2 = preview_x1 + MIN (preview_width, sel_width);
  preview_y2 = preview_y1 + MIN (preview_height, sel_height);

  /*
   * Filter type controls...
   */

  frame = gtk_frame_new (_("Type"));
  gtk_box_pack_start (GTK_BOX (hbox), frame, TRUE, TRUE, 0);
  gtk_widget_show (frame);

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

  button = gtk_check_button_new_with_mnemonic (_("_Adaptive"));
  gtk_box_pack_start (GTK_BOX (vbox), button, FALSE, FALSE, 0);
  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button),
				(filter_type & FILTER_ADAPTIVE) ? TRUE : FALSE);
  gtk_widget_show (button);

  g_signal_connect (button, "toggled",
                    G_CALLBACK (dialog_adaptive_callback),
                    NULL);

  button = gtk_check_button_new_with_mnemonic (_("R_ecursive"));
  gtk_box_pack_start (GTK_BOX (vbox), button, FALSE, FALSE, 0);
  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button),
				(filter_type & FILTER_RECURSIVE) ? TRUE : FALSE);
  gtk_widget_show (button);

  g_signal_connect (button, "toggled",
                    G_CALLBACK (dialog_recursive_callback),
                    NULL);

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

  table = gtk_table_new (3, 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);

  /*
   * Box size (radius) control...
   */

  adj = gimp_scale_entry_new (GTK_TABLE (table), 0, 0,
			      _("_Radius:"), SCALE_WIDTH, ENTRY_WIDTH,
			      despeckle_radius, 1, MAX_RADIUS, 1, 5, 0,
			      TRUE, 0, 0,
			      NULL, NULL);
  g_signal_connect (adj, "value_changed",
                    G_CALLBACK (dialog_iscale_update),
                    &despeckle_radius);

  /*
   * Black level control...
   */

  adj = gimp_scale_entry_new (GTK_TABLE (table), 0, 1,
			      _("_Black Level:"), SCALE_WIDTH, ENTRY_WIDTH,
			      black_level, -1, 255, 1, 8, 0,
			      TRUE, 0, 0,
			      NULL, NULL);
  g_signal_connect (adj, "value_changed",
                    G_CALLBACK (dialog_iscale_update),
                    &black_level);

  /*
   * White level control...
   */

  adj = gimp_scale_entry_new (GTK_TABLE (table), 0, 2,
			      _("_White Level:"), SCALE_WIDTH, ENTRY_WIDTH,
			      white_level, 0, 256, 1, 8, 0,
			      TRUE, 0, 0,
			      NULL, NULL);
  g_signal_connect (adj, "value_changed",
                    G_CALLBACK (dialog_iscale_update),
                    &white_level);

  /*
   * Show it and wait for the user to do something...
   */

  gtk_widget_show (dialog);

  preview_update_new ();

  run = (gimp_dialog_run (GIMP_DIALOG (dialog)) == GTK_RESPONSE_OK);

  gtk_widget_destroy (dialog);

  /*
   * Free the preview data...
   */

  preview_exit ();

  /*
   * Return ok/cancel...
   */

  return run;
}


/*
 * 'preview_init()' - Initialize the preview window...
 */

static void
preview_init (void)
{
 
}


/*
 * 'preview_srcoll_callback()' - Update the preview when a scrollbar is moved.
 */

static void
preview_srcoll_callback (void)
{
  preview_x1 = sel_x1 + GTK_ADJUSTMENT (hscroll_data)->value;
  preview_y1 = sel_y1 + GTK_ADJUSTMENT (vscroll_data)->value;
  preview_x2 = preview_x1 + MIN (preview_width, sel_width);
  preview_y2 = preview_y1 + MIN (preview_height, sel_height);

  preview_update_new ();
}



static void
preview_exit (void)
{
}

static void
dialog_iscale_update (GtkAdjustment *adjustment,
		      gint          *value)
{
  *value = adjustment->value;

  if (value == &despeckle_radius)
    preview_init ();

  preview_update_new ();
}

static void
dialog_adaptive_callback (GtkWidget *widget,
			  gpointer   data)
{
  if (GTK_TOGGLE_BUTTON (widget)->active)
    filter_type |= FILTER_ADAPTIVE;
  else
    filter_type &= ~FILTER_ADAPTIVE;

  preview_update_new ();
}

static void
dialog_recursive_callback (GtkWidget *widget,
			   gpointer  data)
{
  if (GTK_TOGGLE_BUTTON (widget)->active)
    filter_type |= FILTER_RECURSIVE;
  else
    filter_type &= ~FILTER_RECURSIVE;

  preview_update_new ();
}

static void fillMask (guchar *mask, gint width) 
  {
   /*    Radius 3 generates a mask like :
         -     +
  	 3210123
  	
      -3 0011100
       2 0111110
       1 1111111
       0 1111111
       1 1111111
       2 0111110
      +3 0011100
      
   */
    gint r = (width-1)/2;
    gint r2 = (gint) (r*r) + 1;
    gint x,y;
    for (x=-r; x<=r; x++)
     {
      for (y=-r; y<=r; y++)
       {
        if ((x*x+y*y)<=r2)
          mask[r+x+(r+y)*width]=1;
        else  
          mask[r+x+(r+y)*width]=0;      
       }
     }
}

/* School book sort */
static void quickSort(guchar *buffer, gint size)
{
  q_sort(buffer, 0, size - 1);
}
static void q_sort(guchar *buffer, gint left, gint right)
{
  gint p, l, r;

  l = left;
  r = right;
  p = buffer[left];
  while (left < right)
  {
    while ((buffer[right] >= p) && (left < right))
      right--;
    if (left != right)
    {
      buffer[left] = buffer[right];
      left++;
    }
    while ((buffer[left] <= p) && (left < right))
      left++;
    if (left != right)
    {
      buffer[right] = buffer[left];
      right--;
    }
  }
  buffer[left] = p;
  p = left;
  left = l;
  right = r;
  if (left < p)
    q_sort(buffer, l, p-1);
  if (right > p)
    q_sort(buffer, p+1, r);
}

/* School book sort */
static void bubbleSort(guchar *buffer, gint size)
{
  int i, j, t;

  for (i = (size - 1); i >= 0; i--)
  {
    for (j = 1; j <= i; j++)
    {
      if (buffer[j-1] > buffer[j])
      {
        t = buffer[j-1];
        buffer[j-1] = buffer[j];
        buffer[j] = t;
      }
    }
  }
}
 
/* would not dear to call it improved despeckle algorithm */
static void despeckle_new (guchar *dst_buffer, gint width, gint height, gint bpp, gint initialradius) 
  {
    gint    maskwidth;
    guchar *mask=NULL;              /* Mask buffer                                */
    guchar *row_buffer = NULL;	    /* Row operations buffer                      */
    guchar *rec_buffer = NULL;	    /* Non FILTER_RECURSIVE buffer                */
    guchar *values = NULL;          /* Channel buffer                             */
    gint   row, col;                /* Row and column for buffer                  */
    gint   maskrow, maskcol;        /* Row and column for mask buffer             */
    gint   absrow, abscol;          /* Absolute row/column for surrounding pixels */
    gint   valuecount;              /* Count sected pixels                        */ 
    gint   pos,rowpos,rowposalpha;  /* Position within buffer                     */ 
    gint   medpos;                  /* Median value position in value buffer      */  
    gint   alphasum=0;              /* Sum of alpha channel values                */
    gint   radiusrgba[4];           /* Working radius                             */
    gint   radius,channel;          /* Non specific loop variable                 */    
    gint  hist0=0;
    gint  hist255=0;
    radiusrgba[0]=    radiusrgba[1]=    radiusrgba[2]=    radiusrgba[3]= initialradius;
       
    /* filter temporary buffer */
    rec_buffer = g_new (guchar, width * height * bpp);
    memcpy (rec_buffer, dst_buffer, width * height * bpp);
   
    /* Process each row */
    for ( row = 0 ; row < height ; row++ )
      {            
        /* Make sure row buffer is fresh */
        g_free(row_buffer);
        row_buffer = g_new(guchar, width*bpp);        
        /* copy contents from tomporary buffer in row buffer */
        memcpy (row_buffer, (rec_buffer+(row*width*bpp)), width*bpp);

        /* process each column */	
        for ( col = 0 ; col < width ; col++) 
    	  {
            /* Calculate starting with alpha */
            for(channel=bpp-1;channel>=0;channel--)
              {
                /* Position in buffer */
                pos = ((row * width)+col)*bpp+channel;
                rowpos = col*bpp+channel;
                rowposalpha = col*bpp+3;

                /* addaptive radius per channel */
                radius = radiusrgba[channel];
                maskwidth = radius * 2 + 1;

                /* Calculate mask */
                g_free(mask); 
                mask     = g_new(guchar, (maskwidth*maskwidth));
                fillMask(mask, maskwidth);          
               
                /* Value buffer */
                g_free(values);                
                values   = g_new(guchar, (maskwidth*maskwidth));

                hist0   = 0;
                hist255 = 0;                
                valuecount=0;
                for ( maskrow=0 ; maskrow < maskwidth ; maskrow++ )  
                  {
                    for ( maskcol=0 ; maskcol < maskwidth ; maskcol++ )
                      {
                        abscol = col-radius+maskcol;
                        absrow = row-radius+maskrow;
                        if ( abscol >= 0     &&
                             absrow >= 0     &&
                             abscol <  width && 
                             absrow < height &&
                             mask[maskrow*maskwidth+maskcol] & 1
                           )   
                          {                             
                             if (channel < 3 )
                               {
                                 if ( *(rec_buffer+pos) <= black_level)
                                   hist0 ++;
                                 else if (*(rec_buffer+pos) >= white_level)
                                   hist255 ++;
                                 if (*(rec_buffer+pos) < white_level && *(rec_buffer+pos) > black_level)
                                   {
                                     *(values+valuecount) = *(rec_buffer+pos);
                                     valuecount++;           	          
                                   }
                               }
                             else 
                               {
                                  *(values+valuecount) = *(rec_buffer+pos);
                                  valuecount++;           	                                          
                               }
                          }
                      }
                  }
                /* 
                 * Sort pixels and
                 * Assign the median value...
                 */

                switch (valuecount)                
                  {
                    case 0:   /* No values within histogram */
                      {
                         *(row_buffer+rowpos) = 255;
                         break;
                      }
                    case 1:   /* Only one value within histogram so assign */
                      {                        
                        if (channel == 3)                          
                          alphasum =(*(values) == 0) ? 1 :  *(values);
                        *(row_buffer+rowpos)   = *(values); 
                        break;
                      }  
                    case 2:   /* Two values median = mean */
                      {
                        if (channel == 3 )
                          {
                             alphasum =   *(values) + *(values+1); 
                             *(row_buffer+rowpos) =  alphasum/2 ;               	
                             alphasum =(alphasum == 0) ? 1 : alphasum;
                          }
                        else
                          {
                            alphasum =(alphasum == 0) ? 1 : alphasum;
                            if (bpp==4) 
                               *(row_buffer+rowpos)   = (*(row_buffer+rowposalpha) * (*(values) + *(values+1)))/alphasum;
                             else
                               *(row_buffer+rowpos)   = (*(values) + *(values+1))/2;
                          }
                        break;
                      }
                    default:
                      {
                         if (valuecount > SORT_PIVOT)
                           quickSort(values, valuecount);
                         else
                           bubbleSort(values, valuecount);                 
                         if (valuecount & 1) 
                           {
                              medpos = valuecount/2;
                              *(row_buffer+rowpos)   = *(values+medpos);             	
                           }
                         else
                           {
                              if (channel == 3)
                                {
                                   alphasum =   *(values) + *(values+1); 
                                   *(row_buffer+rowpos) =  alphasum/2 ;               	
                                }
                              else
                                {
                                   /* compensate for base 0 */
                                   medpos = (valuecount-1)/2;
                                   alphasum =(alphasum == 0) ? 1 : alphasum;
                                   if (bpp==4)
                                     *(row_buffer+rowpos)   = ( *(row_buffer+rowposalpha) * 
                                                                (*(values+medpos) + *(values+medpos+1))
                                                              ) /alphasum;
                                   else
                                     *(row_buffer+rowpos)   = (*(values+medpos) + *(values+medpos+1))/2;
                                }
                           }
                      }
                  }  
                if (filter_type & FILTER_RECURSIVE ) 
                  *(rec_buffer+pos) = *(row_buffer+rowpos);
                /*
	         * Check the histogram and adjust the radius accordingly...
	         */

	        if (filter_type & FILTER_ADAPTIVE)
		  {
		    if (hist0 >= radius || hist255 >= radius)
		      {
		        if (radius < initialradius)
		  	radius ++;
		      }
		    else if (radius > 1)
		      radius --;
                    radiusrgba[channel]=radius;
 		  }              
             } /* channel */
          } /* colum counter x */
        /* Copy temporatry buffer back to destination buffer */
        memcpy ((dst_buffer+(row*width*bpp)),row_buffer, width*bpp);
      } /* row counter y */

      g_free(mask);
      g_free(values);
      g_free(row_buffer);      
      g_free(rec_buffer);
  }
  
  
  /*
   * 'preview_update()' - Update the preview window.
   */
  
  static void
  preview_update_new (void)
  {
    GimpPixelRgn	src_rgn;	/* Source image region */
    gint	 x, y, offset;		/* Current location in image */
    guchar	 check;		/* Checkerboard pattern */
    guchar      *preview_src = NULL;
    gint         radius=despeckle_radius;
    gint         pos, preview_pos;                                         /* Position within buffer                     */ 
    guchar      *render_buffer=NULL;   /* Buffer to hold rendered image */
    gint        bpp;
    
   /*
    * Setup for filter...
    */
    gimp_pixel_rgn_init (&src_rgn, drawable,
  		       preview_x1, preview_y1, preview_width, preview_height,
  		       FALSE, FALSE);
    bpp        = gimp_drawable_bpp (drawable->drawable_id);
    /*
     * Pre-load the preview rectangle...
     */
    g_free(preview_src);
    preview_src  = g_new (guchar, preview_width * preview_height * bpp);
    gimp_pixel_rgn_get_rect (&src_rgn, preview_src, preview_x1, preview_y1,
  			   preview_width, preview_height);
  
    despeckle_new (preview_src, preview_width,  preview_height, bpp, radius); 
    
    /*
     * Draw this row...
     */
    /* Preview is RGB */
    g_free(render_buffer);
    render_buffer = g_new (guchar, preview_width * preview_height * 3);
    for ( y = 0 ; y < preview_height ; y++ )
      for ( x = 0 ; x < preview_width ; x++ )     
        {
          pos = (y*preview_width+x)*bpp;
          preview_pos = (y*preview_width+x)*3;
          switch (bpp)
            { 
              case 1:
                *(render_buffer+preview_pos)   = *(preview_src+pos);
                *(render_buffer+preview_pos+1) = *(preview_src+pos);
                *(render_buffer+preview_pos+2) = *(preview_src+pos);
                break;
              case 2:
                if (*(preview_src+pos) == 255)
                  {
                    *(render_buffer+preview_pos)   = *(preview_src+pos);
                    *(render_buffer+preview_pos+1) = *(preview_src+pos);
                    *(render_buffer+preview_pos+2) = *(preview_src+pos);
                  }
                else
                  {
                    if ((y & GIMP_CHECK_SIZE) ^ (x & GIMP_CHECK_SIZE))
                      check = GIMP_CHECK_LIGHT * 255;
                    else
  	              check = GIMP_CHECK_DARK * 255;
                    if (*(preview_src+pos) == 0)
                      {
                        *(render_buffer+preview_pos)   = check;
                        *(render_buffer+preview_pos+1) = check;
                        *(render_buffer+preview_pos+2) = check;
                      }
  	            else
                      {
                        *(render_buffer+preview_pos)   = check + ((*(preview_src+pos) - check) * *(preview_src+pos+1)) / 255;
                        *(render_buffer+preview_pos+1) = *(render_buffer+preview_pos);
                        *(render_buffer+preview_pos+2) = *(render_buffer+preview_pos);
                      }
  	          };
                break;
              case 3:
                *(render_buffer+preview_pos)   = *(preview_src+pos);
                *(render_buffer+preview_pos+1) = *(preview_src+pos+1);
                *(render_buffer+preview_pos+2) = *(preview_src+pos+2);
                break;
              case 4:
                if (*(preview_src+pos+3) == 255)
  	          {
                    *(render_buffer+preview_pos)   = *(preview_src+pos);
                    *(render_buffer+preview_pos+1) = *(preview_src+pos+1);
                    *(render_buffer+preview_pos+2) = *(preview_src+pos+2);
  	          }
                else
                  {
                    if ((y & GIMP_CHECK_SIZE) ^ (x & GIMP_CHECK_SIZE))
                      check = GIMP_CHECK_LIGHT * 255;
                    else
                      check = GIMP_CHECK_DARK * 255;
                    if (*(preview_src+pos+3) == 0) 
                      {
                        *(render_buffer+preview_pos)   = check;
                        *(render_buffer+preview_pos+1) = check;
                        *(render_buffer+preview_pos+2) = check;
                      }
  	            else
  	              {
                        *(render_buffer+preview_pos)   = check + ((*(preview_src+pos+0) - check) * *(preview_src+pos+3)) / 255;
                        *(render_buffer+preview_pos+1) = check + ((*(preview_src+pos+1) - check) * *(preview_src+pos+3)) / 255;
                        *(render_buffer+preview_pos+2) = check + ((*(preview_src+pos+2) - check) * *(preview_src+pos+3)) / 255;
  	              }
  	          }
                break;
  	    }
     
        }
      
      
      /*
       * Update the screen...
       */
      for (y = 0; y < preview_height; y++) 
        {           
           offset = y * 3 * preview_width;
           gtk_preview_draw_row(GTK_PREVIEW (preview), render_buffer+offset, 0, y, preview_width);
        }
      gtk_widget_queue_draw (preview);
      g_free(render_buffer);   
      g_free(preview_src);
  }
