Official tutorials on GIMP plug-ins and GEGL filters development

Cross-posting this from/to other media where GIMP may have an official presence since I believe it’s important we get as much feedback as possible before release.

So in the last few days, I’ve started writing tutorials on writing various kinds of programmatic extensions for GIMP, i.e. plug-ins but also filters. It is not finished work. I will likely add more sections, or more parts in existing sections, and I’m sure I will tweak text as I read again and again.

People who know me also know I have a hard time writing concisely (and for a good tutorial, I believe it needs to be both concise and detailed at times, in the same time, which is something hard to achieve! I worked quite hard already on current text and structure!).

Anyway as I know there are quite some people in this forum interested in plug-in developments, here are the 2 tutorials:

Still work-in-progress (both) but I welcome feedbacks! Thanks! :hugs:

P.S.: note that the feedback is not only about the docs, but also about issues in the API itself. If you notice issues when trying to make your plug-in and it feels that the problem is that something is not ideal in the API, we want to hear from it. We likely won’t do major restructuration before 3.0.0, yet we are still able to do some logic fixes here and there.

3 Likes

Hi Jehan. Thank you for these tutorials. They have cast light in a number of areas I was uncertain about. Now they have inspired me to learn about C plugins and filters. So I have started working my way through the tutorials systematically. I have just finished with the code for the first hello-world plugin and am trying to compile it according to the instructions. And I get
main.c:2:10: fatal error: libgimp/gimp.h: No such file or directory
2 | #include <libgimp/gimp.h>
| ^~~~~~~~~~~~~~~~
compilation terminated.

Using Ubuntu 24.4.02, Gimp 3.0 RC3 flatpak. I have done some C programming but usually work with Python. Any suggestions please?
JimDee2

Which OS/package is it? The header gimp.h might be missing.

Typically, if you run pkg-config --cflags gimp-3.0, it will return a bunch of -I folders where headers are looked for. Most likely the first one of them will be a path ending with gimp/include/gimp-3.0. And this is where you should find a subfolder libgimp and inside it a header gimp.h.

If this is one of our packages (e.g. Windows installer/macOS packages), we’ll want to check if the file is missing.

If this is flatpak, there might be some ways to work within the sandbox (to be investigated). If this is a Linux distribution’s package, maybe it should be reported to the distribution that they are supposed to also ship with the public header for plug-ins developers (GIMP being a desktop software, but also a development platform).

Oups you gave the answer already! I should have read the comment more thoroughly!

I’ll have to make some tests. The flatpak is a very special case because it’s in a sandbox. So it is expected that my current command won’t work. I’ll try to find a solution and post later.

Thank you.
Following your suggestion gave

Package gimp-3.0 was not found in the pkg-config search path.
Perhaps you should add the directory containing `gimp-3.0.pc’
to the PKG_CONFIG_PATH environment variable
Package ‘gimp-3.0’, required by ‘virtual:world’, not found

It is the Gimp 3.0 RC3 flatpak with Ubuntu 24.4.02 so I guess the files are sandboxed. How do I need to install Gimp 3.0 to make the files accessible? I am sure I am not the only one who will have this problem.

JmDee2

Ok so I made some quick tests.

So to enter the sandbox:

  1. You must also have installed the flatpak of the GNOME SDK. Since you said it’s the RC3 flatpak, I am going to assume you use the beta branch (change the FLATPAK_BRANCH to stable for our stable flatpak and master for our nightly one):
FLATPAK_BRANCH=beta
FLATPAK_SDK=`flatpak --system remote-info flathub org.gimp.GIMP//$FLATPAK_BRANCH | grep Sdk: |sed 's/^ *Sdk: *\(.*\)/\1/'`
flatpak install flathub $FLATPAK_SDK
  1. Then with this command, you can enter the sandbox:
flatpak run --devel --env=PKG_CONFIG_PATH=/app/lib/pkgconfig --env=LD_LIBRARY_PATH=/app/lib --command=bash org.gimp.GIMP//beta
  1. Now cd to your plug-in source directory.
  2. There compile with:
gcc main.c -lexiv2 `pkg-config --cflags --libs gimp-3.0 gimpui-3.0` -o c-hello-world

2 small caveats in this procedure:

  1. Now I am unsure why, I had to link with Exiv2 explicitly (-lexiv2). On my main system, I don’t need to do this, and since neither our API nor GExiv2 API exposes any symbol of Exiv2 as far as I know, this should not be needed. It’s weird. Even ldd /app/lib/libgexiv2.so finds properly libexiv2.so.28 (from the same directory!). (issue found, see my next command, I also edited the command to enter the sandbox)
  2. While writing this tutorial, I fixed a few bugs which I encountered. One of these is that gimp_text_layer_set_font_size() (added in the complex GUI part of the tutorial) was not working when setting gimp_unit_pixel (). Your code will still compile but GIMP RC3 will pop up an error when you’ll run it. So just remove this part of the tutorial (or use the nightly flatpak which already has the fix).

