[gimp-web-devel/hugo: 4/7] writing a plug in translation
- From: Pat David <patdavid src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gimp-web-devel/hugo: 4/7] writing a plug in translation
- Date: Sun, 24 Jul 2022 14:30:18 +0000 (UTC)
commit 5896aebcd09f5d4cafb2623c5426d48f410f9ca3
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 +++++++++++++++------------------
public/index.xml | 28 +-
public/sitemap.xml | 10 +-
public/writing-a-plug-in/1/index.html | 531 ++++-------
public/writing-a-plug-in/2/index.html | 698 +++++++-------
public/writing-a-plug-in/3/index.html | 1536 ++++++++++++++-----------------
public/writing-a-plug-in/index.html | 4 +-
public/writing-a-plug-in/index.xml | 31 +-
10 files changed, 2545 insertions(+), 3327 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>.
diff --git a/public/index.xml b/public/index.xml
index c3dbbb4..76dda6b 100644
--- a/public/index.xml
+++ b/public/index.xml
@@ -8,6 +8,17 @@
<language>en</language>
<copyright>Copyright 2003-2022 The GIMP Development Team</copyright>
<lastBuildDate>Tue, 19 Jul 2022 00:00:00 +0000</lastBuildDate><atom:link
href="https://developer.gimp.org/index.xml" rel="self" type="application/rss+xml" />
+ <item>
+ <title>How to write a GIMP plug-in, part III</title>
+ <link>https://developer.gimp.org/writing-a-plug-in/3/</link>
+ <pubDate>Sat, 23 Jul 2022 00:00:00 +0000</pubDate>
+
+ <guid>https://developer.gimp.org/writing-a-plug-in/3/</guid>
+ <description>Written By Dave Neary.
+In the second part, 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&rsquo;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&rsquo;s pixel value with the average value in the
neighbourhood.</description>
+ </item>
+
<item>
<title>Frequently Asked Questions</title>
<link>https://developer.gimp.org/faq.html</link>
@@ -131,10 +142,9 @@ Below is a list of links to get you started with Gitlab:</description>
<pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
<guid>https://developer.gimp.org/writing-a-plug-in/1/</guid>
- <description>Written By Dave Neary
+ <description>Written By Dave Neary.
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 part, I present a plug-in&rsquo;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 &lt;para role=&quot;images&quot;&gt; &lt;ulink
url=&quot;architecture.png&quot;&gt; &lt;mediaobject&gt; &lt;imageobject&gt;
&lt;imagedata fileref=&quot;architecture-small.</description>
+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.</description>
</item>
<item>
@@ -143,22 +153,12 @@ Architecture &lt;para role=&quot;images&quot;&gt; &lt;ulink
<pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
<guid>https://developer.gimp.org/writing-a-plug-in/2/</guid>
- <description>Written By Dave Neary
+ <description>Written By Dave Neary.
In the first part, I presented essential elements to build a plug-in interface with The GIMP. Now we will
produce a simple but useful algorithm that we could use in our plug-in.
Introduction The algorithm we are going to implement is a simple blur. It is included in The GIMP as
&ldquo;Filters-&gt;Blur-&gt;Blur&rdquo; with default parameters.
That algorithm is very simple. Each pixel in our image is replaced by a mean value of its
neighbours.</description>
</item>
- <item>
- <title>How to write a GIMP plug-in, part III</title>
- <link>https://developer.gimp.org/writing-a-plug-in/3/</link>
- <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
-
- <guid>https://developer.gimp.org/writing-a-plug-in/3/</guid>
- <description>Written By Dave Neary
-In the &lt;olink targetdocent=&quot;writing-a-plug-in-2&quot;&gt;second
part&lt;/olink&gt;, 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. &lt;para&gt; 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.</description>
- </item>
-
<item>
<title>Screenshots</title>
<link>https://developer.gimp.org/screenshots.html</link>
diff --git a/public/sitemap.xml b/public/sitemap.xml
index 801ad39..a9fda31 100644
--- a/public/sitemap.xml
+++ b/public/sitemap.xml
@@ -2,6 +2,12 @@
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
xmlns:xhtml="http://www.w3.org/1999/xhtml">
<url>
+ <loc>https://developer.gimp.org/writing-a-plug-in/3/</loc>
+ <lastmod>2022-07-23T00:00:00+00:00</lastmod>
+ </url><url>
+ <loc>https://developer.gimp.org/writing-a-plug-in/</loc>
+ <lastmod>2022-07-23T00:00:00+00:00</lastmod>
+ </url><url>
<loc>https://developer.gimp.org/faq.html</loc>
<lastmod>2022-07-20T00:00:00+00:00</lastmod>
</url><url>
@@ -57,8 +63,6 @@
<loc>https://developer.gimp.org/writing-a-plug-in/1/</loc>
</url><url>
<loc>https://developer.gimp.org/writing-a-plug-in/2/</loc>
- </url><url>
- <loc>https://developer.gimp.org/writing-a-plug-in/3/</loc>
</url><url>
<loc>https://developer.gimp.org/screenshots.html</loc>
</url><url>
@@ -67,7 +71,5 @@
<loc>https://developer.gimp.org/gimpcon/2003/gimpcon2003-mins-1/</loc>
</url><url>
<loc>https://developer.gimp.org/gimpcon/2003/gimpcon2003-mins-2/</loc>
- </url><url>
- <loc>https://developer.gimp.org/writing-a-plug-in/</loc>
</url>
</urlset>
diff --git a/public/writing-a-plug-in/1/index.html b/public/writing-a-plug-in/1/index.html
index e8bb0fd..4d98f52 100644
--- a/public/writing-a-plug-in/1/index.html
+++ b/public/writing-a-plug-in/1/index.html
@@ -103,30 +103,21 @@
<div class="post">
<h1>How to write a GIMP plug-in</h1>
<time datetime=0001-01-01T00:00:00Z class="post-date">Mon, Jan 1, 0001</time>
- <p>Written By <!-- raw HTML omitted -->Dave Neary<!-- raw HTML omitted --></p>
+ <p>Written By <a href="mailto:bolsh NOSPAM gimp org">Dave Neary</a>.</p>
<p>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.</p>
-<!-- raw HTML omitted -->
-<!-- raw HTML omitted -->
+<h2 id="introduction">Introduction</h2>
+<p>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.</p>
<p>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.</p>
<h2 id="architecture">Architecture</h2>
-<pre><code><para role="images">
- <ulink url="architecture.png">
- <mediaobject>
- <imageobject>
- <imagedata fileref="architecture-small.png"/>
- </imageobject>
- <textobject>
- <phrase>Architecture</phrase>
- </textobject>
- </mediaobject>
- Architecture
- </ulink>
-</para>
-</code></pre>
+<p>architecture.png
+<a href="Architecture">architecture.png</a></p>
<p>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
@@ -144,343 +135,183 @@ be called as a normal one.</p>
<p>This was the introduction - now, we will look closer at our
first plug-in, a “Hello, world!”.</p>
<h2 id="compiling-the-plug-in">Compiling the plug-in</h2>
-<pre><code><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>
-</code></pre>
-<!-- raw HTML omitted -->
-<!-- raw HTML omitted -->
-<pre><code><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>
-</code></pre>
-<!-- raw HTML omitted -->
-<!-- raw HTML omitted -->
-<pre><code><programlisting>
-<![CDATA[
- #include &lt;libgimp/gimp.h&gt;
- ]]>
-</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>
-</code></pre>
-<!-- raw HTML omitted -->
-<!-- raw HTML omitted -->
-<pre><code><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>
-</code></pre>
-<!-- raw HTML omitted -->
-<!-- raw HTML omitted -->
-<pre><code><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)
+<p>To be able to compile simple plug-ins for The GIMP, one needs
+libgimp headers, as well as an associated utility named
+gimptool.</p>
+<p>With that utility, one can install a plug-in either in a private
+directory (<code>~/.gimp-2.0/plug-ins</code>), or in the global plug-in
+directory.</p>
+<p>Syntax is</p>
+<pre tabindex="0"><code>gimptool-2.0 --install plugin.c or gimptool-2.0 --install-admin plugin.c
+</code></pre><p>This utility, with other options, can also be used to install
+scripts, or uninstall plug-ins.</p>
+<h2 id="behaviour">Behaviour</h2>
+<p>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.</p>
+<h2 id="essentials">Essentials</h2>
+<pre tabindex="0"><code>#include <libgimp/gimp.h>
+</code></pre><p>This header makes all basic plug-in elements available to us.</p>
+<pre tabindex="0"><code>GimpPlugInInfo PLUG_IN_INFO = {
+ init,
+ quit,
+ query,
+ run
+};
+</code></pre><p>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.</p>
+<p>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.</p>
+<p>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.</p>
+<p>The query() function is called the first time the plug-in is
+present, and then each time the plug-in changes.</p>
+<p>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</p>
+<pre tabindex="0"><code>void run (const gchar *name,
+ gint nparams,
+ const GimpParam *param,
+ gint *nreturn_vals,
+ GimpParam **return_vals);
+</code></pre><h2 id="main-">MAIN ()</h2>
+<p>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.</p>
+<h2 id="the-query-function">The query() function</h2>
+<p><code>query()</code> 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.</p>
+<p>For our “Hello, world!” plug-in, the query function will look
+like this:</p>
+<pre tabindex="0"><code>static void
+query (void)
+{
+ static GimpParamDef args[] = {
{
- 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");
+ GIMP_PDB_INT32,
+ "run-mode",
+ "Run mode"
+ },
+ {
+ GIMP_PDB_IMAGE,
+ "image",
+ "Input image"
+ },
+ {
+ GIMP_PDB_DRAWABLE,
+ "drawable",
+ "Input drawable"
}
- ]]>
-</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>
-</code></pre>
-<!-- raw HTML omitted -->
-<!-- raw HTML omitted -->
-<pre><code><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>
-</code></pre>
-<!-- raw HTML omitted -->
-<!-- raw HTML omitted -->
-<pre><code><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>
-</code></pre>
-<!-- raw HTML omitted -->
-<!-- raw HTML omitted -->
-<pre><code><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>
-</code></pre>
-<!-- raw HTML omitted -->
-<!-- raw HTML omitted -->
-<pre><code><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>
-</code></pre>
-<!-- raw HTML omitted -->
+ 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");
+}
+</code></pre><p>GimpParamDef contains three things - the parameter type, its
+name, and a string describing the parameter.</p>
+<p><code>gimp_install_procedure</code> 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.</p>
+<p>“<code>RGB*, GRAY*</code>” declares the image types handled. It can be <code>RGB</code>,
+<code>INDEXED</code> or <code>GRAY</code>, with or without Alpha. So “<code>RGB*, GRAY*</code>”
+describes RGB, <code>RGBA</code>, <code>GRAY</code> or <code>GRAY</code> image type.</p>
+<p>GIMP_PLUGIN declares this procedure to be external, and not to
+be executed in The GIMP core.</p>
+<p>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 “<code>Xtns->Plug-in Details</code>” plug-in.</p>
+<p>plug-in-details.png
+plug-in-details-small.png
+Plug-in details</p>
+<p>plug-in-menu.png
+plug-in-menu-small.png
+Our plug-in is in the menus</p>
+<h2 id="the-run-function">The run() function</h2>
+<p>The other required function for PLUG_IN_INFO is run. The core of
+the plug-in stands there.</p>
+<p>Output values (return_vals in the prototype) must have at least
+one value associated - the plug-in status. Typically, this
+parameter will hold “<code>GIMP_PDB_SUCCESS</code>”.</p>
+<h2 id="run-modes">Run-modes</h2>
+<p>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.</p>
+<p>The “<code>run_mode</code>” input parameter can hold one of these values:
+“<code>GIMP_RUN_INTERACTIVE</code>”, “<code>GIMP_RUN_NONINTERACTIVE</code>” or
+“<code>GIMP_RUN_WITH_LAST_VALS</code>”.</p>
+<p>“<code>GIMP_RUN_INTERACTIVE</code>” 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.</p>
+<p>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:</p>
+<pre tabindex="0"><code>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");
+}
+</code></pre><p>Now, when we run our plug-in, there is action:</p>
+<p>[hello.png](Hello, world!)</p>
+<p>Have a look at the full <a href="hello.c">hello.c</a> plug-in code.</p>
+<h2 id="next-part">Next part</h2>
+<p>In <!-- raw HTML omitted -->next part<!-- raw HTML omitted -->
+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.</p>
<!-- raw HTML omitted -->
+<p>This work is licensed under a <!-- raw HTML omitted -->Creative
+Commons Attribution-NonCommercial-ShareAlike 2.5
+License<!-- raw HTML omitted -->.</p>
</div>
</main>
diff --git a/public/writing-a-plug-in/2/index.html b/public/writing-a-plug-in/2/index.html
index e1796e1..f80c908 100644
--- a/public/writing-a-plug-in/2/index.html
+++ b/public/writing-a-plug-in/2/index.html
@@ -103,9 +103,9 @@
<div class="post">
<h1>How to write a GIMP plug-in, part II</h1>
<time datetime=0001-01-01T00:00:00Z class="post-date">Mon, Jan 1, 0001</time>
- <p>Written By <!-- raw HTML omitted -->Dave Neary<!-- raw HTML omitted --></p>
-<p>In the <!-- raw HTML omitted -->first
-part<!-- raw HTML omitted -->, I presented essential elements to build a plug-in
+ <p>Written By <a href="mailto:bolsh NOSPAM gimp org">Dave Neary</a>.</p>
+<p>In the <a href="writing-a-plug-in/1">first part</a>,
+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.</p>
<h2 id="introduction">Introduction</h2>
@@ -118,8 +118,7 @@ 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.</p>
<p>With this method, edge differences are splatted, giving a
-blurred result. One can choose another radius, using a (2r + 1)
-x (2r + 1) matrix.</p>
+blurred result. One can choose another radius, using a <code>(2r + 1) x (2r + 1)</code> matrix.</p>
<h2 id="image-structure">Image structure</h2>
<p>Last month, we wrote a run() function that did nothing useful.
Let’s look again at run() prototype:</p>
@@ -138,179 +137,126 @@ 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”.</p>
-<pre><code><para role="images">
- <ulink url="GimpImage.png">
- <mediaobject>
- <imageobject>
- <imagedata fileref="GimpImage-small.png"/>
- </imageobject>
- <textobject>
- <phrase>Drawables</phrase>
- </textobject>
- </mediaobject>
- Drawables
- </ulink>
-</code></pre>
+<p>GimpImage.png
+GimpImage-small.png
+Drawables</p>
<h2 id="accessing-the-data">Accessing the data</h2>
-<pre><code><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>
-</code></pre>
+<p>To get a GimpDrawable from its identifier, we need the
+<code>gimp_drawable_get()</code> function:</p>
+<pre tabindex="0"><code>GimpDrawable *gimp_drawable_get (gint32 drawable_id);
+</code></pre><p>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 <!-- raw HTML omitted -->in
+the API<!-- raw HTML omitted -->.</p>
+<p>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.</p>
+<p>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.</p>
<!-- raw HTML omitted -->
+<p>The main functions to get and set image data are:</p>
+<pre tabindex="0"><code>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);
+</code></pre><p>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.</p>
<h2 id="updating-the-image">Updating the image</h2>
<p>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:</p>
-<pre tabindex="0"><code> gimp_displays_flush ();
- gimp_drawable_detach (drawable);
+<pre tabindex="0"><code>gimp_displays_flush ();
+gimp_drawable_detach (drawable);
</code></pre><h2 id="implementing-blur">Implementing blur()</h2>
<p>To be able to try out several different processing methods, we
will delegate the job to a blur() function. Our run() is below.</p>
-<pre tabindex="0"><code> 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 tabindex="0"><code>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);
+}
</code></pre><p>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
@@ -340,108 +286,108 @@ 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.</p>
-<pre tabindex="0"><code> 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 tabindex="0"><code>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);
+}
</code></pre><h2 id="row-processing">Row processing</h2>
<p>Our function has a bug drawback: performance. On a 300x300
selection, with the timing code uncommented, blur() took 12
@@ -451,124 +397,104 @@ compare, on the same selection, Gaussian blur took 3 seconds.</p>
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:</p>
-<pre><code><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));
- }
-
- 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 tabindex="0"><code>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));
}
- ]]>
-</programlisting>
-
-<para>
- Have a look at the <ulink url="myblur1.c">slow</ulink> or
- <ulink url="myblur2.c">fast</ulink> blur complete code.
-</para>
-</code></pre>
-<!-- raw HTML omitted -->
-<!-- raw HTML omitted -->
-<pre><code><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>
-</code></pre>
-<!-- raw HTML omitted -->
-<!-- raw HTML omitted -->
-<pre><code><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>
-</code></pre>
-<!-- raw HTML omitted -->
+
+ 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);
+}
+</code></pre><p>Have a look at the <a href="myblur1.c">slow</a> or <a href="myblur2.c">fast</a> blur
complete code.</p>
+<h2 id="next-part">Next part</h2>
+<p>In <a href="writing-a-plug-in/3">next part</a>,
+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.</p>
<!-- raw HTML omitted -->
+<p>This work is licensed under a <!-- raw HTML omitted -->Creative
+Commons Attribution-NonCommercial-ShareAlike 2.5
+License<!-- raw HTML omitted -->.</p>
</div>
</main>
diff --git a/public/writing-a-plug-in/3/index.html b/public/writing-a-plug-in/3/index.html
index 3aa9aa6..9efbe22 100644
--- a/public/writing-a-plug-in/3/index.html
+++ b/public/writing-a-plug-in/3/index.html
@@ -102,888 +102,676 @@
<main class="content container">
<div class="post">
<h1>How to write a GIMP plug-in, part III</h1>
- <time datetime=0001-01-01T00:00:00Z class="post-date">Mon, Jan 1, 0001</time>
- <p>Written By <!-- raw HTML omitted -->Dave Neary<!-- raw HTML omitted --></p>
-<pre><code>In the <olink targetdocent="writing-a-plug-in-2">second
-part</olink>, I told you about manipulating image data by pixel or
+ <time datetime=2022-07-23T00:00:00Z class="post-date">Sat, Jul 23, 2022</time>
+ <p>Written By <a href="mailto:bolsh NOSPAM gimp org">Dave Neary</a>.</p>
+<p>In the <a href="writing-a-plug-in/2">second part</a>,
+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.
-</code></pre>
-<!-- raw HTML omitted -->
-<!-- raw HTML omitted -->
-<pre><code><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>
-</code></pre>
+graphical interface to allow changing that parameter.</p>
+<h2 id="introduction">Introduction</h2>
+<p>Let’s have a look at our simple algorithm: for each pixel,
+generate a <code>(2r+1)x(2r+1)</code> neighbourhood and for each layer,
+replace the layer’s pixel value with the average value in the
+neighbourhood.</p>
+<p>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.</p>
+<p>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.</p>
+<p>First, a word on tiles.</p>
+<h2 id="tile-management">Tile management</h2>
+<p>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.</p>
+<p>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:</p>
+<pre tabindex="0"><code>gimp_tile_cache_ntiles (gulong ntiles);
+</code></pre><p>In the second part example, we called <code>gimp_pixel_rgn_get_row()</code>
+and <code>gimp_pixel_rgn_set_row()</code> but without using any cache.</p>
+<p>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.</p>
+<pre tabindex="0"><code>gimp_tile_cache_ntiles (2 * (drawable->width /
+ gimp_tile_width () + 1));
+</code></pre><p>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.</p>
+<p>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.</p>
+<h2 id="algorithm-generalisation">Algorithm generalisation</h2>
+<p>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:</p>
+<ul>
+<li>allocate space for 2r+1 tile rows</li>
+<li>initialise this rows array, taking care of borders</li>
+<li>for each tile row
+<ul>
+<li>for each pixel in the tile row</li>
+<li>compute the neighbourhood average, taking care of borders</li>
+</ul>
+</li>
+<li>get a new tile row and cycle rows</li>
+</ul>
+<p>This algorithm is more complex than the last one, because the
+average computing will be a O(r²) algorithm.</p>
+<p>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.</p>
+<pre tabindex="0"><code>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;
+}
+</code></pre><h2 id="adding-a-graphical-interface-and-saving-parameters">Adding a graphical interface and
saving parameters</h2>
+<p>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.</p>
+<p>First we create a structure to allow saving and returning
+options. Usually one does this even for plug-ins with only one
+parameter.</p>
+<pre tabindex="0"><code>typedef struct
+{
+ gint radius;
+} MyBlurVals;
+
+
+/* Set up default values for options */
+static MyBlurVals bvals =
+{
+ 3 /* radius */
+};
+</code></pre><p>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.</p>
+<p>Finally, in interactive mode, we add a few lines that will build
+the graphical interface allowing options modification.</p>
+<pre tabindex="0"><code>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;
+}
+</code></pre><h2 id="the-graphical-interface">The graphical interface</h2>
+<p>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.</p>
+<p>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.</p>
+<p>Our final dialog will look like this (tree generated with
+Glade):</p>
+<p>glade-tree.png
+glade-tree-small.png
+Glade tree</p>
+<p>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:</p>
+<p>blur_dialog1.png
+Blur dialog</p>
+<pre tabindex="0"><code>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;
+}
+</code></pre><h2 id="adding-a-gimppreview">Adding a GimpPreview</h2>
+<p>Adding a GimpPreview is quite easy. First we create a GtkWidget
+with <code>gimp_drawable_preview_new()</code>, 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.</p>
+<p>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.</p>
+<p>To take GimpPreview limits, we use gimp_preview_get_position()
+and <code>gimp_preview_get_size()</code>, so we can generate only what will
+be displayed.</p>
+<p>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.</p>
+<p>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.</p>
<!-- raw HTML omitted -->
<!-- raw HTML omitted -->
-<pre><code><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[
+<p>Here are the two functions in their last version:</p>
+<pre tabindex="0"><code>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));
- ]]>
-</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>
-</code></pre>
-<!-- raw HTML omitted -->
+ 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;
+}
+</code></pre><p>Have a look at the <!-- raw HTML omitted -->tiled<!-- raw HTML omitted -->,
+<!-- raw HTML omitted -->UI<!-- raw HTML omitted --> or
+<!-- raw HTML omitted -->preview<!-- raw HTML omitted --> blur complete code.</p>
+<h2 id="conclusion">Conclusion</h2>
+<p>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.</p>
+<h2 id="thanks">Thanks</h2>
+<p>Thanks to my wife Anne and to David Odin (preview master) for
+helping me while I was writing this article.</p>
<!-- raw HTML omitted -->
-<pre><code><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>
-</code></pre>
-<pre tabindex="0"><code> <![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));
-
- 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
- {
- gimp_drawable_mask_bounds (drawable->drawable_id,
- &x1, &y1,
- &x2, &y2);
- width = x2 - x1;
- height = y2 - y1;
- }
+<p>This work is licensed under a <!-- raw HTML omitted -->Creative
+Commons Attribution-NonCommercial-ShareAlike 2.5
+License<!-- raw HTML omitted -->.</p>
- 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>
-</code></pre>
</div>
</main>
diff --git a/public/writing-a-plug-in/index.html b/public/writing-a-plug-in/index.html
index 148c07b..09039eb 100644
--- a/public/writing-a-plug-in/index.html
+++ b/public/writing-a-plug-in/index.html
@@ -103,11 +103,11 @@
<main class="content container">
<ul class="posts">
<li>
+ <span><a href="https://developer.gimp.org/writing-a-plug-in/3/">How to write a GIMP plug-in, part
III</a> <time class="pull-right post-list" datetime="2022-07-23T00:00:00Z">Sat, Jul 23, 2022</time></span>
+ </li><li>
<span><a href="https://developer.gimp.org/writing-a-plug-in/1/">How to write a GIMP plug-in</a> <time
class="pull-right post-list" datetime="0001-01-01T00:00:00Z">Mon, Jan 1, 0001</time></span>
</li><li>
<span><a href="https://developer.gimp.org/writing-a-plug-in/2/">How to write a GIMP plug-in, part II</a>
<time class="pull-right post-list" datetime="0001-01-01T00:00:00Z">Mon, Jan 1, 0001</time></span>
- </li><li>
- <span><a href="https://developer.gimp.org/writing-a-plug-in/3/">How to write a GIMP plug-in, part
III</a> <time class="pull-right post-list" datetime="0001-01-01T00:00:00Z">Mon, Jan 1, 0001</time></span>
</li>
</ul>
</main>
diff --git a/public/writing-a-plug-in/index.xml b/public/writing-a-plug-in/index.xml
index 7254ad4..e087186 100644
--- a/public/writing-a-plug-in/index.xml
+++ b/public/writing-a-plug-in/index.xml
@@ -6,17 +6,28 @@
<description>Recent content in Writing-a-plug-ins on GIMP Developer Resources</description>
<generator>Hugo -- gohugo.io</generator>
<language>en</language>
- <copyright>Copyright 2003-2022 The GIMP Development Team</copyright><atom:link
href="https://developer.gimp.org/writing-a-plug-in/index.xml" rel="self" type="application/rss+xml" />
+ <copyright>Copyright 2003-2022 The GIMP Development Team</copyright>
+ <lastBuildDate>Sat, 23 Jul 2022 00:00:00 +0000</lastBuildDate><atom:link
href="https://developer.gimp.org/writing-a-plug-in/index.xml" rel="self" type="application/rss+xml" />
+ <item>
+ <title>How to write a GIMP plug-in, part III</title>
+ <link>https://developer.gimp.org/writing-a-plug-in/3/</link>
+ <pubDate>Sat, 23 Jul 2022 00:00:00 +0000</pubDate>
+
+ <guid>https://developer.gimp.org/writing-a-plug-in/3/</guid>
+ <description>Written By Dave Neary.
+In the second part, 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&rsquo;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&rsquo;s pixel value with the average value in the
neighbourhood.</description>
+ </item>
+
<item>
<title>How to write a GIMP plug-in</title>
<link>https://developer.gimp.org/writing-a-plug-in/1/</link>
<pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
<guid>https://developer.gimp.org/writing-a-plug-in/1/</guid>
- <description>Written By Dave Neary
+ <description>Written By Dave Neary.
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 part, I present a plug-in&rsquo;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 &lt;para role=&quot;images&quot;&gt; &lt;ulink
url=&quot;architecture.png&quot;&gt; &lt;mediaobject&gt; &lt;imageobject&gt;
&lt;imagedata fileref=&quot;architecture-small.</description>
+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.</description>
</item>
<item>
@@ -25,21 +36,11 @@ Architecture &lt;para role=&quot;images&quot;&gt; &lt;ulink
<pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
<guid>https://developer.gimp.org/writing-a-plug-in/2/</guid>
- <description>Written By Dave Neary
+ <description>Written By Dave Neary.
In the first part, I presented essential elements to build a plug-in interface with The GIMP. Now we will
produce a simple but useful algorithm that we could use in our plug-in.
Introduction The algorithm we are going to implement is a simple blur. It is included in The GIMP as
&ldquo;Filters-&gt;Blur-&gt;Blur&rdquo; with default parameters.
That algorithm is very simple. Each pixel in our image is replaced by a mean value of its
neighbours.</description>
</item>
- <item>
- <title>How to write a GIMP plug-in, part III</title>
- <link>https://developer.gimp.org/writing-a-plug-in/3/</link>
- <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
-
- <guid>https://developer.gimp.org/writing-a-plug-in/3/</guid>
- <description>Written By Dave Neary
-In the &lt;olink targetdocent=&quot;writing-a-plug-in-2&quot;&gt;second
part&lt;/olink&gt;, 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. &lt;para&gt; 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.</description>
- </item>
-
</channel>
</rss>
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]