[xiph-commits] r15574 - in trunk: . gimp-montypak

xiphmont at svn.xiph.org xiphmont at svn.xiph.org
Fri Dec 12 21:06:16 PST 2008


Author: xiphmont
Date: 2008-12-12 21:06:15 -0800 (Fri, 12 Dec 2008)
New Revision: 15574

Added:
   trunk/gimp-montypak/
   trunk/gimp-montypak/Makefile
   trunk/gimp-montypak/blur.c
   trunk/gimp-montypak/denoise
   trunk/gimp-montypak/denoise.c
   trunk/gimp-montypak/scanclean
   trunk/gimp-montypak/scanclean.c
   trunk/gimp-montypak/wavelet.c
Log:
Initial code import of a small side-project.  Because derf asked.


Added: trunk/gimp-montypak/Makefile
===================================================================
--- trunk/gimp-montypak/Makefile	                        (rev 0)
+++ trunk/gimp-montypak/Makefile	2008-12-13 05:06:15 UTC (rev 15574)
@@ -0,0 +1,64 @@
+# Fuck Automake
+# Fuck the horse it rode in on
+# and Fuck its little dog Libtool too
+
+PREFIX    = ~/.gimp-2.4
+MAJOR     = 0
+MINOR     = 0
+SUBMINOR  = 0
+
+CC        = gcc 
+LD        = gcc
+INSTALL   = install
+STRIP     = strip
+LDCONFIG  = /sbin/ldconfig
+
+VERSION   = $(MAJOR).$(MINOR).$(SUBMINOR)
+BINDIR    = $(PREFIX)//plug-ins
+
+MAN	  =
+PKGARG	  = "gimp-2.0 gimpui-2.0 gtk+-2.0 >= 2.10 "
+GCF       = -std=gnu99 `pkg-config --cflags $(PKGARG)` -DVERSION="\"$(VERSION)\""
+LDF       = -L/lib `pkg-config --libs $(PKGARG)`
+
+all:    
+	pkg-config --cflags $(PKGARG) 1>/dev/null
+	$(MAKE) montypak CFLAGS='-O2 -g $(GCF) '
+
+debug:
+	pkg-config --cflags $(PKGARG) 1>/dev/null
+	$(MAKE) montypak CFLAGS='-g -Wall -W -Wno-unused-parameter -D__NO_MATH_INLINES $(GCF) '
+
+profile:
+	pkg-config --cflags $(PKGARG) 1>/dev/null
+	$(MAKE) montypak CFLAGS='-g -pg -O2 $(GCF) '
+
+clean:
+	rm -f *.o *.d *.d.* *.pc gmon.out $(TARGET)
+
+distclean: clean
+	rm -f core core.* *~ 
+
+%.d: %.c
+	$(CC) -M $(CFLAGS) $< > $@.$$$$; sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' < $@.$$$$ > $@; rm -f $@.$$$$
+
+ifeq ($(MAKECMDGOALS),target)
+include $(SRC:.c=.d)
+endif
+
+montypak: denoise scanclean
+
+denoise.o: wavelet.c
+
+denoise:  denoise.o
+	$(LD) $< $(CFLAGS) -o $@ $(LIBS) $(LDF)
+
+scanclean.o: blur.c
+
+scanclean: scanclean.o wavelet.c blur.c
+	$(LD) $< $(CFLAGS) -o $@ $(LIBS) $(LDF)
+
+install: all
+	$(INSTALL) -d -m 0755 $(BINDIR)
+	$(INSTALL) -m 0755 denoise $(BINDIR)
+	$(INSTALL) -m 0755 scanclean $(BINDIR)

Added: trunk/gimp-montypak/blur.c
===================================================================
--- trunk/gimp-montypak/blur.c	                        (rev 0)
+++ trunk/gimp-montypak/blur.c	2008-12-13 05:06:15 UTC (rev 15574)
@@ -0,0 +1,368 @@
+/*
+ * blur.c
+ * Taken from unsharp.c, originally:
+ * Copyright (C) 1999 Winston Chang
+ *                    <winstonc at cs.wisc.edu>
+ *                    <winston at stdout.org>
+ *
+ * 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>
+
+static gint      gen_convolve_matrix (gdouble         std_dev,
+                                      gdouble       **cmatrix);
+static gdouble * gen_lookup_table    (const gdouble  *cmatrix,
+                                      gint            cmatrix_length);
+static void      unsharp_region      (GimpPixelRgn   *srcPTR,
+                                      GimpPixelRgn   *dstPTR,
+                                      gint            bytes,
+                                      gdouble         radius,
+                                      gdouble         amount,
+                                      gint            x1,
+                                      gint            x2,
+                                      gint            y1,
+                                      gint            y2,
+                                      gboolean        show_progress);
+
+static void      unsharp_mask        (GimpDrawable   *drawable,
+                                      gdouble         radius,
+                                      gdouble         amount);
+
+static gboolean  unsharp_mask_dialog (GimpDrawable   *drawable);
+static void      preview_update      (GimpPreview    *preview);
+
+
+/* This function is written as if it is blurring a column at a time,
+ * even though it can operate on rows, too.  There is no difference
+ * in the processing of the lines, at least to the blur_line function.
+ */
+static void
+blur_line (const gdouble *cmatrix,
+           const gint     cmatrix_length,
+           const guchar  *src,
+           guchar        *dest,
+           const gint     len){
+  const gdouble *cmatrix_p;
+  const guchar  *src_p;
+  const gint     cmatrix_middle = cmatrix_length / 2;
+  gint           row;
+  gint           i, j;
+
+  /* This first block is the same as the optimized version --
+   * it is only used for very small pictures, so speed isn't a
+   * big concern.
+   */
+  if (cmatrix_length > len) {
+    for (row = 0; row < len; row++){
+      /* find the scale factor */
+      gdouble scale = 0;
+      gdouble sum = 0;
+
+      for (j = 0; j < len; j++){
+	/* if the index is in bounds, add it to the scale counter */
+	if (j + cmatrix_middle - row >= 0 &&
+	    j + cmatrix_middle - row < cmatrix_length)
+	  scale += cmatrix[j];
+      }
+
+      src_p = src;
+      for (j = 0; j < len; j++){
+	if (j + cmatrix_middle - row >= 0 &&
+	    j + cmatrix_middle - row < cmatrix_length)
+	  sum += *src_p++ * cmatrix[j];
+      }
+	  
+      *dest++ = (guchar) ROUND (sum / scale);
+    }
+  }else{
+    /* for the edge condition, we only use available info and scale to one */
+    for (row = 0; row < cmatrix_middle; row++){
+      /* find scale factor */
+      gdouble scale = 0;
+      gdouble sum = 0;
+      
+      for (j = cmatrix_middle - row; j < cmatrix_length; j++)
+	scale += cmatrix[j];
+      
+      src_p = src;
+      for (j = cmatrix_middle - row; j < cmatrix_length; j++)
+	sum += *src_p++ * cmatrix[j];
+      
+      *dest++ = (guchar) ROUND (sum / scale);
+    }
+
+    /* go through each pixel in each col */
+    for (; row < len - cmatrix_middle; row++){
+      gdouble sum = 0;
+      src_p = src++;
+	    
+      for (j = 0; j < cmatrix_length; j++)
+	sum += *src_p++ * cmatrix[j];
+
+      *dest++ = (guchar) ROUND (sum);
+    }
+
+    /* for the edge condition, we only use available info and scale to one */
+    for (; row < len; row++){
+      /* find scale factor */
+      gdouble scale = 0;
+      gdouble sum = 0;
+      
+      for (j = 0; j < len - row + cmatrix_middle; j++)
+	scale += cmatrix[j];
+
+      src_p = src++;
+      for (j = 0; j < len - row + cmatrix_middle; j++)
+	sum += *src_p++ * cmatrix[j];
+
+      *dest++ = (guchar) ROUND (sum / scale);
+    }
+  }
+}
+
+static void
+blur_lined (const gdouble *cmatrix,
+           const gint     cmatrix_length,
+           const double  *src,
+           double        *dest,
+           const gint     len){
+  const gdouble *cmatrix_p;
+  const double  *src_p;
+  const gint     cmatrix_middle = cmatrix_length / 2;
+  gint           row;
+  gint           i, j;
+
+  /* This first block is the same as the optimized version --
+   * it is only used for very small pictures, so speed isn't a
+   * big concern.
+   */
+  if (cmatrix_length > len) {
+    for (row = 0; row < len; row++){
+      /* find the scale factor */
+      gdouble scale = 0;
+      gdouble sum = 0;
+
+      for (j = 0; j < len; j++){
+	/* if the index is in bounds, add it to the scale counter */
+	if (j + cmatrix_middle - row >= 0 &&
+	    j + cmatrix_middle - row < cmatrix_length)
+	  scale += cmatrix[j];
+      }
+
+      src_p = src;
+      for (j = 0; j < len; j++){
+	if (j + cmatrix_middle - row >= 0 &&
+	    j + cmatrix_middle - row < cmatrix_length)
+	  sum += *src_p++ * cmatrix[j];
+      }
+	  
+      *dest++ = sum / scale;
+    }
+  }else{
+    /* for the edge condition, we only use available info and scale to one */
+    for (row = 0; row < cmatrix_middle; row++){
+      /* find scale factor */
+      gdouble scale = 0;
+      gdouble sum = 0;
+      
+      for (j = cmatrix_middle - row; j < cmatrix_length; j++)
+	scale += cmatrix[j];
+      
+      src_p = src;
+      for (j = cmatrix_middle - row; j < cmatrix_length; j++)
+	sum += *src_p++ * cmatrix[j];
+      
+      *dest++ = sum / scale;
+    }
+
+    /* go through each pixel in each col */
+    for (; row < len - cmatrix_middle; row++){
+      gdouble sum = 0;
+      src_p = src++;
+	    
+      for (j = 0; j < cmatrix_length; j++)
+	sum += *src_p++ * cmatrix[j];
+
+      *dest++ = sum;
+    }
+
+    /* for the edge condition, we only use available info and scale to one */
+    for (; row < len; row++){
+      /* find scale factor */
+      gdouble scale = 0;
+      gdouble sum = 0;
+      
+      for (j = 0; j < len - row + cmatrix_middle; j++)
+	scale += cmatrix[j];
+
+      src_p = src++;
+      for (j = 0; j < len - row + cmatrix_middle; j++)
+	sum += *src_p++ * cmatrix[j];
+
+      *dest++ = sum / scale;
+    }
+  }
+}
+
+static void blur (guchar *b,
+		  int     w,
+		  int     h,
+		  double  radius){
+
+  gdouble *cmatrix = NULL;
+  gint     cmatrix_length = gen_convolve_matrix (radius, &cmatrix);
+
+  /* allocate buffers */
+  guchar *src  = g_new (guchar, w+h);
+  guchar *dest = g_new (guchar, w+h);
+  int x,y;
+
+
+  /* blur the rows */
+  for (y = 0; y < h; y++){
+    memcpy(src,b+y*w,sizeof(*b)*w);
+    blur_line (cmatrix, cmatrix_length, src, dest, w);
+    memcpy(b+y*w,dest,sizeof(*b)*w);
+  }
+
+  /* blur the cols */
+  for (x = 0; x < w; x++){
+    for(y = 0; y < h; y++) src[y]=b[y*w+x];
+    blur_line (cmatrix, cmatrix_length, src, dest, h);
+    for(y = 0; y < h; y++) b[y*w+x]=dest[y];
+  }
+
+  g_free (dest);
+  g_free (src);
+  g_free (cmatrix);
+}
+
+static void blurd (double *b,
+		   int     w,
+		   int     h,
+		   double  radius){
+  
+  gdouble *cmatrix = NULL;
+  gint     cmatrix_length = gen_convolve_matrix (radius, &cmatrix);
+
+  /* allocate buffers */
+  double *src  = g_new (double, w+h);
+  double *dest = g_new (double, w+h);
+  int x,y;
+
+
+  /* blur the rows */
+  for (y = 0; y < h; y++){
+    memcpy(src,b+y*w,sizeof(*b)*w);
+    blur_lined (cmatrix, cmatrix_length, src, dest, w);
+    memcpy(b+y*w,dest,sizeof(*b)*w);
+  }
+
+  /* blur the cols */
+  for (x = 0; x < w; x++){
+    for(y = 0; y < h; y++) src[y]=b[y*w+x];
+    blur_lined (cmatrix, cmatrix_length, src, dest, h);
+    for(y = 0; y < h; y++) b[y*w+x]=dest[y];
+  }
+
+  g_free (dest);
+  g_free (src);
+  g_free (cmatrix);
+}
+
+/* generates a 1-D convolution matrix to be used for each pass of
+ * a two-pass gaussian blur.  Returns the length of the matrix.
+ */
+static gint
+gen_convolve_matrix (gdouble   radius,
+                     gdouble **cmatrix_p)
+{
+  gdouble *cmatrix;
+  gdouble  std_dev;
+  gdouble  sum;
+  gint     matrix_length;
+  gint     i, j;
+
+  /* we want to generate a matrix that goes out a certain radius
+   * from the center, so we have to go out ceil(rad-0.5) pixels,
+   * inlcuding the center pixel.  Of course, that's only in one direction,
+   * so we have to go the same amount in the other direction, but not count
+   * the center pixel again.  So we double the previous result and subtract
+   * one.
+   * The radius parameter that is passed to this function is used as
+   * the standard deviation, and the radius of effect is the
+   * standard deviation * 2.  It's a little confusing.
+   */
+  radius = fabs (radius) + 1.0;
+
+  std_dev = radius;
+  radius = std_dev * 2;
+
+  /* go out 'radius' in each direction */
+  matrix_length = 2 * ceil (radius - 0.5) + 1;
+  if (matrix_length <= 0)
+    matrix_length = 1;
+
+  *cmatrix_p = g_new (gdouble, matrix_length);
+  cmatrix = *cmatrix_p;
+
+  /*  Now we fill the matrix by doing a numeric integration approximation
+   * from -2*std_dev to 2*std_dev, sampling 50 points per pixel.
+   * We do the bottom half, mirror it to the top half, then compute the
+   * center point.  Otherwise asymmetric quantization errors will occur.
+   *  The formula to integrate is e^-(x^2/2s^2).
+   */
+
+  /* first we do the top (right) half of matrix */
+  for (i = matrix_length / 2 + 1; i < matrix_length; i++)
+    {
+      gdouble base_x = i - (matrix_length / 2) - 0.5;
+
+      sum = 0;
+      for (j = 1; j <= 50; j++)
+        {
+          gdouble r = base_x + 0.02 * j;
+
+          if (r <= radius)
+            sum += exp (- SQR (r) / (2 * SQR (std_dev)));
+        }
+
+      cmatrix[i] = sum / 50;
+    }
+
+  /* mirror the thing to the bottom half */
+  for (i = 0; i <= matrix_length / 2; i++)
+    cmatrix[i] = cmatrix[matrix_length - 1 - i];
+
+  /* find center val -- calculate an odd number of quanta to make it symmetric,
+   * even if the center point is weighted slightly higher than others. */
+  sum = 0;
+  for (j = 0; j <= 50; j++)
+    sum += exp (- SQR (- 0.5 + 0.02 * j) / (2 * SQR (std_dev)));
+
+  cmatrix[matrix_length / 2] = sum / 51;
+
+  /* normalize the distribution by scaling the total sum to one */
+  sum = 0;
+  for (i = 0; i < matrix_length; i++)
+    sum += cmatrix[i];
+
+  for (i = 0; i < matrix_length; i++)
+    cmatrix[i] = cmatrix[i] / sum;
+
+  return matrix_length;
+}
+

