gimp/plug-ins/common/sel_gauss.c

457 lines
11 KiB
C

/* Selective gaussian blur filter for the GIMP, version 0.1
* Adapted from the original gaussian blur filter by Spencer Kimball and
* Peter Mattis.
*
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
* Copyright (C) 1999 Thom van Os <thom@vanos.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*
*
* Changelog:
*
* v0.1 990202, TVO
* First release
*
* To do:
* - support for horizontal or vertical only blur
* - use memory more efficiently, smaller regions at a time
* - integrating with other convolution matrix based filters ?
* - create more selective and adaptive filters
* - threading
* - optimization
*
*/
#include "config.h"
#include <stdio.h>
#include <stdlib.h>
#include <gtk/gtk.h>
#include <libgimp/gimp.h>
#include <libgimp/gimpui.h>
#include "libgimp/stdplugins-intl.h"
typedef struct
{
gdouble radius;
gint maxdelta;
} BlurValues;
typedef struct
{
gint run;
} BlurInterface;
/* Declare local functions.
*/
static void query (void);
static void run (gchar *name,
gint nparams,
GParam *param,
gint *nreturn_vals,
GParam **return_vals);
static void sel_gauss (GDrawable *drawable,
gdouble radius,
gint maxdelta);
static gint sel_gauss_dialog (void);
static void sel_gauss_ok_callback (GtkWidget *widget,
gpointer data);
GPlugInInfo PLUG_IN_INFO =
{
NULL, /* init_proc */
NULL, /* quit_proc */
query, /* query_proc */
run, /* run_proc */
};
static BlurValues bvals =
{
5.0, /* radius */
50 /* maxdelta */
};
static BlurInterface bint =
{
FALSE /* run */
};
MAIN ()
static void
query (void)
{
static GParamDef args[] =
{
{ PARAM_INT32, "run_mode", "Interactive, non-interactive" },
{ PARAM_IMAGE, "image", "Input image (unused)" },
{ PARAM_DRAWABLE, "drawable", "Input drawable" },
{ PARAM_FLOAT, "radius", "Radius of gaussian blur (in pixels > 1.0)" },
{ PARAM_INT32, "maxdelta", "Maximum delta" },
};
static GParamDef *return_vals = NULL;
static gint nargs = sizeof (args) / sizeof (args[0]);
static gint nreturn_vals = 0;
INIT_I18N();
gimp_install_procedure ("plug_in_sel_gauss",
"Applies a selective gaussian blur to the specified drawable.",
"This filter functions similar to the regular gaussian blur filter except that neighbouring pixels that differ more than the given maxdelta parameter will not be blended with. This way with the correct parameters, an image can be smoothed out without losing details. However, this filter can be rather slow.",
"Thom van Os",
"Thom van Os",
"1999",
N_("<Image>/Filters/Blur/Selective Gaussian Blur..."),
"RGB*, GRAY*",
PROC_PLUG_IN,
nargs, nreturn_vals,
args, return_vals);
}
static void
run (gchar *name,
gint nparams,
GParam *param,
gint *nreturn_vals,
GParam **return_vals)
{
static GParam values[1];
GRunModeType run_mode;
GStatusType status = STATUS_SUCCESS;
GDrawable *drawable;
gdouble radius;
run_mode = param[0].data.d_int32;
*nreturn_vals = 1;
*return_vals = values;
INIT_I18N_UI();
values[0].type = PARAM_STATUS;
values[0].data.d_status = status;
switch (run_mode)
{
case RUN_INTERACTIVE:
/* Possibly retrieve data */
gimp_get_data ("plug_in_sel_gauss", &bvals);
/* First acquire information with a dialog */
if (! sel_gauss_dialog ())
return;
break;
case RUN_NONINTERACTIVE:
/* Make sure all the arguments are there! */
if (nparams != 7)
status = STATUS_CALLING_ERROR;
if (status == STATUS_SUCCESS)
{
bvals.radius = param[3].data.d_float;
bvals.maxdelta = CLAMP (param[4].data.d_int32, 0, 255);
}
if (status == STATUS_SUCCESS && (bvals.radius < 1.0))
status = STATUS_CALLING_ERROR;
break;
case RUN_WITH_LAST_VALS:
/* Possibly retrieve data */
gimp_get_data ("plug_in_sel_gauss", &bvals);
break;
default:
break;
}
if (status != STATUS_SUCCESS)
{
values[0].data.d_status = status;
return;
}
/* Get the specified drawable */
drawable = gimp_drawable_get (param[2].data.d_drawable);
/* Make sure that the drawable is gray or RGB color */
if (gimp_drawable_is_rgb (drawable->id) ||
gimp_drawable_is_gray (drawable->id))
{
gimp_progress_init (_("Selective Gaussian Blur"));
radius = fabs (bvals.radius) + 1.0;
/* run the gaussian blur */
sel_gauss (drawable, radius, bvals.maxdelta);
/* Store data */
if (run_mode == RUN_INTERACTIVE)
gimp_set_data ("plug_in_sel_gauss",
&bvals, sizeof (BlurValues));
if (run_mode != RUN_NONINTERACTIVE)
gimp_displays_flush ();
}
else
{
gimp_message (_("sel_gauss: Cannot operate on indexed color images"));
status = STATUS_EXECUTION_ERROR;
}
gimp_drawable_detach (drawable);
values[0].data.d_status = status;
}
static gint
sel_gauss_dialog (void)
{
GtkWidget *dlg;
GtkWidget *frame;
GtkWidget *table;
GtkWidget *spinbutton;
GtkObject *adj;
gchar **argv;
gint argc;
argc = 1;
argv = g_new (gchar *, 1);
argv[0] = g_strdup ("gauss");
gtk_init (&argc, &argv);
gtk_rc_parse (gimp_gtkrc ());
dlg = gimp_dialog_new (_("Selective Gaussian Blur"), "sel_gauss",
gimp_plugin_help_func, "filters/sel_gauss.html",
GTK_WIN_POS_MOUSE,
FALSE, TRUE, FALSE,
_("OK"), sel_gauss_ok_callback,
NULL, NULL, NULL, TRUE, FALSE,
_("Cancel"), gtk_widget_destroy,
NULL, 1, NULL, FALSE, TRUE,
NULL);
gtk_signal_connect (GTK_OBJECT (dlg), "destroy",
GTK_SIGNAL_FUNC (gtk_main_quit),
NULL);
/* parameter settings */
frame = gtk_frame_new (_("Parameter Settings"));
gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_ETCHED_IN);
gtk_container_border_width (GTK_CONTAINER (frame), 6);
gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dlg)->vbox), frame,
TRUE, TRUE, 0);
table = gtk_table_new (2, 3, FALSE);
gtk_table_set_col_spacings (GTK_TABLE (table), 4);
gtk_table_set_row_spacings (GTK_TABLE (table), 2);
gtk_container_set_border_width (GTK_CONTAINER (table), 4);
gtk_container_add (GTK_CONTAINER (frame), table);
spinbutton = gimp_spin_button_new (&adj,
bvals.radius, 1.0, G_MAXINT, 1.0, 5.0,
0, 1, 2);
gimp_table_attach_aligned (GTK_TABLE (table), 0, 0,
_("Blur Radius:"), 1.0, 0.5,
spinbutton, 1, TRUE);
gtk_signal_connect (GTK_OBJECT (adj), "value_changed",
GTK_SIGNAL_FUNC (gimp_double_adjustment_update),
&bvals.radius);
adj = gimp_scale_entry_new (GTK_TABLE (table), 0, 1,
_("Max. Delta:"), 128, 0,
bvals.maxdelta, 0, 255, 1, 8, 0,
TRUE, 0, 0,
FALSE, FALSE);
gtk_signal_connect (GTK_OBJECT (adj), "value_changed",
GTK_SIGNAL_FUNC (gimp_int_adjustment_update),
&bvals.maxdelta);
gtk_widget_show (table);
gtk_widget_show (frame);
gtk_widget_show (dlg);
gtk_main ();
gdk_flush ();
return bint.run;
}
static void
sel_gauss_ok_callback (GtkWidget *widget,
gpointer data)
{
bint.run = TRUE;
gtk_widget_destroy (GTK_WIDGET (data));
}
static void
init_matrix (gdouble radius,
gdouble **mat,
gint num)
{
int dx, dy;
gdouble sd, c1, c2;
/* This formula isn't really correct, but it'll do */
sd = radius / 3.329042969;
c1 = 1.0 / sqrt(2.0 * G_PI * sd);
c2 = -2.0 * (sd * sd);
for (dy=0; dy<num; dy++)
{
for (dx=dy; dx<num; dx++)
{
mat[dx][dy] = c1 * exp(((dx*dx)+ (dy*dy))/ c2);
mat[dy][dx] = mat[dx][dy];
}
}
}
static void
matrixmult (guchar *src,
guchar *dest,
gint width,
gint height,
gdouble **mat,
gint numrad,
gint bytes,
gint has_alpha,
gint maxdelta)
{
gint i, j, b, nb, x, y;
gint six, dix, tmp;
gdouble sum, fact, d, alpha=1.0;
nb = bytes - (has_alpha?1:0);
for (y = 0; y< height; y++)
{
for (x = 0; x< width; x++)
{
dix = (bytes*((width*y)+x));
if (has_alpha)
dest[dix+nb] = src[dix+nb];
for (b=0; b<nb; b++)
{
sum = 0.0;
fact = 0.0;
for (i= 1-numrad; i<numrad; i++)
{
if (((x+i)< 0) || ((x+i)>= width))
continue;
for (j= 1-numrad; j<numrad; j++)
{
if (((y+j)<0)||((y+j)>=height))
continue;
six = (bytes*((width*(y+j))+x+i));
if (has_alpha)
{
if (!src[six+nb])
continue;
alpha = (double)src[six+nb] / 255.0;
}
tmp = src[dix+b] - src[six+b];
if ((tmp>maxdelta)||
(tmp<-maxdelta))
continue;
d = mat[ABS(i)][ABS(j)];
if (has_alpha)
{
d *= alpha;
}
sum += d * src[six+b];
fact += d;
}
}
if (fact == 0.0)
dest[dix+b] = src[dix+b];
else
dest[dix+b] = sum/fact;
}
}
if (!(y % 5))
gimp_progress_update((double)y / (double)height);
}
}
static void
sel_gauss (GDrawable *drawable,
gdouble radius,
gint maxdelta)
{
GPixelRgn src_rgn, dest_rgn;
gint width, height;
gint bytes;
gint has_alpha;
guchar *dest;
guchar *src;
gint x1, y1, x2, y2;
gint i;
gdouble **mat;
gint numrad;
gimp_drawable_mask_bounds (drawable->id, &x1, &y1, &x2, &y2);
width = (x2 - x1);
height = (y2 - y1);
bytes = drawable->bpp;
has_alpha = gimp_drawable_has_alpha(drawable->id);
if ((width < 1) || (height < 1) || (bytes < 1))
return;
numrad = (gint)(radius + 1.0);
mat = (gdouble **) g_malloc (numrad * sizeof(gdouble *));
for (i=0; i<numrad; i++)
mat[i] = (gdouble *) g_malloc (numrad * sizeof(gdouble));
init_matrix(radius, mat, numrad);
src = (guchar *) g_malloc (width * height * bytes);
dest = (guchar *) g_malloc (width * height * bytes);
gimp_pixel_rgn_init (&src_rgn, drawable, x1, y1, drawable->width,
drawable->height, FALSE, FALSE);
gimp_pixel_rgn_get_rect (&src_rgn, src, x1, y1, width, height);
matrixmult (src, dest, width, height, mat, numrad,
bytes, has_alpha, maxdelta);
gimp_pixel_rgn_init (&dest_rgn, drawable, x1, y1, width, height,
TRUE, TRUE);
gimp_pixel_rgn_set_rect (&dest_rgn, dest, x1, y1, width, height);
/* merge the shadow, update the drawable */
gimp_drawable_flush (drawable);
gimp_drawable_merge_shadow (drawable->id, TRUE);
gimp_drawable_update (drawable->id, x1, y1, width, height);
/* free up buffers */
g_free (src);
g_free (dest);
for (i=0; i<numrad; i++)
g_free (mat[i]);
g_free (mat);
}