[gimp-web-devel/hugo: 6/16] writing a plug in translation
- From: Jehan <jehanp src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gimp-web-devel/hugo: 6/16] writing a plug in translation
- Date: Sat, 6 Aug 2022 14:04:34 +0000 (UTC)
commit 7d740f01460eaa4a004f0025d092e07d2383db48
Author: robin-swift <robinswiftart gmail com>
Date: Sat Jul 23 23:31:14 2022 +0100
writing a plug in translation
content/writing-a-plug-in/1.md | 642 +++++++---------
content/writing-a-plug-in/2.md | 764 +++++++++----------
content/writing-a-plug-in/3.md | 1628 ++++++++++++++++++----------------------
3 files changed, 1352 insertions(+), 1682 deletions(-)
---
diff --git a/content/writing-a-plug-in/1.md b/content/writing-a-plug-in/1.md
index 084676e..e8581c1 100644
--- a/content/writing-a-plug-in/1.md
+++ b/content/writing-a-plug-in/1.md
@@ -1,19 +1,18 @@
----
-title: "How to write a GIMP plug-in"
-abbrev: "Writing A Plug-In"
-description: "Write your own"
----
++++
+title = "How to write a GIMP plug-in"
+abbrev = "Writing A Plug-In"
+description = "Write your own"
+author = "Dave Neary"
++++
-Written By <ulink url="mailto:bolsh NOSPAM gimp org">Dave Neary</ulink>
+Written By [Dave Neary](mailto:bolsh NOSPAM gimp org).
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.
-<section>
-<title>Introduction</title>
+## Introduction
-<para>
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
@@ -25,19 +24,8 @@ directly manipulate it.
## Architecture
- <para role="images">
- <ulink url="architecture.png">
- <mediaobject>
- <imageobject>
- <imagedata fileref="architecture-small.png"/>
- </imageobject>
- <textobject>
- <phrase>Architecture</phrase>
- </textobject>
- </mediaobject>
- Architecture
- </ulink>
- </para>
+architecture.png
+[architecture.png](Architecture)
The GIMP script interface is centered on the Procedural database
(PDB). At startup, The GIMP looks into a predefined set of
@@ -62,369 +50,249 @@ first plug-in, a "Hello, world!".
## Compiling the plug-in
- <para>
- To be able to compile simple plug-ins for The GIMP, one needs
- libgimp headers, as well as an associated utility named
- gimptool.
- </para>
-
- <para>
- 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.
- </para>
-
- <para>
- Syntax is
- </para>
-
- <programlisting>
- <![CDATA[
- gimptool-2.0 --install plugin.c or gimptool-2.0 --install-admin plugin.c
- ]]>
- </programlisting>
-
- <para>
- This utility, with other options, can also be used to install
- scripts, or uninstall plug-ins.
- </para>
- </section>
-
- <section>
- <title>Behaviour</title>
-
- <para>
- 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.
- </para>
- </section>
-
- <section>
- <title>Essentials</title>
-
- <programlisting>
- <![CDATA[
- #include <libgimp/gimp.h>
- ]]>
- </programlisting>
-
- <para>
- This header makes all basic plug-in elements available to us.
- </para>
-
- <programlisting>
- <![CDATA[
- GimpPlugInInfo PLUG_IN_INFO = {
- init,
- quit,
- query,
- run
- };
- ]]>
- </programlisting>
-
- <para>
- 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.
- </para>
-
- <para>
- 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.
- </para>
-
- <para>
- 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.
- </para>
-
- <para>
- The query() function is called the first time the plug-in is
- present, and then each time the plug-in changes.
- </para>
-
- <para>
- 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
- </para>
-
- <programlisting>
- <![CDATA[
- void run (const gchar *name,
- gint nparams,
- const GimpParam *param,
- gint *nreturn_vals,
- GimpParam **return_vals);
- ]]>
- </programlisting>
-
- </section>
-
- <section>
- <title>MAIN ()</title>
-
- <para>
- 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.
- </para>
- </section>
-
- <section>
- <title>The query() function</title>
-
- <para>
- 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.
- </para>
-
- <para>
- For our "Hello, world!" plug-in, the query function will look
- like this:
- </para>
-
- <programlisting>
- <![CDATA[
- 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",
- "<Image>/Filters/Misc");
- }
- ]]>
- </programlisting>
-
- <para>
- GimpParamDef contains three things - the parameter type, its
- name, and a string describing the parameter.
- </para>
-
- <para>
- 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.
- </para>
-
- <para>
- "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.
- </para>
-
- <para>
- GIMP_PLUGIN declares this procedure to be external, and not to
- be executed in The GIMP core.
- </para>
-
- <para>
- 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.
- </para>
-
- <para role="images">
- <ulink url="plug-in-details.png">
- <mediaobject>
- <imageobject>
- <imagedata fileref="plug-in-details-small.png"/>
- </imageobject>
- <textobject>
- <phrase>Plug-in details</phrase>
- </textobject>
- </mediaobject>
- Plug-in details
- </ulink>
- </para>
-
- <para role="images">
- <ulink url="plug-in-menu.png">
- <mediaobject>
- <imageobject>
- <imagedata fileref="plug-in-menu-small.png"/>
- </imageobject>
- <textobject>
- <phrase>Our plug-in is in the menus</phrase>
- </textobject>
- </mediaobject>
- Our plug-in is in the menus
- </ulink>
- </para>
- </section>
-
- <section>
- <title>The run() function</title>
-
- <para>
- The other required function for PLUG_IN_INFO is run. The core of
- the plug-in stands there.
- </para>
-
- <para>
- 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".
- </para>
- </section>
-
- <section>
- <title>Run-modes</title>
-
- <para>
- 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.
- </para>
-
- <para>
- The "run_mode" input parameter can hold one of these values:
- "GIMP_RUN_INTERACTIVE", "GIMP_RUN_NONINTERACTIVE" or
- "GIMP_RUN_WITH_LAST_VALS".
- </para>
-
- <para>
- "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.
- </para>
-
- <para>
- 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:
- </para>
-
- <programlisting>
- <![CDATA[
- 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");
- }
- ]]>
- </programlisting>
-
- <para>
- Now, when we run our plug-in, there is action:
- </para>
-
- <para role="images">
- <mediaobject>
- <imageobject>
- <imagedata fileref="hello.png"/>
- </imageobject>
- <textobject>
- <phrase>Hello, world!</phrase>
- </textobject>
- </mediaobject>
- </para>
-
- <para>
- Have a look at the full <ulink url="hello.c">hello.c</ulink> plug-in code.
- </para>
- </section>
-
- <section>
- <title>Next part</title>
-
- <para>
- In <olink targetdocent="writing-a-plug-in-2">next part</olink>
- 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.
- </para>
- </section>
-
- <section>
- <para role="images">
- <ulink url="http://creativecommons.org/licenses/by-nc-sa/2.5/">
- <mediaobject>
- <imageobject>
- <imagedata fileref="http://creativecommons.org/images/public/somerights20.gif"/>
- </imageobject>
- <textobject>
- <phrase>Creative Commons License</phrase>
- </textobject>
- </mediaobject>
- </ulink>
- </para>
-
- <para>
- This work is licensed under a <ulink
- url="http://creativecommons.org/licenses/by-nc-sa/2.5/">Creative
- Commons Attribution-NonCommercial-ShareAlike 2.5
- License</ulink>.
- </para>
- </section>
-
-</webpage>
+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",
+ "<Image>/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.png
+plug-in-details-small.png
+Plug-in details
+
+plug-in-menu.png
+plug-in-menu-small.png
+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.png](Hello, world!)
+
+Have a look at the full [hello.c](hello.c) plug-in code.
+
+## Next part
+
+In <olink targetdocent="writing-a-plug-in-2">next part</olink>
+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.
+
+<para role="images">
+<ulink url="http://creativecommons.org/licenses/by-nc-sa/2.5/">
+<mediaobject>
+<imageobject>
+<imagedata fileref="http://creativecommons.org/images/public/somerights20.gif"/>
+</imageobject>
+<textobject>
+<phrase>Creative Commons License</phrase>
+</textobject>
+</mediaobject>
+</ulink>
+
+This work is licensed under a <ulink
+url="http://creativecommons.org/licenses/by-nc-sa/2.5/">Creative
+Commons Attribution-NonCommercial-ShareAlike 2.5
+License</ulink>.
diff --git a/content/writing-a-plug-in/2.md b/content/writing-a-plug-in/2.md
index 92a8b8e..6d959bc 100644
--- a/content/writing-a-plug-in/2.md
+++ b/content/writing-a-plug-in/2.md
@@ -1,13 +1,14 @@
----
-title: "How to write a GIMP plug-in, part II"
-abbrev: "Part II"
-description: "Write your own"
----
++++
+title = "How to write a GIMP plug-in, part II"
+abbrev = "Part II"
+description = "Write your own"
+author = "Dave Neary"
++++
-Written By <ulink url="mailto:bolsh NOSPAM gimp org">Dave Neary</ulink>
+Written By [Dave Neary](mailto:bolsh NOSPAM gimp org).
-In the <olink targetdocent="writing-a-plug-in-1">first
-part</olink>, I presented essential elements to build a plug-in
+In the [first part](writing-a-plug-in/1),
+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.
@@ -24,8 +25,8 @@ 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.
+blurred result. One can choose another radius, using a `(2r + 1)
+x (2r + 1)` matrix.
## Image structure
@@ -52,128 +53,92 @@ word "drawable" is often used in GIMP internal structures. A
raw data. So : layers, layer masks, selections are all
"drawables".
- <para role="images">
- <ulink url="GimpImage.png">
- <mediaobject>
- <imageobject>
- <imagedata fileref="GimpImage-small.png"/>
- </imageobject>
- <textobject>
- <phrase>Drawables</phrase>
- </textobject>
- </mediaobject>
- Drawables
- </ulink>
+GimpImage.png
+GimpImage-small.png
+Drawables
## Accessing the data
- <para>
- To get a GimpDrawable from its identifier, we need the
- gimp_drawable_get() function:
- </para>
-
- <programlisting>
- <![CDATA[
- GimpDrawable *gimp_drawable_get (gint32 drawable_id);
- ]]>
- </programlisting>
-
- <para>
- 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 <ulink
- url="http://developer.gimp.org/api/2.0/libgimp/libgimp-gimpdrawable.html">in
- the API</ulink>.
- </para>
-
- <para>
- 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.
- </para>
-
- <para>
- 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.
- </para>
-
- <para role="images">
- <ulink url="tiles.png">
- <mediaobject>
- <imageobject>
- <imagedata fileref="tiles-small.png"/>
- </imageobject>
- <textobject>
- <phrase>Tiles</phrase>
- </textobject>
- </mediaobject>
- Tiles
- </ulink>
- </para>
-
- <para>
- The main functions to get and set image data are:
- </para>
-
- <programlisting>
- <![CDATA[
- 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);
- ]]>
- </programlisting>
-
- <para>
- 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.
- </para>
- </section>
-
+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 <ulink
+url="http://developer.gimp.org/api/2.0/libgimp/libgimp-gimpdrawable.html">in
+the API</ulink>.
+
+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.
+
+<ulink url="tiles.png">
+<imagedata fileref="tiles-small.png"/>
+<phrase>Tiles</phrase>
+
+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
@@ -182,8 +147,8 @@ the display must be updated. This is done with the following
function:
```
- gimp_displays_flush ();
- gimp_drawable_detach (drawable);
+gimp_displays_flush ();
+gimp_drawable_detach (drawable);
```
## Implementing blur()
@@ -192,48 +157,48 @@ 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);
- }
+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.
@@ -270,108 +235,108 @@ 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);
- }
+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
@@ -386,137 +351,122 @@ 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:
- <programlisting>
- <![CDATA[
- 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));
- }
+```
+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](myblur1.c) or [fast](myblur2.c) blur complete code.
+
+## Next part
+
+In [next part](writing-a-plug-in/3),
+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.
+
+
+<para role="images">
+<ulink url="http://creativecommons.org/licenses/by-nc-sa/2.5/">
+<mediaobject>
+<imageobject>
+<imagedata fileref="http://creativecommons.org/images/public/somerights20.gif"/>
+</imageobject>
+<textobject>
+<phrase>Creative Commons License</phrase>
+</textobject>
+</mediaobject>
+</ulink>
+
+This work is licensed under a <ulink
+url="http://creativecommons.org/licenses/by-nc-sa/2.5/">Creative
+Commons Attribution-NonCommercial-ShareAlike 2.5
+License</ulink>.
- 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);
- }
- ]]>
- </programlisting>
-
- <para>
- Have a look at the <ulink url="myblur1.c">slow</ulink> or
- <ulink url="myblur2.c">fast</ulink> blur complete code.
- </para>
- </section>
-
- <section>
- <title>Next part</title>
-
- <para>
- In <olink targetdocent="writing-a-plug-in-3">next part</olink>,
- 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.
- </para>
- </section>
-
- <section>
- <para role="images">
- <ulink url="http://creativecommons.org/licenses/by-nc-sa/2.5/">
- <mediaobject>
- <imageobject>
- <imagedata fileref="http://creativecommons.org/images/public/somerights20.gif"/>
- </imageobject>
- <textobject>
- <phrase>Creative Commons License</phrase>
- </textobject>
- </mediaobject>
- </ulink>
- </para>
-
- <para>
- This work is licensed under a <ulink
- url="http://creativecommons.org/licenses/by-nc-sa/2.5/">Creative
- Commons Attribution-NonCommercial-ShareAlike 2.5
- License</ulink>.
- </para>
- </section>
-
-</webpage>
diff --git a/content/writing-a-plug-in/3.md b/content/writing-a-plug-in/3.md
index 620bb43..233d834 100644
--- a/content/writing-a-plug-in/3.md
+++ b/content/writing-a-plug-in/3.md
@@ -1,895 +1,747 @@
----
-title: "How to write a GIMP plug-in, part III"
-abbrev: "Part III"
-description: "Write your own"
----
-
-Written By <ulink url="mailto:bolsh NOSPAM gimp org">Dave Neary</ulink>
-
- In the <olink targetdocent="writing-a-plug-in-2">second
- part</olink>, I told you about manipulating image data by pixel or
- row. This time, I will go farther and process data by tile, which
- will improve our plug-in performance. I will also update our
- algorithm to take larger radius into account, and build a
- graphical interface to allow changing that parameter.
- </para>
-
- <section>
- <title>Introduction</title>
-
- <para>
- Let's have a look at our simple algorithm: for each pixel,
- generate a (2r+1)x(2r+1) neighbourhood and for each layer,
- replace the layer's pixel value with the average value in the
- neighbourhood.
- </para>
-
- <para>
- It's a bit more complex than that - we have to be careful near
- image borders for example, but this algorithm makes a blur
- effect that is not so bad in general.
- </para>
-
- <para>
- But until now, we wrote the algorithm for a 3x3 neighbourhood.
- Time has come to generalise this part and to introduce the
- radius as a parameter.
- </para>
-
- <para>
- First, a word on tiles.
- </para>
- </section>
-
- <section>
- <title>Tile management</title>
-
- <para>
- A tile is an image data block with a 64x64 size. Usually, tiles
- are sent to the plug-in on demand one by one, by shared memory.
- Of course this process needs huge resources and should be
- avoided.
- </para>
-
- <para>
- Usually, one doesn't need any particular cache, each tile is
- sent when one needs it and freed when one asks for another one.
- Nevertheless, we can tell our plug-in to keep a tile cache to
- avoid this constant round trip, by calling the function:
- </para>
-
- <programlisting>
- <![CDATA[
- gimp_tile_cache_ntiles (gulong ntiles);
- ]]>
- </programlisting>
-
- <para>
- In the second part example, we called gimp_pixel_rgn_get_row()
- and gimp_pixel_rgn_set_row() but without using any cache.
- </para>
-
- <para>
- The number of tiles in a tile row will be the layer width
- divided by the tile width, plus one. So, for a layer width of
- 65, we will cache two tiles. As we usually also process shadow
- tiles, we can double that number to compute the ideal cache size
- for our plug-in.
- </para>
-
- <programlisting>
- <![CDATA[
- gimp_tile_cache_ntiles (2 * (drawable->width /
- gimp_tile_width () + 1));
- ]]>
- </programlisting>
-
- <para>
- With the cache, our slow plug-in becomes fast. On a 300x300
- selection, our last blur took 3 seconds, but on a 2000x1500
- selection it was much slower - 142 seconds.
- </para>
-
- <para>
- Adding the above line of code, things are getting better: 11
- seconds. We still lose transition time when we reach tile
- borders, we can go down to 10 seconds when multiplying by 4
- instead of 2 (meaning we cache two tiles rows), but the more
- tiles we cache, the more hard disk access we make, which reduce
- the time gain at a point.
- </para>
- </section>
-
- <section>
- <title>Algorithm generalisation</title>
-
- <para>
- We can modify the algorithm to take a parameter into account:
- radius. With a radius of 3, the neighbourhood of a pixel will be
- 7x7, instead of 3x3 with a radius of 1. To achieve this I modify
- the previous algorithm:
- <itemizedlist>
- <listitem>allocate space for 2r+1 tile rows</listitem>
- <listitem>initialise this rows array, taking care of
- borders</listitem>
- <listitem>for each tile row
- <itemizedlist>
- <listitem>for each pixel in the tile row
- <itemizedlist>
- <listitem>compute the neighbourhood average, taking
- care of borders</listitem>
- </itemizedlist>
- </listitem>
- <listitem>get a new tile row and cycle rows</listitem>
- </itemizedlist>
- </listitem>
- </itemizedlist>
- </para>
-
- <para>
- This algorithm is more complex than the last one, because the
- average computing will be a O(r²) algorithm.
- </para>
-
- <para>
- The modified code to get this behaviour is below. Most of the
- work is done in the process_row function. init_mem and shuffle
- are there to keep the blur code clean and small.
- </para>
++++
+title = "How to write a GIMP plug-in, part III"
+abbrev = "Part III"
+description = "Write your own"
+date = "2022-07-23"
+author = "Dave Neary"
++++
+
+Written By [Dave Neary](mailto:bolsh NOSPAM gimp org).
+
+In the [second part](writing-a-plug-in/2),
+I told you about manipulating image data by pixel or
+row. This time, I will go farther and process data by tile, which
+will improve our plug-in performance. I will also update our
+algorithm to take larger radius into account, and build a
+graphical interface to allow changing that parameter.
+
+## Introduction
+
+Let's have a look at our simple algorithm: for each pixel,
+generate a `(2r+1)x(2r+1)` neighbourhood and for each layer,
+replace the layer's pixel value with the average value in the
+neighbourhood.
+
+It's a bit more complex than that - we have to be careful near
+image borders for example, but this algorithm makes a blur
+effect that is not so bad in general.
+
+But until now, we wrote the algorithm for a 3x3 neighbourhood.
+Time has come to generalise this part and to introduce the
+radius as a parameter.
+
+First, a word on tiles.
+
+## Tile management
+
+A tile is an image data block with a 64x64 size. Usually, tiles
+are sent to the plug-in on demand one by one, by shared memory.
+Of course this process needs huge resources and should be
+avoided.
+
+Usually, one doesn't need any particular cache, each tile is
+sent when one needs it and freed when one asks for another one.
+Nevertheless, we can tell our plug-in to keep a tile cache to
+avoid this constant round trip, by calling the function:
```
- <![CDATA[
- static void blur (GimpDrawable *drawable);
-
- static void init_mem (guchar ***row,
- guchar **outrow,
- gint num_bytes);
- static void process_row (guchar **row,
- guchar *outrow,
- gint x1,
- gint y1,
- gint width,
- gint height,
- gint channels,
- gint i);
- static void shuffle (GimpPixelRgn *rgn_in,
- guchar **row,
- gint x1,
- gint y1,
- gint width,
- gint height,
- gint ypos);
-
- /* The radius is still a constant, we'll change that when the
- * graphical interface will be built. */
- static gint radius = 3;
- ...
-
- static void
- blur (GimpDrawable *drawable)
- {
- gint i, ii, channels;
- gint x1, y1, x2, y2;
- GimpPixelRgn rgn_in, rgn_out;
- guchar **row;
- guchar *outrow;
- gint width, height;
-
- gimp_progress_init ("My Blur...");
-
- /* Gets upper left and lower right coordinates,
- * and layers number in the image */
- gimp_drawable_mask_bounds (drawable->drawable_id,
- &x1, &y1,
- &x2, &y2);
- width = x2 - x1;
- height = y2 - y1;
-
- channels = gimp_drawable_bpp (drawable->drawable_id);
-
- /* Allocate a big enough tile cache */
- gimp_tile_cache_ntiles (2 * (drawable->width /
- gimp_tile_width () + 1));
-
- /* 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,
- width, height,
- FALSE, FALSE);
- gimp_pixel_rgn_init (&rgn_out,
- drawable,
- x1, y1,
- width, height,
- TRUE, TRUE);
-
- /* Allocate memory for input and output tile rows */
- init_mem (&row, &outrow, width * channels);
-
- for (ii = -radius; ii <= radius; ii++)
- {
- gimp_pixel_rgn_get_row (&rgn_in,
- row[radius + ii],
- x1, y1 + CLAMP (ii, 0, height - 1),
- width);
- }
-
- for (i = 0; i < height; i++)
- {
- /* To be done for each tile row */
- process_row (row,
- outrow,
- x1, y1,
- width, height,
- channels,
- i);
- gimp_pixel_rgn_set_row (&rgn_out,
- outrow,
- x1, i + y1,
- width);
- /* shift tile rows to insert the new one at the end */
- shuffle (&rgn_in,
- row,
- x1, y1,
- width, height,
- i);
- if (i % 10 == 0)
- gimp_progress_update ((gdouble) i / (gdouble) height);
- }
-
- /* We could also put that in a separate function but it's
- * rather simple */
- for (ii = 0; ii < 2 * radius + 1; ii++)
- g_free (row[ii]);
-
- g_free (row);
- g_free (outrow);
-
- /* Update the modified region */
- gimp_drawable_flush (drawable);
- gimp_drawable_merge_shadow (drawable->drawable_id, TRUE);
- gimp_drawable_update (drawable->drawable_id,
- x1, y1,
- width, height);
- }
-
- static void
- init_mem (guchar ***row,
- guchar **outrow,
- gint num_bytes)
- {
- gint i;
-
- /* Allocate enough memory for row and outrow */
- *row = g_new (char *, (2 * radius + 1));
-
- for (i = -radius; i <= radius; i++)
- (*row)[i + radius] = g_new (guchar, num_bytes);
-
- *outrow = g_new (guchar, num_bytes);
- }
-
- static void
- process_row (guchar **row,
- guchar *outrow,
- gint x1,
- gint y1,
- gint width,
- gint height,
- gint channels,
- gint i)
- {
- gint j;
-
- for (j = 0; j < width; j++)
- {
- gint k, ii, jj;
- gint left = (j - radius),
- right = (j + radius);
-
- /* For each layer, compute the average of the
- * (2r+1)x(2r+1) pixels */
- for (k = 0; k < channels; k++)
- {
- gint sum = 0;
-
- for (ii = 0; ii < 2 * radius + 1; ii++)
- for (jj = left; jj <= right; jj++)
- sum += row[ii][channels * CLAMP (jj, 0, width - 1) + k];
-
- outrow[channels * j + k] =
- sum / (4 * radius * radius + 4 * radius + 1);
- }
- }
- }
-
- static void
- shuffle (GimpPixelRgn *rgn_in,
- guchar **row,
- gint x1,
- gint y1,
- gint width,
- gint height,
- gint ypos)
- {
- gint i;
- guchar *tmp_row;
-
- /* Get tile row (i + radius + 1) into row[0] */
- gimp_pixel_rgn_get_row (rgn_in,
- row[0],
- x1, MIN (ypos + radius + y1, y1 + height - 1),
- width);
-
- /* Permute row[i] with row[i-1] and row[0] with row[2r] */
- tmp_row = row[0];
- for (i = 1; i < 2 * radius + 1; i++)
- row[i - 1] = row[i];
- row[2 * radius] = tmp_row;
- }
- ]]>
- </programlisting>
- </section>
-
- <section>
- <title>Adding a graphical interface and saving parameters</title>
-
- <para>
- To let the user modify the radius, or let a non-interactive
- script give it as a parameter, we now need to get back to our
- run() function and settle some simple things.
- </para>
-
- <para>
- First we create a structure to allow saving and returning
- options. Usually one does this even for plug-ins with only one
- parameter.
- </para>
-
- <programlisting>
- <![CDATA[
- typedef struct
- {
- gint radius;
- } MyBlurVals;
-
-
- /* Set up default values for options */
- static MyBlurVals bvals =
- {
- 3 /* radius */
- };
- ]]>
- </programlisting>
-
- <para>
- Next, we modify the run() function so that execution modes are
- taken into account. In interactive mode and repeat last filter
- mode, we try to get the last values used by the gimp_get_data()
- function, which takes a unique data identifier as its first
- input parameter. Usually, one uses the procedure's name.
- </para>
-
- <para>
- Finally, in interactive mode, we add a few lines that will build
- the graphical interface allowing options modification.
- </para>
-
- <programlisting>
- <![CDATA[
- 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);
-
- switch (run_mode)
- {
- case GIMP_RUN_INTERACTIVE:
- /* Get options last values if needed */
- gimp_get_data ("plug-in-myblur", &bvals);
-
- /* Display the dialog */
- if (! blur_dialog (drawable))
- return;
- break;
-
- case GIMP_RUN_NONINTERACTIVE:
- if (nparams != 4)
- status = GIMP_PDB_CALLING_ERROR;
- if (status == GIMP_PDB_SUCCESS)
- bvals.radius = param[3].data.d_int32;
- break;
-
- case GIMP_RUN_WITH_LAST_VALS:
- /* Get options last values if needed */
- gimp_get_data ("plug-in-myblur", &bvals);
- break;
-
- default:
- break;
- }
-
- blur (drawable);
-
- gimp_displays_flush ();
- gimp_drawable_detach (drawable);
-
- /* Finally, set options in the core */
- if (run_mode == GIMP_RUN_INTERACTIVE)
- gimp_set_data ("plug-in-myblur", &bvals, sizeof (MyBlurVals));
+gimp_tile_cache_ntiles (gulong ntiles);
+```
- return;
- }
- ]]>
- </programlisting>
- </section>
-
- <section>
- <title>The graphical interface</title>
-
- <para>
- I won't detail GTK+ programming as this is done very well in
- other places. Our first try will be very simple. We will use the
- utility widget of GIMP, the GimpDialog, to create a window with
- a header, a numeric control of type GtkSpinButton (associated
- with a GtkAdjustment) and its label, nicely framed in a
- GtkFrame.
- </para>
-
- <para>
- In the following parts, in order to show how easy one can do
- such things, I will add a preview in the dialog to show real
- time effects of the parameters.
- </para>
-
- <para>
- Our final dialog will look like this (tree generated with
- Glade):
- </para>
-
- <para role="images">
- <ulink url="glade-tree.png">
- <mediaobject>
- <imageobject>
- <imagedata fileref="glade-tree-small.png"/>
- </imageobject>
- <textobject>
- <phrase>Glade tree</phrase>
- </textobject>
- </mediaobject>
- Glade tree
- </ulink>
- </para>
-
- <para>
- In The GIMP 2.2, there is a number of widgets that come bundled
- with parameters that allow a coherent behaviour, consistent with
- GNOME Human Interface Guidelines. GimpPreview also appeared in
- 2.2. Let's make a first try without it:
- </para>
-
- <para role="images">
- <mediaobject>
- <imageobject>
- <imagedata fileref="blur_dialog1.png"/>
- </imageobject>
- <textobject>
- <phrase>Blur dialog</phrase>
- </textobject>
- </mediaobject>
- </para>
-
- <programlisting>
- <![CDATA[
- static gboolean
- blur_dialog (GimpDrawable *drawable)
- {
- GtkWidget *dialog;
- GtkWidget *main_vbox;
- GtkWidget *main_hbox;
- GtkWidget *frame;
- GtkWidget *radius_label;
- GtkWidget *alignment;
- GtkWidget *spinbutton;
- GtkObject *spinbutton_adj;
- GtkWidget *frame_label;
- gboolean run;
-
- gimp_ui_init ("myblur", FALSE);
-
- dialog = gimp_dialog_new ("My blur", "myblur",
- NULL, 0,
- gimp_standard_help_func, "plug-in-myblur",
-
- GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
- GTK_STOCK_OK, GTK_RESPONSE_OK,
-
- NULL);
-
- main_vbox = gtk_vbox_new (FALSE, 6);
- gtk_container_add (GTK_CONTAINER (GTK_DIALOG (dialog)->vbox), main_vbox);
- gtk_widget_show (main_vbox);
-
- frame = gtk_frame_new (NULL);
- gtk_widget_show (frame);
- gtk_box_pack_start (GTK_BOX (main_vbox), frame, TRUE, TRUE, 0);
- gtk_container_set_border_width (GTK_CONTAINER (frame), 6);
-
- alignment = gtk_alignment_new (0.5, 0.5, 1, 1);
- gtk_widget_show (alignment);
- gtk_container_add (GTK_CONTAINER (frame), alignment);
- gtk_alignment_set_padding (GTK_ALIGNMENT (alignment), 6, 6, 6, 6);
-
- main_hbox = gtk_hbox_new (FALSE, 0);
- gtk_widget_show (main_hbox);
- gtk_container_add (GTK_CONTAINER (alignment), main_hbox);
-
- radius_label = gtk_label_new_with_mnemonic ("_Radius:");
- gtk_widget_show (radius_label);
- gtk_box_pack_start (GTK_BOX (main_hbox), radius_label, FALSE, FALSE, 6);
- gtk_label_set_justify (GTK_LABEL (radius_label), GTK_JUSTIFY_RIGHT);
-
- spinbutton_adj = gtk_adjustment_new (3, 1, 16, 1, 5, 5);
- spinbutton = gtk_spin_button_new (GTK_ADJUSTMENT (spinbutton_adj), 1, 0);
- gtk_widget_show (spinbutton);
- gtk_box_pack_start (GTK_BOX (main_hbox), spinbutton, FALSE, FALSE, 6);
- gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (spinbutton), TRUE);
-
- frame_label = gtk_label_new ("<b>Modify radius</b>");
- gtk_widget_show (frame_label);
- gtk_frame_set_label_widget (GTK_FRAME (frame), frame_label);
- gtk_label_set_use_markup (GTK_LABEL (frame_label), TRUE);
-
- g_signal_connect (spinbutton_adj, "value_changed",
- G_CALLBACK (gimp_int_adjustment_update),
- &bvals.radius);
- gtk_widget_show (dialog);
-
- run = (gimp_dialog_run (GIMP_DIALOG (dialog)) == GTK_RESPONSE_OK);
-
- gtk_widget_destroy (dialog);
-
- return run;
- }
- ]]>
- </programlisting>
- </section>
-
- <section>
- <title>Adding a GimpPreview</title>
-
- <para>
- Adding a GimpPreview is quite easy. First we create a GtkWidget
- with gimp_drawable_preview_new(), then we attach an invalidated
- signal to it, which will call the blur function to update the
- preview. We also add a second parameter to MyBlurVals to
- remember the activation state of the preview.
- </para>
-
- <para>
- A method to update easily the preview is to add a preview
- parameter in the blur function, and if preview is not NULL, to
- take GimpPreview limits. So when we call blur from run(), we set
- the preview parameter to NULL.
- </para>
-
- <para>
- To take GimpPreview limits, we use gimp_preview_get_position()
- and gimp_preview_get_size(), so we can generate only what will
- be displayed.
- </para>
-
- <para>
- To achieve this the right way we'll tune some of the code - we
- don't need to update the progress bar while generating the
- preview, and we should tell at GimpPixelRgn init time that the
- tiles should not be sent back to the core.
- </para>
-
- <para>
- Finally, we display the updated preview with the
- gimp_drawable_preview_draw_region() function. We get a dialog
- box that shows us in real time the plug-in effects. Moreover,
- thanks to the GIMP core, our plug-in already takes selections
- into account.
- </para>
-
- <para role="images">
- <ulink url="blur_dialog2.png">
- <mediaobject>
- <imageobject>
- <imagedata fileref="blur_dialog2-small.png"/>
- </imageobject>
- <textobject>
- <phrase>Blur dialog, improved</phrase>
- </textobject>
- </mediaobject>
- Blur dialog, improved
- </ulink>
- </para>
-
- <para role="images">
- <ulink url="blur_select.png">
- <mediaobject>
- <imageobject>
- <imagedata fileref="blur_select-small.png"/>
- </imageobject>
- <textobject>
- <phrase>Blur a selection</phrase>
- </textobject>
- </mediaobject>
- Blur a selection
- </ulink>
- </para>
-
- <para>
- Here are the two functions in their last version:
- </para>
-
- <programlisting>
- <![CDATA[
- static void
- blur (GimpDrawable *drawable,
- GimpPreview *preview)
- {
- gint i, ii, channels;
- gint x1, y1, x2, y2;
- GimpPixelRgn rgn_in, rgn_out;
- guchar **row;
- guchar *outrow;
- gint width, height;
-
- if (!preview)
- gimp_progress_init ("My Blur...");
-
- /* Gets upper left and lower right coordinates,
- * and layers number in the image */
- if (preview)
- {
- gimp_preview_get_position (preview, &x1, &y1);
- gimp_preview_get_size (preview, &width, &height);
- x2 = x1 + width;
- y2 = y1 + height;
- }
- else
+In the second part example, we called `gimp_pixel_rgn_get_row()`
+and `gimp_pixel_rgn_set_row()` but without using any cache.
+
+The number of tiles in a tile row will be the layer width
+divided by the tile width, plus one. So, for a layer width of
+65, we will cache two tiles. As we usually also process shadow
+tiles, we can double that number to compute the ideal cache size
+for our plug-in.
+
+```
+gimp_tile_cache_ntiles (2 * (drawable->width /
+ gimp_tile_width () + 1));
+```
+
+With the cache, our slow plug-in becomes fast. On a 300x300
+selection, our last blur took 3 seconds, but on a 2000x1500
+selection it was much slower - 142 seconds.
+
+Adding the above line of code, things are getting better: 11
+seconds. We still lose transition time when we reach tile
+borders, we can go down to 10 seconds when multiplying by 4
+instead of 2 (meaning we cache two tiles rows), but the more
+tiles we cache, the more hard disk access we make, which reduce
+the time gain at a point.
+
+## Algorithm generalisation
+
+We can modify the algorithm to take a parameter into account:
+radius. With a radius of 3, the neighbourhood of a pixel will be
+7x7, instead of 3x3 with a radius of 1. To achieve this I modify
+the previous algorithm:
+
+* allocate space for 2r+1 tile rows
+* initialise this rows array, taking care of borders
+* for each tile row
+ * for each pixel in the tile row
+ * compute the neighbourhood average, taking care of borders
+* get a new tile row and cycle rows
+
+This algorithm is more complex than the last one, because the
+average computing will be a O(r²) algorithm.
+
+The modified code to get this behaviour is below. Most of the
+work is done in the process_row function. init_mem and shuffle
+are there to keep the blur code clean and small.
+
+```
+static void blur (GimpDrawable *drawable);
+
+static void init_mem (guchar ***row,
+ guchar **outrow,
+ gint num_bytes);
+static void process_row (guchar **row,
+ guchar *outrow,
+ gint x1,
+ gint y1,
+ gint width,
+ gint height,
+ gint channels,
+ gint i);
+static void shuffle (GimpPixelRgn *rgn_in,
+ guchar **row,
+ gint x1,
+ gint y1,
+ gint width,
+ gint height,
+ gint ypos);
+
+/* The radius is still a constant, we'll change that when the
+ * graphical interface will be built. */
+static gint radius = 3;
+...
+
+static void
+blur (GimpDrawable *drawable)
+{
+ gint i, ii, channels;
+ gint x1, y1, x2, y2;
+ GimpPixelRgn rgn_in, rgn_out;
+ guchar **row;
+ guchar *outrow;
+ gint width, height;
+
+ gimp_progress_init ("My Blur...");
+
+ /* Gets upper left and lower right coordinates,
+ * and layers number in the image */
+ gimp_drawable_mask_bounds (drawable->drawable_id,
+ &x1, &y1,
+ &x2, &y2);
+ width = x2 - x1;
+ height = y2 - y1;
+
+ channels = gimp_drawable_bpp (drawable->drawable_id);
+
+ /* Allocate a big enough tile cache */
+ gimp_tile_cache_ntiles (2 * (drawable->width /
+ gimp_tile_width () + 1));
+
+ /* 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,
+ width, height,
+ FALSE, FALSE);
+ gimp_pixel_rgn_init (&rgn_out,
+ drawable,
+ x1, y1,
+ width, height,
+ TRUE, TRUE);
+
+ /* Allocate memory for input and output tile rows */
+ init_mem (&row, &outrow, width * channels);
+
+ for (ii = -radius; ii <= radius; ii++)
+ {
+ gimp_pixel_rgn_get_row (&rgn_in,
+ row[radius + ii],
+ x1, y1 + CLAMP (ii, 0, height - 1),
+ width);
+ }
+
+ for (i = 0; i < height; i++)
+ {
+ /* To be done for each tile row */
+ process_row (row,
+ outrow,
+ x1, y1,
+ width, height,
+ channels,
+ i);
+ gimp_pixel_rgn_set_row (&rgn_out,
+ outrow,
+ x1, i + y1,
+ width);
+ /* shift tile rows to insert the new one at the end */
+ shuffle (&rgn_in,
+ row,
+ x1, y1,
+ width, height,
+ i);
+ if (i % 10 == 0)
+ gimp_progress_update ((gdouble) i / (gdouble) height);
+ }
+
+ /* We could also put that in a separate function but it's
+ * rather simple */
+ for (ii = 0; ii < 2 * radius + 1; ii++)
+ g_free (row[ii]);
+
+ g_free (row);
+ g_free (outrow);
+
+ /* Update the modified region */
+ gimp_drawable_flush (drawable);
+ gimp_drawable_merge_shadow (drawable->drawable_id, TRUE);
+ gimp_drawable_update (drawable->drawable_id,
+ x1, y1,
+ width, height);
+}
+
+static void
+init_mem (guchar ***row,
+ guchar **outrow,
+ gint num_bytes)
+{
+ gint i;
+
+ /* Allocate enough memory for row and outrow */
+ *row = g_new (char *, (2 * radius + 1));
+
+ for (i = -radius; i <= radius; i++)
+ (*row)[i + radius] = g_new (guchar, num_bytes);
+
+ *outrow = g_new (guchar, num_bytes);
+}
+
+static void
+process_row (guchar **row,
+ guchar *outrow,
+ gint x1,
+ gint y1,
+ gint width,
+ gint height,
+ gint channels,
+ gint i)
+{
+ gint j;
+
+ for (j = 0; j < width; j++)
+ {
+ gint k, ii, jj;
+ gint left = (j - radius),
+ right = (j + radius);
+
+ /* For each layer, compute the average of the
+ * (2r+1)x(2r+1) pixels */
+ for (k = 0; k < channels; k++)
{
- gimp_drawable_mask_bounds (drawable->drawable_id,
- &x1, &y1,
- &x2, &y2);
- width = x2 - x1;
- height = y2 - y1;
+ gint sum = 0;
+
+ for (ii = 0; ii < 2 * radius + 1; ii++)
+ for (jj = left; jj <= right; jj++)
+ sum += row[ii][channels * CLAMP (jj, 0, width - 1) + k];
+
+ outrow[channels * j + k] =
+ sum / (4 * radius * radius + 4 * radius + 1);
}
+ }
+}
+
+static void
+shuffle (GimpPixelRgn *rgn_in,
+ guchar **row,
+ gint x1,
+ gint y1,
+ gint width,
+ gint height,
+ gint ypos)
+{
+ gint i;
+ guchar *tmp_row;
+
+ /* Get tile row (i + radius + 1) into row[0] */
+ gimp_pixel_rgn_get_row (rgn_in,
+ row[0],
+ x1, MIN (ypos + radius + y1, y1 + height - 1),
+ width);
+
+ /* Permute row[i] with row[i-1] and row[0] with row[2r] */
+ tmp_row = row[0];
+ for (i = 1; i < 2 * radius + 1; i++)
+ row[i - 1] = row[i];
+ row[2 * radius] = tmp_row;
+}
+```
+
+## Adding a graphical interface and saving parameters
+
+To let the user modify the radius, or let a non-interactive
+script give it as a parameter, we now need to get back to our
+run() function and settle some simple things.
+
+First we create a structure to allow saving and returning
+options. Usually one does this even for plug-ins with only one
+parameter.
+
+```
+typedef struct
+{
+ gint radius;
+} MyBlurVals;
+
+
+/* Set up default values for options */
+static MyBlurVals bvals =
+{
+ 3 /* radius */
+};
+```
+
+Next, we modify the run() function so that execution modes are
+taken into account. In interactive mode and repeat last filter
+mode, we try to get the last values used by the gimp_get_data()
+function, which takes a unique data identifier as its first
+input parameter. Usually, one uses the procedure's name.
+
+Finally, in interactive mode, we add a few lines that will build
+the graphical interface allowing options modification.
+
+
+```
+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);
+
+ switch (run_mode)
+ {
+ case GIMP_RUN_INTERACTIVE:
+ /* Get options last values if needed */
+ gimp_get_data ("plug-in-myblur", &bvals);
+
+ /* Display the dialog */
+ if (! blur_dialog (drawable))
+ return;
+ break;
+
+ case GIMP_RUN_NONINTERACTIVE:
+ if (nparams != 4)
+ status = GIMP_PDB_CALLING_ERROR;
+ if (status == GIMP_PDB_SUCCESS)
+ bvals.radius = param[3].data.d_int32;
+ break;
+
+ case GIMP_RUN_WITH_LAST_VALS:
+ /* Get options last values if needed */
+ gimp_get_data ("plug-in-myblur", &bvals);
+ break;
+
+ default:
+ break;
+ }
+
+ blur (drawable);
+
+ gimp_displays_flush ();
+ gimp_drawable_detach (drawable);
+
+ /* Finally, set options in the core */
+ if (run_mode == GIMP_RUN_INTERACTIVE)
+ gimp_set_data ("plug-in-myblur", &bvals, sizeof (MyBlurVals));
+
+ return;
+}
+```
+
+## The graphical interface
+
+I won't detail GTK+ programming as this is done very well in
+other places. Our first try will be very simple. We will use the
+utility widget of GIMP, the GimpDialog, to create a window with
+a header, a numeric control of type GtkSpinButton (associated
+with a GtkAdjustment) and its label, nicely framed in a
+GtkFrame.
+
+In the following parts, in order to show how easy one can do
+such things, I will add a preview in the dialog to show real
+time effects of the parameters.
+
+Our final dialog will look like this (tree generated with
+Glade):
+
+glade-tree.png
+glade-tree-small.png
+Glade tree
+
+In The GIMP 2.2, there is a number of widgets that come bundled
+with parameters that allow a coherent behaviour, consistent with
+GNOME Human Interface Guidelines. GimpPreview also appeared in
+2.2. Let's make a first try without it:
+
+blur_dialog1.png
+Blur dialog
+
+```
+static gboolean
+blur_dialog (GimpDrawable *drawable)
+{
+ GtkWidget *dialog;
+ GtkWidget *main_vbox;
+ GtkWidget *main_hbox;
+ GtkWidget *frame;
+ GtkWidget *radius_label;
+ GtkWidget *alignment;
+ GtkWidget *spinbutton;
+ GtkObject *spinbutton_adj;
+ GtkWidget *frame_label;
+ gboolean run;
+
+ gimp_ui_init ("myblur", FALSE);
+
+ dialog = gimp_dialog_new ("My blur", "myblur",
+ NULL, 0,
+ gimp_standard_help_func, "plug-in-myblur",
+
+ GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+ GTK_STOCK_OK, GTK_RESPONSE_OK,
+
+ NULL);
+
+ main_vbox = gtk_vbox_new (FALSE, 6);
+ gtk_container_add (GTK_CONTAINER (GTK_DIALOG (dialog)->vbox), main_vbox);
+ gtk_widget_show (main_vbox);
+
+ frame = gtk_frame_new (NULL);
+ gtk_widget_show (frame);
+ gtk_box_pack_start (GTK_BOX (main_vbox), frame, TRUE, TRUE, 0);
+ gtk_container_set_border_width (GTK_CONTAINER (frame), 6);
+
+ alignment = gtk_alignment_new (0.5, 0.5, 1, 1);
+ gtk_widget_show (alignment);
+ gtk_container_add (GTK_CONTAINER (frame), alignment);
+ gtk_alignment_set_padding (GTK_ALIGNMENT (alignment), 6, 6, 6, 6);
+
+ main_hbox = gtk_hbox_new (FALSE, 0);
+ gtk_widget_show (main_hbox);
+ gtk_container_add (GTK_CONTAINER (alignment), main_hbox);
+
+ radius_label = gtk_label_new_with_mnemonic ("_Radius:");
+ gtk_widget_show (radius_label);
+ gtk_box_pack_start (GTK_BOX (main_hbox), radius_label, FALSE, FALSE, 6);
+ gtk_label_set_justify (GTK_LABEL (radius_label), GTK_JUSTIFY_RIGHT);
+
+ spinbutton_adj = gtk_adjustment_new (3, 1, 16, 1, 5, 5);
+ spinbutton = gtk_spin_button_new (GTK_ADJUSTMENT (spinbutton_adj), 1, 0);
+ gtk_widget_show (spinbutton);
+ gtk_box_pack_start (GTK_BOX (main_hbox), spinbutton, FALSE, FALSE, 6);
+ gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (spinbutton), TRUE);
+
+ frame_label = gtk_label_new ("<b>Modify radius</b>");
+ gtk_widget_show (frame_label);
+ gtk_frame_set_label_widget (GTK_FRAME (frame), frame_label);
+ gtk_label_set_use_markup (GTK_LABEL (frame_label), TRUE);
+
+ g_signal_connect (spinbutton_adj, "value_changed",
+ G_CALLBACK (gimp_int_adjustment_update),
+ &bvals.radius);
+ gtk_widget_show (dialog);
+
+ run = (gimp_dialog_run (GIMP_DIALOG (dialog)) == GTK_RESPONSE_OK);
+
+ gtk_widget_destroy (dialog);
+
+ return run;
+}
+```
+
+## Adding a GimpPreview
+
+Adding a GimpPreview is quite easy. First we create a GtkWidget
+with `gimp_drawable_preview_new()`, then we attach an invalidated
+signal to it, which will call the blur function to update the
+preview. We also add a second parameter to MyBlurVals to
+remember the activation state of the preview.
+
+A method to update easily the preview is to add a preview
+parameter in the blur function, and if preview is not NULL, to
+take GimpPreview limits. So when we call blur from run(), we set
+the preview parameter to NULL.
+
+To take GimpPreview limits, we use gimp_preview_get_position()
+and `gimp_preview_get_size()`, so we can generate only what will
+be displayed.
+
+To achieve this the right way we'll tune some of the code - we
+don't need to update the progress bar while generating the
+preview, and we should tell at GimpPixelRgn init time that the
+tiles should not be sent back to the core.
+
+Finally, we display the updated preview with the
+gimp_drawable_preview_draw_region() function. We get a dialog
+box that shows us in real time the plug-in effects. Moreover,
+thanks to the GIMP core, our plug-in already takes selections
+into account.
+
+<ulink url="blur_dialog2.png">
+<imagedata fileref="blur_dialog2-small.png"/>
+<phrase>Blur dialog, improved</phrase>
+
+<ulink url="blur_select.png">
+<imagedata fileref="blur_select-small.png"/>
+<phrase>Blur a selection</phrase>
+
+Here are the two functions in their last version:
+
+```
+static void
+blur (GimpDrawable *drawable,
+ GimpPreview *preview)
+{
+ gint i, ii, channels;
+ gint x1, y1, x2, y2;
+ GimpPixelRgn rgn_in, rgn_out;
+ guchar **row;
+ guchar *outrow;
+ gint width, height;
+
+ if (!preview)
+ gimp_progress_init ("My Blur...");
+
+ /* Gets upper left and lower right coordinates,
+ * and layers number in the image */
+ if (preview)
+ {
+ gimp_preview_get_position (preview, &x1, &y1);
+ gimp_preview_get_size (preview, &width, &height);
+ x2 = x1 + width;
+ y2 = y1 + height;
+ }
+ else
+ {
+ gimp_drawable_mask_bounds (drawable->drawable_id,
+ &x1, &y1,
+ &x2, &y2);
+ width = x2 - x1;
+ height = y2 - y1;
+ }
+
+ channels = gimp_drawable_bpp (drawable->drawable_id);
+
+ /* Allocate a big enough tile cache */
+ gimp_tile_cache_ntiles (2 * (drawable->width /
+ gimp_tile_width () + 1));
+
+ /* 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,
+ width, height,
+ FALSE, FALSE);
+ gimp_pixel_rgn_init (&rgn_out,
+ drawable,
+ x1, y1,
+ width, height,
+ preview == NULL, TRUE);
+
+ /* Allocate memory for input and output tile rows */
+ init_mem (&row, &outrow, width * channels);
+
+ for (ii = -bvals.radius; ii <= bvals.radius; ii++)
+ {
+ gimp_pixel_rgn_get_row (&rgn_in,
+ row[bvals.radius + ii],
+ x1, y1 + CLAMP (ii, 0, height - 1),
+ width);
+ }
+
+ for (i = 0; i < height; i++)
+ {
+ /* To be done for each tile row */
+ process_row (row,
+ outrow,
+ x1, y1,
+ width, height,
+ channels,
+ i);
+ gimp_pixel_rgn_set_row (&rgn_out,
+ outrow,
+ x1, i + y1,
+ width);
+ /* shift tile rows to insert the new one at the end */
+ shuffle (&rgn_in,
+ row,
+ x1, y1,
+ width, height,
+ i);
+ if (i % 10 == 0 && !preview)
+ gimp_progress_update ((gdouble) i / (gdouble) height);
+ }
+
+ for (ii = 0; ii < 2 * bvals.radius + 1; ii++)
+ g_free (row[ii]);
+
+ g_free (row);
+ g_free (outrow);
+
+ /* Update the modified region */
+ if (preview)
+ {
+ gimp_drawable_preview_draw_region (GIMP_DRAWABLE_PREVIEW (preview),
+ &rgn_out);
+ }
+ else
+ {
+ gimp_drawable_flush (drawable);
+ gimp_drawable_merge_shadow (drawable->drawable_id, TRUE);
+ gimp_drawable_update (drawable->drawable_id,
+ x1, y1,
+ width, height);
+ }
+}
+
+static gboolean
+blur_dialog (GimpDrawable *drawable)
+{
+ GtkWidget *dialog;
+ GtkWidget *main_vbox;
+ GtkWidget *main_hbox;
+ GtkWidget *preview;
+ GtkWidget *frame;
+ GtkWidget *radius_label;
+ GtkWidget *alignment;
+ GtkWidget *spinbutton;
+ GtkObject *spinbutton_adj;
+ GtkWidget *frame_label;
+ gboolean run;
+
+ gimp_ui_init ("myblur", FALSE);
+
+ dialog = gimp_dialog_new ("My blur", "myblur",
+ NULL, 0,
+ gimp_standard_help_func, "plug-in-myblur",
+
+ GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+ GTK_STOCK_OK, GTK_RESPONSE_OK,
+
+ NULL);
+
+ main_vbox = gtk_vbox_new (FALSE, 6);
+ gtk_container_add (GTK_CONTAINER (GTK_DIALOG (dialog)->vbox), main_vbox);
+ gtk_widget_show (main_vbox);
+
+ preview = gimp_drawable_preview_new (drawable, &bvals.preview);
+ gtk_box_pack_start (GTK_BOX (main_vbox), preview, TRUE, TRUE, 0);
+ gtk_widget_show (preview);
+
+ frame = gimp_frame_new ("Blur radius");
+ gtk_box_pack_start (GTK_BOX (main_vbox), frame, FALSE, FALSE, 0);
+ gtk_widget_show (frame);
+
+ alignment = gtk_alignment_new (0.5, 0.5, 1, 1);
+ gtk_widget_show (alignment);
+ gtk_container_add (GTK_CONTAINER (frame), alignment);
+ gtk_alignment_set_padding (GTK_ALIGNMENT (alignment), 6, 6, 6, 6);
+
+ main_hbox = gtk_hbox_new (FALSE, 12);
+ gtk_container_set_border_width (GTK_CONTAINER (main_hbox), 12);
+ gtk_widget_show (main_hbox);
+ gtk_container_add (GTK_CONTAINER (alignment), main_hbox);
+
+ radius_label = gtk_label_new_with_mnemonic ("_Radius:");
+ gtk_widget_show (radius_label);
+ gtk_box_pack_start (GTK_BOX (main_hbox), radius_label, FALSE, FALSE, 6);
+ gtk_label_set_justify (GTK_LABEL (radius_label), GTK_JUSTIFY_RIGHT);
+
+ spinbutton = gimp_spin_button_new (&spinbutton_adj, bvals.radius,
+ 1, 32, 1, 1, 1, 5, 0);
+ gtk_box_pack_start (GTK_BOX (main_hbox), spinbutton, FALSE, FALSE, 0);
+ gtk_widget_show (spinbutton);
+
+ g_signal_connect_swapped (preview, "invalidated",
+ G_CALLBACK (blur),
+ drawable);
+ g_signal_connect_swapped (spinbutton_adj, "value_changed",
+ G_CALLBACK (gimp_preview_invalidate),
+ preview);
+
+ blur (drawable, GIMP_PREVIEW (preview));
+
+ g_signal_connect (spinbutton_adj, "value_changed",
+ G_CALLBACK (gimp_int_adjustment_update),
+ &bvals.radius);
+ gtk_widget_show (dialog);
+
+ run = (gimp_dialog_run (GIMP_DIALOG (dialog)) == GTK_RESPONSE_OK);
+
+ gtk_widget_destroy (dialog);
+
+ return run;
+}
+```
+
+Have a look at the <ulink url="myblur3.c">tiled</ulink>,
+<ulink url="myblur4.c">UI</ulink> or
+<ulink url="myblur5.c">preview</ulink> blur complete code.
+
+## Conclusion
+
+In these articles, we saw basic concepts for several aspects of
+a GIMP plug-in. We messed with image data treatment through a
+simple algorithm, and followed a path that showed us how to
+avoid performance problems. Finally, we generalised the
+algorithm and added parameters to it, and we used some GIMP
+widgets to make a nice user interface.
+
+## Thanks
+
+Thanks to my wife Anne and to David Odin (preview master) for
+helping me while I was writing this article.
+
+<ulink url="http://creativecommons.org/licenses/by-nc-sa/2.5/">
+<imagedata fileref="http://creativecommons.org/images/public/somerights20.gif"/>
+<phrase>Creative Commons License</phrase>
- channels = gimp_drawable_bpp (drawable->drawable_id);
-
- /* Allocate a big enough tile cache */
- gimp_tile_cache_ntiles (2 * (drawable->width /
- gimp_tile_width () + 1));
-
- /* 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,
- width, height,
- FALSE, FALSE);
- gimp_pixel_rgn_init (&rgn_out,
- drawable,
- x1, y1,
- width, height,
- preview == NULL, TRUE);
-
- /* Allocate memory for input and output tile rows */
- init_mem (&row, &outrow, width * channels);
-
- for (ii = -bvals.radius; ii <= bvals.radius; ii++)
- {
- gimp_pixel_rgn_get_row (&rgn_in,
- row[bvals.radius + ii],
- x1, y1 + CLAMP (ii, 0, height - 1),
- width);
- }
-
- for (i = 0; i < height; i++)
- {
- /* To be done for each tile row */
- process_row (row,
- outrow,
- x1, y1,
- width, height,
- channels,
- i);
- gimp_pixel_rgn_set_row (&rgn_out,
- outrow,
- x1, i + y1,
- width);
- /* shift tile rows to insert the new one at the end */
- shuffle (&rgn_in,
- row,
- x1, y1,
- width, height,
- i);
- if (i % 10 == 0 && !preview)
- gimp_progress_update ((gdouble) i / (gdouble) height);
- }
-
- for (ii = 0; ii < 2 * bvals.radius + 1; ii++)
- g_free (row[ii]);
-
- g_free (row);
- g_free (outrow);
-
- /* Update the modified region */
- if (preview)
- {
- gimp_drawable_preview_draw_region (GIMP_DRAWABLE_PREVIEW (preview),
- &rgn_out);
- }
- else
- {
- gimp_drawable_flush (drawable);
- gimp_drawable_merge_shadow (drawable->drawable_id, TRUE);
- gimp_drawable_update (drawable->drawable_id,
- x1, y1,
- width, height);
- }
- }
-
- static gboolean
- blur_dialog (GimpDrawable *drawable)
- {
- GtkWidget *dialog;
- GtkWidget *main_vbox;
- GtkWidget *main_hbox;
- GtkWidget *preview;
- GtkWidget *frame;
- GtkWidget *radius_label;
- GtkWidget *alignment;
- GtkWidget *spinbutton;
- GtkObject *spinbutton_adj;
- GtkWidget *frame_label;
- gboolean run;
-
- gimp_ui_init ("myblur", FALSE);
-
- dialog = gimp_dialog_new ("My blur", "myblur",
- NULL, 0,
- gimp_standard_help_func, "plug-in-myblur",
-
- GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
- GTK_STOCK_OK, GTK_RESPONSE_OK,
-
- NULL);
-
- main_vbox = gtk_vbox_new (FALSE, 6);
- gtk_container_add (GTK_CONTAINER (GTK_DIALOG (dialog)->vbox), main_vbox);
- gtk_widget_show (main_vbox);
-
- preview = gimp_drawable_preview_new (drawable, &bvals.preview);
- gtk_box_pack_start (GTK_BOX (main_vbox), preview, TRUE, TRUE, 0);
- gtk_widget_show (preview);
-
- frame = gimp_frame_new ("Blur radius");
- gtk_box_pack_start (GTK_BOX (main_vbox), frame, FALSE, FALSE, 0);
- gtk_widget_show (frame);
-
- alignment = gtk_alignment_new (0.5, 0.5, 1, 1);
- gtk_widget_show (alignment);
- gtk_container_add (GTK_CONTAINER (frame), alignment);
- gtk_alignment_set_padding (GTK_ALIGNMENT (alignment), 6, 6, 6, 6);
-
- main_hbox = gtk_hbox_new (FALSE, 12);
- gtk_container_set_border_width (GTK_CONTAINER (main_hbox), 12);
- gtk_widget_show (main_hbox);
- gtk_container_add (GTK_CONTAINER (alignment), main_hbox);
-
- radius_label = gtk_label_new_with_mnemonic ("_Radius:");
- gtk_widget_show (radius_label);
- gtk_box_pack_start (GTK_BOX (main_hbox), radius_label, FALSE, FALSE, 6);
- gtk_label_set_justify (GTK_LABEL (radius_label), GTK_JUSTIFY_RIGHT);
-
- spinbutton = gimp_spin_button_new (&spinbutton_adj, bvals.radius,
- 1, 32, 1, 1, 1, 5, 0);
- gtk_box_pack_start (GTK_BOX (main_hbox), spinbutton, FALSE, FALSE, 0);
- gtk_widget_show (spinbutton);
-
- g_signal_connect_swapped (preview, "invalidated",
- G_CALLBACK (blur),
- drawable);
- g_signal_connect_swapped (spinbutton_adj, "value_changed",
- G_CALLBACK (gimp_preview_invalidate),
- preview);
-
- blur (drawable, GIMP_PREVIEW (preview));
-
- g_signal_connect (spinbutton_adj, "value_changed",
- G_CALLBACK (gimp_int_adjustment_update),
- &bvals.radius);
- gtk_widget_show (dialog);
-
- run = (gimp_dialog_run (GIMP_DIALOG (dialog)) == GTK_RESPONSE_OK);
-
- gtk_widget_destroy (dialog);
-
- return run;
- }
- ]]>
- </programlisting>
-
- <para>
- Have a look at the <ulink url="myblur3.c">tiled</ulink>,
- <ulink url="myblur4.c">UI</ulink> or
- <ulink url="myblur5.c">preview</ulink> blur complete code.
- </para>
- </section>
-
- <section>
- <title>Conclusion</title>
-
- <para>
- In these articles, we saw basic concepts for several aspects of
- a GIMP plug-in. We messed with image data treatment through a
- simple algorithm, and followed a path that showed us how to
- avoid performance problems. Finally, we generalised the
- algorithm and added parameters to it, and we used some GIMP
- widgets to make a nice user interface.
- </para>
- </section>
-
- <section>
- <title>Thanks</title>
-
- <para>
- Thanks to my wife Anne and to David Odin (preview master) for
- helping me while I was writing this article.
- </para>
- </section>
-
- <section>
- <para role="images">
- <ulink url="http://creativecommons.org/licenses/by-nc-sa/2.5/">
- <mediaobject>
- <imageobject>
- <imagedata fileref="http://creativecommons.org/images/public/somerights20.gif"/>
- </imageobject>
- <textobject>
- <phrase>Creative Commons License</phrase>
- </textobject>
- </mediaobject>
- </ulink>
- </para>
-
- <para>
- This work is licensed under a <ulink
- url="http://creativecommons.org/licenses/by-nc-sa/2.5/">Creative
- Commons Attribution-NonCommercial-ShareAlike 2.5
- License</ulink>.
- </para>
- </section>
+This work is licensed under a <ulink
+url="http://creativecommons.org/licenses/by-nc-sa/2.5/">Creative
+Commons Attribution-NonCommercial-ShareAlike 2.5
+License</ulink>.
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]