Added: trunk/gimp-montypak/denoise
===================================================================
(Binary files differ)


Property changes on: trunk/gimp-montypak/denoise
___________________________________________________________________
Name: svn:executable
   + 
Name: svn:mime-type
   + application/octet-stream

Added: trunk/gimp-montypak/denoise.c
===================================================================
--- trunk/gimp-montypak/denoise.c	                        (rev 0)
+++ trunk/gimp-montypak/denoise.c	2008-12-13 05:06:15 UTC (rev 15574)
@@ -0,0 +1,531 @@
+/*
+ *   Wavelet Denoise filter for GIMP - The GNU Image Manipulation Program
+ *
+ *   Copyright (C) 2008 Monty
+ *   Code based on research by Crystal Wagner and Prof. Ivan Selesnik, 
+ *   Polytechnic University, Brooklyn, NY
+ *   See: http://taco.poly.edu/selesi/DoubleSoftware/
+ *
+ *   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 <string.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include <libgimp/gimp.h>
+#include <libgimp/gimpui.h>
+
+/*
+ * Constants...
+ */
+
+#define PLUG_IN_PROC    "plug-in-denoise"
+#define PLUG_IN_BINARY  "denoise"
+#define PLUG_IN_VERSION "28 Oct 2008"
+
+/*
+ * Local functions...
+ */
+
+static void     query (void);
+static void     run   (const gchar      *name,
+                       gint              nparams,
+                       const GimpParam  *param,
+                       gint             *nreturn_vals,
+                       GimpParam       **returm_vals);
+
+static void     denoise        (GimpDrawable *drawable);
+
+static gboolean denoise_dialog (GimpDrawable *drawable);
+static void     denoise_work(int w, int h, int bpp, guchar *buffer, int progress);
+
+static void     preview_update (GimpPreview  *preview);
+
+/*
+ * Globals...
+ */
+
+const GimpPlugInInfo PLUG_IN_INFO =
+{
+  NULL,  /* init_proc  */
+  NULL,  /* quit_proc  */
+  query, /* query_proc */
+  run    /* run_proc   */
+};
+
+typedef struct
+{
+  float filter;
+  int soft;
+  int multiscale;
+  float f1;
+  float f2;
+  float f3;
+  float f4;
+
+} DenoiseParams;
+
+static DenoiseParams denoise_params =
+{
+  2, 0, 0,
+  0.,0.,0.,0.,
+
+};
+
+static GtkWidget    *preview;
+static GtkObject    *madj[4];
+
+MAIN ()
+
+#include "wavelet.c"
+
+static void
+query (void)
+{
+  static const GimpParamDef   args[] =
+  {
+    { GIMP_PDB_INT32,    "run-mode",      "Interactive, non-interactive"      },
+    { GIMP_PDB_IMAGE,    "image",         "Input image"                       },
+    { GIMP_PDB_DRAWABLE, "drawable",      "Input drawable"                    },
+    { GIMP_PDB_FLOAT,    "filter",        "Global denoise filter strength"    },
+    { GIMP_PDB_INT32,    "soft",          "Use soft thresholding"             },
+    { GIMP_PDB_INT32,    "multiscale",    "Enable multiscale adjustment"      },
+    { GIMP_PDB_FLOAT,    "f1",            "Fine detail denoise"               },
+    { GIMP_PDB_FLOAT,    "f2",            "Detail denoise"                    },
+    { GIMP_PDB_FLOAT,    "f3",            "Mid denoise"                       },
+    { GIMP_PDB_FLOAT,    "f4",            "Coarse denoise"                    },
+  };
+
+  gimp_install_procedure (PLUG_IN_PROC,
+                          "Denoise filter",
+			  "This plugin uses directional wavelets to remove random "
+			  "noise from an image; at an extreme produces an airbrush-like"
+			  "effect.",
+                          "Monty <monty at xiph.org>",
+                          "Copyright 2008 by Monty",
+                          PLUG_IN_VERSION,
+                          "_Denoise...",
+                          "RGB*, GRAY*",
+                          GIMP_PLUGIN,
+                          G_N_ELEMENTS (args), 0,
+                          args, NULL);
+
+  gimp_plugin_menu_register (PLUG_IN_PROC, "<Image>/Filters/Montypak");
+}
+
+static void
+run (const gchar      *name,
+     gint              nparams,
+     const GimpParam  *param,
+     gint             *nreturn_vals,
+     GimpParam       **return_vals)
+{
+  GimpRunMode        run_mode;  /* Current run mode */
+  GimpPDBStatusType  status;    /* Return status */
+  GimpParam         *values;    /* Return values */
+  GimpDrawable      *drawable;  /* Current image */
+
+  /*
+   * Initialize parameter data...
+   */
+
+  status   = GIMP_PDB_SUCCESS;
+  run_mode = param[0].data.d_int32;
+  values = g_new (GimpParam, 1);
+
+  *nreturn_vals = 1;
+  *return_vals  = values;
+
+  values[0].type          = GIMP_PDB_STATUS;
+  values[0].data.d_status = status;
+
+  /*
+   * Get drawable information...
+   */
+
+  drawable = gimp_drawable_get (param[2].data.d_drawable);
+  gimp_tile_cache_ntiles (2 * drawable->ntile_cols);
+
+  /*
+   * See how we will run
+   */
+
+  switch (run_mode)
+    {
+    case GIMP_RUN_INTERACTIVE:
+      /*
+       * Possibly retrieve data...
+       */
+      gimp_get_data (PLUG_IN_PROC, &denoise_params);
+
+      /*
+       * Get information from the dialog...
+       */
+      if (!denoise_dialog (drawable))
+        return;
+      break;
+
+    case GIMP_RUN_NONINTERACTIVE:
+      /*
+       * Make sure all the arguments are present...
+       */
+      if (nparams != 10)
+        status = GIMP_PDB_CALLING_ERROR;
+      else{
+        denoise_params.filter = param[3].data.d_float;
+	denoise_params.soft   = param[4].data.d_int32;
+	denoise_params.multiscale = param[5].data.d_int32;
+        denoise_params.f1 = param[6].data.d_float;
+        denoise_params.f2 = param[7].data.d_float;
+        denoise_params.f3 = param[8].data.d_float;
+        denoise_params.f4 = param[9].data.d_float;
+      }
+      break;
+
+    case GIMP_RUN_WITH_LAST_VALS:
+      /*
+       * Possibly retrieve data...
+       */
+      gimp_get_data (PLUG_IN_PROC, &denoise_params);
+      break;
+
+    default:
+      status = GIMP_PDB_CALLING_ERROR;
+      break;
+    }
+
+  if (status == GIMP_PDB_SUCCESS)
+    {
+      if ((gimp_drawable_is_rgb (drawable->drawable_id) ||
+           gimp_drawable_is_gray (drawable->drawable_id)))
+        {
+          /*
+           * Run!
+           */
+          denoise (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_PROC,
+                           &denoise_params, sizeof (DenoiseParams));
+        }
+      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);
+}
+
+static void denoise (GimpDrawable *drawable){
+
+  GimpPixelRgn  src_rgn;        /* Source image region */
+  GimpPixelRgn  dst_rgn;        /* Destination image region */
+  gint          x1,x2;
+  gint          y1,y2;
+  gint          w;
+  gint          h;
+  gint          bpp;
+  guchar       *buffer;
+
+  gimp_drawable_mask_bounds (drawable->drawable_id,
+                             &x1, &y1, &x2, &y2);
+
+  w = x2 - x1;
+  h = y2 - y1;
+  bpp    = gimp_drawable_bpp (drawable->drawable_id);
+
+  /*
+   * Let the user know what we're doing...
+   */
+  gimp_progress_init( "Denoising");
+
+  /*
+   * Setup for filter...
+   */
+
+  gimp_pixel_rgn_init (&src_rgn, drawable,
+                       x1, y1, w, h, FALSE, FALSE);
+  gimp_pixel_rgn_init (&dst_rgn, drawable,
+                       x1, y1, w, h, TRUE, TRUE);
+
+  /***************************************/
+  buffer = g_new (guchar, w * h * bpp);
+  gimp_pixel_rgn_get_rect (&src_rgn, buffer, x1, y1, w, h);
+  denoise_work(w,h,bpp,buffer,1);
+  gimp_pixel_rgn_set_rect (&dst_rgn, buffer, x1, y1, w, h);
+  /**************************************/
+
+
+  /*
+   * Update the screen...
+   */
+
+  gimp_drawable_flush (drawable);
+  gimp_drawable_merge_shadow (drawable->drawable_id, TRUE);
+  gimp_drawable_update (drawable->drawable_id, x1, y1, w, h);
+		      
+  g_free(buffer);
+}
+
+static void dialog_soft_callback (GtkWidget *widget,
+                          gpointer   data)
+{
+  denoise_params.soft = (GTK_TOGGLE_BUTTON (widget)->active);
+  if(denoise_params.filter>0. ||
+     (denoise_params.multiscale && 
+      (denoise_params.f1>0. ||
+       denoise_params.f2>0. ||
+       denoise_params.f3>0. ||
+       denoise_params.f4>0.)))
+    gimp_preview_invalidate (GIMP_PREVIEW (preview));
+}
+
+
+static void dialog_multiscale_callback (GtkWidget *widget,
+                          gpointer   data)
+{
+  denoise_params.multiscale = (GTK_TOGGLE_BUTTON (widget)->active);
+  gimp_scale_entry_set_sensitive(madj[0],denoise_params.multiscale);
+  gimp_scale_entry_set_sensitive(madj[1],denoise_params.multiscale);
+  gimp_scale_entry_set_sensitive(madj[2],denoise_params.multiscale);
+  gimp_scale_entry_set_sensitive(madj[3],denoise_params.multiscale);
+  if(denoise_params.f1>0. ||
+     denoise_params.f2>0. ||
+     denoise_params.f3>0. ||
+     denoise_params.f4>0.)
+    gimp_preview_invalidate (GIMP_PREVIEW (preview));
+}
+
+
+static gboolean denoise_dialog (GimpDrawable *drawable)
+{
+  GtkWidget *dialog;
+  GtkWidget *main_vbox;
+  GtkWidget *table;
+  GtkWidget *button;
+  GtkObject *adj;
+  gboolean   run;
+
+  gimp_ui_init (PLUG_IN_BINARY, TRUE);
+
+  dialog = gimp_dialog_new ("Denoise", PLUG_IN_BINARY,
+                            NULL, 0,
+                            gimp_standard_help_func, PLUG_IN_PROC,
+			    
+                            GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+                            GTK_STOCK_OK,     GTK_RESPONSE_OK,
+			    
+                            NULL);
+  
+  gtk_dialog_set_alternative_button_order (GTK_DIALOG (dialog),
+                                           GTK_RESPONSE_OK,
+                                           GTK_RESPONSE_CANCEL,
+                                           -1);
+
+  gimp_window_set_transient (GTK_WINDOW (dialog));
+
+  main_vbox = gtk_vbox_new (FALSE, 12);
+  gtk_container_set_border_width (GTK_CONTAINER (main_vbox), 12);
+  gtk_container_add (GTK_CONTAINER (GTK_DIALOG (dialog)->vbox), main_vbox);
+  gtk_widget_show (main_vbox);
+
+  preview = gimp_drawable_preview_new (drawable, NULL);
+  gtk_box_pack_start (GTK_BOX (main_vbox), preview, TRUE, TRUE, 0);
+  gtk_widget_show (preview);
+
+  g_signal_connect (preview, "invalidated",
+                    G_CALLBACK (preview_update),
+                    NULL);
+
+  /* Main filter strength adjust */
+  table = gtk_table_new (1, 3, FALSE);
+  gtk_table_set_col_spacings (GTK_TABLE (table), 6);
+  gtk_box_pack_start (GTK_BOX (main_vbox), table, FALSE, FALSE, 0);
+  gtk_widget_show (table);
+
+  adj = gimp_scale_entry_new (GTK_TABLE (table), 0, 0,
+                              "_Denoise Master", 300, 0,
+                              denoise_params.filter,
+                              0, 20, .1, 1, 1,
+                              TRUE, 0, 0,
+                              NULL, NULL);
+  g_signal_connect (adj, "value-changed",
+                    G_CALLBACK (gimp_float_adjustment_update),
+                    &denoise_params.filter);
+  g_signal_connect_swapped (adj, "value-changed",
+                            G_CALLBACK (gimp_preview_invalidate),
+                            preview);
+
+
+  /* Threshold shape */
+  button = gtk_check_button_new_with_mnemonic ("So_ft thresholding");
+  gtk_box_pack_start (GTK_BOX (main_vbox), button, FALSE, FALSE, 0);
+  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button),
+                                denoise_params.soft);
+  gtk_widget_show (button);
+  g_signal_connect (button, "toggled",
+                    G_CALLBACK (dialog_soft_callback),
+                    NULL);
+
+  /* multiscale adjust select */
+  button = gtk_check_button_new_with_mnemonic ("Multiscale _adjustment");
+  gtk_box_pack_start (GTK_BOX (main_vbox), button, FALSE, FALSE, 0);
+  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button),
+                                denoise_params.multiscale);
+  gtk_widget_show (button);
+  g_signal_connect (button, "toggled",
+                    G_CALLBACK (dialog_multiscale_callback),
+                    NULL);
+
+  /* Subadjustments for autoclean */
+  table = gtk_table_new (4, 4, FALSE);
+  gtk_table_set_col_spacings (GTK_TABLE (table), 6);
+  gtk_table_set_col_spacing (GTK_TABLE (table), 0, 20);
+  gtk_box_pack_start (GTK_BOX (main_vbox), table, FALSE, FALSE, 0);
+  gtk_widget_show (table);
+
+  /* fine detail adjust */
+  madj[0] = adj = gimp_scale_entry_new (GTK_TABLE (table), 1, 0,
+				       "_Very fine denoise:", 300, 0,
+				       denoise_params.f1,
+				       0, 20, .1, 1, 1,
+				       TRUE, 0, 0,
+				       NULL, NULL);
+  g_signal_connect (adj, "value-changed",
+                    G_CALLBACK (gimp_float_adjustment_update),
+                    &denoise_params.f1);
+  g_signal_connect_swapped (adj, "value-changed",
+                            G_CALLBACK (gimp_preview_invalidate),
+                            preview);
+
+  /* detail adjust */
+  madj[1] = adj = gimp_scale_entry_new (GTK_TABLE (table), 1, 1,
+				       "_Fine denoise:", 300, 0,
+				       denoise_params.f2,
+				       0, 20, .1, 1, 1,
+				       TRUE, 0, 0,
+				       NULL, NULL);
+  g_signal_connect (adj, "value-changed",
+                    G_CALLBACK (gimp_float_adjustment_update),
+                    &denoise_params.f2);
+  g_signal_connect_swapped (adj, "value-changed",
+                            G_CALLBACK (gimp_preview_invalidate),
+                            preview);
+
+  /* mid adjust */
+  madj[2] = adj = gimp_scale_entry_new (GTK_TABLE (table), 1, 2,
+				       "_Mid denoise:", 300, 0,
+				       denoise_params.f3,
+				       0, 20, .1, 1, 1,
+				       TRUE, 0, 0,
+				       NULL, NULL);
+  g_signal_connect (adj, "value-changed",
+                    G_CALLBACK (gimp_float_adjustment_update),
+                    &denoise_params.f3);
+  g_signal_connect_swapped (adj, "value-changed",
+                            G_CALLBACK (gimp_preview_invalidate),
+                            preview);
+
+  /* Coarse adjust */
+  madj[3] = adj = gimp_scale_entry_new (GTK_TABLE (table), 1, 3,
+                              "_Coarse denoise:", 300, 0,
+                              denoise_params.f4,
+                              0, 20, .1, 1, 1,
+                              TRUE, 0, 0,
+                              NULL, NULL);
+  g_signal_connect (adj, "value-changed",
+                    G_CALLBACK (gimp_float_adjustment_update),
+                    &denoise_params.f4);
+  g_signal_connect_swapped (adj, "value-changed",
+                            G_CALLBACK (gimp_preview_invalidate),
+                            preview);
+
+  gimp_scale_entry_set_sensitive(madj[0],denoise_params.multiscale);
+  gimp_scale_entry_set_sensitive(madj[1],denoise_params.multiscale);
+  gimp_scale_entry_set_sensitive(madj[2],denoise_params.multiscale);
+  gimp_scale_entry_set_sensitive(madj[3],denoise_params.multiscale);
+
+  gtk_widget_show (dialog);
+
+  run = (gimp_dialog_run (GIMP_DIALOG (dialog)) == GTK_RESPONSE_OK);
+
+  gtk_widget_destroy (dialog);
+
+  return run;
+}
+
+static void preview_update (GimpPreview *preview)
+{
+  GimpDrawable *drawable;
+  GimpPixelRgn  rgn;            /* previw region */
+  gint          x1, y1;
+  gint          w,h;
+  guchar       *buffer;
+  gint          bpp;        /* Bytes-per-pixel in image */
+
+
+  gimp_preview_get_position (preview, &x1, &y1);
+  gimp_preview_get_size (preview, &w, &h);
+  drawable = gimp_drawable_preview_get_drawable (GIMP_DRAWABLE_PREVIEW (preview));
+  bpp = gimp_drawable_bpp (drawable->drawable_id);
+  buffer = g_new (guchar, w * h * bpp);
+  gimp_pixel_rgn_init (&rgn, drawable,
+                       x1, y1, w, h,
+                       FALSE, FALSE);
+  gimp_pixel_rgn_get_rect (&rgn, buffer, x1, y1, w, h);
+  denoise_work(w,h,bpp,buffer,0);
+  gimp_preview_draw_buffer (preview, buffer, w*bpp);
+  gimp_drawable_flush (drawable);
+
+  g_free (buffer);
+}
+
+static void denoise_work(int width, int height, int planes, guchar *buffer, int pr){
+  int i;
+  double T[16];
+
+  for(i=0;i<16;i++)
+    T[i]=denoise_params.filter;
+  if(denoise_params.multiscale){
+    T[0]+=denoise_params.f1;
+    T[1]+=denoise_params.f2;
+    T[2]+=denoise_params.f3;
+    for(i=3;i<16;i++)
+      T[i]+=denoise_params.f4;
+  }
+
+  wavelet_filter(width, height, planes, buffer, pr, T, denoise_params.soft);
+
+}
+

