C plug-ins - Basics
[Theory] Introduction
Plug-ins are the historical way of extending GIMP. They are independent processes started by the core GIMP process and exchanging information through Inter-Process Communication (the “wire”). This is a very robust architecture because even if a plug-in were to crash, the core process (where the image is) would not budge.
Though historically image filters (code taking an image in input, modifying it and outputting a new image) were implemented as plug-ins, these days, we recommend to write them as GEGL Operations. This will make your filter previewable instantly on canvas, including with “Split view”, and it makes it usable as a non-destructive effect!
Plug-ins are better suited for various kinds of more organizational work, for instance if you want to edit, create, delete various layers, channels or vector paths, create new images, resize, and so on.
[Theory] Library Organization
GIMP provides a public API for constructing plug-ins, split in 2 main libraries:
The former is our base library and is always needed by any plug-in. The latter additionally links our graphical toolkit (GTK) and is usually needed when you want to add a graphical interface to your plug-in.
Note: in reality these libraries are themselves made of several sub-libraries, but from plug-in developer perspective, you can consider each as a single piece.
These 2 libraries and most of their functions are available to C plug-ins, as well as to all supported bindings. We will talk about this more in the Python tutorial.
Script-Fu is a special case because it is not a binding of these libraries.
[Theory] Plug-In Structure
Our API is heavily object-oriented. A plug-in itself is a subclass of
the GimpPlugIn
class with a few virtual methods to
implement:
init_procedures()
query_procedures()
create_procedure()
(mandatory)quit()
(optional)set_i18n()
(optional)
Note that your plug-in needs to implement at least one of
init_procedures()
or query_procedures()
(most commonly the latter;
we will see later the difference) and must implement
create_procedure()
.
Then you simply run the GIMP_MAIN()
macro in C plug-ins (or the gimp_main()
function in bindings).
And that’s about it!
Note: to know more about object-oriented code in libgimp, you may be interested in reading a GObject tutorial.
[Code] Basic Hello World
Let’s write the most simple C plug-in possible. For bindings, check the equivalent “Hello World” plug-in in the Python tutorial.
What this plug-in does is displaying “Hello World!” as a status message, which is pretty useless. But it’s a first step. It doesn’t even have a proper description nor will it have a menu entry.
/* Always include this in all plug-ins */
#include <libgimp/gimp.h>
/* The name of my PDB procedure */
#define PLUG_IN_PROC "plug-in-zemarmot-c-demo-hello-world"
/* Our custom class HelloWorld is derived from GimpPlugIn. */
struct _HelloWorld
{
GimpPlugIn parent_instance;
};
#define HELLO_WORLD_TYPE (hello_world_get_type())
G_DECLARE_FINAL_TYPE (HelloWorld, hello_world, HELLO_WORLD,, GimpPlugIn)
/* Declarations */
static GList * hello_world_query_procedures (GimpPlugIn *plug_in);
static GimpProcedure * hello_world_create_procedure (GimpPlugIn *plug_in,
const gchar *name);
static GimpValueArray * hello_world_run (GimpProcedure *procedure,
GimpRunMode run_mode,
GimpImage *image,
GimpDrawable **drawables,
GimpProcedureConfig *config,
gpointer run_data);
G_DEFINE_TYPE (HelloWorld, hello_world, GIMP_TYPE_PLUG_IN)
static void
hello_world_class_init (HelloWorldClass *klass)
{
GimpPlugInClass *plug_in_class = GIMP_PLUG_IN_CLASS (klass);
plug_in_class->query_procedures = hello_world_query_procedures;
plug_in_class->create_procedure = hello_world_create_procedure;
}
static void
hello_world_init (HelloWorld *hello_world)
{
}
static GList *
hello_world_query_procedures (GimpPlugIn *plug_in)
{
return g_list_append (NULL, g_strdup (PLUG_IN_PROC));
}
static GimpProcedure *
hello_world_create_procedure (GimpPlugIn *plug_in,
const gchar *name)
{
GimpProcedure *procedure = NULL;
if (g_strcmp0 (name, PLUG_IN_PROC) == 0)
procedure = gimp_image_procedure_new (plug_in, name,
GIMP_PDB_PROC_TYPE_PLUGIN,
hello_world_run, NULL, NULL);
return procedure;
}
static GimpValueArray *
hello_world_run (GimpProcedure *procedure,
GimpRunMode run_mode,
GimpImage *image,
GimpDrawable **drawables,
GimpProcedureConfig *config,
gpointer run_data)
{
gimp_message ("Hello World!");
return gimp_procedure_new_return_values (procedure, GIMP_PDB_SUCCESS, NULL);
}
/* Generate needed main() code */
GIMP_MAIN (HELLO_WORLD_TYPE)
Assuming the code is in a file named main.c
, this plug-in can be
compiled with GCC with the following command line:
gcc main.c `pkg-config --cflags --libs gimp-3.0` -o c-hello-world
[Theory] File Architecture
Now create a directory c-hello-world
under the plug-ins
folder in
your plug-ins/
directory.
In particular:
- In Linux or other OS respecting the XDG Base Directory
Specification,
create:
$XDG_CONFIG_HOME/GIMP/3.0/plug-ins/c-hello-world/
($XDG_CONFIG_HOME
defaults to$HOME/.config/
) - In Windows, create:
%APPDATA%\GIMP\3.0\plug-ins\c-hello-world\
- In macOS, create:
NSApplicationSupportDirectory/GIMP/3.0/c-hello-world/
Now you can:
- install the executable
c-hello-world
previously compiled withgcc
into this directory; - start GIMP;
- open the “Search Actions” dialog (either with default shortcut
/ or from menu
Help > Search and Run a Command
); - type
pdb
in the search field, which should list the “Procedure Browser”, then hit Enter; - in the Procedure Browser, search
plug-in-c-demo-hello-world
(the created procedure); - you should find the entry, which is pretty minimal: no description
and with only the few basic arguments (a
run-mode
, animage
and an array ofdrawables
).
Congratulations, you just made your first C plug-in!
Now you may wonder how to run it. Right now, since this plug-in is so minimal, it can only be called from another plug-in, from one of the debug consoles, or as a batch command. We will talk about these use cases later.
In our next [Code]
section, we will make the plug-in easier to run
from the interface. For now, let’s study this code.
[Theory] Decipher our Hello World
PDB Procedure Name
The name of our procedure is how it is stored in the Procedural DataBase (PDB), which is why this is what we searched in the Procedure Browser:
#define PLUG_IN_PROC "plug-in-zemarmot-c-demo-hello-world"
We store it in a macro since we reuse this string in a few places. This way, we prevent useless copy-paste bugs, and it is simpler to search it in your plug-in code.
We will talk later about how exactly the PDB makes a plug-in much more useful than solely a feature called in the interface. For now, just remember that the procedure name is important and should be wisely chosen:
-
You want to avoid name clashes with other people. If you take a very generic name, someone might have the same idea as you, therefore both plug-ins cannot be installed at the same time.
This is why I decide to start all my custom procedures as
plug-in-zemarmot-
, per our ZeMarmot film project name as some kind of namespace. -
Secondly if you care at all about the stability of your procedure (for people who want to depend on it in other scripts), you should choose a procedure name which you won’t want to change later on.
That is why I am using quite a descriptive name,
c-demo-hello-world
(I know my tutorial is going to demonstrate Python or Script-Fu plug-ins too).
GObject Boilerplate Code
These pieces of code are basically boilerplate code used in
GObject to declare then
define our new class HelloWorld
as a subclass of GimpPlugIn
:
struct _HelloWorld
{
GimpPlugIn parent_instance;
};
#define HELLO_WORLD_TYPE (hello_world_get_type())
G_DECLARE_FINAL_TYPE (HelloWorld, hello_world, HELLO_WORLD,, GimpPlugIn)
G_DEFINE_TYPE (HelloWorld, hello_world, GIMP_TYPE_PLUG_IN)
static void
hello_world_class_init (HelloWorldClass *klass)
{
GimpPlugInClass *plug_in_class = GIMP_PLUG_IN_CLASS (klass);
plug_in_class->query_procedures = hello_world_query_procedures;
plug_in_class->create_procedure = hello_world_create_procedure;
}
static void
hello_world_init (HelloWorld *hello_world)
{
}
Note that the hello_world_init()
and hello_world_class_init()
functions are mandatory. For our demo, we don’t need to do anything in
the former, but we use class_init()
to override 2 class methods.
GimpPlugIn Virtual Methods
query_procedures()
The method query_procedures()
will be called after every update of
either GIMP or the plug-in. It won’t be called on intermediate runs (the
result is cached), which is what makes the first run after any update a
bit slower.
What this function must return is simply a
GList
of PDB procedure
names implemented by this plug-in.
In particular it means that a single plug-in may define several procedures. This is useful for related features where you want to share code. Since our demo plug-in only defines a single procedure, we just return a list with 1 allocated string.
create_procedure()
The method create_procedure()
returns the interface of this procedure,
as well as various metadata, such as a title, menu path, authorship. We
will see these usage soon. Our initial version was basic enough that it
didn’t have any of these.
This method is called immediately after a query_procedures()
call at
startup time (one time for each returned procedure name) but also every
time you run the procedure during a session of GIMP.
GIMP_MAIN
Finally the GIMP_MAIN
macro will generate a standard main()
function
for you (differently depending on the platform you build for) then will
call gimp_main()
for you the correct way.
For C plug-ins, you should never call gimp_main()
directly, always the
GIMP_MAIN()
macro instead, with the class type of your plug-in. For
bindings, this is the opposite.
[Code] Adding Plug-In Metadata
To make your plug-in a bit more useful, you want to add some kind of
metadata. This all happens in create_procedure()
. I am therefore only
displaying the new version of this function:
static GimpProcedure *
hello_world_create_procedure (GimpPlugIn *plug_in,
const gchar *name)
{
GimpProcedure *procedure = NULL;
if (g_strcmp0 (name, PLUG_IN_PROC) == 0)
{
procedure = gimp_image_procedure_new (plug_in, name,
GIMP_PDB_PROC_TYPE_PLUGIN,
hello_world_run, NULL, NULL);
gimp_procedure_set_sensitivity_mask (procedure, GIMP_PROCEDURE_SENSITIVE_ALWAYS);
gimp_procedure_set_menu_label (procedure, "_C Hello World");
gimp_procedure_add_menu_path (procedure, "<Image>/Hell_o Worlds/");
gimp_procedure_set_documentation (procedure,
"Official Hello World Tutorial in C",
"Some longer text to explain about this procedure. "
"This is mostly for other developers calling this procedure.",
NULL);
gimp_procedure_set_attribution (procedure, "Jehan",
"Jehan, ZeMarmot project",
"2025");
}
return procedure;
}
Now:
- compile again your plug-in and restart GIMP.
- Notice a new “Hello Worlds” menu.
- Click it (alternatively, use the mnemonic by hitting Alt-o).
- Select the “C Hello World” entry.
- Notice a status message saying “Hello World!”
[Theory] Decipher the Updated Hello World
By default, image procedures can be run when one or more drawable are
selected in an image. You may tighten the usage (e.g. you may want to
work with only 1 drawable), or support more cases (such as no drawables
selected, and even no images opened). This is what
gimp_procedure_set_sensitivity_mask()
is about.
When your plug-in is meant to always work, whether or not an image is
opened, and whatever the number of selected drawable, then you can set
GIMP_PROCEDURE_SENSITIVE_ALWAYS
.
Since our plug-in just prints a message, this is our case.
Now we want to give a label to our plug-in, which is what
gimp_procedure_set_menu_label()
is about.
Thanks to this label, the plug-in can now also be searchable in the
“Search Actions” dialog we used earlier.
If we want our plug-in to be visible in a menu, we use
gimp_procedure_add_menu_path()
Note the syntax:
- The first part between less-than and more-than signs
"<Image>"
is the menu location. There are a few supported menu locations, but for now, we will only name the “Image” menu location which simply corresponds to the main menu above the canvas. - All parts after this are menus and submenus, separated by slash (/) characters. In particular here, this plug-in will create (unless it already exists) a top menu “Hello Worlds”. Note that it is possible to use existing menus, and even default menus. In the later case, please use the English version. For instance, say that you want in fact to install your procedure in a “Hello Worlds” submenu under “Filters”. Then you would call:
gimp_procedure_add_menu_path (procedure, "<Image>/Filters/Hell_o Worlds/");
Note how, while you may create a mnemonic key (_o
) for your new submenu,
you must not care about the existing mnemonic key on the “Filters” top
menu.
Now you want to write a bit more text about your plug-in, for people to
understand what it is from within the software. This is when you use
gimp_procedure_set_documentation()
.
The first string is usually for a text slightly longer than the label,
which will be used in tooltips, or as subtitle explanation in the
“Search Actions”
dialog.
The second string is mostly used in the Procedure Dialog. It is for an
even longer explanation, in particular
for more technical documentation targetted at other developers who would
call your procedure from their own plug-ins. If you don’t need the
technical string, just leave it to NULL
rather than copying the other
string.
The last argument is an help ID. For now, just leave it to NULL
too.
Finally the
gimp_procedure_set_attribution()
is for standard authorship information. The first string is the list of
authors. The second string is for slightly longer copyright information.
The last string is an initial creation date.
[Code] Advanced Hello World
Now let’s make a slightly more useful-looking Hello World. Instead of just making a status message, we will actually create a text layer in an existing image.
So the first change is to the sensitivity mask. Say we want to only accept 2 cases: when a single layer is selected (in this case, the new text layer will be created just above it) or when no drawables are selected (in this case, the new text layer will simply be at the top).
We change the sensitivity mask this way:
gimp_procedure_set_sensitivity_mask (procedure, GIMP_PROCEDURE_SENSITIVE_DRAWABLE | GIMP_PROCEDURE_SENSITIVE_NO_DRAWABLES);
Now we want to update the run()
function:
static GimpValueArray *
hello_world_run (GimpProcedure *procedure,
GimpRunMode run_mode,
GimpImage *image,
GimpDrawable **drawables,
GimpProcedureConfig *config,
gpointer run_data)
{
GimpTextLayer *text_layer;
GimpLayer *parent = NULL;
gint position = 0;
gint n_drawables;
n_drawables = gimp_core_object_array_get_length ((GObject **) drawables);
if (n_drawables > 1)
{
GError *error = NULL;
g_set_error (&error, GIMP_PLUG_IN_ERROR, 0,
"Procedure '%s' works with zero or one layer.",
PLUG_IN_PROC);
return gimp_procedure_new_return_values (procedure,
GIMP_PDB_CALLING_ERROR,
error);
}
else if (n_drawables == 1)
{
GimpDrawable *drawable = drawables[0];
if (! GIMP_IS_LAYER (drawable))
{
GError *error = NULL;
g_set_error (&error, GIMP_PLUG_IN_ERROR, 0,
"Procedure '%s' works with layers only.",
PLUG_IN_PROC);
return gimp_procedure_new_return_values (procedure,
GIMP_PDB_CALLING_ERROR,
error);
}
parent = GIMP_LAYER (gimp_item_get_parent (GIMP_ITEM (drawable)));
position = gimp_image_get_item_position (image, GIMP_ITEM (drawable));
}
text_layer = gimp_text_layer_new (image, "Hello World!", gimp_context_get_font(),
20.0, gimp_unit_pixel ());
gimp_image_insert_layer (image, GIMP_LAYER (text_layer), parent, position);
return gimp_procedure_new_return_values (procedure, GIMP_PDB_SUCCESS, NULL);
}
Note that despite the change to gimp_procedure_set_sensitivity_mask()
,
we still handle the various possible cases, because what the sensitivity
mask changes is only GUI-sensitivity: the procedure will be shown
insensitive in menus and in the “Search Actions” dialog. Yet it is still
possible to run this procedure, e.g. from other plug-ins.
The first error case we handle is when more than 1 drawable is selected. The second error case is when the only drawable is not a layer (the selected drawable may be a channel).
When any error occurs, we create a
GError
and return from
the plug-in with GIMP_PDB_CALLING_ERROR
.
If we are in one of the supported cases, we continue, first computing the position in the layer tree we want to insert our new text layer in. Then we create the layer with our “Hello World” text. And finally we insert it.
Conclusion
You can now test this version of the tutorial “Hello World” and notice how it creates a new layer. I will let you study the various used functions yourselves with our API reference documentation.
Now this basic tutorial is still very limited. Maybe we could try to
compute a nicer default font size, maybe by verifying the whole image’s
dimensions with gimp_image_get_width()
and gimp_image_get_height()
,
with some heuristic to determine a nicer font size to fill the whole
image without going off-canvas.
Then maybe we compute the proper offsets for the text to be exactly in
the middle and set these with
gimp_layer_set_offsets()
.
I will leave such improvements as exercise to the reader.
Our tutorial is not going to dig much further into all the very advanced
features made available in our libgimp
API, since nearly everything is
doable by plug-ins. You should study the API reference to discover all
the features, or read code of existing plug-ins to see what other people
have been doing.
Our next part will focus on adding a graphical interface to our existing Hello World plug-in. I suggest to read it even if you are not interested in making your plug-in graphical as it also explains major concepts about PDB procedures.
- Next tutorial: C plug-in GUI