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:

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:

  1. install the executable c-hello-world previously compiled with gcc into this directory;
  2. start GIMP;
  3. open the “Search Actions” dialog (either with default shortcut / or from menu Help > Search and Run a Command);
  4. type pdb in the search field, which should list the “Procedure Browser”, then hit Enter;
  5. in the Procedure Browser, search plug-in-c-demo-hello-world (the created procedure);
  6. you should find the entry, which is pretty minimal: no description and with only the few basic arguments (a run-mode, an image and an array of drawables).

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:

  1. 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.

  2. 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.