[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