How to write a GIMP plug-in, part II
Written By Dave Neary.
In the first part, I presented essential elements to build a plug-in interface with The GIMP. Now we will produce a simple but useful algorithm that we could use in our plug-in.
Introduction
The algorithm we are going to implement is a simple blur. It is included in The GIMP as “Filters->Blur->Blur” with default parameters.
That algorithm is very simple. Each pixel in our image is replaced by a mean value of its neighbours. For example, if we look at the simplest case where the neighbourhood is 3x3 (see figure 1), in that case the center value will be replaced with 5, the mean of the 9 numbers in its neighbourhood.
With this method, edge differences are splatted, giving a
blurred result. One can choose another radius, using a (2r + 1) x (2r + 1)
matrix.
Image structure
In Part I we wrote a run() function that did nothing useful. Let’s look again at run() prototype:
static void run (const gchar *name,
gint nparams,
const GimpParam *param,
gint *nreturn_vals,
GimpParam **return_vals);
We saw that for a filter (i.e. a plug-in that modifies the image), the first three input parameters were the run mode, an identifier for the image, and another one for the active drawable (layer or mask).
A GIMP image is a structure that contains, among others, guides, layers, layer masks, and any data associated to the image. The word “drawable” is often used in GIMP internal structures. A “drawable” is an object where you can get, and sometimes modify, raw data. So : layers, layer masks, selections are all “drawables”.
Drawables
Accessing the data
To get a GimpDrawable from its identifier, we need the
gimp_drawable_get()
function:
GimpDrawable *gimp_drawable_get (gint32 drawable_id);
From this structure, one can access drawable data through a GimpPixelRgn structure, and one can check the drawable type (RGB, gray level). The full listing of functions available for a GimpDrawable can be found in the API.
Two very important functions for plug-ins are gimp_drawable_mask_bounds() and gimp_pixel_rgn_init(). The first gives the active selection limits on the drawable, and the second initialises the GimpPixelRgn we will use to access the data.
As soon as we have a well initialised GimpPixelRgn, we can access the image data in several different ways, by pixel, by rectangle, by row or by column. The best method will depend on the algorithm one plans to use. Moreover, The GIMP uses a tile-based architecture, and loading or unloading data is expensive, so we should not use it more than necessary.
Tiles
The main functions to get and set image data are:
void gimp_pixel_rgn_get_pixel (GimpPixelRgn *pr,
guchar *buf,
gint x,
gint y);
void gimp_pixel_rgn_get_row (GimpPixelRgn *pr,
guchar *buf,
gint x,
gint y,
gint width);
void gimp_pixel_rgn_get_col (GimpPixelRgn *pr,
guchar *buf,
gint x,
gint y,
gint height);
void gimp_pixel_rgn_get_rect (GimpPixelRgn *pr,
guchar *buf,
gint x,
gint y,
gint width,
gint height);
void gimp_pixel_rgn_set_pixel (GimpPixelRgn *pr,
const guchar *buf,
gint x,
gint y);
void gimp_pixel_rgn_set_row (GimpPixelRgn *pr,
const guchar *buf,
gint x,
gint y,
gint width);
void gimp_pixel_rgn_set_col (GimpPixelRgn *pr,
const guchar *buf,
gint x,
gint y,
gint height);
void gimp_pixel_rgn_set_rect (GimpPixelRgn *pr,
const guchar *buf,
gint x,
gint y,
gint width,
gint height);
There is also another way to access image data (it’s even used more often), that allows to manage data at the tile level. We will look at it in detail later.
Updating the image
At last, a plug-in that has modified a drawable data must flush it to send data to the core, and to tell the application that the display must be updated. This is done with the following function:
gimp_displays_flush ();
gimp_drawable_detach (drawable);
Implementing blur()
To be able to try out several different processing methods, we will delegate the job to a blur() function. Our run() is below.
static void
run (const gchar *name,
gint nparams,
const GimpParam *param,
gint *nreturn_vals,
GimpParam **return_vals)
{
static GimpParam values[1];
GimpPDBStatusType status = GIMP_PDB_SUCCESS;
GimpRunMode run_mode;
GimpDrawable *drawable;
/* Setting mandatory output values */
*nreturn_vals = 1;
*return_vals = values;
values[0].type = GIMP_PDB_STATUS;
values[0].data.d_status = status;
/* Getting run_mode - we won't display a dialog if
* we are in NONINTERACTIVE mode */
run_mode = param[0].data.d_int32;
/* Get the specified drawable */
drawable = gimp_drawable_get (param[2].data.d_drawable);
gimp_progress_init ("My Blur...");
/* Let's time blur
*
* GTimer timer = g_timer_new time ();
*/
blur (drawable);
/* g_print ("blur() took %g seconds.\n", g_timer_elapsed (timer));
* g_timer_destroy (timer);
*/
gimp_displays_flush ();
gimp_drawable_detach (drawable);
}
There are a few lines here that need to be explained a bit more.
The call to gimp_progress_init()
initialises a progress
measurement for our plug-in. Later, if we call
gimp_progress_update(double percent)
, the percentage given as an
input parameter will be shown graphically. The run_mode
tells us
whether the plug-in was launched in a way such as we can display
a graphical interface or not. Possible values are
GIMP_RUN_INTERACTIVE
, GIMP_RUN_NONINTERACTIVE
or
GIMP_RUN_WITH_LAST_VALS
, which mean the plug-in was executed
from The GIMP, from a script, or from the “Repeat last filter”
menu entry.
Regarding the blur algorithm itself, the first version using
gimp_pixel_rgn_(get|set)_pixel()
is found below. Some functions
in it have not been explained yet.
gimp_drawable_mask_bounds()
allows calculation of the filter’s
effect limits, excluding any region that is not in the active
selection. Limiting the processing this way allows an important
performance improvement.
gimp_pixel_rgn_init()
takes as input parameters the drawable,
its limits for the processing, and two booleans that
significantly modify the behaviour of the resulting GimpPixelRgn.
The first one tells that “set” operations must be done on shadow
tiles, in order to leave original data as is until
gimp_drawable_merge_shadow()
is called, when all modified data
will be merged. The second one tells that modified tiles should
be tagged “dirty” and sent to the core to be merged. Most of the
time, to read data, one uses FALSE and FALSE for these two
parameters, and to write data, one uses TRUE and TRUE. Other
combinations are possible but seldom used.
static void
blur (GimpDrawable *drawable)
{
gint i, j, k, channels;
gint x1, y1, x2, y2;
GimpPixelRgn rgn_in, rgn_out;
guchar output[4];
/* Gets upper left and lower right coordinates,
* and layers number in the image */
gimp_drawable_mask_bounds (drawable->drawable_id,
&x1, &y1,
&x2, &y2);
channels = gimp_drawable_bpp (drawable->drawable_id);
/* Initialises two PixelRgns, one to read original data,
* and the other to write output data. That second one will
* be merged at the end by the call to
* gimp_drawable_merge_shadow() */
gimp_pixel_rgn_init (&rgn_in,
drawable,
x1, y1,
x2 - x1, y2 - y1,
FALSE, FALSE);
gimp_pixel_rgn_init (&rgn_out,
drawable,
x1, y1,
x2 - x1, y2 - y1,
TRUE, TRUE);
for (i = x1; i < x2; i++)
{
for (j = y1; j < y2; j++)
{
guchar pixel[9][4];
/* Get nine pixels */
gimp_pixel_rgn_get_pixel (&rgn_in,
pixel[0],
MAX (i - 1, x1),
MAX (j - 1, y1));
gimp_pixel_rgn_get_pixel (&rgn_in,
pixel[1],
MAX (i - 1, x1),
j);
gimp_pixel_rgn_get_pixel (&rgn_in,
pixel[2],
MAX (i - 1, x1),
MIN (j + 1, y2 - 1));
gimp_pixel_rgn_get_pixel (&rgn_in,
pixel[3],
i,
MAX (j - 1, y1));
gimp_pixel_rgn_get_pixel (&rgn_in,
pixel[4],
i,
j);
gimp_pixel_rgn_get_pixel (&rgn_in,
pixel[5],
i,
MIN (j + 1, y2 - 1));
gimp_pixel_rgn_get_pixel (&rgn_in,
pixel[6],
MIN (i + 1, x2 - 1),
MAX (j - 1, y1));
gimp_pixel_rgn_get_pixel (&rgn_in,
pixel[7],
MIN (i + 1, x2 - 1),
j);
gimp_pixel_rgn_get_pixel (&rgn_in,
pixel[8],
MIN (i + 1, x2 - 1),
MIN (j + 1, y2 - 1));
/* For each layer, compute the average of the
* nine */
for (k = 0; k < channels; k++)
{
int tmp, sum = 0;
for (tmp = 0; tmp < 9; tmp++)
sum += pixel[tmp][k];
output[k] = sum / 9;
}
gimp_pixel_rgn_set_pixel (&rgn_out,
output,
i, j);
}
if (i % 10 == 0)
gimp_progress_update ((gdouble) (i - x1) / (gdouble) (x2 - x1));
}
/* Update the modified region */
gimp_drawable_flush (drawable);
gimp_drawable_merge_shadow (drawable->drawable_id, TRUE);
gimp_drawable_update (drawable->drawable_id,
x1, y1,
x2 - x1, y2 - y1);
}
Row processing
Our function has a bug drawback: performance. On a 300x300
selection, with the timing code uncommented, blur()
took 12
minutes on my K6-2 350MHz, well loaded with other stuff. To
compare, on the same selection, Gaussian blur took 3 seconds.
If we modify our function to rather use
gimp_pixel_rgn_(get|set)_row()
the result is far better. We
reduce the timing for the 300x300 selection from 760 seconds to
6 seconds. blur() V2 is below:
static void
blur (GimpDrawable *drawable)
{
gint i, j, k, channels;
gint x1, y1, x2, y2;
GimpPixelRgn rgn_in, rgn_out;
guchar *row1, *row2, *row3;
guchar *outrow;
gimp_drawable_mask_bounds (drawable->drawable_id,
&x1, &y1,
&x2, &y2);
channels = gimp_drawable_bpp (drawable->drawable_id);
gimp_pixel_rgn_init (&rgn_in,
drawable,
x1, y1,
x2 - x1, y2 - y1,
FALSE, FALSE);
gimp_pixel_rgn_init (&rgn_out,
drawable,
x1, y1,
x2 - x1, y2 - y1,
TRUE, TRUE);
/* Initialise enough memory for row1, row2, row3, outrow */
row1 = g_new (guchar, channels * (x2 - x1));
row2 = g_new (guchar, channels * (x2 - x1));
row3 = g_new (guchar, channels * (x2 - x1));
outrow = g_new (guchar, channels * (x2 - x1));
for (i = y1; i < y2; i++)
{
/* Get row i-1, i, i+1 */
gimp_pixel_rgn_get_row (&rgn_in,
row1,
x1, MAX (y1, i - 1),
x2 - x1);
gimp_pixel_rgn_get_row (&rgn_in,
row2,
x1, i,
x2 - x1);
gimp_pixel_rgn_get_row (&rgn_in,
row3,
x1, MIN (y2 - 1, i + 1),
x2 - x1);
for (j = x1; j < x2; j++)
{
/* For each layer, compute the average of the nine
* pixels */
for (k = 0; k < channels; k++)
{
int sum = 0;
sum = row1[channels * MAX ((j - 1 - x1), 0) + k] +
row1[channels * (j - x1) + k] +
row1[channels * MIN ((j + 1 - x1), x2 - x1 - 1) + k] +
row2[channels * MAX ((j - 1 - x1), 0) + k] +
row2[channels * (j - x1) + k] +
row2[channels * MIN ((j + 1 - x1), x2 - x1 - 1) + k] +
row3[channels * MAX ((j - 1 - x1), 0) + k] +
row3[channels * (j - x1) + k] +
row3[channels * MIN ((j + 1 - x1), x2 - x1 - 1) + k];
outrow[channels * (j - x1) + k] = sum / 9;
}
}
gimp_pixel_rgn_set_row (&rgn_out,
outrow,
x1, i,
x2 - x1);
if (i % 10 == 0)
gimp_progress_update ((gdouble) (i - y1) / (gdouble) (y2 - y1));
}
g_free (row1);
g_free (row2);
g_free (row3);
g_free (outrow);
gimp_drawable_flush (drawable);
gimp_drawable_merge_shadow (drawable->drawable_id, TRUE);
gimp_drawable_update (drawable->drawable_id,
x1, y1,
x2 - x1, y2 - y1);
}
Have a look at the slow or fast blur complete code.
Next part
In next part, we will see how to process the image tile by tile. We will also have a look at preferences, by modifying our algorithm so it can take an input parameter.
This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 2.5 License.