Difference between revisions of "Hacking:How to write a GIMP plug-in"

From GIMP Developer Wiki
Jump to: navigation, search
(integrate How to write a GIMP plug-in from developer.gimp.org (1))
(integrate How to write a GIMP plug-in from developer.gimp.org (2))
Line 4: Line 4:
  
  
 +
=Essentials=
 
In this article, I present GIMP plug-ins basics and introduce the libgimp API. I will also show how to use the PDB to make our plug-in available to other script authors.
 
In this article, I present GIMP plug-ins basics and introduce the libgimp API. I will also show how to use the PDB to make our plug-in available to other script authors.
 
=Essentials=
 
  
 
==Introduction==
 
==Introduction==
Line 201: Line 200:
  
 
Have a look at the full hello.c plug-in code.
 
Have a look at the full hello.c plug-in code.
 +
 +
Next part
 +
In the next part we will go on, making a more useful plug-in that will get its hands on image data. We will see how to use The GIMP image architecture to make the plug-in perform better, processing the image tile by tile.
 +
 +
=Processing images=
 +
 +
==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==
 +
 +
Last month, we wrote a run() function that did nothing useful. Let's look again at run() prototype:
 +
<pre>   
 +
      static void run (const gchar    *name,
 +
                      gint            nparams,
 +
                      const GimpParam *param,
 +
                      gint            *nreturn_vals,
 +
                      GimpParam      **return_vals);
 +
</pre>
 +
   
 +
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".
 +
 +
[[File:GimpImage.png|194px|Drawables]]
 +
 +
Drawables
 +
 +
==Accessing the data==
 +
 +
To get a GimpDrawable from its identifier, we need the gimp_drawable_get() function:
 +
<pre>   
 +
      GimpDrawable *gimp_drawable_get (gint32 drawable_id);
 +
</pre>   
 +
   
 +
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.
 +
 +
[[File:tiles.png|323px|Tiles]]
 +
 +
Tiles
 +
 +
The main functions to get and set image data are:
 +
<pre>   
 +
      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);
 +
</pre>   
 +
   
 +
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:
 +
<pre>   
 +
      gimp_displays_flush ();
 +
      gimp_drawable_detach (drawable);
 +
</pre> 
 +
   
 +
==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.
 +
<pre>   
 +
      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);
 +
      }
 +
</pre> 
 +
   
 +
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.
 +
<pre>   
 +
      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);
 +
      }
 +
</pre>   
 +
   
 +
==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:
 +
<pre>   
 +
      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);
 +
      }
 +
</pre>   
 +
   
 +