In any case, I did test. Doing this will compile the C plug-in, then the flatpak will be properly registered when running the flatpak, and it will run properly (after removing the call from my second point just above).

Enjoy!

Oh I just understood this one, a minute or 2 after posting this tutorial. The answer was in my part of the comment:

Anyway it’s because the linker doesn’t find the library because the prefix in sandbox is /app/. So same as for the pkg-config path, we must also edit $LD_LIBRARY_PATH.

Basically enter the sandbox with:

flatpak run --devel --env=PKG_CONFIG_PATH=/app/lib/pkgconfig --env=LD_LIBRARY_PATH=/app/lib --command=bash org.gimp.GIMP//beta

And now you are good to go!

P.S.: I’ll edit my previous comment.

P.P.S.: also eventually creating a meson file for a C plug-in would also be a good idea, rather than playing manually with a compiler command line, and pkg-config.

There already was/is a tutorial here
https://testing.docs.gimp.org/3.0/en/gimp-using-python-plug-in-tutorial.html
which I found very helpful.
Could that be linked / integrated as well?

Yeah we were discussing about this just yesterday, I think. The advantage of the one in the main docs is that it is localized in many languages. So it’s helpful for people who don’t understand English very well.

The drawbacks, I’d say, is related: since it is localized, it’s harder to edit (every edit creates work for dozens of people who need to retranslate). Also in a generic doc, maybe going too deep may not be desired. Finally I think that the developer website is the better place for such tutorials (basically I am envisioning 2 distinct sections in the dev website: one for core developers; another for anyone wishing to create resources with GIMP, i.e. plug-ins, filters, but even maybe brushes and other stuff in the future).

So I think that the plan right now would be:

  • Integrate whatever may be missing from the main docs tutorial into the new tutorial;
  • Keep the main docs tutorial more or less as-is;
  • Add a link from the main docs tutorial to the dev one, with something saying “For more in-depth tutorial and docs, go to…” (or something along these lines);
  • Continue working mostly on the dev docs tutorial.
1 Like

Thank you very much. Just tested it and all is good. Rather fiddly though and meson and a build file is probably easier for those less familiar with compiling code.
JimDee2

Yeah a meson build would definitely be best. I’ll write down a small section for this in the tutorial.

Though I do always like to start by giving the “raw compiler command” first for a tutorial, because it shows what’s going on under the hood. If we start with build system scripts directly, it makes the whole compiling some kind of “magic”.

This all being said, the whole part about compiling inside the flatpak sandbox is not supported by any build system. I.e. that even with a meson script (or CMake, or autotools…), you’d still have to run the flatpak command to enter your sandbox before running meson/configure/cmake then ninja/make.

I think I’ll add some “flatpak environment” support to my crossroad tool (which is the tool I created for simplifying cross-building, but even for my daily native programming). Some others (including me!) may be interested.

What could be improved for the API is this:

For example: I want to use script-fu-add-bevel in my python script.
Find it in the console and it tells me to do it like this:

procedure = Gimp.get_pdb().lookup_procedure('script-fu-add-bevel')
config = procedure.create_config()
config.set_property('run-mode', Gimp.RunMode.INTERACTIVE)
config.set_property('image', image)
config.set_property('drawables', drawables)
config.set_property('adjustment', adjustment)
config.set_property('toggle', toggle)
config.set_property('toggle-2', toggle_2)
result = procedure.run(config)
success = result.index(0)

which won’t work, because the drawable(s) have to be defined like so:

config.set_core_object_array('drawables', [drawables])

It would be very nice if the concole could give the correct code here.

Oh. Well that’s a bug, because the set_core_object_array() is kinda recent and we must have forgotten to update the suggestion code of the Python Console.

We’d welcome a bug report so that we don’t forget.

So it should rather be:

config.set_core_object_array('drawables', drawables)

(with a “s” at the end, we’d expect drawables to be a list of drawables; even though it’s obviously a random name proposition and of course, you could have a single GimpDrawable named “drawables”, though I’d question the choice of variable name :stuck_out_tongue:)

reported here:

2 Likes

Totally agree with your approach that developer related things should be mentioned/linked in main docs, but mainly placed in developers wiki/website.

