HCoder.org

GIMP Scripting

The last month or so I have been working on a project that I will talk about shortly. For that I had to do some simple image edits often, and after the third time or so I figured it was better to script them. I had done a little GIMP scripting before so I figured it wouldn’t be hard to write a couple of scripts to do what I wanted (which was very simple to start with). However, every time I had to do something with GIMP scripting I forget the details and I need to check again, so I figured it would be useful to leave some notes for myself and for others who might be interested.

Scripting languages

There are two main scripting engines you can use in GIMP: Script-Fu (which uses Scheme, a Lisp dialect), and python-fu (which uses Python). I have used both at some point but this tutorial will use Script-Fu. Unfortunately, making an introduction to Scheme as a language would be too long for this blog post, but you can check the Script-Fu tutorial in the official documentation.

Note that in the example I’m using some function defined in GIMP 2.10 and later, so the example code will not work as-is with older versions of the software (in particular, you will have to delete the calls to the functions gimp-context-set-stroke-method and gimp-context-set-line-width, and change gimp-drawable-edit-stroke-selection to gimp-edit-stroke… but if you do that, the script will lose some functionality).

Types of scripts/uses

There are two typical uses for these scripts: adding extra menu entries so we can call our script from within GIMP itself, and adding functions that we can call from the command-line. The former typically receive images or layers, and the latter typically receive filenames. In both cases we define functions that do what we want (from images/layers or from filenames) and then we either register them in the menu, or leave them as-is so we can call them from the command-line.

Defining a simple function

Create a new file add-border-to-image.scm inside your GIMP scripts/ directory. If you don’t know where that is, go to Edit ➝ Preferences ➝ Folders ➝ Scripts. You can use any of the folders in that list, or even add a new one.

In that file, enter the following text:

(define (add-border-to-image image)
  (let* ((drawable (car (gimp-image-get-active-layer image))))
    ;; The context push allows us to change settings (like the
    ;; foreground colour) and go back to the previous settings
    ;; when we pop it. The gimp-image-undo-group-* makes sure
    ;; that all the operations are considered only one with
    ;; regards to undo.
    (gimp-context-push)
    (gimp-image-undo-group-start image)

    (gimp-selection-all image)
    (gimp-selection-shrink image 2)

    (gimp-context-set-stroke-method STROKE-LINE)
    (gimp-context-set-line-width 4)
    (gimp-context-set-foreground "#657487")
    (gimp-drawable-edit-stroke-selection drawable)

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

This code defines a function called add-border-to-image which receives an image and paints a 4-pixel border on it. It also makes sure that all the operations are considered only one for undo purposes.

Once we have that function, we can add a menu entry for it, or we can prepare it so it’s easy to call from the command-line.

Functions already defined in GIMP

When you write these scripts you will need to check which functions are already defined in GIMP (like gimp-selection-all or gimp-drawable-edit-stroke-selection here). You do that by going to Filters ➝ Script-Fu ➝ Console and then clicking on the “Browse…” button. You will see a list of functions and a search box you can use to search for them. Note that by default the search only looks for functions with those names, so you might want to change that to “by description”.

Adding a menu entry for the function

If we want to be able to paint borders on an image we have open in GIMP, we can add the following code at the end of the file, then either restart GIMP or go to Filters ➝ Script-Fu ➝ Refresh Scripts:

(script-fu-register "add-border-to-image"
                    "<Image>/Edit/Add border"
                    "Paints a 4-pixel border"
                    "Esteban Manchado Velázquez"
                    "Esteban Manchado Velázquez"
                    "2020"
                    "RGB*"
                    SF-IMAGE "Image" 0)

This will add a new menu entry under Edit called “Add border”. The entry will be grayed out if you don’t have any image open. If you do, you will be able to click on the new option and see the newly added border.

Note: for some reason that I haven’t been able to find out, you will need to click anywhere on the image for the border to appear. This is only a problem when using it from the menu.

Calling the function from the command-line

To call the function from the command-line we will have to create a new function that receives a file path pattern (could be a file path or something like images*.png), opens the file(s), calls the function we defined, and then saves the file(s) somewhere. Add this at the top of add-border-to-image.scm:

(define (add-border-to-file file-pattern)
  (let* ((filelist (cadr (file-glob file-pattern 1))))
    (while (not (null? filelist))
           (let* ((filename (car filelist))
                  (image (car (gimp-file-load RUN-NONINTERACTIVE
                                              filename
                                              filename)))
                  (drawable
                   (car (gimp-image-get-active-layer image)))
                  (output-filename
                   (string-append (car (strbreakup
                                        filename "."))
                                  "-focused.png")))
             (add-border-to-image image)

             (gimp-file-save RUN-NONINTERACTIVE
                             image drawable
                             output-filename output-filename)
             (gimp-image-delete image))
           (set! filelist (cdr filelist)))))

This will receive one parameter, namely file-pattern, interpret it as a file pattern with possible wildcards (with the file-glob function) and go through every file that matches that pattern. Then, for every file, it will open it, calculate the output filename for that file, call add-border-to-image with the open image, and then save the result in the calculated output file and close the image.

Calling the function from the command-line

The command-line incantation to call this function is not trivial and I always forget it. We need to call the gimp program with the -i option (so that the user interface doesn’t load) and the -b option (for batch) and pass it some Scheme to call the function we want (in this case, add-border-to-file). We also need to call the special function gimp-quit so that GIMP quits after the function has finished. We do so by calling it like this:

gimp -i -b '(add-border-to-file "img*.png")' -b '(gimp-quit 0)'

Conclusions

Automating repetitive tasks with Script-Fu can be extremely useful and save us a bunch of time. We can expose the functionality we create in two ways: via GIMP’s own menus, and via the command-line’s “batch mode”. If one is used to Lisp dialects, using Script-Fu with Scheme is not too difficult; otherwise, Python-Fu can be a better alternative. Both languages have an interactive console inside of GIMP to try things out, plus a documentation browser to see and search for available Script-Fu functions.

If one wants to learn more details, the official documentation has a Script-Fu tutorial.