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:
- The PDB Procedure name;
- The procedure title;
- The procedure blurb (what will show in tooltips);
- The procedure authors (second argument of C
gimp_procedure_set_attribution()
) - The procedure copyright (third argument of C
gimp_procedure_set_attribution()
) - The procedure creation date (fourth argument of C
gimp_procedure_set_attribution()
) - The allowed image types (see
gimp_procedure_set_image_types()
) - The sensitivity mask among one of
SF-TWO-OR-MORE-DRAWABLE
,SF-ONE-OR-MORE-DRAWABLE
orSF-ONE-DRAWABLE
(similar togimp_procedure_set_sensitivity_mask()
) - And finally a variable-length list of arguments, each time made of 3 arguments: first an argument type, then a label, then a specification or default value (depending on the argument type).
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 theturbulence
.
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.