Have a look at the [[Hacking:slow plug-in|slow]] or [[Hacking:fast plug-in|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.
  
  
In next part we will go on, making a more useful plug-in that will get its hands on image data. We will see how to use The GIMP image architecture to make the plug-in perform better, processing the image tile by tile.
 
  
  

Revision as of 19:29, 26 January 2014

Written by Dave Neary.


Essentials

In this article, I present GIMP plug-ins basics and introduce the libgimp API. I will also show how to use the PDB to make our plug-in available to other script authors.

Introduction

New developers are often intimidated by The GIMP size and its reputation. They think that writing a plug-in would be a difficult task. The goal of these articles is to dumb this feeling down, by showing how easily one can make a C plug-in.

In this part, I present a plug-in's basic elements. We will see how to install a plug-in and how to get data from an image and directly manipulate it.


Architecture

Architecture


The GIMP script interface is centered on the Procedural database (PDB). At startup, The GIMP looks into a predefined set of places for scripts and plug-ins, and asks each new script to identify itself.

The plug-in declares itself to the PDB at that time, and passes informations like the position it wishes to get in the menu hierarchy, input parameters, and output parameters.

When a script or a plug-in wants to use our plug-in, it gets through the PDB, which manages communicating parameters in one direction and the other in a transparent way.

Internal functions that wish to get exposed to plug-ins have to be packaged first in the core, that will register them in the PDB, and secondly in the libgimp that will allow the function to be called as a normal one.

This was the introduction - now, we will look closer at our first plug-in, a "Hello, world!".


Compiling the plug-in

To be able to compile simple plug-ins for The GIMP, one needs libgimp headers, as well as an associated utility named gimptool.

With that utility, one can install a plug-in either in a private directory (~/.gimp-2.0/plug-ins), or in the global plug-in directory.

Syntax is

    
       gimptool-2.0 --install plugin.c or gimptool-2.0 --install-admin plugin.c

This utility, with other options, can also be used to install scripts, or uninstall plug-ins.


Behaviour

A GIMP plug-in can typically behave three different ways. It can take image data, modify it, and send back the modified image, like edge detection. It can generate an image and send it back, like some script-fus, or file reading plug-ins like jpeg. Or it can get an image, and process it without modifying its data, like a file saver plug-in.


Essentials

      #include <libgimp/gimp.h>

This header makes all basic plug-in elements available to us.

    
      GimpPlugInInfo PLUG_IN_INFO = {
        init,
        quit,
        query,
        run
      };

This structure has to have that name. It contains four pointers to functions, which will be called at set times of the plug-in life. init and quit are optional, and thus can hold NULL values, but the last two functions, query and run, are mandatory.

The init() function is called each time The GIMP starts up. This function is not typically used. Some plug-ins use it to make a secondary search that is not done by the core. This function is not used by any standard GIMP plug-in, but could be useful for example for a plug-in that would like to register some procedure conditionally on some files presence.

The quit() function is not used much either. It is called when The GIMP is about to be closed, to allow it to free some resources. It is used in the script-fu plug-in.

The query() function is called the first time the plug-in is present, and then each time the plug-in changes.

The run() function is the plug-in's centrepiece. It is called when the plug-in is asked to run. It gets the plug-in name (as a plug-in can register several procedures), input parameters, and a pointer to output parameters, then determines if it is launched in a interactive way or by a script, and does all the plug-in processing. Its prototype is

    
      void run (const gchar      *name,
                gint              nparams,
                const GimpParam  *param,
                gint             *nreturn_vals,
                GimpParam       **return_vals);


MAIN ()

MAIN is a C macro that holds a bit of dark magic to initialise arguments. It also calls the appropriate PLUG_IN_INFO function depending on the timing. Your plug-in needs it.

The query() function

query() deals with the procedure registration and input arguments definition. These informations are saved to speed up startup time, and refreshed only when the plug-in is modified.

For our "Hello, world!" plug-in, the query function will look like this:

    
      static void
      query (void)
        {
          static GimpParamDef args[] = {
            {
              GIMP_PDB_INT32,
              "run-mode",
              "Run mode"
            },
            {
              GIMP_PDB_IMAGE,
              "image",
              "Input image"
            },
            {
              GIMP_PDB_DRAWABLE,
              "drawable",
              "Input drawable"
            }
          };

          gimp_install_procedure (
            "plug-in-hello",
            "Hello, world!",
            "Displays \"Hello, world!\" in a dialog",
            "David Neary",
            "Copyright David Neary",
            "2004",
            "_Hello world...",
            "RGB*, GRAY*",
            GIMP_PLUGIN,
            G_N_ELEMENTS (args), 0,
            args, NULL);

            gimp_plugin_menu_register ("plug-in-hello",
                                       "/Filters/Misc"); 
        }

GimpParamDef contains three things - the parameter type, its name, and a string describing the parameter.

gimp_install_procedure declares the procedure name, some description and help strings, menu path where the plug-in should sit, image types handled by the plug-in, and at the end, input and output parameters number, as well as the parameters descriptors.

"RGB*, GRAY*" declares the image types handled. It can be RGB, INDEXED or GRAY, with or without Alpha. So "RGB*, GRAY*" describes RGB, RGBA, GRAY or GRAY image type.

GIMP_PLUGIN declares this procedure to be external, and not to be executed in The GIMP core.

By adding a stub run function now, we can check that our plug-in has all the essential elements, and test that it registers itself in the PDB with the "Xtns->Plug-in Details" plug-in.

Plug-in details

Plug-in details

Our plug-in is in the menus

Our plug-in is in the menus

The run() function

The other required function for PLUG_IN_INFO is run. The core of the plug-in stands there.

Output values (return_vals in the prototype) must have at least one value associated - the plug-in status. Typically, this parameter will hold "GIMP_PDB_SUCCESS".


Run-modes

One can run a plug-in in several different ways, it can be run from a GIMP menu if The GIMP is run interactively, or from a script or a batch, or from the "Filters->Repeat Last" shortcut.

The "run_mode" input parameter can hold one of these values: "GIMP_RUN_INTERACTIVE", "GIMP_RUN_NONINTERACTIVE" or "GIMP_RUN_WITH_LAST_VALS".

"GIMP_RUN_INTERACTIVE" is typically the only case where one creates an options dialog. Otherwise, one directly calls the processing with values from input parameters or from memory.

For our test plug-in, we will simply display a dialog containing a "Hello, world!" message. Thankfully, this is really easy with GTK+. Our run function could be:

    
      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;

        /* 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;

        if (run_mode != GIMP_RUN_NONINTERACTIVE)
          g_message("Hello, world!\n");
      }


Now, when we run our plug-in, there is action:

Hello, world!

Have a look at the full hello.c plug-in code.

Next part In the next part we will go on, making a more useful plug-in that will get its hands on image data. We will see how to use The GIMP image architecture to make the plug-in perform better, processing the image tile by tile.

Processing images

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

Last month, 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

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

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.



Creative Commons License

This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 2.5 License.