mirror of https://github.com/GNOME/gimp.git
1072 lines
25 KiB
C
1072 lines
25 KiB
C
/* Convolution Matrix plug-in for the GIMP -- Version 0.1
|
|
* Copyright (C) 1997 Lauri Alanko <la@iki.fi>
|
|
*
|
|
*
|
|
* 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.
|
|
*
|
|
* The GNU General Public License is also available from
|
|
* http://www.fsf.org/copyleft/gpl.html
|
|
*
|
|
*
|
|
* CHANGELOG:
|
|
* v0.13 15.12.2000
|
|
* Made the PDB interface actually work. (Simon Budig <simon@gimp.org>)
|
|
*
|
|
* v0.12 15.9.1997
|
|
* Got rid of the unportable snprintf. Also made some _tiny_ GUI fixes.
|
|
*
|
|
* v0.11 20.7.1997
|
|
* Negative values in the matrix are now abs'ed when used to weight
|
|
* alpha. Embossing effects should work properly now. Also fixed a
|
|
* totally idiotic bug with embossing.
|
|
*
|
|
* v0.1 2.7.1997
|
|
* Initial release. Works... kinda.
|
|
*
|
|
*
|
|
* TODO:
|
|
*
|
|
* - remove channels selector (that's what the channels dialog is for)
|
|
* - remove idiotic slowdowns
|
|
* - clean up code
|
|
* - preview
|
|
* - optimize properly
|
|
* - save & load matrices
|
|
* - spiffy frontend for designing matrices
|
|
*
|
|
* What else?
|
|
*
|
|
*
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <time.h>
|
|
#include <string.h>
|
|
#include <sys/types.h>
|
|
#ifdef HAVE_UNISTD_H
|
|
#include <unistd.h>
|
|
#endif
|
|
|
|
#include <gtk/gtk.h>
|
|
|
|
#include <libgimp/gimp.h>
|
|
#include <libgimp/gimpui.h>
|
|
|
|
#include "libgimp/stdplugins-intl.h"
|
|
|
|
|
|
#define RESPONSE_RESET 1
|
|
|
|
typedef enum
|
|
{
|
|
EXTEND,
|
|
WRAP,
|
|
CLEAR,
|
|
MIRROR
|
|
} BorderMode;
|
|
|
|
static GimpDrawable *drawable;
|
|
|
|
static gchar * const channel_labels[] =
|
|
{
|
|
N_("Gr_ey"),
|
|
N_("Re_d"),
|
|
N_("_Green"),
|
|
N_("_Blue"),
|
|
N_("_Alpha")
|
|
};
|
|
|
|
static gchar * const bmode_labels[] =
|
|
{
|
|
N_("E_xtend"),
|
|
N_("_Wrap"),
|
|
N_("Cro_p")
|
|
};
|
|
|
|
/* Declare local functions. */
|
|
static void query (void);
|
|
static void run (const gchar *name,
|
|
gint nparams,
|
|
const GimpParam *param,
|
|
gint *nreturn_vals,
|
|
GimpParam **return_vals);
|
|
|
|
static gboolean dialog (void);
|
|
|
|
static void convmatrix (void);
|
|
static void check_config (void);
|
|
|
|
|
|
GimpPlugInInfo PLUG_IN_INFO =
|
|
{
|
|
NULL, /* init_proc */
|
|
NULL, /* quit_proc */
|
|
query, /* query_proc */
|
|
run, /* run_proc */
|
|
};
|
|
|
|
static gint bytes;
|
|
static gboolean run_flag = FALSE;
|
|
|
|
typedef struct
|
|
{
|
|
gfloat matrix[5][5];
|
|
gfloat divisor;
|
|
gfloat offset;
|
|
gint alpha_alg;
|
|
BorderMode bmode;
|
|
gint channels[5];
|
|
gint autoset;
|
|
} config;
|
|
|
|
static const config default_config =
|
|
{
|
|
{
|
|
{ 0.0, 0.0, 0.0, 0.0, 0.0 },
|
|
{ 0.0, 0.0, 0.0, 0.0, 0.0 },
|
|
{ 0.0, 0.0, 1.0, 0.0, 0.0 },
|
|
{ 0.0, 0.0, 0.0, 0.0, 0.0 },
|
|
{ 0.0, 0.0, 0.0, 0.0, 0.0 }
|
|
}, /* matrix */
|
|
1, /* divisor */
|
|
0, /* offset */
|
|
1, /* Alpha-handling algorithm */
|
|
CLEAR, /* border-mode */
|
|
{ 1, 1, 1, 1, 1 }, /* Channels mask */
|
|
0 /* autoset */
|
|
};
|
|
|
|
static config my_config;
|
|
|
|
struct
|
|
{
|
|
GtkWidget *matrix[5][5];
|
|
GtkWidget *divisor;
|
|
GtkWidget *offset;
|
|
GtkWidget *alpha_alg;
|
|
GtkWidget *bmode[3];
|
|
GtkWidget *channels[5];
|
|
GtkWidget *autoset;
|
|
} my_widgets;
|
|
|
|
|
|
MAIN ()
|
|
|
|
static void
|
|
query (void)
|
|
{
|
|
static GimpParamDef args[] =
|
|
{
|
|
{ GIMP_PDB_INT32, "run_mode", "Interactive, non-interactive" },
|
|
{ GIMP_PDB_IMAGE, "image", "Input image (unused)" },
|
|
{ GIMP_PDB_DRAWABLE, "drawable", "Input drawable" },
|
|
{ GIMP_PDB_INT32, "argc_matrix", "The number of elements in the following array. Should be always 25." },
|
|
{ GIMP_PDB_FLOATARRAY, "matrix", "The 5x5 convolution matrix" },
|
|
{ GIMP_PDB_INT32, "alpha_alg", "Enable weighting by alpha channel" },
|
|
{ GIMP_PDB_FLOAT, "divisor", "Divisor" },
|
|
{ GIMP_PDB_FLOAT, "offset", "Offset" },
|
|
|
|
{ GIMP_PDB_INT32, "argc_channels", "The number of elements in following array. Should be always 5." },
|
|
{ GIMP_PDB_INT32ARRAY, "channels", "Mask of the channels to be filtered" },
|
|
{ GIMP_PDB_INT32, "bmode", "Mode for treating image borders" }
|
|
};
|
|
|
|
gimp_install_procedure ("plug_in_convmatrix",
|
|
"A generic 5x5 convolution matrix",
|
|
"",
|
|
"Lauri Alanko",
|
|
"Lauri Alanko",
|
|
"1997",
|
|
N_("_Convolution Matrix..."),
|
|
"RGB*, GRAY*",
|
|
GIMP_PLUGIN,
|
|
G_N_ELEMENTS (args), 0,
|
|
args, NULL);
|
|
|
|
gimp_plugin_menu_register ("plug_in_convmatrix",
|
|
N_("<Image>/Filters/Generic"));
|
|
}
|
|
|
|
static void
|
|
run (const gchar *name,
|
|
gint nparams,
|
|
const GimpParam *param,
|
|
gint *nreturn_vals,
|
|
GimpParam **return_vals)
|
|
{
|
|
static GimpParam values[1];
|
|
GimpRunMode run_mode;
|
|
GimpPDBStatusType status = GIMP_PDB_SUCCESS;
|
|
gint x, y;
|
|
|
|
INIT_I18N ();
|
|
|
|
*nreturn_vals = 1;
|
|
*return_vals = values;
|
|
|
|
run_mode = param[0].data.d_int32;
|
|
|
|
/* Get the specified drawable */
|
|
drawable = gimp_drawable_get (param[2].data.d_drawable);
|
|
|
|
/* The plug-in is not able to handle images smaller than 3 pixels */
|
|
if (drawable->width < 3 || drawable->height < 3)
|
|
{
|
|
g_message (_("Convolution Matrix does not work on layers "
|
|
"smaller than 3 pixels."));
|
|
status = GIMP_PDB_EXECUTION_ERROR;
|
|
values[0].type = GIMP_PDB_STATUS;
|
|
values[0].data.d_status = status;
|
|
return;
|
|
}
|
|
|
|
my_config = default_config;
|
|
if (run_mode == GIMP_RUN_NONINTERACTIVE)
|
|
{
|
|
if (nparams != 11)
|
|
{
|
|
status = GIMP_PDB_CALLING_ERROR;
|
|
}
|
|
else
|
|
{
|
|
if (param[3].data.d_int32 != 25)
|
|
{
|
|
status = GIMP_PDB_CALLING_ERROR;
|
|
}
|
|
else
|
|
{
|
|
for (y = 0; y < 5; y++)
|
|
for (x = 0; x < 5; x++)
|
|
my_config.matrix[x][y]=param[4].data.d_floatarray[y*5+x];
|
|
}
|
|
|
|
my_config.alpha_alg = param[5].data.d_int32;
|
|
my_config.divisor = param[6].data.d_float;
|
|
my_config.offset = param[7].data.d_float;
|
|
|
|
|
|
if (param[8].data.d_int32 != 5)
|
|
{
|
|
status = GIMP_PDB_CALLING_ERROR;
|
|
}
|
|
else
|
|
{
|
|
for (y = 0; y < 5; y++)
|
|
my_config.channels[y] = param[9].data.d_int32array[y];
|
|
}
|
|
|
|
my_config.bmode = param[10].data.d_int32;
|
|
|
|
check_config ();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
gimp_get_data ("plug_in_convmatrix", &my_config);
|
|
|
|
if (run_mode == GIMP_RUN_INTERACTIVE)
|
|
{
|
|
/* Oh boy. We get to do a dialog box, because we can't really
|
|
* expect the user to set us up with the right values using gdb.
|
|
*/
|
|
check_config ();
|
|
|
|
if (! dialog ())
|
|
{
|
|
/* The dialog was closed, or something similarly evil happened. */
|
|
status = GIMP_PDB_EXECUTION_ERROR;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (status == GIMP_PDB_SUCCESS)
|
|
{
|
|
/* Make sure that the drawable is gray or RGB color */
|
|
if (gimp_drawable_is_rgb (drawable->drawable_id) ||
|
|
gimp_drawable_is_gray (drawable->drawable_id))
|
|
{
|
|
gimp_progress_init (_("Applying convolution"));
|
|
gimp_tile_cache_ntiles (2 * (drawable->width /
|
|
gimp_tile_width () + 1));
|
|
|
|
convmatrix ();
|
|
|
|
if (run_mode != GIMP_RUN_NONINTERACTIVE)
|
|
gimp_displays_flush ();
|
|
|
|
if (run_mode == GIMP_RUN_INTERACTIVE)
|
|
gimp_set_data ("plug_in_convmatrix",
|
|
&my_config, sizeof (my_config));
|
|
}
|
|
else
|
|
{
|
|
status = GIMP_PDB_EXECUTION_ERROR;
|
|
}
|
|
|
|
gimp_drawable_detach (drawable);
|
|
}
|
|
|
|
values[0].type = GIMP_PDB_STATUS;
|
|
values[0].data.d_status = status;
|
|
}
|
|
|
|
|
|
/* A generic wrapper to gimp_pixel_rgn_get_row which handles unlimited
|
|
* wrapping or gives you transparent regions outside the image
|
|
*/
|
|
|
|
static void
|
|
my_get_row (GimpPixelRgn *PR,
|
|
guchar *dest,
|
|
gint x,
|
|
gint y,
|
|
gint w)
|
|
{
|
|
gint width, height, bytes;
|
|
gint i;
|
|
|
|
width = PR->drawable->width;
|
|
height = PR->drawable->height;
|
|
bytes = PR->drawable->bpp;
|
|
|
|
/* Y-wrappings */
|
|
|
|
switch (my_config.bmode)
|
|
{
|
|
case WRAP:
|
|
/* Wrapped, so we get the proper row from the other side */
|
|
while (y < 0) /* This is the _sure_ way to wrap. :) */
|
|
y += height;
|
|
while (y >= height)
|
|
y -= height;
|
|
break;
|
|
|
|
case CLEAR:
|
|
/* Beyond borders, so set full transparent. */
|
|
if (y < 0 || y >= height)
|
|
{
|
|
memset (dest, 0, w * bytes);
|
|
return; /* Done, so back. */
|
|
}
|
|
case MIRROR:
|
|
/* The border lines are _not_ duplicated in the mirror image */
|
|
/* is this right? */
|
|
while (y < 0 || y >= height)
|
|
{
|
|
if (y < 0)
|
|
y = -y; /* y=-y-1 */
|
|
if (y >= height)
|
|
y = 2 * height - y - 2; /* y=2*height-y-1 */
|
|
}
|
|
break;
|
|
|
|
case EXTEND:
|
|
y = CLAMP (y , 0 , height - 1);
|
|
break;
|
|
}
|
|
|
|
switch (my_config.bmode)
|
|
{
|
|
case CLEAR:
|
|
if (x < 0)
|
|
{
|
|
i = MIN (w, -x);
|
|
memset (dest, 0, i * bytes);
|
|
dest += i * bytes;
|
|
w -= i;
|
|
x += i;
|
|
}
|
|
if (w)
|
|
{
|
|
i = MIN (w, width);
|
|
gimp_pixel_rgn_get_row (PR, dest, x, y, i);
|
|
dest += i * bytes;
|
|
w -= i;
|
|
x += i;
|
|
}
|
|
if (w)
|
|
memset (dest, 0, w * bytes);
|
|
break;
|
|
|
|
case WRAP:
|
|
while (x < 0)
|
|
x += width;
|
|
i = MIN (w, width - x);
|
|
gimp_pixel_rgn_get_row (PR, dest, x, y, i);
|
|
w -= i;
|
|
dest += i * bytes;
|
|
x = 0;
|
|
while (w)
|
|
{
|
|
i = MIN (w, width);
|
|
gimp_pixel_rgn_get_row (PR, dest, x, y, i);
|
|
w -= i;
|
|
dest += i * bytes;
|
|
}
|
|
break;
|
|
|
|
case EXTEND:
|
|
if (x < 0)
|
|
{
|
|
gimp_pixel_rgn_get_pixel (PR, dest, 0, y);
|
|
x++;
|
|
w--;
|
|
dest += bytes;
|
|
|
|
while (x < 0 && w)
|
|
{
|
|
for (i = 0; i < bytes; i++)
|
|
{
|
|
*dest = *(dest - bytes);
|
|
dest++;
|
|
}
|
|
x++;
|
|
w--;
|
|
}
|
|
}
|
|
if (w && width - x > 0)
|
|
{
|
|
i = MIN (w, width - x);
|
|
gimp_pixel_rgn_get_row (PR, dest, x, y, i);
|
|
w -= i;
|
|
dest += i * bytes;
|
|
}
|
|
while (w)
|
|
{
|
|
for (i = 0; i < bytes; i++)
|
|
{
|
|
*dest= *(dest - bytes);
|
|
dest++;
|
|
}
|
|
x++;
|
|
w--;
|
|
}
|
|
break;
|
|
|
|
case MIRROR: /* Not yet handled */
|
|
break;
|
|
}
|
|
}
|
|
|
|
static gfloat
|
|
calcmatrix (guchar **srcrow,
|
|
gint xoff,
|
|
gint i)
|
|
{
|
|
static gfloat matrixsum = 0;
|
|
static gint bytes = 0;
|
|
|
|
gfloat sum = 0;
|
|
gfloat alphasum = 0;
|
|
gfloat temp;
|
|
gint x, y;
|
|
|
|
if (!bytes)
|
|
{
|
|
bytes = drawable->bpp;
|
|
for (y = 0; y < 5; y++)
|
|
for (x = 0; x < 5; x++)
|
|
{
|
|
temp = my_config.matrix[x][y];
|
|
matrixsum += ABS (temp);
|
|
}
|
|
}
|
|
for (y = 0; y < 5; y++)
|
|
for (x = 0; x < 5; x++)
|
|
{
|
|
temp = my_config.matrix[x][y];
|
|
if (i != (bytes - 1) && my_config.alpha_alg == 1)
|
|
{
|
|
temp *= srcrow[y][xoff + x * bytes +bytes - 1 - i];
|
|
alphasum += ABS (temp);
|
|
}
|
|
temp *= srcrow[y][xoff + x * bytes];
|
|
sum += temp;
|
|
}
|
|
sum /= my_config.divisor;
|
|
if (i != (bytes - 1) && my_config.alpha_alg == 1)
|
|
{
|
|
if (alphasum != 0)
|
|
sum = sum * matrixsum / alphasum;
|
|
else
|
|
sum = 0;
|
|
/* sum = srcrow[2][xoff + 2 * bytes] * my_config.matrix[2][2];*/
|
|
}
|
|
sum += my_config.offset;
|
|
|
|
return sum;
|
|
}
|
|
|
|
static void
|
|
convmatrix (void)
|
|
{
|
|
GimpPixelRgn srcPR, destPR;
|
|
gint width, height, row, col;
|
|
gint w, h, i;
|
|
gint sx1, sy1, sx2, sy2;
|
|
gint x1, x2, y1, y2;
|
|
guchar *destrow[3];
|
|
guchar *srcrow[5];
|
|
guchar *temprow;
|
|
gfloat sum;
|
|
gint xoff;
|
|
gint chanmask[4];
|
|
|
|
/* Get the input area. This is the bounding box of the selection in
|
|
* the image (or the entire image if there is no selection). Only
|
|
* operating on the input area is simply an optimization. It doesn't
|
|
* need to be done for correct operation. (It simply makes it go
|
|
* faster, since fewer pixels need to be operated on).
|
|
*/
|
|
gimp_drawable_mask_bounds (drawable->drawable_id, &sx1, &sy1, &sx2, &sy2);
|
|
w = sx2 - sx1;
|
|
h = sy2 - sy1;
|
|
|
|
/* Get the size of the input image. (This will/must be the same
|
|
* as the size of the output image.
|
|
*/
|
|
width = drawable->width;
|
|
height = drawable->height;
|
|
bytes = drawable->bpp;
|
|
|
|
if (gimp_drawable_is_rgb (drawable->drawable_id))
|
|
for (i = 0; i <3; i++)
|
|
chanmask[i] = my_config.channels[i + 1];
|
|
else /* Grayscale */
|
|
chanmask[0] = my_config.channels[0];
|
|
|
|
if (gimp_drawable_has_alpha (drawable->drawable_id))
|
|
chanmask[bytes - 1] = my_config.channels[4];
|
|
|
|
for (i = 0; i < 5; i++)
|
|
srcrow[i] = g_new (guchar, (w + 4) * bytes);
|
|
for (i = 0; i < 3; i++)
|
|
destrow[i]= g_new (guchar, w * bytes);
|
|
|
|
/* initialize the pixel regions */
|
|
x1 = MAX (sx1 - 2, 0);
|
|
y1 = MAX (sy1 - 2, 0);
|
|
x2 = MIN (sx2 + 2, width);
|
|
y2 = MIN (sy2 + 2, height);
|
|
gimp_pixel_rgn_init (&srcPR, drawable,
|
|
x1, y1, x2 - x1, y2 - y1, FALSE, FALSE);
|
|
gimp_pixel_rgn_init (&destPR, drawable, sx1, sy1, w, h, TRUE, TRUE);
|
|
|
|
/* initialize source arrays */
|
|
for (i = 0; i < 5; i++)
|
|
my_get_row (&srcPR, srcrow[i], sx1 - 2, sy1 + i - 2, w + 4);
|
|
|
|
for (row = sy1; row < sy2; row++)
|
|
{
|
|
xoff = 0;
|
|
|
|
for (col = sx1; col < sx2; col++)
|
|
for (i = 0; i < bytes; i++)
|
|
{
|
|
if (chanmask[i] <= 0)
|
|
sum = srcrow[2][xoff + 2 * bytes];
|
|
else
|
|
sum = calcmatrix(srcrow, xoff, i);
|
|
|
|
destrow[2][xoff]= (guchar) CLAMP (sum, 0, 255);
|
|
xoff++;
|
|
}
|
|
|
|
if (row > sy1 + 1)
|
|
gimp_pixel_rgn_set_row (&destPR, destrow[0], sx1, row - 2, w);
|
|
|
|
temprow = destrow[0];
|
|
destrow[0] = destrow[1];
|
|
destrow[1] = destrow[2];
|
|
destrow[2] = temprow;
|
|
temprow = srcrow[0];
|
|
|
|
for (i = 0; i < 4; i++)
|
|
srcrow[i] = srcrow[i + 1];
|
|
|
|
srcrow[4] = temprow;
|
|
my_get_row (&srcPR, srcrow[4], sx1 - 2, row + 3, w + 4);
|
|
|
|
if (row % 10 == 0)
|
|
gimp_progress_update ((double) (row - sy1) / h);
|
|
}
|
|
|
|
/* put the final rows in the buffer in place */
|
|
if (h < 3)
|
|
gimp_pixel_rgn_set_row (&destPR, destrow[2], sx1, row - 3, w);
|
|
if (h > 1)
|
|
gimp_pixel_rgn_set_row (&destPR, destrow[0], sx1, row - 2, w);
|
|
if (h > 2)
|
|
gimp_pixel_rgn_set_row (&destPR, destrow[1], sx1, row - 1, w);
|
|
|
|
/* update the timred region */
|
|
gimp_drawable_flush (drawable);
|
|
gimp_drawable_merge_shadow (drawable->drawable_id, TRUE);
|
|
gimp_drawable_update (drawable->drawable_id, sx1, sy1, sx2 - sx1, sy2 - sy1);
|
|
}
|
|
|
|
/***************************************************
|
|
* GUI stuff
|
|
*/
|
|
|
|
static void
|
|
fprint (gfloat f,
|
|
gchar *buffer,
|
|
gsize len)
|
|
{
|
|
gint i, t;
|
|
|
|
g_snprintf (buffer, len, "%.7f", f);
|
|
buffer[len - 1] = '\0';
|
|
|
|
for (t = 0; t < len - 1 && buffer[t] != '.'; t++);
|
|
|
|
i = t + 1;
|
|
|
|
while (buffer[i] != '\0')
|
|
{
|
|
if (buffer[i] != '0')
|
|
t = i + 1;
|
|
|
|
i++;
|
|
}
|
|
|
|
buffer[t] = '\0';
|
|
}
|
|
|
|
static void
|
|
redraw_matrix (void)
|
|
{
|
|
gint x, y;
|
|
gchar buffer[12];
|
|
|
|
for (y = 0; y < 5; y++)
|
|
for (x = 0; x < 5; x++)
|
|
{
|
|
fprint (my_config.matrix[x][y], buffer, sizeof (buffer));
|
|
gtk_entry_set_text (GTK_ENTRY (my_widgets.matrix[x][y]), buffer);
|
|
}
|
|
}
|
|
|
|
static void
|
|
redraw_channels (void)
|
|
{
|
|
gint i;
|
|
|
|
for (i = 0; i < 5; i++)
|
|
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (my_widgets.channels[i]),
|
|
my_config.channels[i] > 0);
|
|
}
|
|
|
|
static void
|
|
redraw_autoset (void)
|
|
{
|
|
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (my_widgets.autoset),
|
|
my_config.autoset);
|
|
}
|
|
|
|
static void
|
|
redraw_alpha_alg (void)
|
|
{
|
|
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (my_widgets.alpha_alg),
|
|
my_config.alpha_alg > 0);
|
|
}
|
|
|
|
static void
|
|
redraw_off_and_div (void)
|
|
{
|
|
gchar buffer[12];
|
|
|
|
fprint (my_config.divisor, buffer, sizeof (buffer));
|
|
gtk_entry_set_text (GTK_ENTRY (my_widgets.divisor), buffer);
|
|
|
|
fprint (my_config.offset, buffer, sizeof (buffer));
|
|
gtk_entry_set_text (GTK_ENTRY (my_widgets.offset), buffer);
|
|
}
|
|
|
|
static void
|
|
redraw_bmode (void)
|
|
{
|
|
gtk_toggle_button_set_active
|
|
(GTK_TOGGLE_BUTTON (my_widgets.bmode[my_config.bmode]), TRUE);
|
|
}
|
|
|
|
static void
|
|
redraw_all (void)
|
|
{
|
|
redraw_matrix ();
|
|
redraw_off_and_div ();
|
|
redraw_autoset ();
|
|
redraw_alpha_alg ();
|
|
redraw_bmode ();
|
|
redraw_channels ();
|
|
}
|
|
|
|
static void
|
|
check_matrix (void)
|
|
{
|
|
gint x, y;
|
|
gboolean valid = FALSE;
|
|
gfloat sum = 0.0;
|
|
|
|
for (y = 0; y < 5; y++)
|
|
for (x = 0; x < 5; x++)
|
|
{
|
|
sum += my_config.matrix[x][y];
|
|
if (my_config.matrix[x][y] != 0.0)
|
|
valid = TRUE;
|
|
}
|
|
|
|
if (my_config.autoset)
|
|
{
|
|
if (sum > 0)
|
|
{
|
|
my_config.offset = 0;
|
|
my_config.divisor = sum;
|
|
}
|
|
else if (sum < 0)
|
|
{
|
|
my_config.offset = 255;
|
|
my_config.divisor = -sum;
|
|
}
|
|
else
|
|
{
|
|
my_config.offset = 128;
|
|
/* The sum is 0, so this is probably some sort of
|
|
* embossing filter. Should divisor be autoset to 1
|
|
* or left undefined, ie. for the user to define? */
|
|
my_config.divisor = 1;
|
|
}
|
|
redraw_off_and_div ();
|
|
}
|
|
/* gtk_widget_set_sensitive(my_widgets.ok,valid); */
|
|
}
|
|
|
|
static void
|
|
response_callback (GtkWidget *widget,
|
|
gint response_id,
|
|
gpointer data)
|
|
{
|
|
switch (response_id)
|
|
{
|
|
case RESPONSE_RESET:
|
|
my_config = default_config;
|
|
check_config ();
|
|
redraw_all ();
|
|
break;
|
|
|
|
case GTK_RESPONSE_OK:
|
|
run_flag = TRUE;
|
|
|
|
default:
|
|
gtk_widget_destroy (GTK_WIDGET (widget));
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Checks that the configuration is valid for the image type */
|
|
static void
|
|
check_config (void)
|
|
{
|
|
gint i;
|
|
|
|
for (i = 0; i < 5; i++)
|
|
if (my_config.channels[i] < 0)
|
|
my_config.channels[i] = 0;
|
|
|
|
if (gimp_drawable_is_rgb (drawable->drawable_id))
|
|
my_config.channels[0] = -1;
|
|
else if (gimp_drawable_is_gray (drawable->drawable_id))
|
|
for (i = 1; i < 4; i++)
|
|
my_config.channels[i] = -1;
|
|
|
|
if (!gimp_drawable_has_alpha (drawable->drawable_id))
|
|
{
|
|
my_config.channels[4] = -1;
|
|
my_config.alpha_alg = -1;
|
|
my_config.bmode = EXTEND;
|
|
}
|
|
}
|
|
|
|
static void
|
|
entry_callback (GtkWidget *widget,
|
|
gpointer data)
|
|
{
|
|
gfloat *value = (gfloat *) data;
|
|
|
|
*value = atof (gtk_entry_get_text (GTK_ENTRY (widget)));
|
|
|
|
#if 0
|
|
check_matrix ();
|
|
#else
|
|
if (widget == my_widgets.divisor)
|
|
gtk_dialog_set_response_sensitive (GTK_DIALOG (gtk_widget_get_toplevel (widget)),
|
|
GTK_RESPONSE_OK, (*value != 0.0));
|
|
else if (widget != my_widgets.offset)
|
|
check_matrix ();
|
|
#endif
|
|
}
|
|
|
|
static void
|
|
my_toggle_callback (GtkWidget *widget,
|
|
gpointer data)
|
|
{
|
|
gint val = GTK_TOGGLE_BUTTON (widget)->active;
|
|
|
|
if (val)
|
|
*(gint *) data = TRUE;
|
|
else
|
|
*(gint *) data = FALSE;
|
|
|
|
if (widget == my_widgets.alpha_alg)
|
|
{
|
|
gtk_widget_set_sensitive (my_widgets.bmode[CLEAR], val);
|
|
if (val == 0 && my_config.bmode == CLEAR)
|
|
{
|
|
my_config.bmode = EXTEND;
|
|
redraw_bmode ();
|
|
}
|
|
}
|
|
else if (widget == my_widgets.autoset)
|
|
{
|
|
gtk_widget_set_sensitive (my_widgets.divisor, !val);
|
|
gtk_widget_set_sensitive (my_widgets.offset, !val);
|
|
check_matrix ();
|
|
}
|
|
}
|
|
|
|
static void
|
|
my_bmode_callback (GtkWidget *widget,
|
|
gpointer data)
|
|
{
|
|
my_config.bmode = GPOINTER_TO_INT (data) - 1;
|
|
}
|
|
|
|
static gboolean
|
|
dialog (void)
|
|
{
|
|
GtkWidget *dlg;
|
|
GtkWidget *main_hbox;
|
|
GtkWidget *table;
|
|
GtkWidget *label;
|
|
GtkWidget *entry;
|
|
GtkWidget *button;
|
|
GtkWidget *box;
|
|
GtkWidget *inbox;
|
|
GtkWidget *vbox;
|
|
GtkWidget *frame;
|
|
gint x, y, i;
|
|
GSList *group;
|
|
|
|
gimp_ui_init ("convmatrix", FALSE);
|
|
|
|
dlg = gimp_dialog_new (_("Convolution Matrix"), "convmatrix",
|
|
NULL, 0,
|
|
gimp_standard_help_func, "plug-in-convmatrix",
|
|
|
|
GIMP_STOCK_RESET, RESPONSE_RESET,
|
|
GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
|
|
GTK_STOCK_OK, GTK_RESPONSE_OK,
|
|
|
|
NULL);
|
|
|
|
g_signal_connect (dlg, "response",
|
|
G_CALLBACK (response_callback),
|
|
NULL);
|
|
g_signal_connect (dlg, "destroy",
|
|
G_CALLBACK (gtk_main_quit),
|
|
NULL);
|
|
|
|
main_hbox = gtk_hbox_new (FALSE, 12);
|
|
gtk_container_set_border_width (GTK_CONTAINER (main_hbox), 12);
|
|
gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dlg)->vbox), main_hbox,
|
|
TRUE, TRUE, 0);
|
|
|
|
vbox = gtk_vbox_new (FALSE, 12);
|
|
gtk_box_pack_start (GTK_BOX (main_hbox), vbox, TRUE, TRUE, 0);
|
|
|
|
frame = gimp_frame_new (_("Matrix"));
|
|
gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, FALSE, 0);
|
|
|
|
inbox = gtk_vbox_new (FALSE, 12);
|
|
gtk_container_add (GTK_CONTAINER (frame), inbox);
|
|
|
|
table = gtk_table_new (5, 5, FALSE);
|
|
gtk_table_set_row_spacings (GTK_TABLE (table), 4);
|
|
gtk_table_set_col_spacings (GTK_TABLE (table), 4);
|
|
gtk_box_pack_start (GTK_BOX (inbox), table, TRUE, TRUE, 0);
|
|
|
|
for (y = 0; y < 5; y++)
|
|
for (x = 0; x < 5; x++)
|
|
{
|
|
my_widgets.matrix[x][y] = entry = gtk_entry_new ();
|
|
gtk_widget_set_size_request (entry, 40, -1);
|
|
gtk_table_attach (GTK_TABLE (table), entry, x, x+1, y, y+1,
|
|
GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 0);
|
|
gtk_widget_show (entry);
|
|
|
|
g_signal_connect (entry, "changed",
|
|
G_CALLBACK (entry_callback),
|
|
&my_config.matrix[x][y]);
|
|
}
|
|
|
|
gtk_widget_show (table);
|
|
|
|
box = gtk_hbox_new (FALSE, 6);
|
|
gtk_box_pack_start (GTK_BOX (inbox), box, FALSE, FALSE, 0);
|
|
|
|
table = gtk_table_new (1, 2, FALSE);
|
|
gtk_table_set_col_spacings (GTK_TABLE (table), 6);
|
|
gtk_box_pack_start (GTK_BOX (box), table, TRUE, FALSE, 0);
|
|
|
|
label = gtk_label_new_with_mnemonic (_("D_ivisor:"));
|
|
gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
|
|
gtk_table_attach_defaults (GTK_TABLE (table), label, 0, 1, 0, 1);
|
|
gtk_widget_show (label);
|
|
|
|
my_widgets.divisor = entry = gtk_entry_new ();
|
|
gtk_widget_set_size_request (entry, 40, -1);
|
|
gtk_table_attach_defaults (GTK_TABLE (table), entry, 1, 2, 0, 1);
|
|
gtk_widget_show (entry);
|
|
gtk_label_set_mnemonic_widget (GTK_LABEL (label), entry);
|
|
|
|
g_signal_connect (entry, "changed",
|
|
G_CALLBACK (entry_callback),
|
|
&my_config.divisor);
|
|
|
|
gtk_widget_show (table);
|
|
|
|
table = gtk_table_new (1, 2, FALSE);
|
|
gtk_table_set_col_spacings (GTK_TABLE (table), 6);
|
|
gtk_box_pack_start (GTK_BOX (box), table, TRUE, FALSE, 0);
|
|
|
|
label = gtk_label_new_with_mnemonic (_("O_ffset:"));
|
|
gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
|
|
gtk_table_attach_defaults (GTK_TABLE (table), label, 0, 1, 0, 1);
|
|
gtk_widget_show (label);
|
|
|
|
my_widgets.offset = entry = gtk_entry_new ();
|
|
gtk_widget_set_size_request (entry, 40, -1);
|
|
gtk_table_attach_defaults (GTK_TABLE (table), entry, 1, 2, 0, 1);
|
|
gtk_widget_show (entry);
|
|
gtk_label_set_mnemonic_widget (GTK_LABEL (label), entry);
|
|
|
|
g_signal_connect (entry, "changed",
|
|
G_CALLBACK (entry_callback),
|
|
&my_config.offset);
|
|
|
|
gtk_widget_show (table);
|
|
|
|
gtk_widget_show (box);
|
|
|
|
gtk_widget_show (inbox);
|
|
gtk_widget_show (frame);
|
|
|
|
box = gtk_vbox_new (FALSE, 6);
|
|
gtk_box_pack_start (GTK_BOX (vbox), box, FALSE, FALSE, 0);
|
|
|
|
my_widgets.autoset = button =
|
|
gtk_check_button_new_with_mnemonic (_("A_utomatic"));
|
|
gtk_box_pack_start (GTK_BOX (box), button, TRUE, FALSE, 0);
|
|
gtk_widget_show (button);
|
|
|
|
g_signal_connect (button, "toggled",
|
|
G_CALLBACK (my_toggle_callback),
|
|
&my_config.autoset);
|
|
|
|
my_widgets.alpha_alg = button =
|
|
gtk_check_button_new_with_mnemonic (_("A_lpha-weighting"));
|
|
if (my_config.alpha_alg == -1)
|
|
gtk_widget_set_sensitive (button, FALSE);
|
|
gtk_box_pack_start (GTK_BOX (box), button, TRUE, FALSE, 0);
|
|
gtk_widget_show (button);
|
|
|
|
g_signal_connect (button, "toggled",
|
|
G_CALLBACK (my_toggle_callback),
|
|
&my_config.alpha_alg);
|
|
|
|
gtk_widget_show (box);
|
|
gtk_widget_show (vbox);
|
|
|
|
inbox = gtk_vbox_new (FALSE, 12);
|
|
gtk_box_pack_start (GTK_BOX (main_hbox), inbox, FALSE, FALSE, 0);
|
|
|
|
frame = gimp_frame_new (_("Border"));
|
|
gtk_box_pack_start (GTK_BOX (inbox), frame, FALSE, FALSE, 0);
|
|
|
|
box = gtk_vbox_new (FALSE, 2);
|
|
gtk_container_add (GTK_CONTAINER (frame), box);
|
|
|
|
group = NULL;
|
|
|
|
for (i = 0; i < 3; i++)
|
|
{
|
|
my_widgets.bmode[i] = button =
|
|
gtk_radio_button_new_with_mnemonic (group, gettext (bmode_labels[i]));
|
|
group = gtk_radio_button_get_group (GTK_RADIO_BUTTON (button));
|
|
gtk_box_pack_start (GTK_BOX (box), button, FALSE, FALSE, 0);
|
|
gtk_widget_show (button);
|
|
|
|
g_signal_connect (button, "toggled",
|
|
G_CALLBACK (my_bmode_callback),
|
|
GINT_TO_POINTER (i + 1));
|
|
}
|
|
|
|
gtk_widget_show (box);
|
|
gtk_widget_show (frame);
|
|
|
|
frame = gimp_frame_new (_("Channels"));
|
|
gtk_box_pack_start (GTK_BOX (inbox), frame, FALSE, FALSE, 0);
|
|
|
|
box = gtk_vbox_new (FALSE, 2);
|
|
gtk_container_add (GTK_CONTAINER (frame), box);
|
|
|
|
for (i = 0; i < 5; i++)
|
|
{
|
|
my_widgets.channels[i] = button =
|
|
gtk_check_button_new_with_mnemonic (gettext (channel_labels[i]));
|
|
|
|
if (my_config.channels[i] < 0)
|
|
gtk_widget_set_sensitive (button, FALSE);
|
|
|
|
gtk_box_pack_start (GTK_BOX (box), button, FALSE, FALSE, 0);
|
|
gtk_widget_show (button);
|
|
|
|
g_signal_connect (button, "toggled",
|
|
G_CALLBACK (my_toggle_callback),
|
|
&my_config.channels[i]);
|
|
}
|
|
|
|
gtk_widget_show (box);
|
|
gtk_widget_show (frame);
|
|
|
|
gtk_widget_show (inbox);
|
|
|
|
gtk_widget_show (main_hbox);
|
|
|
|
gtk_widget_show (dlg);
|
|
redraw_all ();
|
|
|
|
gtk_widget_set_sensitive (my_widgets.bmode[CLEAR],
|
|
(my_config.alpha_alg > 0));
|
|
|
|
gtk_main ();
|
|
|
|
return run_flag;
|
|
}
|