gimp/plug-ins/common/randomize.c

783 lines
21 KiB
C

/****************************************************************************
* This is a plugin for the GIMP v 1.0 or later. Documentation is
* available at http://www.rru.com/~meo/gimp/ .
*
* Copyright (C) 1997-8 Miles O'Neal <meo@rru.com> http://www.rru.com/~meo/
* GUI based on GTK code from:
* alienmap (Copyright (C) 1996, 1997 Daniel Cotting)
* plasma (Copyright (C) 1996 Stephen Norris),
* oilify (Copyright (C) 1996 Torsten Martinsen),
* ripple (Copyright (C) 1997 Brian Degenhardt) and
* whirl (Copyright (C) 1997 Federico Mena Quintero).
*
* 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.
*
****************************************************************************/
/****************************************************************************
* Randomize:
*
* randomize version 1.7 (1 May 1998, MEO)
*
* Please send any patches or suggestions to the author: meo@rru.com .
*
* This plug-in adds a user-defined amount of randomization to an
* image. Variations include:
*
* - hurling (spewing random colors)
* - picking a nearby pixel at random
* - slurring (a crude form of melting)
*
* In any case, for each pixel in the selection or image,
* whether to change the pixel is decided by picking a
* random number, weighted by the user's "randomization" percentage.
* If the random number is in range, the pixel is modified. Picking
* one selects the new pixel value at random from the current and
* adjacent pixels. Hurling assigns a random value to the pixel.
* Slurring sort of melts downwards; if a pixel is to be slurred,
* there is an 80% chance the pixel above be used; otherwise, one
* of the pixels adjacent to the one above is used (even odds as
* to which it will be).
*
* Picking, hurling and slurring work with any image type.
*
* This plug-in's effectiveness varies a lot with the type
* and clarity of the image being "randomized".
*
* Hurling more than 75% or so onto an existing image will
* make the image nearly unrecognizable. By 90% hurl, most
* images are indistinguishable from random noise.
*
* The repeat count is especially useful with slurring.
*
* TODO List
*
* - add a real melt function
****************************************************************************/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <gtk/gtk.h>
#include <libgimp/gimp.h>
#include <libgimp/gimpui.h>
#include "libgimp/stdplugins-intl.h"
/*********************************
*
* PLUGIN-SPECIFIC CONSTANTS
*
********************************/
/*
* progress meter update frequency
*/
#define PROG_UPDATE_TIME ((row % 10) == 0)
gchar *PLUG_IN_NAME[] =
{
"plug_in_randomize_hurl",
"plug_in_randomize_pick",
"plug_in_randomize_slur",
};
gchar *RNDM_VERSION[] =
{
N_("Random Hurl 1.7"),
N_("Random Pick 1.7"),
N_("Random Slur 1.7"),
};
#define RNDM_HURL 1
#define RNDM_PICK 2
#define RNDM_SLUR 3
#define SEED_TIME 10
#define SEED_USER 11
#define SCALE_WIDTH 100
gint rndm_type = RNDM_HURL; /* hurl, pick, etc. */
/*********************************
*
* PLUGIN-SPECIFIC STRUCTURES AND DATA
*
********************************/
typedef struct
{
gdouble rndm_pct; /* likelihood of randomization (as %age) */
gdouble rndm_rcount; /* repeat count */
gint seed_type; /* seed init. type - current time or user value */
gint rndm_seed; /* seed value for rand() function */
} RandomizeVals;
static RandomizeVals pivals =
{
50.0,
1.0,
SEED_TIME,
0,
};
typedef struct
{
gint run;
} RandomizeInterface;
static RandomizeInterface rndm_int =
{
FALSE /* have we run? */
};
/*********************************
*
* LOCAL FUNCTIONS
*
********************************/
static void query (void);
static void run (gchar *name,
gint nparams,
GimpParam *param,
gint *nreturn_vals,
GimpParam **return_vals);
static void randomize (GimpDrawable *drawable);
static inline void randomize_prepare_row (GimpPixelRgn *pixel_rgn,
guchar *data,
gint x,
gint y,
gint w);
static gint randomize_dialog (void);
static void randomize_ok_callback (GtkWidget *widget,
gpointer data);
/************************************ Guts ***********************************/
GimpPlugInInfo PLUG_IN_INFO =
{
NULL, /* init_proc */
NULL, /* quit_proc */
query, /* query_proc */
run, /* run_proc */
};
MAIN ()
/*********************************
*
* query() - query_proc
*
* called by the GIMP to learn about this plug-in
*
********************************/
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_FLOAT, "rndm_pct", "Randomization percentage (1.0 - 100.0)" },
{ GIMP_PDB_FLOAT, "rndm_rcount", "Repeat count (1.0 - 100.0)" },
{ GIMP_PDB_INT32, "seed_type", "Seed type (10 = current time, 11 = seed value)" },
{ GIMP_PDB_INT32, "rndm_seed", "Seed value (used only if seed type is 11)" }
};
static gint nargs = sizeof(args) / sizeof (args[0]);
const gchar *hurl_blurb =
"Add a random factor to the image by hurling random data at it.";
const gchar *pick_blurb =
"Add a random factor to the image by picking a random adjacent pixel.";
const gchar *slur_blurb =
"Add a random factor to the image by slurring (similar to melting).";
const gchar *hurl_help =
"This plug-in ``hurls'' randomly-valued pixels onto the selection or image. You may select the percentage of pixels to modify and the number of times to repeat the process.";
const gchar *pick_help =
"This plug-in replaces a pixel with a random adjacent pixel. You may select the percentage of pixels to modify and the number of times to repeat the process.";
const gchar *slur_help =
"This plug-in slurs (melts like a bunch of icicles) an image. You may select the percentage of pixels to modify and the number of times to repeat the process.";
const gchar *author = "Miles O'Neal <meo@rru.com> http://www.rru.com/~meo/";
const gchar *copyrights = "Miles O'Neal, Spencer Kimball, Peter Mattis, Torsten Martinsen, Brian Degenhardt, Federico Mena Quintero, Stephen Norris, Daniel Cotting";
const gchar *copyright_date = "1995-1998";
gimp_install_procedure (PLUG_IN_NAME[0],
(gchar *) hurl_blurb,
(gchar *) hurl_help,
(gchar *) author,
(gchar *) copyrights,
(gchar *) copyright_date,
N_("<Image>/Filters/Noise/Hurl..."),
"RGB*, GRAY*, INDEXED*",
GIMP_PLUGIN,
nargs, 0,
args, NULL);
gimp_install_procedure (PLUG_IN_NAME[1],
(gchar *) pick_blurb,
(gchar *) pick_help,
(gchar *) author,
(gchar *) copyrights,
(gchar *) copyright_date,
N_("<Image>/Filters/Noise/Pick..."),
"RGB*, GRAY*, INDEXED*",
GIMP_PLUGIN,
nargs, 0,
args, NULL);
gimp_install_procedure (PLUG_IN_NAME[2],
(gchar *) slur_blurb,
(gchar *) slur_help,
(gchar *) author,
(gchar *) copyrights,
(gchar *) copyright_date,
N_("<Image>/Filters/Noise/Slur..."),
"RGB*, GRAY*, INDEXED*",
GIMP_PLUGIN,
nargs, 0,
args, NULL);
}
/*********************************
*
* run() - main routine
*
* This handles the main interaction with the GIMP itself,
* and invokes the routine that actually does the work.
*
********************************/
static void
run (gchar *name,
gint nparams,
GimpParam *param,
gint *nreturn_vals,
GimpParam **return_vals)
{
GimpDrawable *drawable;
GimpRunModeType run_mode;
GimpPDBStatusType status = GIMP_PDB_SUCCESS; /* assume the best! */
gchar *rndm_type_str = '\0';
gchar prog_label[32];
static GimpParam values[1];
/*
* Get the specified drawable, do standard initialization.
*/
if (strcmp(name, PLUG_IN_NAME[0]) == 0)
rndm_type = RNDM_HURL;
else if (strcmp(name, PLUG_IN_NAME[1]) == 0)
rndm_type = RNDM_PICK;
else if (strcmp(name, PLUG_IN_NAME[2]) == 0)
rndm_type = RNDM_SLUR;
run_mode = param[0].data.d_int32;
drawable = gimp_drawable_get(param[2].data.d_drawable);
values[0].type = GIMP_PDB_STATUS;
values[0].data.d_status = status;
*nreturn_vals = 1;
*return_vals = values;
/*
* Make sure the drawable type is appropriate.
*/
if (gimp_drawable_is_rgb(drawable->id) ||
gimp_drawable_is_gray(drawable->id) ||
gimp_drawable_is_indexed(drawable->id))
{
switch (run_mode)
{
/*
* If we're running interactively, pop up the dialog box.
*/
case GIMP_RUN_INTERACTIVE:
INIT_I18N_UI();
gimp_get_data(PLUG_IN_NAME[rndm_type - 1], &pivals);
if (!randomize_dialog()) /* return on Cancel */
return;
break;
/*
* If we're not interactive (probably scripting), we
* get the parameters from the param[] array, since
* we don't use the dialog box. Make sure they all
* parameters have legitimate values.
*/
case GIMP_RUN_NONINTERACTIVE:
if (nparams != 7)
{
status = GIMP_PDB_CALLING_ERROR;
}
else
{
pivals.rndm_pct = (gdouble) param[3].data.d_float;
pivals.rndm_rcount = (gdouble) param[4].data.d_float;
pivals.seed_type = (gint) param[5].data.d_int32;
pivals.rndm_seed = (gint) param[6].data.d_int32;
if ((rndm_type != RNDM_PICK &&
rndm_type != RNDM_SLUR &&
rndm_type != RNDM_HURL) ||
(pivals.rndm_pct < 1.0 || pivals.rndm_pct > 100.0) ||
(pivals.rndm_rcount < 1.0 || pivals.rndm_rcount > 100.0))
{
status = GIMP_PDB_CALLING_ERROR;
}
}
break;
/*
* If we're running with the last set of values, get those values.
*/
case GIMP_RUN_WITH_LAST_VALS:
gimp_get_data (PLUG_IN_NAME[rndm_type - 1], &pivals);
break;
/*
* Hopefully we never get here!
*/
default:
break;
}
if (status == GIMP_PDB_SUCCESS)
{
/*
* JUST DO IT!
*/
switch (rndm_type)
{
case RNDM_HURL: rndm_type_str = "hurl"; break;
case RNDM_PICK: rndm_type_str = "pick"; break;
case RNDM_SLUR: rndm_type_str = "slur"; break;
}
sprintf (prog_label, "%s (%s)", gettext(RNDM_VERSION[rndm_type - 1]),
gettext(rndm_type_str));
gimp_progress_init(prog_label);
gimp_tile_cache_ntiles(2 * (drawable->width / gimp_tile_width() + 1));
/*
* Initialize the rand() function seed
*/
if (pivals.seed_type == SEED_TIME)
pivals.rndm_seed = time(NULL);
srand (pivals.rndm_seed);
randomize (drawable);
/*
* If we ran interactively (even repeating) update the display.
*/
if (run_mode != GIMP_RUN_NONINTERACTIVE)
{
gimp_displays_flush();
}
/*
* If we use the dialog popup, set the data for future use.
*/
if (run_mode == GIMP_RUN_INTERACTIVE)
{
gimp_set_data(PLUG_IN_NAME[rndm_type - 1], &pivals,
sizeof(RandomizeVals));
}
}
}
else
{
/*
* If we got the wrong drawable type, we need to complain.
*/
status = GIMP_PDB_EXECUTION_ERROR;
}
/*
* DONE!
* Set the status where the GIMP can see it, and let go
* of the drawable.
*/
values[0].data.d_status = status;
gimp_drawable_detach(drawable);
}
/*********************************
*
* randomize_prepare_row()
*
* Get a row of pixels. If the requested row
* is off the edge, clone the edge row.
*
********************************/
static inline void
randomize_prepare_row (GimpPixelRgn *pixel_rgn,
guchar *data,
int x,
int y,
int w)
{
gint b;
if (y == 0)
{
gimp_pixel_rgn_get_row(pixel_rgn, data, x, (y + 1), w);
}
else if (y == pixel_rgn->h)
{
gimp_pixel_rgn_get_row(pixel_rgn, data, x, (y - 1), w);
}
else
{
gimp_pixel_rgn_get_row(pixel_rgn, data, x, y, w);
}
/*
* Fill in edge pixels
*/
for (b = 0; b < pixel_rgn->bpp; b++)
{
data[-(gint)pixel_rgn->bpp + b] = data[b];
data[w * pixel_rgn->bpp + b] = data[(w - 1) * pixel_rgn->bpp + b];
}
}
/*********************************
*
* randomize()
*
* Actually mess with the image.
*
********************************/
static void
randomize (GimpDrawable *drawable)
{
GimpPixelRgn srcPR, destPR, destPR2, *sp, *dp, *tp;
gint width, height;
gint bytes;
guchar *dest, *d;
guchar *prev_row, *pr;
guchar *cur_row, *cr;
guchar *next_row, *nr;
guchar *tmp;
gint row, col;
gint x1, y1, x2, y2;
gint cnt;
gint has_alpha, ind;
/*
* 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->id, &x1, &y1, &x2, &y2);
/*
* Get the size of the input image. (This will/must be the same
* as the size of the output image. Also get alpha info.
*/
width = drawable->width;
height = drawable->height;
bytes = drawable->bpp;
has_alpha = gimp_drawable_has_alpha(drawable->id);
/*
* allocate row buffers
*/
prev_row = g_new (guchar, (x2 - x1 + 2) * bytes);
cur_row = g_new (guchar, (x2 - x1 + 2) * bytes);
next_row = g_new (guchar, (x2 - x1 + 2) * bytes);
dest = g_new (guchar, (x2 - x1) * bytes);
/*
* initialize the pixel regions
*/
gimp_pixel_rgn_init(&srcPR, drawable, 0, 0, width, height, FALSE, FALSE);
gimp_pixel_rgn_init(&destPR, drawable, 0, 0, width, height, TRUE, TRUE);
gimp_pixel_rgn_init(&destPR2, drawable, 0, 0, width, height, TRUE, TRUE);
sp = &srcPR;
dp = &destPR;
tp = NULL;
pr = prev_row + bytes;
cr = cur_row + bytes;
nr = next_row + bytes;
for (cnt = 1; cnt <= pivals.rndm_rcount; cnt++)
{
/*
* prepare the first row and previous row
*/
randomize_prepare_row(sp, pr, x1, y1 - 1, (x2 - x1));
randomize_prepare_row(dp, cr, x1, y1, (x2 - x1));
/*
* loop through the rows, applying the selected convolution
*/
for (row = y1; row < y2; row++)
{
/* prepare the next row */
randomize_prepare_row(sp, nr, x1, row + 1, (x2 - x1));
d = dest;
ind = 0;
for (col = 0; col < (x2 - x1) * bytes; col++)
{
if (((rand() % 100)) <= (gint) pivals.rndm_pct)
{
switch (rndm_type)
{
/*
* HURL
* Just assign a random value.
*/
case RNDM_HURL:
*d++ = rand() % 256;
break;
/*
* PICK
* pick at random from a neighboring pixel.
*/
case RNDM_PICK:
switch (rand() % 9)
{
case 0:
*d++ = (gint) pr[col - bytes];
break;
case 1:
*d++ = (gint) pr[col];
break;
case 2:
*d++ = (gint) pr[col + bytes];
break;
case 3:
*d++ = (gint) cr[col - bytes];
break;
case 4:
*d++ = (gint) cr[col];
break;
case 5:
*d++ = (gint) cr[col + bytes];
break;
case 6:
*d++ = (gint) nr[col - bytes];
break;
case 7:
*d++ = (gint) nr[col];
break;
case 8:
*d++ = (gint) nr[col + bytes];
break;
}
break;
/*
* SLUR
* 80% chance it's from directly above,
* 10% from above left,
* 10% from above right.
*/
case RNDM_SLUR:
switch (rand() % 10)
{
case 0:
*d++ = (gint) pr[col - bytes];
break;
case 9:
*d++ = (gint) pr[col + bytes];
break;
default:
*d++ = (gint) pr[col];
break;
}
break;
}
/*
* Otherwise, this pixel was not selected for randomization,
* so use the current value.
*/
}
else
{
*d++ = (gint) cr[col];
}
}
/*
* Save the modified row, shuffle the row pointers, and every
* so often, update the progress meter.
*/
gimp_pixel_rgn_set_row(dp, dest, x1, row, (x2 - x1));
tmp = pr;
pr = cr;
cr = nr;
nr = tmp;
if (PROG_UPDATE_TIME)
gimp_progress_update((double) row / (double) (y2 - y1));
}
/*
* if we have more cycles to perform, swap the src and dest Pixel Regions
*/
if (cnt < pivals.rndm_rcount)
{
if (tp != NULL)
{
tp = dp;
dp = sp;
sp = tp;
}
else
{
tp = &srcPR;
sp = &destPR;
dp = &destPR2;
}
}
}
gimp_progress_update((double) 100);
/*
* update the randomized region
*/
gimp_drawable_flush(drawable);
gimp_drawable_merge_shadow(drawable->id, TRUE);
gimp_drawable_update(drawable->id, x1, y1, (x2 - x1), (y2 - y1));
/*
* clean up after ourselves.
*/
g_free (prev_row);
g_free (cur_row);
g_free (next_row);
g_free (dest);
}
/*********************************
*
* GUI ROUTINES
*
********************************/
/*********************************
*
* randomize_dialog() - set up the plug-in's dialog box
*
********************************/
static gint
randomize_dialog (void)
{
GtkWidget *dlg;
GtkWidget *frame;
GtkWidget *table;
GtkWidget *seed_hbox;
GtkObject *adj;
gimp_ui_init ("randomize", FALSE);
dlg = gimp_dialog_new (gettext (RNDM_VERSION[rndm_type - 1]), "randomize",
gimp_standard_help_func, "filters/randomize.html",
GTK_WIN_POS_MOUSE,
FALSE, TRUE, FALSE,
_("OK"), randomize_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);
gimp_help_init ();
/*
* Parameter settings
*
* First set up the basic containers, label them, etc.
*/
frame = gtk_frame_new (_("Parameter Settings"));
gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_ETCHED_IN);
gtk_container_set_border_width (GTK_CONTAINER (frame), 6);
gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dlg)->vbox), frame, TRUE, TRUE, 0);
table = gtk_table_new (3, 3, FALSE);
gtk_table_set_col_spacings (GTK_TABLE (table), 4);
gtk_table_set_row_spacings (GTK_TABLE (table), 2);
gtk_container_set_border_width (GTK_CONTAINER (table), 4);
gtk_container_add (GTK_CONTAINER (frame), table);
gtk_widget_show(table);
/* Random Seed */
seed_hbox = gimp_random_seed_new (&pivals.rndm_seed,
&pivals.seed_type,
SEED_TIME, SEED_USER);
gimp_table_attach_aligned (GTK_TABLE (table), 0, 0,
_("Random Seed:"), 1.0, 0.5,
seed_hbox, 1, TRUE);
/*
* Randomization percentage label & scale (1 to 100)
*/
adj = gimp_scale_entry_new (GTK_TABLE (table), 0, 1,
_("Randomization (%):"), SCALE_WIDTH, 0,
pivals.rndm_pct, 1.0, 100.0, 1.0, 10.0, 0,
TRUE, 0, 0,
_("Percentage of pixels to be filtered"), NULL);
gtk_signal_connect (GTK_OBJECT (adj), "value_changed",
GTK_SIGNAL_FUNC (gimp_int_adjustment_update),
&pivals.rndm_pct);
/*
* Repeat count label & scale (1 to 100)
*/
adj = gimp_scale_entry_new (GTK_TABLE (table), 0, 2,
_("Repeat:"), SCALE_WIDTH, 0,
pivals.rndm_rcount, 1.0, 100.0, 1.0, 10.0, 0,
TRUE, 0, 0,
_("Number of times to apply filter"), NULL);
gtk_signal_connect (GTK_OBJECT (adj), "value_changed",
GTK_SIGNAL_FUNC (gimp_double_adjustment_update),
&pivals.rndm_rcount);
gtk_widget_show (frame);
gtk_widget_show (dlg);
gtk_main ();
gimp_help_free ();
gdk_flush ();
return rndm_int.run;
}
static void
randomize_ok_callback (GtkWidget *widget,
gpointer data)
{
rndm_int.run = TRUE;
gtk_widget_destroy (GTK_WIDGET (data));
}