Also there is imho no need to translate the developers website, because English is generally used for development as main language (also built-in into the language/API syntax itself) so most developers are used to communicate in English.

Hi Jehan, I am continuing working through the tutorials and have now reached the Python part with which I am much more familiar. There are a couple of minor points which make Gimp reject the code. The first is that it does not like the “blurb” part of the add_argument lines being None but insists on some sort of string entry. The second point is that the “invert_title” term in the dialog_fill_frame line should be True (as it is in the C version). Oh and there are trailing semi-colons, although Python ignores those.

I hope these points are helpful. Really interesting tutorial, Thanks
JimDee2

That’s an annotation bug (the annotation used by the introspection code to construct the Python binding). I’ve fixed it a week ago (while writing the tutorial). So using None is only possible in dev code right now, not in RC3. For the time being, just use an empty or whatever other bogus string.

Thanks. I fixed it.

Oh and there are trailing semi-colons, although Python ignores those.

Oups. I fixed those too.
I’ve written various pieces of code by copy-pasting C code then changing it to Python. I guessed I forgot to remove the semicolons sometimes. :sweat_smile:

I hope these points are helpful.

Definitely helpful. Thanks for noticing these.

Really interesting tutorial, Thanks

Cool! :blush:

@Jehan I’ve sent you some notes about python on Signal (not sure how frequently you are using it :thinking:)

Edit: sorry Matrix

Looked at the Python part.

I think you should add several examples of how to navigate the doc (the classes, but also the functions and the enumerations), especially when you have to go across modules (methods that come from GObject…) . In my experience this was the difficult part.

Telling explicitly the correspondences between C and Python types would be useful.

For instance the varargs functions (variable-length arguments à-la printf) don’t have a Python version.

Is this a restriction of the GObject bindings? Because it’s supported in Python.

if not isinstance(drawables[0], Gimp.Layer):

The API is missing is a single call that tells if a drawable is a layer, a group, a mask, or a channel… (this is not what Drawable::Type() is about, unfortunately). The current situation entails a lot of spaghetti code. For instance, to tell if a layer is a plain layer you have to test that it is not a group, and this happens often because a GroupLayer is a drawable… on which you can’t draw :smiling_imp:. Maybe Gimp::Drawable should define a DrawableType virtual method, and each subclass return its identifier (LAYER, GROUP, MASK, CHANNEL).

Something that is missing in the doc is the I18 support, in other words, how to deliver a translatable plugin. Of course as far as I can tell it is pretty much the standard gettext() system, and Gimp’s I18N support in plugins isn’t that useful, and perhaps even more restrictive than the native gettext: you can’t initialize static strings because with the Gimp API the language is defined after the statements that create the static strings have been executed.

Yes. After some discussion with @MichalVasut who had a similar question, I am editing the text to say (“by GObject-Introspection” added at the end of sentence):

Only very few libgimp or libgimpui functions are not available in bindings, and only because this is not supported by GObject-Introspection.

Well for this case, you could just test the exact type of the layer object then (type(layer) == Gimp.Layer). We do the same in C.

Or am I missing something and that’s not what you meant?

Something that is missing in the doc is the I18 support, in other words, how to deliver a translatable plugin.

Yeah I was thinking about adding this. I moved a bit to fixing some code issues, but plan to update the tutorial a bit further soon.

Of course as far as I can tell it is pretty much the standard gettext() system, and Gimp’s I18N support in plugins isn’t that useful

Sure, it’s just gettext because that’s what we use. I find it useful though, because we just kinda create a “default i18n structure” so that people won’t have to wonder “how to do it”. They can just follow the instructions.

Now it is still possible to bypass these defaults so that people can do differently. Or even use another i18n system than gettext if they fancied.

One of the big difference in GIMP 3.0 plug-ins with 2.x series, in term of internationalization is that everything is plug-in side. Typically the whole part within the create_procedure() used to be localized core-side, i.e. that the plug-in name or descriptions were passed as English (in other words marked for localization with N_()) in GIMP 2.x. But this doesn’t work well with the “third-party extensions as first class citizens” future of GIMP I am envisioning.

So now the whole plug-in strings are localized on plug-in side. No exceptions. This makes the whole thing simpler for plug-in developers, less reliance on “gettext-only” (so more freedom for third-party devs) and less “should I localized with _() or N_()?” questions, which even I had far too often in plug-ins).

you can’t initialize static strings because with the Gimp API the language is defined after the statements that create the static strings have been executed.

What do you mean? I don’t understand this sentence (“initialize” static strings? In particular in the context of localizing strings).
Maybe with an example of something we can’t do, I may understand better, if needed.