Added: trunk/gimp-montypak/scanclean
===================================================================
(Binary files differ)


Property changes on: trunk/gimp-montypak/scanclean
___________________________________________________________________
Name: svn:executable
   + 
Name: svn:mime-type
   + application/octet-stream

Added: trunk/gimp-montypak/scanclean.c
===================================================================
--- trunk/gimp-montypak/scanclean.c	                        (rev 0)
+++ trunk/gimp-montypak/scanclean.c	2008-12-13 05:06:15 UTC (rev 15574)
@@ -0,0 +1,1170 @@
+/*
+ *   Text scan cleanup helper for GIMP - The GNU Image Manipulation Program
+ *
+ *   Copyright 2008 Monty
+ *
+ *   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 <string.h>
+#include <stdlib.h>
+
+#include <libgimp/gimp.h>
+#include <libgimp/gimpui.h>
+
+/*
+ * Constants...
+ */
+
+#define PLUG_IN_PROC    "plug-in-scanclean"
+#define PLUG_IN_BINARY  "scanclean"
+#define PLUG_IN_VERSION "23 Nov 2008"
+
+/*
+ * Local functions...
+ */
+
+static void     query (void);
+static void     run   (const gchar      *name,
+                       gint              nparams,
+                       const GimpParam  *param,
+                       gint             *nreturn_vals,
+                       GimpParam       **returm_vals);
+
+static void     scanclean_pre    (GimpDrawable *drawable);
+static void     scanclean        (GimpDrawable *drawable);
+static void     scanclean_work   (int w,int h,int bpp,guchar *buffer, int pr);
+
+static gboolean scanclean_dialog (GimpDrawable *drawable);
+
+static void     preview_update (GimpPreview  *preview);
+static void     dialog_autolevel_callback  (GtkWidget     *widget,
+                                            gpointer       data);
+static void     dialog_autoclean_callback  (GtkWidget     *widget,
+					   gpointer       data);
+static void     dialog_autorotate_callback  (GtkWidget     *widget,
+					     gpointer       data);
+static void     dialog_autocrop_callback  (GtkWidget     *widget,
+					   gpointer       data);
+static void     dialog_scale_callback     (GtkWidget     *widget,
+					   gpointer       data);
+
+/*
+ * Globals...
+ */
+
+const GimpPlugInInfo PLUG_IN_INFO =
+{
+  NULL,  /* init_proc  */
+  NULL,  /* quit_proc  */
+  query, /* query_proc */
+  run    /* run_proc   */
+};
+
+typedef struct
+{
+  gint black_percent;
+  gint white_percent;
+
+  gint autolevel;
+
+  gint autoclean;
+  gint mass;
+  gint distance;
+
+  gint autorotate;
+
+  gint autocrop;
+  gint binding;
+
+  gint scalen;
+  gint scaled;
+  gint sharpen;
+} ScancleanParams;
+
+static ScancleanParams scanclean_params =
+{
+  0,100,
+  
+  1,
+
+  1,6,4,
+  
+  1,
+
+  1,0,
+
+  1,1,30
+};
+
+static GtkWidget    *preview;
+
+MAIN ()
+
+static void
+query (void)
+{
+  static const 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,    "black_percent", "Black level adjustment" },
+    { GIMP_PDB_INT32,    "white_percent", "White level adjustment" },
+    { GIMP_PDB_INT32,    "autoclean",     "Clean up splotches/smudges"},
+    { GIMP_PDB_INT32,    "mass",          "Autoclean 'mass' threshold adjustment"},
+    { GIMP_PDB_INT32,    "distance",      "Autoclean 'distance' threshold adjustment"},
+    { GIMP_PDB_INT32,    "autorotate",    "Autorotate text to level"},
+    { GIMP_PDB_INT32,    "autocrop",      "Autocrop image down to text"},
+    { GIMP_PDB_INT32,    "binding",       "Where autocrop should look for a binding"},
+    { GIMP_PDB_INT32,    "scalen",        "Image scaling numerator"},
+    { GIMP_PDB_INT32,    "scaled",        "Image scaling denominator"},
+    { GIMP_PDB_INT32,    "sharpen",       "Post-scale sharpening amount"}
+
+  };
+
+  gimp_install_procedure (PLUG_IN_PROC,
+                          "Clean up scanned text",
+			  "This plugin is used to automatically perform "
+			  "most of the drudge tasks in cleaning up pages of scanned text. "
+			  "Works on selection, deselect non-text.",
+                          "Monty <monty at xiph.org>",
+                          "Copyright 2008 by Monty",
+                          PLUG_IN_VERSION,
+                          "S_canclean...",
+                          "RGB*, GRAY*",
+                          GIMP_PLUGIN,
+                          G_N_ELEMENTS (args), 0,
+                          args, NULL);
+
+  gimp_plugin_menu_register (PLUG_IN_PROC, "<Image>/Filters/Montypak");
+}
+
+static void
+run (const gchar      *name,
+     gint              nparams,
+     const GimpParam  *param,
+     gint             *nreturn_vals,
+     GimpParam       **return_vals)
+{
+  GimpRunMode        run_mode;  /* Current run mode */
+  GimpPDBStatusType  status;    /* Return status */
+  GimpParam         *values;    /* Return values */
+  GimpDrawable      *drawable;  /* Current image */
+
+  /*
+   * Initialize parameter data...
+   */
+
+  status   = GIMP_PDB_SUCCESS;
+  run_mode = param[0].data.d_int32;
+  values = g_new (GimpParam, 1);
+
+  *nreturn_vals = 1;
+  *return_vals  = values;
+
+  values[0].type          = GIMP_PDB_STATUS;
+  values[0].data.d_status = status;
+
+  /*
+   * Get drawable information...
+   */
+
+  drawable = gimp_drawable_get (param[2].data.d_drawable);
+  gimp_tile_cache_ntiles (2 * drawable->ntile_cols);
+
+  scanclean_pre(drawable);
+
+  /*
+   * See how we will run
+   */
+
+  switch (run_mode)
+    {
+    case GIMP_RUN_INTERACTIVE:
+      /*
+       * Possibly retrieve data...
+       */
+      gimp_get_data (PLUG_IN_PROC, &scanclean_params);
+
+      /*
+       * Get information from the dialog...
+       */
+      if (!scanclean_dialog (drawable))
+        return;
+      break;
+
+    case GIMP_RUN_NONINTERACTIVE:
+      /*
+       * Make sure all the arguments are present...
+       */
+      if (nparams != 15)
+        status = GIMP_PDB_CALLING_ERROR;
+      else
+        scanclean_params.black_percent = param[3].data.d_int32;
+        scanclean_params.white_percent = param[4].data.d_int32;
+        scanclean_params.autolevel = param[5].data.d_int32;
+        scanclean_params.autoclean = param[6].data.d_int32;
+        scanclean_params.mass = param[7].data.d_int32;
+        scanclean_params.distance = param[8].data.d_int32;
+        scanclean_params.autorotate = param[9].data.d_int32;
+        scanclean_params.autocrop = param[10].data.d_int32;
+        scanclean_params.binding = param[11].data.d_int32;
+        scanclean_params.scalen = param[12].data.d_int32;
+        scanclean_params.scaled = param[13].data.d_int32;
+        scanclean_params.sharpen = param[14].data.d_int32;
+      break;
+
+    case GIMP_RUN_WITH_LAST_VALS:
+      /*
+       * Possibly retrieve data...
+       */
+      gimp_get_data (PLUG_IN_PROC, &scanclean_params);
+      break;
+
+    default:
+      status = GIMP_PDB_CALLING_ERROR;
+      break;
+    }
+
+  if (status == GIMP_PDB_SUCCESS)
+    {
+      if ((gimp_drawable_is_rgb (drawable->drawable_id) ||
+           gimp_drawable_is_gray (drawable->drawable_id)))
+        {
+          /*
+           * Run!
+           */
+          scanclean (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_PROC,
+                           &scanclean_params, sizeof (ScancleanParams));
+        }
+      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);
+}
+
+static void scanclean (GimpDrawable *drawable){
+
+  GimpPixelRgn  src_rgn;        /* Source image region */
+  GimpPixelRgn  dst_rgn;        /* Destination image region */
+  gint          x1,x2;
+  gint          y1,y2;
+  gint          w;
+  gint          h;
+  gint          bpp;
+  guchar       *buffer;
+
+  gimp_drawable_mask_bounds (drawable->drawable_id,
+                             &x1, &y1, &x2, &y2);
+
+  w = x2 - x1;
+  h = y2 - y1;
+  bpp    = gimp_drawable_bpp (drawable->drawable_id);
+
+  /*
+   * Let the user know what we're doing...
+   */
+  gimp_progress_init( "Cleaning scanned text...");
+
+  /*
+   * Setup for filter...
+   */
+
+  gimp_pixel_rgn_init (&src_rgn, drawable,
+                       x1, y1, w, h, FALSE, FALSE);
+  gimp_pixel_rgn_init (&dst_rgn, drawable,
+                       x1, y1, w, h, TRUE, TRUE);
+
+  /***************************************/
+  buffer = g_new (guchar, w * h * bpp);
+  gimp_pixel_rgn_get_rect (&src_rgn, buffer, x1, y1, w, h);
+  scanclean_work(w,h,bpp,buffer,1);
+  gimp_pixel_rgn_set_rect (&dst_rgn, buffer, x1, y1, w, h);
+  /**************************************/
+
+  /*
+   * Update the screen...
+   */
+
+  gimp_drawable_flush (drawable);
+  gimp_drawable_merge_shadow (drawable->drawable_id, TRUE);
+  gimp_drawable_update (drawable->drawable_id, x1, y1, w, h);
+                      
+  g_free(buffer);
+}
+
+/*
+ * 'scanclean_dialog()' - Popup a dialog window for the filter box size...
+ */
+
+static gboolean
+scanclean_dialog (GimpDrawable *drawable)
+{
+  GtkWidget *dialog;
+  GtkWidget *main_vbox;
+  GtkWidget *table;
+  GtkWidget *button;
+  GtkObject *adj;
+  gboolean   run;
+
+  gimp_ui_init (PLUG_IN_BINARY, TRUE);
+
+  dialog = gimp_dialog_new ("Scanclean", PLUG_IN_BINARY,
+                            NULL, 0,
+                            gimp_standard_help_func, PLUG_IN_PROC,
+
+                            GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+                            GTK_STOCK_OK,     GTK_RESPONSE_OK,
+
+                            NULL);
+
+  gtk_dialog_set_alternative_button_order (GTK_DIALOG (dialog),
+                                           GTK_RESPONSE_OK,
+                                           GTK_RESPONSE_CANCEL,
+                                           -1);
+
+  gimp_window_set_transient (GTK_WINDOW (dialog));
+
+  main_vbox = gtk_vbox_new (FALSE, 12);
+  gtk_container_set_border_width (GTK_CONTAINER (main_vbox), 12);
+  gtk_container_add (GTK_CONTAINER (GTK_DIALOG (dialog)->vbox), main_vbox);
+  gtk_widget_show (main_vbox);
+
+  preview = gimp_drawable_preview_new (drawable, NULL);
+  gtk_box_pack_start (GTK_BOX (main_vbox), preview, TRUE, TRUE, 0);
+  gtk_widget_show (preview);
+
+  g_signal_connect (preview, "invalidated",
+                    G_CALLBACK (preview_update),
+                    NULL);
+
+  /* Black level adjust */
+  table = gtk_table_new (1, 3, FALSE);
+  gtk_table_set_col_spacings (GTK_TABLE (table), 6);
+  gtk_box_pack_start (GTK_BOX (main_vbox), table, FALSE, FALSE, 0);
+  gtk_widget_show (table);
+
+  adj = gimp_scale_entry_new (GTK_TABLE (table), 0, 0,
+                              "_Black level adjust:", 400, 0,
+                              scanclean_params.black_percent,
+                              0, 100, 1, 10, 0,
+                              TRUE, 0, 0,
+                              NULL, NULL);
+  g_signal_connect (adj, "value-changed",
+                    G_CALLBACK (gimp_int_adjustment_update),
+                    &scanclean_params.black_percent);
+  g_signal_connect_swapped (adj, "value-changed",
+                            G_CALLBACK (gimp_preview_invalidate),
+                            preview);
+
+  /* White level adjust */
+  table = gtk_table_new (1, 3, FALSE);
+  gtk_table_set_col_spacings (GTK_TABLE (table), 6);
+  gtk_box_pack_start (GTK_BOX (main_vbox), table, FALSE, FALSE, 0);
+  gtk_widget_show (table);
+
+  adj = gimp_scale_entry_new (GTK_TABLE (table), 0, 0,
+                              "_White level adjust:", 400, 0,
+                              scanclean_params.white_percent,
+                              0, 100, 1, 10, 0,
+                              TRUE, 0, 0,
+                              NULL, NULL);
+  g_signal_connect (adj, "value-changed",
+                    G_CALLBACK (gimp_int_adjustment_update),
+                    &scanclean_params.white_percent);
+  g_signal_connect_swapped (adj, "value-changed",
+                            G_CALLBACK (gimp_preview_invalidate),
+                            preview);
+
+  /* autolevel select */
+  button = gtk_check_button_new_with_mnemonic ("Auto_level");
+  gtk_box_pack_start (GTK_BOX (main_vbox), button, FALSE, FALSE, 0);
+  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button),
+                                scanclean_params.autolevel);
+  gtk_widget_show (button);
+  g_signal_connect (button, "toggled",
+                    G_CALLBACK (dialog_autolevel_callback),
+                    NULL);
+
+  /* autoclean select */
+  button = gtk_check_button_new_with_mnemonic ("Auto_clean");
+  gtk_box_pack_start (GTK_BOX (main_vbox), button, FALSE, FALSE, 0);
+  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button),
+                                scanclean_params.autoclean);
+  gtk_widget_show (button);
+  g_signal_connect (button, "toggled",
+                    G_CALLBACK (dialog_autoclean_callback),
+                    NULL);
+
+  /* Subadjustments for autoclean */
+  table = gtk_table_new (2, 4, FALSE);
+  gtk_table_set_col_spacings (GTK_TABLE (table), 6);
+  gtk_table_set_col_spacing (GTK_TABLE (table), 0, 20);
+  gtk_box_pack_start (GTK_BOX (main_vbox), table, FALSE, FALSE, 0);
+  gtk_widget_show (table);
+
+
+  /* autoclean mass adjust */
+  adj = gimp_scale_entry_new (GTK_TABLE (table), 1, 0,
+                              "_Mass threshold:", 400, 0,
+                              scanclean_params.mass,
+                              1, 100, 1, 10, 0,
+                              TRUE, 0, 0,
+                              NULL, NULL);
+  g_signal_connect (adj, "value-changed",
+                    G_CALLBACK (gimp_int_adjustment_update),
+                    &scanclean_params.mass);
+  g_signal_connect_swapped (adj, "value-changed",
+                            G_CALLBACK (gimp_preview_invalidate),
+                            preview);
+
+  /* autoclean distance adjust */
+  adj = gimp_scale_entry_new (GTK_TABLE (table), 1, 1,
+                              "_Distance threshold:", 400, 0,
+                              scanclean_params.distance,
+                              1, 20, 1, 10, 0,
+                              TRUE, 0, 0,
+                              NULL, NULL);
+  g_signal_connect (adj, "value-changed",
+                    G_CALLBACK (gimp_int_adjustment_update),
+                    &scanclean_params.distance);
+  g_signal_connect_swapped (adj, "value-changed",
+                            G_CALLBACK (gimp_preview_invalidate),
+                            preview);
+
+  /* autorotate select */
+  button = gtk_check_button_new_with_mnemonic ("Auto_rotate");
+  gtk_box_pack_start (GTK_BOX (main_vbox), button, FALSE, FALSE, 0);
+  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button),
+                                scanclean_params.autorotate);
+  gtk_widget_show (button);
+  g_signal_connect (button, "toggled",
+                    G_CALLBACK (dialog_autorotate_callback),
+                    NULL);
+
+  /* scale select */
+  button = gtk_check_button_new_with_mnemonic ("_Scale 2:1");
+  gtk_box_pack_start (GTK_BOX (main_vbox), button, FALSE, FALSE, 0);
+  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button),
+                                scanclean_params.scaled!=1);
+  gtk_widget_show (button);
+  g_signal_connect (button, "toggled",
+                    G_CALLBACK (dialog_scale_callback),
+                    NULL);
+
+  /* autocrop select */
+  button = gtk_check_button_new_with_mnemonic ("Auto_crop");
+  gtk_box_pack_start (GTK_BOX (main_vbox), button, FALSE, FALSE, 0);
+  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button),
+                                scanclean_params.autocrop);
+  gtk_widget_show (button);
+  g_signal_connect (button, "toggled",
+                    G_CALLBACK (dialog_autocrop_callback),
+                    NULL);
+
+  gtk_widget_show (dialog);
+
+  run = (gimp_dialog_run (GIMP_DIALOG (dialog)) == GTK_RESPONSE_OK);
+
+  gtk_widget_destroy (dialog);
+
+  return run;
+}
+
+static void dialog_autolevel_callback (GtkWidget *widget,
+                          gpointer   data)
+{
+  scanclean_params.autolevel = (GTK_TOGGLE_BUTTON (widget)->active);
+  gimp_preview_invalidate (GIMP_PREVIEW (preview));
+}
+
+
+static void dialog_autoclean_callback (GtkWidget *widget,
+                          gpointer   data)
+{
+  scanclean_params.autoclean = (GTK_TOGGLE_BUTTON (widget)->active);
+  gimp_preview_invalidate (GIMP_PREVIEW (preview));
+}
+
+static void dialog_autorotate_callback (GtkWidget *widget,
+                          gpointer   data)
+{
+  scanclean_params.autorotate = (GTK_TOGGLE_BUTTON (widget)->active);
+  gimp_preview_invalidate (GIMP_PREVIEW (preview));
+}
+
+static void dialog_autocrop_callback (GtkWidget *widget,
+                          gpointer   data)
+{
+  scanclean_params.autocrop = (GTK_TOGGLE_BUTTON (widget)->active);
+}
+
+
+static void dialog_scale_callback (GtkWidget *widget,
+                          gpointer   data)
+{
+  if(GTK_TOGGLE_BUTTON (widget)->active){
+    scanclean_params.scalen=1;
+    scanclean_params.scaled=2;
+  }else{
+    scanclean_params.scalen=1;
+    scanclean_params.scaled=1;
+  }
+}
+
+static void preview_update (GimpPreview *preview)
+{
+  GimpDrawable *drawable;
+  GimpPixelRgn  rgn;            /* previw region */
+  gint          x1, y1;
+  gint          w,h;
+  guchar       *buffer;
+  gint          bpp;        /* Bytes-per-pixel in image */
+
+
+  gimp_preview_get_position (preview, &x1, &y1);
+  gimp_preview_get_size (preview, &w, &h);
+  drawable = gimp_drawable_preview_get_drawable (GIMP_DRAWABLE_PREVIEW (preview));
+  bpp = gimp_drawable_bpp (drawable->drawable_id);
+  buffer = g_new (guchar, w * h * bpp);
+  gimp_pixel_rgn_init (&rgn, drawable,
+                       x1, y1, w, h,
+                       FALSE, FALSE);
+  gimp_pixel_rgn_get_rect (&rgn, buffer, x1, y1, w, h);
+  scanclean_work(w,h,bpp,buffer,0);
+  gimp_preview_draw_buffer (preview, buffer, w*bpp);
+  gimp_drawable_flush (drawable);
+
+  g_free (buffer);
+}
+
+static void scanclean_pre (GimpDrawable *drawable){
+  GimpPixelRgn  src_rgn;        /* Source image region */
+  gint          x1,x2;
+  gint          y1,y2;
+  gint          w;
+  gint          h;
+  gint          bpp;
+  guchar       *buffer;
+
+  gimp_drawable_mask_bounds (drawable->drawable_id,
+                             &x1, &y1, &x2, &y2);
+
+  w = x2 - x1;
+  h = y2 - y1;
+  bpp    = gimp_drawable_bpp (drawable->drawable_id);
+
+  /*
+   * Let the user know what we're doing...
+   */
+  gimp_progress_init( "");
+
+  /*
+   * Setup for filter...
+   */
+
+  //gimp_pixel_rgn_init (&src_rgn, drawable,
+  //                     x1, y1, w, h, FALSE, FALSE);
+//buffer = g_new (guchar, w * h * bpp);
+//gimp_pixel_rgn_get_rect (&src_rgn, buffer, x1, y1, w, h);
+
+  /* precomputation work */
+
+
+
+
+  /* done */
+                      
+  // g_free(buffer);
+}
+
+#include <stdio.h>
+#define sq(x) ((int)(x)*(int)(x))
+#define clamp(x) ((x)<0?0:((x)>255?255:(x)))
+
+static int collect_add_row(guchar *b, int *s, int *ss, int w, int h, int row, int x1, int x2){
+  int i,j;
+  if(row<0||row>=h)return 0;
+  if(x1<0)x1=0;
+  if(x2>w)x2=w;
+  i=row*w+x1;
+  j=row*w+x2;
+  while(i<j){
+    *s+=b[i];
+    *ss+=b[i]*b[i];
+    i++;
+  }
+  return(x2-x1);
+}
+
+static int collect_sub_row(guchar *b, int *s, int *ss, int w, int h, int row, int x1, int x2){
+  int i,j;
+  if(row<0||row>=h)return 0;
+  if(x1<0)x1=0;
+  if(x2>w)x2=w;
+  i=row*w+x1;
+  j=row*w+x2;
+  while(i<j){
+    *s-=b[i];
+    *ss-=b[i]*b[i];
+    i++;
+  }
+  return(x1-x2);
+}
+
+static int collect_add_col(guchar *b, int *s, int *ss, int w, int h, int col, int y1, int y2){
+  int i,j;
+  if(col<0||col>=w)return 0;
+  if(y1<0)y1=0;
+  if(y2>h)y2=h;
+  i=y1*w+col;
+  j=y2*w+col;
+  while(i<j){
+    *s+=b[i];
+    *ss+=b[i]*b[i];
+    i+=w;
+  }
+  return(y2-y1);
+}
+
+static int collect_sub_col(guchar *b, int *s, int *ss, int w, int h, int col, int y1, int y2){
+  int i,j;
+  if(col<0||col>=w)return 0;
+  if(y1<0)y1=0;
+  if(y2>h)y2=h;
+  i=y1*w+col;
+  j=y2*w+col;
+  while(i<j){
+    *s-=b[i];
+    *ss-=b[i]*b[i];
+    i+=w;
+  }
+  return(y1-y2);
+}
+
+#include "blur.c"
+
+/* sliding window mean/variance collection */
+#if 0
+static inline void collect_var(guchar *b, double *v, guchar *m, int w, int h, int n){
+  int i;
+  memcpy(m,b,sizeof(*b)*w*h);
+  blur(m,w,h,n);
+
+  for(i=0;i<w*h;i++)
+    v[i] = (double)b[i]*b[i];
+  blurd(v,w,h,n);
+
+  for(i=0;i<w*h;i++)
+    v[i] -= (double)m[i]*m[i]; 
+
+}
+#else
+
+static inline void collect_var(guchar *b, double *v, guchar *m, int w, int h, int n){
+  int x,y;
+  int sum=0;
+  int ssum=0;
+  int acc=0;
+  double d = 1;
+
+  /* prime */
+  for(y=0;y<=n;y++)
+    acc+=collect_add_row(b,&sum,&ssum,w,h,y,0,n+1);
+  d=1./acc;
+
+  for(x=0;x<w;){
+    /* even x == increasing y */
+
+    m[x] = sum*d;
+    v[x] = ssum*d - m[x]*m[x];
+
+    for(y=1;y<h;y++){
+      int nn=collect_add_row(b,&sum,&ssum,w,h,y+n,x-n,x+n+1) +
+	collect_sub_row(b,&sum,&ssum,w,h,y-n-1,x-n,x+n+1);
+      if(nn){
+	acc += nn;
+	d = 1./acc;
+      }
+      
+      m[y*w+x] = sum*d;
+      v[y*w+x] = ssum*d - m[y*w+x]*m[y*w+x];
+
+    }
+
+    x++;
+    if(x>=w)break;
+
+    acc+=collect_add_col(b,&sum,&ssum,w,h,x+n,h-n-1,h);
+    acc+=collect_sub_col(b,&sum,&ssum,w,h,x-n-1,h-n-1,h);
+    d=1./acc;
+
+    /* odd x == decreasing y */
+    m[(h-1)*w+x] = sum*d;
+    v[(h-1)*w+x] = ssum*d - m[(h-1)*w+x]*m[(h-1)*w+x];
+
+    for(y=h-2;y>=0;y--){
+      int nn=collect_add_row(b,&sum,&ssum,w,h,y-n,x-n,x+n+1) +
+	collect_sub_row(b,&sum,&ssum,w,h,y+n+1,x-n,x+n+1);
+      if(nn){
+	acc += nn;
+	d = 1./acc;
+      }
+      
+      m[y*w+x] = sum*d;
+      v[y*w+x] = ssum*d - m[y*w+x]*m[y*w+x];
+    }
+    x++;
+    acc+=collect_add_col(b,&sum,&ssum,w,h,x+n,0,n+1);
+    acc+=collect_sub_col(b,&sum,&ssum,w,h,x-n-1,0,n+1);
+    d=1./acc;
+
+  }
+
+}
+#endif
+static inline void metrics(guchar *b, float *v, float *m, int w, int n){
+  int i,j;
+  int sum=0,ssum=0;
+  float d = 1./sq(n*2+1);
+  b-=w*n;
+
+  for(i=-n;i<=n;i++){
+    for(j=-n;j<=n;j++){
+      sum += *(b+j);
+      ssum += *(b+j) * *(b+j);
+    }
+    b+=w;
+  }
+
+  *m = sum * d;
+  *v = ssum*d - *m**m;
+}
+
+static inline void bound_metrics(guchar *b, float *v, float *m, int w, int h, int x, int y, int nn){
+  int i,j;
+  int sum=0,ssum=0,n=0;
+
+  for(i=y-nn;i<=y+nn;i++){
+    if(i>=0 && i<h){ 
+      for(j=x-nn;j<=x+nn;j++){
+	if(j>=0 && j<w){ 
+	  sum += b[i*w+j];
+	  ssum += (int)b[i*w+j]*b[i*w+j];
+	  n++;
+	}
+      }
+    }
+  }
+
+  m[y*w+x] = (float)sum/n;
+  v[y*w+x] = (float)ssum/n - m[y*w+x]*m[y*w+x];
+}
+
+static int cmpint(const void *p1, const void *p2){
+  int a = *(int *)p1;
+  int b = *(int *)p2;
+  return a-b;
+}
+
+static void background_heal(guchar *bp,guchar* fp,int w,int h,int rn){
+  int x,y;
+  for(y=0;y<h;y++)
+    for(x=0;x<w;x++)
+      if(fp[y*w+x]){
+	int i;
+	int j;
+	int acc=0,nn=0;
+	int b=0,bn=0;
+	int n;
+	for(n=1;(n<rn || nn<20) && n<50;n++){
+	  j = x-n;
+	  if(j>=0)
+	    for(i=y-n+1;i<y+n-1;i++)
+	      if(i>=0 && i<h){
+		if(!fp[i*w+j]){
+		  acc+=bp[i*w+j];
+		  nn++;
+		}
+	      }
+	  
+	  j = x+n;
+	  if(j<w)
+	    for(i=y-n+1;i<y+n-1;i++)
+	      if(i>=0 && i<h){
+		if(!fp[i*w+j]){
+		  acc+=bp[i*w+j];
+		  nn++;
+		}
+	      }
+	  
+	  i = y-n;
+	  if(i>=0)
+	    for(j=x-n;j<x+n;j++)
+	      if(j>=0 && j<w){
+		if(!fp[i*w+j]){
+		  acc+=bp[i*w+j];
+		  nn++;
+		}
+	      }
+	  
+	  i = y+n;
+	  if(i<h)
+	    for(j=x-n;j<x+n;j++)
+	      if(j>=0 && j<w){
+		if(!fp[i*w+j]){
+		  acc+=bp[i*w+j];
+		  nn++;  
+		}
+	      }
+	}
+	if(nn)
+	  bp[y*w+x]= ROUND((float)acc/nn);
+	else
+	  bp[y*w+x]= 255;
+      }
+}
+
+static int flood_recurse(guchar *f, int w,int h,int x,int y,int d){
+  if(d<10000){
+    int loop=0;
+    if(y>=0 && y<h && x>=0 && x < w){
+      if(f[y*w+x]==1){
+	int x1=x, x2=x+1;
+	f[y*w+x]=2;
+	while(x1>0 && f[y*w+x1-1]==1){
+	  x1--;
+	  f[y*w+x1]=2;
+	}
+	while(x2<w && f[y*w+x2]==1){
+	  f[y*w+x2]=2;
+	  x2++;
+	}
+	for(x=x1;x<x2;x++){
+	  int y1=y,y2=y+1,yy;
+
+	  while(y1>0 && f[(y1-1)*w+x]==1){
+	    y1--;
+	    f[y1*w+x]=2;
+	  }
+	  while(y2<h && f[y2*w+x]==1){
+	    f[y2*w+x]=2;
+	    y2++;
+	  }
+
+	  for(yy=y1;yy<y2;yy++){
+	    loop |= flood_recurse(f,w,h,x-1,yy,d+1);
+	    loop |= flood_recurse(f,w,h,x+1,yy,d+1);
+	  }
+
+	}
+      }
+    }
+    return loop;
+  }else
+    return 1;
+}
+
+static void flood_foreground(guchar *f, int w, int h){
+  int x,y,loop=1;
+  while(loop){
+    loop=0;
+    for(y=0;y<h;y++)
+      for(x=0;x<w;x++)
+	if(f[y*w+x]==3){
+	  loop |= flood_recurse(f,w,h,x,y-1,0);
+	  loop |= flood_recurse(f,w,h,x-1,y,0);
+	  loop |= flood_recurse(f,w,h,x,y+1,0);
+	  loop |= flood_recurse(f,w,h,x+1,y,0);
+	}
+  }
+  
+  for(y=0;y<h;y++)
+    for(x=0;x<w;x++)
+      if(f[y*w+x]<2)
+	f[y*w+x]=0;
+}
+
+static void median_filter(guchar *d, guchar *o,int w, int h){
+  int x,y;
+
+  for(y=1;y<h-1;y++){
+    for(x=1;x<w-1;x++){
+      int a[8];
+      int i = y*w+x,j,k;
+      int v = 255; 
+      a[0]=d[i-1];
+      a[1]=d[i+w-1];
+      a[2]=d[i+w];
+      a[3]=d[i+w+1];
+      a[4]=d[i+1];
+      a[5]=d[i-w+1];
+      a[6]=d[i-w];
+      a[7]=d[i-w-1];
+
+      for(j=0;j<7;j++){
+	int ld = 255;
+	for(k=0;k<3;k++){
+	  int ii = (j+k+3<8?j+k+3:j+k-5);
+	  if(a[ii]<ld)ld=a[ii];
+	}
+	if(ld<a[j])ld=a[j];
+	ld=clamp(255-((255-ld)*2));
+	if(ld<v)v=ld;
+      }
+
+      if(d[i]>=v){
+	o[i]=d[i];
+	continue;
+      }
+
+      {
+	int d0=255,d1=255;
+	for(j=0;j<7;j++)
+	  if(a[j]<d0){
+	    d1=d0;
+	    d0=a[j];
+	  }else 
+	    if(a[j]<d1)d1=a[j];
+	
+	if(d[i]<d1)
+	  o[i]=d1;
+	else
+	  o[i]=d[i];
+      }
+    }
+  }
+}
+
+static void grow_foreground(guchar *foreground, int w, int h){
+  // horizontal
+  int y,x;
+  for(y=0;y<h;y++){  
+    guchar *p = foreground+w*y;
+    
+    if(p[1]>1 && !p[0])p[0]=1;
+    for(x=0;x<w-2;x++){
+      if(p[x]>1){
+	if(!p[x+1])p[x+1]=1;
+	if(!p[x+2])p[x+2]=1;
+      }
+      if(p[x+2]>1){
+	if(!p[x+1])p[x+1]=1;
+	if(!p[x])p[x]=1;
+      }
+    }
+    if(p[x]>1 && !p[x+1])p[x+1]=1;
+  }
+  
+  // vertical 
+  {
+    guchar *p = foreground;
+    guchar *q = foreground+w;
+    for(x=0;x<w;x++)
+      if(q[x]>1 && !p[x])p[x]=1;
+  }
+  for(y=0;y<h-2;y++){  
+    guchar *p = foreground+w*y;
+    guchar *q = foreground+w*(y+1);
+    guchar *r = foreground+w*(y+2);
+    for(x=0;x<w;x++){
+      if(p[x]>1){
+	if(!q[x])q[x]=1;
+	if(!r[x])r[x]=1;
+      }
+      if(r[x]>1){
+	if(!p[x])p[x]=1;
+	if(!q[x])q[x]=1;
+      }
+    }
+  }
+  {
+    guchar *p = foreground+w*y;
+    guchar *q = foreground+w*(y+1);
+    for(x=0;x<w;x++)
+      if(p[x]>1 && !q[x])q[x]=1;
+  }
+}
+
+static void scanclean_work(int w, int h, int planes, guchar *buffer, int pr){
+  int i,x,y;
+  guchar *foreground = g_new(guchar,w*h);
+  guchar *filter = g_new(guchar,w*h);
+  guchar *background = g_new(guchar,w*h);
+  double *variances = g_new(double,w*h);
+  guchar *means = g_new(guchar,w*h);
+  double *variances2 = g_new(double,w*h);
+  guchar *means2 = g_new(guchar,w*h);
+  ScancleanParams p = scanclean_params;
+
+  for(i=0;i<planes;i++){
+    double d=0.;
+    memcpy(filter,buffer,sizeof(*buffer)*w*h);
+
+    /* var/mean for Niblack eqs. */
+    collect_var(buffer,variances,means,w,h,20);
+
+    /* Sauvola thresholding for background construction */
+    for(i=0;i<w*h;i++){
+      foreground[i]=0;
+      if(filter[i] < means[i]*(1+.1*(sqrt(variances[i])/128-1.)))foreground[i]=2;
+    }
+
+    /* apply spreading function to means */
+
+    /* grow the outer foreground by two */
+    grow_foreground(foreground,w,h);
+
+    /* build background  */
+    memcpy(background,filter,sizeof(*background)*w*h);
+    background_heal(background,foreground,w,h,5);
+
+    /* re-threshold */
+    /* Subtly different from the Sauvola method above; although the
+       equation looks the same, our threshold is being based on the
+       constructed background, *not* the means. */
+
+    for(i=0;i<w*h;i++){
+      foreground[i]=0;
+      if(filter[i] < background[i]*(1+.5*(sqrt(variances[i])/128-1.)))
+	foreground[i]=3;
+    }
+
+
+    for(i=0;i<w*h;i++){
+      if(!foreground[i])
+	if(filter[i] < background[i]*(1+.15*(sqrt(variances[i])/128-1.)))
+	  foreground[i]=1;
+      
+    }
+
+    
+
+
+    /* flood fill 'sensitive' areas from 'insensitive' areas */
+    flood_foreground(foreground, w, h);
+
+    /* grow the outer foreground by two */
+    //grow_foreground(foreground,w,h);
+
+    {
+      /* compute global distance */
+
+      int dn=0;
+      for(i=0;i<w*h;i++)
+	if(foreground[i]==3){
+	  d+=means[i]-filter[i];
+	  dn++;
+	}
+      
+      d/=dn;
+    }
+
+    if(p.autoclean)
+    {
+      /* The immediate vicinity of a character needs a slight unsharp
+	 mask effect or deep stroke indents will be swallowed by black
+	 in heavier glyphs, like a Times lowercase 'g'.  This
+	 wouldn't be a problem on an ideal scanner, but scanners
+	 aren't ideal and some are worse than others. */
+      guchar * blurbuf = g_new(guchar,w*h);
+      guchar * blurbuf2 = g_new(guchar,w*h);
+      memcpy (blurbuf,filter,sizeof(*blurbuf)*w*h);
+      memcpy (blurbuf2,filter,sizeof(*blurbuf2)*w*h);
+
+      blur(blurbuf,w,h,.5);
+      for(i=0;i<w*h;i++)
+	if(foreground[i]){
+	  int del = (blurbuf2[i]-blurbuf[i]);
+	  means[i]=clamp(means[i]-del);
+	}
+
+      g_free(blurbuf);
+      g_free(blurbuf2);
+    }
+
+
+    
+
+    
+
+    {      
+      //for(i=0;i<w*h;i++){
+      //double white = background[i]-(1.-p.white_percent*.01)*d;
+      //double black = white - d + (p.black_percent*.01)*d;
+      //double dd = white-black;
+      //int val = (buffer[i]-black)/dd*255.;
+      //buffer[i] = clamp(val);
+      //}
+
+      for(i=0;i<w*h;i++){
+	if(foreground[i]){
+	  double white = means[i]-d*(1.-(p.white_percent*.01+.5));
+	  double black = means[i]-d + (p.black_percent*.01)*(means[i]-d);
+	  double dd = white-black;
+	  int val = (filter[i]-black)/dd*255.;
+	  filter[i] = clamp(val);
+	}else
+	  filter[i] = 255.;
+      }
+
+
+    }
+
+
+
+    if(p.autorotate)
+      for(i=0;i<w*h;i++)
+	buffer[i]=sqrt(variances[i]);
+    else
+      memcpy(buffer,filter,sizeof(guchar)*w*h);
+
+
+  }
+
+  if(p.autolevel)
+    memcpy(buffer,means,sizeof(guchar)*w*h);
+  
+  g_free(foreground);
+  g_free(background);
+  g_free(filter);
+  g_free(variances);
+  g_free(means);
+  g_free(variances2);
+  g_free(means2);
+}

