Script-Fu Plug-Ins

Script-Fu is probably the oldest binding system for extending GIMP. It is also a Scheme variant, which evolved independently for many years now.

Script-Fu is quite special, compared to all other bindings, because it is not actually a binding of the libgimp and libgimpui libraries. It is in fact a binding over the PDB directly, with several added functions.

What it means is twofold:

  • First it won’t have all the features of the other bindings;
  • Some things are done a bit differently.

In some other point of views, it is also more reliable because the whole interpreter comes with GIMP (so you should not have issues with the programming language changing on your and functions with different behavior depending on what people install on their machine).

In this tutorial, we will create a small Hello World in Script-Fu to again create a text layer saying “Hello World!”. We will use the version 3 of Script-Fu (since it is possible to write scripts with the version 2 as well in GIMP 3).

Also it will be a proper plug-in running on the new gimp-script-fu-interpreter-3.0 shipped with GIMP, unlike the legacy “scripts” which were running on an always-ON interpreter plug-in. The latter scripts can still be written, but they are less recommended now and may eventually be deprecated. The main disadvantage of legacy Script-Fu scripts is that a single buggy script crashing would bring the whole interpreter down with it (and you had to restart GIMP). The new Script-Fu plug-ins are all independent, just like all plug-ins.

Reimplementing Hello World in Script-Fu v3

Here is what the same C and Python plug-in looks like in Script-Fu:

#!/usr/bin/env gimp-script-fu-interpreter-3.0

(define (script-fu-zemarmot-hello-world
         image
         drawables
         font
         compute-size
         size
         text)
  (script-fu-use-v3)
  (let* ((layer (gimp-text-layer-new image text font size UNIT-PIXEL)))

    (gimp-image-undo-group-start image)

    (gimp-image-insert-layer image layer -1 0)
    (if (= compute-size TRUE)
      (let* ((image-width (gimp-image-get-width image))
             (layer-width (gimp-drawable-get-width layer)))
        (begin
          (set! size (* size (/ image-width layer-width)))
          (gimp-text-layer-set-font-size layer size UNIT-PIXEL)
        )
      )
    )

    (gimp-image-undo-group-end image)
  )
)

(script-fu-register-filter "script-fu-zemarmot-hello-world"
  "Script-Fu v3 Hello World"
  "Official Hello World Tutorial in Script-Fu v3"
  "Jehan"
  "Jehan, Zemarmot project"
  "2025"
  "*"
  SF-ONE-OR-MORE-DRAWABLE
  SF-FONT       "Font"               "Sans-serif"
  SF-TOGGLE     "Compute Ideal Size" #f
  SF-ADJUSTMENT "Font size (pixels)" '(20 1 1000 1 10 0 1)
  SF-STRING     "Text"               "Hello World!"
)

(script-fu-menu-register "script-fu-zemarmot-hello-world" "<Image>/Hello W_orlds")

File Architecture

Same as in C, Python, or any other plug-in, you must create a folder in your plug-ins/ directory and put your Script-Fu file in this folder with the same name (only adding the .scm extension).

For instance, if you write your code in a file named scm-hello-world.scm, install it in a directory named scm-hello-world/.

Then make sure your script file is executable, in the case where you are on a platform where this matters (which is probably any OS but Windows):

chmod u+x scm-hello-world.scm

Now if you restart GIMP, it should pick up your plug-in.

Studying the Script-Fu Hello World

Same as for Python or any other interpreted language, start your script with a shebang naming the new interpreter dedicated to Script-Fu:

#!/usr/bin/env gimp-script-fu-interpreter-3.0

In Script-Fu, we don’t have all the concept of subclassing GimpPlugIn. And as a general rule, we don’t see as much that the API is object-oriented (though it still shows through the function names).

Instead, you just call (script-fu-register-filter) in the end with the following arguments:

Finally you will end with a call to (script-fu-menu-register) which will register the action by its name and giving a menu path where you want it to appear.

Obviously you need to define a run() function, which in Script-Fu must be called the same as your PDB procedure name. This is why I have been defining a function (script-fu-zemarmot-hello-world).

The signature of this run function is:

  • the GimpImage
  • the GimpDrawable list
  • then one argument per registered argument, in the same order as their registration.

Alternative Script-Fu Hello World

You may have noticed that the sensitivity mask which you can set in (script-fu-register-filter) does not have an equivalent for GIMP_PROCEDURE_SENSITIVE_ALWAYS.

In Script-Fu, when you want to make an always-sensitive plug-in, use (script-fu-register-procedure) instead. Let’s do a demo of it. This is the same plug-in except that we now add a width and height argument and create a new image with this.

#!/usr/bin/env gimp-script-fu-interpreter-3.0

(define (script-fu-zemarmot-hello-world2
         width height
         font
         compute-size
         size
         text)
  (script-fu-use-v3)
  (let* ((image (gimp-image-new width height RGB))
         (layer (gimp-text-layer-new image text font size UNIT-PIXEL)))

    (gimp-image-undo-group-start image)

    (gimp-display-new image)
    (gimp-image-insert-layer image layer -1 0)
    (if (= compute-size TRUE)
      (let* ((layer-width (gimp-drawable-get-width layer)))
        (begin
          (set! size (* size (/ width layer-width)))
          (gimp-text-layer-set-font-size layer size UNIT-PIXEL)
        )
      )
    )

    (gimp-image-undo-group-end image)
  )
)