Added: trunk/gimp-montypak/wavelet.c
===================================================================
--- trunk/gimp-montypak/wavelet.c	                        (rev 0)
+++ trunk/gimp-montypak/wavelet.c	2008-12-13 05:06:15 UTC (rev 15574)
@@ -0,0 +1,883 @@
+/*
+ *   Wavelet Denoise filter for GIMP - The GNU Image Manipulation Program
+ *
+ *   Copyright (C) 2008 Monty
+ *   Code based on research by Crystal Wagner and Prof. Ivan Selesnik, 
+ *   Polytechnic University, Brooklyn, NY
+ *   See: http://taco.poly.edu/selesi/DoubleSoftware/
+ *
+ *   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 <string.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include <libgimp/gimp.h>
+#include <libgimp/gimpui.h>
+
+/* convolution code below assumes FSZ is even */
+#define FSZ 14 
+
+static double analysis_fs[2][3][FSZ]={
+  {
+    {
+      +0.00000000000000,
+      +0.00069616789827,
+      -0.02692519074183,
+      -0.04145457368920,
+      +0.19056483888763,
+      +0.58422553883167,
+      +0.58422553883167,
+      +0.19056483888763,
+      -0.04145457368920,
+      -0.02692519074183,
+      +0.00069616789827,
+      +0.00000000000000,
+      +0.00000000000000,
+      +0.00000000000000,
+    },
+    {
+      +0.00000000000000,
+      -0.00014203017443,
+      +0.00549320005590,
+      +0.01098019299363,
+      -0.13644909765612,
+      -0.21696226276259,
+      +0.33707999754362,
+      +0.33707999754362,
+      -0.21696226276259,
+      -0.13644909765612,
+      +0.01098019299363,
+      +0.00549320005590,
+      -0.00014203017443,
+      +0.00000000000000,
+    },
+    {
+      +0.00000000000000,
+      +0.00014203017443,
+      -0.00549320005590,
+      -0.00927404236573,
+      +0.07046152309968,
+      +0.13542356651691,
+      -0.64578354990472,
+      +0.64578354990472,
+      -0.13542356651691,
+      -0.07046152309968,
+      +0.00927404236573,
+      +0.00549320005590,
+      -0.00014203017443,
+      +0.00000000000000,
+    }
+  },
+  {
+    {
+      +0.00000000000000, 
+      +0.00000000000000, 
+      +0.00069616789827,
+      -0.02692519074183,
+      -0.04145457368920,
+      +0.19056483888763,
+      +0.58422553883167,
+      +0.58422553883167,
+      +0.19056483888763,
+      -0.04145457368920,
+      -0.02692519074183,
+      +0.00069616789827,
+      +0.00000000000000,
+      +0.00000000000000,
+    },
+    {
+      +0.00000000000000,
+      +0.00000000000000,
+      -0.00014203017443,
+      +0.00549320005590,
+      +0.01098019299363,
+      -0.13644909765612,
+      -0.21696226276259,
+      +0.33707999754362,
+      +0.33707999754362,
+      -0.21696226276259,
+      -0.13644909765612,
+      +0.01098019299363,
+      +0.00549320005590,
+      -0.00014203017443,
+    },
+    {
+      +0.00000000000000,
+      +0.00000000000000,
+      +0.00014203017443,
+      -0.00549320005590,
+      -0.00927404236573,
+      +0.07046152309968,
+      +0.13542356651691,
+      -0.64578354990472,
+      +0.64578354990472,
+      -0.13542356651691,
+      -0.07046152309968,
+      +0.00927404236573,
+      +0.00549320005590,
+      -0.00014203017443,
+    }
+  }
+};
+
+static double analysis[2][3][FSZ]={
+  {
+    {
+      +0.00000000000000, 
+      +0.00000000000000, 
+      +0.00017870679071, 
+      -0.02488304194507, 
+      +0.00737700819766, 
+      +0.29533805776119, 
+      +0.59529279993637, 
+      +0.45630440337458, 
+      +0.11239376309619, 
+      -0.01971220693439, 
+      -0.00813549683439, 
+      +0.00005956893024, 
+      +0.00000000000000, 
+      +0.00000000000000, 
+    },
+    {
+      +0.00000000000000,
+      +0.00000000000000,
+      -0.00012344587034,
+      +0.01718853971559,
+      -0.00675291099550,
+      +0.02671809818132,
+      -0.64763513288874,
+      +0.47089724990858,
+      +0.16040017815754,
+      -0.01484700537727,
+      -0.00588868840296,
+      +0.00004311757177,
+      +0.00000000000000,
+      +0.00000000000000,
+    },
+    {
+      +0.00000000000000,
+      +0.00000000000000,
+      +0.00001437252392,
+      -0.00200122286479,
+      +0.00027261232228,
+      +0.06840460220387,
+      +0.01936710587994,
+      -0.68031992557818,
+      +0.42976785708978,
+      +0.11428688385011,
+      +0.05057805218407,
+      -0.00037033761102,
+      +0.00000000000000,
+      +0.00000000000000,
+    }
+  },
+  {
+    {
+      +0.00000000000000,
+      +0.00000000000000,
+      +0.00005956893024,
+      -0.00813549683439,
+      -0.01971220693439,
+      +0.11239376309619,
+      +0.45630440337458,
+      +0.59529279993637,
+      +0.29533805776119,
+      +0.00737700819766,
+      -0.02488304194507,
+      +0.00017870679071,
+      +0.00000000000000,
+      +0.00000000000000,
+    },
+
+    {
+      +0.00000000000000,
+      +0.00000000000000,
+      -0.00037033761102,
+      +0.05057805218407,
+      +0.11428688385011,
+      +0.42976785708978,
+      -0.68031992557818,
+      +0.01936710587994,
+      +0.06840460220387,
+      +0.00027261232228,
+      -0.00200122286479,
+      +0.00001437252392,
+      +0.00000000000000,
+      +0.00000000000000,
+    },
+    {
+      +0.00000000000000,
+      +0.00000000000000,
+      +0.00004311757177,  
+      -0.00588868840296,  
+      -0.01484700537727,  
+      +0.16040017815754,  
+      +0.47089724990858,  
+      -0.64763513288874,  
+      +0.02671809818132,  
+      -0.00675291099550,  
+      +0.01718853971559,  
+      -0.00012344587034,  
+      +0.00000000000000,
+      +0.00000000000000,
+    }
+  }
+};
+
+static double synthesis_fs[2][3][FSZ]={
+  {
+    {
+      +0.00000000000000,
+      +0.00000000000000,
+      +0.00000000000000,
+      +0.00069616789827,
+      -0.02692519074183,
+      -0.04145457368920,
+      +0.19056483888763,
+      +0.58422553883167,
+      +0.58422553883167,
+      +0.19056483888763,
+      -0.04145457368920,
+      -0.02692519074183,
+      +0.00069616789827,
+      +0.00000000000000,
+    },
+    {
+      +0.00000000000000,
+      -0.00014203017443,
+      +0.00549320005590,
+      +0.01098019299363,
+      -0.13644909765612,
+      -0.21696226276259,
+      +0.33707999754362,
+      +0.33707999754362,
+      -0.21696226276259,
+      -0.13644909765612,
+      +0.01098019299363,
+      +0.00549320005590,
+      -0.00014203017443,
+      +0.00000000000000,
+    },
+    {
+      +0.00000000000000,
+      -0.00014203017443,
+      +0.00549320005590,
+      +0.00927404236573,
+      -0.07046152309968,
+      -0.13542356651691,
+      +0.64578354990472,
+      -0.64578354990472,
+      +0.13542356651691,
+      +0.07046152309968,
+      -0.00927404236573,
+      -0.00549320005590,
+      +0.00014203017443,
+      +0.00000000000000,
+    }
+  },
+  {
+    {
+      +0.00000000000000, 
+      +0.00000000000000, 
+      +0.00069616789827, 
+      -0.02692519074183, 
+      -0.04145457368920, 
+      +0.19056483888763, 
+      +0.58422553883167, 
+      +0.58422553883167, 
+      +0.19056483888763, 
+      -0.04145457368920, 
+      -0.02692519074183, 
+      +0.00069616789827, 
+      +0.00000000000000, 
+      +0.00000000000000, 
+    },
+    {
+      -0.00014203017443, 
+      +0.00549320005590, 
+      +0.01098019299363, 
+      -0.13644909765612, 
+      -0.21696226276259,
+      +0.33707999754362,
+      +0.33707999754362,
+      -0.21696226276259,
+      -0.13644909765612,
+      +0.01098019299363,
+      +0.00549320005590,
+      -0.00014203017443,
+      +0.00000000000000,
+      +0.00000000000000,
+    },
+    {
+      -0.00014203017443,
+      +0.00549320005590,
+      +0.00927404236573,
+      -0.07046152309968,
+      -0.13542356651691,
+      +0.64578354990472,
+      -0.64578354990472,
+      +0.13542356651691,
+      +0.07046152309968,
+      -0.00927404236573,
+      -0.00549320005590,
+      +0.00014203017443,
+      +0.00000000000000,
+      +0.00000000000000,
+    }
+  }
+};
+
+static double synthesis[2][3][FSZ]={
+  {
+    {
+      +0.00000000000000, 
+      +0.00000000000000, 
+      +0.00005956893024, 
+      -0.00813549683439, 
+      -0.01971220693439, 
+      +0.11239376309619, 
+      +0.45630440337458, 
+      +0.59529279993637, 
+      +0.29533805776119, 
+      +0.00737700819766, 
+      -0.02488304194507, 
+      +0.00017870679071, 
+      +0.00000000000000, 
+      +0.00000000000000, 
+    },
+    {
+      +0.00000000000000,
+      +0.00000000000000,
+      +0.00004311757177,
+      -0.00588868840296,
+      -0.01484700537727,
+      +0.16040017815754,
+      +0.47089724990858,
+      -0.64763513288874,
+      +0.02671809818132,
+      -0.00675291099550,
+      +0.01718853971559,
+      -0.00012344587034,
+      +0.00000000000000,
+      +0.00000000000000,
+    },
+    {
+      +0.00000000000000,
+      +0.00000000000000,
+      -0.00037033761102,
+      +0.05057805218407,
+      +0.11428688385011,
+      +0.42976785708978,
+      -0.68031992557818,
+      +0.01936710587994,
+      +0.06840460220387,
+      +0.00027261232228,
+      -0.00200122286479,
+      +0.00001437252392,
+      +0.00000000000000,
+      +0.00000000000000,
+    }
+  },
+  {
+    {
+      +0.00000000000000,
+      +0.00000000000000,
+      +0.00017870679071,
+      -0.02488304194507,
+      +0.00737700819766,
+      +0.29533805776119,
+      +0.59529279993637,
+      +0.45630440337458,
+      +0.11239376309619,
+      -0.01971220693439,
+      -0.00813549683439,
+      +0.00005956893024,
+      +0.00000000000000,
+      +0.00000000000000,
+    },
+    {
+      +0.00000000000000,
+      +0.00000000000000,
+      +0.00001437252392,
+      -0.00200122286479,
+      +0.00027261232228,
+      +0.06840460220387,
+      +0.01936710587994,
+      -0.68031992557818,
+      +0.42976785708978,
+      +0.11428688385011,
+      +0.05057805218407,
+      -0.00037033761102,
+      +0.00000000000000,
+      +0.00000000000000,
+    },
+    {
+      +0.00000000000000,
+      +0.00000000000000,
+      -0.00012344587034,  
+      +0.01718853971559,  
+      -0.00675291099550,  
+      +0.02671809818132,  
+      -0.64763513288874,  
+      +0.47089724990858,  
+      +0.16040017815754,  
+      -0.01484700537727,  
+      -0.00588868840296,  
+      +0.00004311757177,  
+      +0.00000000000000,
+      +0.00000000000000,
+    }
+  }
+};
+
+typedef struct {
+  double *x;
+  int rows;
+  int cols;
+} m2D;
+
+typedef struct {
+  m2D w[8][2][2];
+} wrank;
+
+static m2D alloc_m2D(int m, int n){
+  m2D ret;
+  ret.rows = m;
+  ret.cols = n;
+  ret.x = calloc(m*n,sizeof(*(ret.x)));
+  return ret;
+}
+
+static void free_m2D(m2D c){
+  if(c.x) free(c.x);
+}
+
+static void complex_threshold(m2D set[4], double T, int soft, int pt, int *pc){
+  int i;
+  int N = set[0].rows*set[0].cols;
+
+  if(T>.01){
+    double TT = T*T;
+
+    if(soft){
+      for(i=0;i<N;i++){
+	double v00 = (set[0].x[i] + set[3].x[i]) * .7071067812;
+	double v11 = (set[0].x[i] - set[3].x[i]) * .7071067812;
+	double v01 = (set[1].x[i] + set[2].x[i]) * .7071067812;
+	double v10 = (set[1].x[i] - set[2].x[i]) * .7071067812;
+	
+	if(v00*v00 + v10*v10 < TT){
+	  v00 = 0.;
+	  v10 = 0.;
+	}else{
+	  double y = sqrt(v00*v00 + v10*v10);
+	  double scale = y/(y+T);
+	  v00 *= scale;
+	  v10 *= scale;
+	}
+	if(v01*v01 + v11*v11 < TT){
+	  v01 = 0.;
+	  v11 = 0.;
+	}else{
+	  double y = sqrt(v01*v01 + v11*v11);
+	  double scale = y/(y+T);
+	  v01 *= scale;
+	  v11 *= scale;
+	}
+
+	set[0].x[i] = (v00 + v11) * .7071067812;
+	set[3].x[i] = (v00 - v11) * .7071067812;
+	set[1].x[i] = (v01 + v10) * .7071067812;
+	set[2].x[i] = (v01 - v10) * .7071067812;
+
+      }
+    }else{
+      for(i=0;i<N;i++){
+	double v00 = (set[0].x[i] + set[3].x[i]) * .7071067812;
+	double v11 = (set[0].x[i] - set[3].x[i]) * .7071067812;
+	double v01 = (set[1].x[i] + set[2].x[i]) * .7071067812;
+	double v10 = (set[1].x[i] - set[2].x[i]) * .7071067812;
+	
+	if(v00*v00 + v10*v10 < TT){
+	  v00 = 0.;
+	  v10 = 0.;
+	}
+	if(v01*v01 + v11*v11 < TT){
+	  v01 = 0.;
+	  v11 = 0.;
+	}
+
+	set[0].x[i] = (v00 + v11) * .7071067812;
+	set[3].x[i] = (v00 - v11) * .7071067812;
+	set[1].x[i] = (v01 + v10) * .7071067812;
+	set[2].x[i] = (v01 - v10) * .7071067812;
+
+      } 
+    }
+  }
+  if(pt)gimp_progress_update((gdouble)((*pc)++)/pt);
+}
+
+/* assume the input is padded, return output that's padded for next stage */
+/* FSZ*2-2 padding, +1 additional padding if vector odd */
+
+static m2D convolve_padded(m2D x, double *f, int pt, int *pc){
+  int i,M = x.rows;
+  int j,in_N = x.cols;
+  int k;
+  int comp_N = (in_N - FSZ + 3) / 2;
+  int out_N = (comp_N+1)/2*2+FSZ*2-2;
+
+  m2D y=alloc_m2D(M,out_N);
+
+  for(i=0;i<M;i++){
+    double *xrow = x.x+i*in_N;
+    double *yrow = y.x+i*out_N+FSZ-1;
+    for(j=0;j<comp_N;j++){
+      double a = 0;
+      for(k=0;k<FSZ;k++)
+	a+=xrow[k]*f[FSZ-k-1];
+      xrow+=2;
+      yrow[j]=a;
+    }
+
+    /* extend output padding */
+    for(j=0;j<FSZ-1;j++){
+      yrow--;
+      yrow[0]=yrow[1];
+    }
+    for(j=FSZ-1+comp_N;j<out_N;j++)
+      yrow[j] = yrow[j-1];
+  }
+  if(pt)gimp_progress_update((gdouble)((*pc)++)/pt);
+  return y;
+}
+
+static m2D convolve_transpose_padded(m2D x, double *f, int pt, int *pc){
+  int i,M = x.rows;
+  int j,in_N = x.cols;
+  int k;
+  int comp_N = (in_N - FSZ + 3) / 2;
+  int out_N = (comp_N+1)/2*2+FSZ*2-2;
+
+  m2D y=alloc_m2D(out_N,M);
+
+  for(i=0;i<M;i++){
+    double *xrow = x.x+i*in_N;
+    double *ycol = y.x + i + M*(FSZ-1);
+    for(j=0;j<comp_N;j++){
+      double a = 0;
+      for(k=0;k<FSZ;k++)
+	a+=xrow[k]*f[FSZ-k-1];
+      xrow+=2;
+      ycol[j*M]=a;
+    }
+
+    /* extend output padding */
+    for(j=0;j<FSZ-1;j++){
+      ycol -= M;
+      ycol[0] = ycol[M];
+    }
+    for(j=FSZ-1+comp_N;j<out_N;j++)
+      ycol[j*M] = ycol[(j-1)*M];
+  }
+  if(pt)gimp_progress_update((gdouble)((*pc)++)/pt);
+  return y;
+}
+
+/* discards the padding added by the matching convolution */
+
+/* y will often be smaller than a full x expansion due to preceeding
+   rounds of padding out to even values; this padding is also
+   discarded */
+
+static m2D deconvolve_padded(m2D y, m2D x, double *f, int pt, int *pc){
+  int i;
+  int j,in_N = x.cols;
+  int k;
+  int out_N = y.cols;
+  int out_M = y.rows;
+
+  for(i=0;i<out_M;i++){
+    double *xrow = x.x+i*in_N+FSZ/2;
+    double *yrow = y.x+i*out_N;
+
+    for(j=0;j<out_N;j+=2){
+      double a = 0;
+      for(k=1;k<FSZ;k+=2)
+	a+=xrow[k>>1]*f[FSZ-k-1];
+      yrow[j]+=a;
+      a=0.;
+      for(k=1;k<FSZ;k+=2)
+	a+=xrow[k>>1]*f[FSZ-k];
+      yrow[j+1]+=a;
+      xrow++;
+    }
+  }
+  if(pt)gimp_progress_update((gdouble)((*pc)++)/pt);
+  free_m2D(x);
+  return y;
+}
+
+/* discards the padding added by the matching convolution */
+
+/* y will often be smaller than a full x expansion due to preceeding
+   rounds of padding out to even values; this padding is also
+   discarded */
+
+static m2D deconvolve_transpose_padded(m2D y, m2D x, double *f, int pt, int *pc){
+  int i;
+  int j,in_N = x.cols;
+  int k;
+  int out_M = y.rows;
+  int out_N = y.cols;
+
+  for(i=0;i<out_N;i++){
+    double *xrow = x.x+i*in_N+FSZ/2;
+    double *ycol = y.x+i;
+    for(j=0;j<out_M;j+=2){
+      double a = 0;
+      for(k=1;k<FSZ;k+=2)
+	a+=xrow[k>>1]*f[FSZ-k-1];
+      ycol[j*out_N]+=a;
+      a=0.;
+      for(k=1;k<FSZ;k+=2)
+	a+=xrow[k>>1]*f[FSZ-k];
+      ycol[(j+1)*out_N]+=a;
+      xrow++;
+    }
+  }
+
+  free_m2D(x);
+  if(pt)gimp_progress_update((gdouble)((*pc)++)/pt);
+  return y;
+}
+
+static void forward_threshold(m2D lo[4], m2D y[4], 
+			      double af[2][3][FSZ], double sf[2][3][FSZ], 
+			      double T, int soft,
+			      int free_input, int pt, int *pc){
+  m2D x[4];
+  m2D temp[4];
+  m2D tempw[4];
+  m2D temp2[4];
+  int r,c,i;
+
+  for(i=0;i<4;i++){
+    x[i] = lo[i];
+    temp[i]  = convolve_transpose_padded(x[i], af[i>>1][2], pt, pc);  
+    tempw[i] = convolve_padded(temp[i], af[i&1][2], pt, pc); /* w 7 */
+  }
+
+  r = tempw[0].rows;
+  c = tempw[0].cols;
+  complex_threshold(tempw, T, soft, pt, pc);
+
+  for(i=0;i<4;i++){
+    temp2[i]=alloc_m2D(c*2 - FSZ*3 + 2, r);
+    y[i] = alloc_m2D(temp2[i].rows, temp2[i].cols*2 - FSZ*3 + 2);
+    deconvolve_transpose_padded(temp2[i],tempw[i],sf[i&1][2], pt, pc);
+    tempw[i] = convolve_padded(temp[i], af[i&1][1], pt, pc); /* w6 */
+  }
+  complex_threshold(tempw, T, soft, pt, pc);
+
+  for(i=0;i<4;i++){
+    deconvolve_transpose_padded(temp2[i],tempw[i],sf[i&1][1], pt, pc);
+    tempw[i] = convolve_padded(temp[i], af[i&1][0], pt, pc); /* w5 */
+    free_m2D(temp[i]);
+  }
+  complex_threshold(tempw, T, soft, pt, pc);
+
+  for(i=0;i<4;i++){
+    deconvolve_transpose_padded(temp2[i],tempw[i],sf[i&1][0], pt, pc);
+    deconvolve_padded(y[i],temp2[i],sf[i>>1][2], pt, pc);
+    temp[i]   = convolve_transpose_padded(x[i], af[i>>1][1], pt, pc);
+    temp2[i]  = alloc_m2D(c*2 - FSZ*3 + 2, r);
+    tempw[i]  = convolve_padded(temp[i], af[i&1][2], pt, pc); /* w4 */
+  }
+  complex_threshold(tempw, T, soft, pt, pc);
+
+  for(i=0;i<4;i++){
+    deconvolve_transpose_padded(temp2[i],tempw[i],sf[i&1][2], pt, pc);
+    tempw[i] = convolve_padded(temp[i], af[i&1][1], pt, pc); /* w3 */
+  }
+  complex_threshold(tempw, T, soft, pt, pc);
+
+  for(i=0;i<4;i++){
+    deconvolve_transpose_padded(temp2[i],tempw[i],sf[i&1][1], pt, pc);
+    tempw[i] = convolve_padded(temp[i], af[i&1][0], pt, pc); /* w2 */
+    free_m2D(temp[i]);
+  }
+  complex_threshold(tempw, T, soft, pt, pc);
+
+  for(i=0;i<4;i++){
+    deconvolve_transpose_padded(temp2[i],tempw[i],sf[i&1][0], pt, pc);
+    deconvolve_padded(y[i],temp2[i],sf[i>>1][1], pt, pc);
+    temp[i]  = convolve_transpose_padded(x[i], af[i>>1][0], pt, pc);
+    if(free_input) free_m2D(x[i]);
+    temp2[i] = alloc_m2D(c*2 - FSZ*3 + 2, r);
+    tempw[i] = convolve_padded(temp[i], af[i&1][2], pt, pc); /* w1 */
+  }
+  complex_threshold(tempw, T, soft, pt, pc);
+
+  for(i=0;i<4;i++){
+    deconvolve_transpose_padded(temp2[i],tempw[i],sf[i&1][2], pt, pc);
+    tempw[i] = convolve_padded(temp[i], af[i&1][1], pt, pc); /* w0 */
+  }
+  complex_threshold(tempw, T, soft, pt, pc);
+
+  for(i=0;i<4;i++){
+    deconvolve_transpose_padded(temp2[i],tempw[i],sf[i&1][1], pt, pc);
+    deconvolve_padded(y[i],temp2[i],sf[i>>1][0], pt, pc);
+    lo[i] = convolve_padded(temp[i], af[i&1][0], pt, pc); /* lo */
+    free_m2D(temp[i]);
+  }
+
+}
+
+static void finish_backward(m2D lo[4], m2D y[4], double sf[2][3][FSZ], int pt, int *pc){
+  int r=lo[0].rows,c=lo[0].cols,i;
+  m2D temp;
+  
+  for(i=0;i<4;i++){
+    temp=alloc_m2D(c*2 - FSZ*3 + 2, r);
+    deconvolve_transpose_padded(temp,lo[i],sf[i&1][0], pt, pc);
+    deconvolve_padded(y[i],temp,sf[i>>1][0], pt, pc);
+    lo[i]=y[i];
+  }
+}
+
+static m2D transform_threshold(m2D x, int J, double T[16], int soft, int pt, int *pc){
+  int i,j;
+  m2D partial_y[J][4];
+  m2D lo[4];
+
+  lo[0] = lo[1] = lo[2] = lo[3] = x;
+  forward_threshold(lo, partial_y[0], analysis_fs, synthesis_fs, T[0], soft, 0, pt, pc);
+
+  for(j=1;j<J;j++)
+    forward_threshold(lo, partial_y[j], analysis, synthesis, T[j], soft, 1, pt, pc);
+
+  for(j=J-1;j;j--)
+    finish_backward(lo, partial_y[j], synthesis, pt, pc);
+  finish_backward(lo, partial_y[0], synthesis_fs, pt, pc);
+
+  {
+    int end = lo[0].rows*lo[0].cols;
+    double *y = lo[0].x;
+    double *p1 = lo[1].x;
+    double *p2 = lo[2].x;
+    double *p3 = lo[3].x;
+    
+    for(j=0;j<end;j++)
+      y[j]+=p1[j]+p2[j]+p3[j];
+    
+    free_m2D(lo[1]);
+    free_m2D(lo[2]);
+    free_m2D(lo[3]);
+  }
+  
+  return lo[0];
+  
+}
+
+static void wavelet_filter(int width, int height, int planes, guchar *buffer, int pr, double T[16],int soft){
+  int J=4;
+  int i,j,p;
+  m2D xc,yc;
+  int pc=0;
+  int pt;
+
+  /* we want J to be as 'deep' as the image to eliminate
+     splotchiness with deep coarse settings */
+  
+  while(1){
+    int mult = 1 << (J+1);
+    if(width/mult < 1) break;
+    if(height/mult < 1) break;
+    J++;
+  }
+
+  if(J>15)J=15;
+  pt=(pr?J*108*planes:0);
+  
+  /* Input matrix must be pre-padded for first stage convolutions;
+     odd->even padding goes on the bottom/right */
+
+  xc = alloc_m2D((height+1)/2*2+FSZ*2-2,
+		 (width+1)/2*2+FSZ*2-2),yc;
+  
+  /* loop through planes */
+  for(p=0;p<planes;p++){
+    guchar *ptr = buffer+p; 
+    
+    /* populate and pad input matrix */
+    for(i=0;i<height;i++){
+      double *row=xc.x + (i+FSZ-1)*xc.cols;
+
+      /* X padding */
+      for(j=0;j<FSZ-1;j++)
+	row[j] = *ptr * .5;
+
+      /* X filling */
+      for(;j<width+FSZ-1;j++){
+	row[j] = *ptr * .5;
+	ptr+=planes;
+      }
+    
+      /* X padding */
+      for(;j<xc.cols;j++)
+	row[j] = row[j-1];
+    }
+
+    /* Y padding */
+    for(i=FSZ-2;i>=0;i--){
+      double *pre=xc.x + (i+1)*xc.cols;
+      double *row=xc.x + i*xc.cols;
+      for(j=0;j<xc.cols;j++)
+	row[j]=pre[j];
+    }
+    for(i=xc.rows-FSZ+1;i<xc.rows;i++){
+      double *pre=xc.x + (i-1)*xc.cols;
+      double *row=xc.x + i*xc.cols;
+      for(j=0;j<xc.cols;j++)
+	row[j]=pre[j];
+    }
+
+    yc=transform_threshold(xc,J,T,soft,pt,&pc);
+    
+    /* pull filtered values back out of padded matrix */
+    ptr = buffer+p; 
+    for(i=0;i<height;i++){
+      double *row = yc.x + (i+FSZ-1)*yc.cols + FSZ-1;
+      for(j=0;j<width;j++){
+	int v = row[j]*.5; 
+	if(v>255)v=255;if(v<0)v=0;
+	*ptr = v;
+	ptr+=planes;
+      }
+    }
+    
+    free_m2D(yc);
+  }
+
+  free_m2D(xc);
+
+}
+



More information about the commits mailing list