(script-fu-register-procedure "script-fu-zemarmot-hello-world2"
  "Script-Fu v3 Hello World 2"
  "Official Hello World Tutorial in Script-Fu v3"
  "Jehan, Zemarmot project"
  "2025"
  SF-ADJUSTMENT "Image Width"        '(1920 10 100000 1 10 0 1)
  SF-ADJUSTMENT "Image Height"       '(1080 10 100000 1 10 0 1)
  SF-FONT       "Font"               "Sans-serif"
  SF-TOGGLE     "Compute Ideal Size" #f
  SF-ADJUSTMENT "Font size (pixels)" '(20 1 1000 1 10 0 1)
  SF-STRING     "Text"               "Hello World!"
)

(script-fu-menu-register "script-fu-zemarmot-hello-world2" "<Image>/Hello W_orlds")

So what are the differences here?

  • The main difference is the absence of the allowed image type and sensitivity mask arguments. Procedures registered this way are always sensitive. This is the Script-Fu variant for being to create an ALWAYS-ON procedure.
  • There is also a single authorship line.
  • Finally the run() method ((script-fu-zemarmot-hello-world2) in this example) doesn’t have image and drawables arguments anymore. You may still query the full list of images with (gimp-get-images) and ((gimp-image-get-selected-layers) internal procedures. But the core process don’t give you information of the currently active image anymore.

And of course, if you start GIMP now, and check the menu without creating nor opening any image on canvas, you will see that “Script-Fu v3 Hello World” is indeed insensitive whereas “Script-Fu v3 Hello World 2” can be clicked!

Calling a Core PDB Procedure in Script-Fu

All the core procedures have an automatically generated Script-Fu function equivalent, which is the same name and with the same arguments in same order. Note that while it is possible to call a PDB core procedure with missing arguments, it is not recommended and a warning will be outputted.

So if creating a text layer should be called like this:

(gimp-text-layer-new image "Hello World" font 20 UNIT-PIXEL)

If you were to call:

(gimp-text-layer-new image "Hello World" font 20)

The script would still go forward but a warning would be outputted on stderr:

scriptfu-WARNING **: 21:31:19.213: Missing arg type: GimpUnit

Not only this, the missing argument may be filled with some invalid defaults and the script will likely not work as you expect. It is undefined behavior.

Finally as we were saying in the PDB tutorial:

libgimp functions are not necessary all PDB procedures, yet all core PDB procedures are also libgimp functions.

So you cannot refer to the libgimp API reference to make sure a Script-Fu function exists.

Instead the best (and exhaustive) reference right now is the Procedure Browser which can be found in the Help menu.

Calling a Plug-In PDB Procedure in Script-Fu

Now in Script-Fu, there is a small difference in the way Plug-In PDB procedures are called! While each plug-in procedure also has a generated function, it is usually callable with random-order arguments and many arguments may be left with default values (though not necessarily all, and in particular the first arguments are usually mandatory).

For instance, the python-fu-foggify plug-in has the following arguments:

  • run-mode
  • image
  • drawables
  • a layer name (string)
  • a color (GeglColor)
  • a turbulence value (double floating point)
  • an opacity value (double floating point)

From a Script-Fu plug-in, I could just call:

(python-fu-foggify #:image img #:drawables (car (gimp-image-get-layers img)) #:opacity 50.0 #:color '(50 4 4))

You may note:

  • how it uses a special syntax #:arg-name before every argument value;
  • how order doesn’t matter: for instance, I set the opacity argument early in the argument list;
  • how you can ignore some arguments, which means they will use the default value. Here I ignored the run-mode and the turbulence.

Note: see also how we serialize a color in Script-Fu (instead of using a GeglColor) though this is not specific to calling plug-in procedures. It’s just that various types of data have special representation in this language.

The special #: syntax for plug-in procedure, allowing to ignore arguments or to order them anyhow you want, is a feature with the purpose of not making the number and order of argument an API-breaking part of the PDB interfaces.

In particular, if we decide to add more arguments to the python-fu-foggify plug-in, it can be done without breaking existing scripts.

Automatic GUI in Script-Fu

Something else you may have noticed is that your Script-Fu plug-in has a dialog even without adding any GUI code.

This has both advantages and drawbacks. In particular it means we are also not able to tweak the order of arguments (except by changing the order when defining them), nor can be reorganize them in containers or otherwise.

It also means we are not able to implement the conditional sensitivity GUI code logic for the size widgets as we did in C, Python (or even when writing a filter as GEGL operation).

Conclusion

We’ve done a bit of a quick skimming of the abilities of a Script-Fu plug-in. We only looked at the latest version of the language, though it has a layer of compatibility allowing to use some older Script-Fu idiosyncrasies from the GIMP 2.x time.

If you want to dive deeper, the Script-Fu User Guide might have more complete information.