Notes on Scripting GIMP with KBM

I had a problem with a KBM macro where the solution required being able to run a software filter on an image initiated by a command line or script, not a GUI. But almost all image manipulation programs, including the archetypal PhotoShop, seem to require interaction with the graphical user interface to be able to do anything at all. Was there a system out there that supported scripting?

The Problem, and the State of the Art

I found three possible solutions all using free software:

  • ImageMagic,
  • BIMP (a batch-mode add-on to GIMP),
  • and the built-in batch mode of GIMP. (GIMP is the Gnu Image Manipulation Program, deliberately created to be a free alternative to PhotoShop.)

While ImageMagic and BIMP look promising, and may be better tools for more complex problems, I found sample code for the GIMP batch mode that required only a little modification to do what I needed to do. I described that solution in Inverting Image Colors Before OCR.

This post describes how I got there, with lots of notes about how to use GIMP's scripting tools. I have not explored anything close to all the possibilities, but I hope this gets you off to a basic working start.

GIMP is a powerful tool and can do many of the "magic brush" kinds of things that PhotoShop can do. It also supports a form of scripting that can manipulate images transparently, without ever having to open up a GUI app. This makes it very useful for image changes that you want to happen silently, in the background.

You can't do the same with PhotoShop. While PS can be run with JavaScript, the GUI has to be open and be the frontmost app. It would be the same trying to use KBM to script all the menu option choices for PhotoShop. Running in the background or manipulating an image on the clipboard in the middle of another task are way beyond what PhotoShop can do (according to the best info I could find in the Adobe Support Community).

A Solution Based on an Existing Tutorial

With a little Googling, I quickly learned that running GIMP from a script is called "batch mode". The GIMP doc has a tutorial on using GIMP Batch Mode. That was my starting point. That tutorial has a couple of sample code examples that show how to apply a filter first to a single file and then to several at once, such as all the files in a particular directory.

The GIMP interface for running scripts is called Script-Fu and can be found near the end of the GIMP Filters menu in the GIMP GUI. There is an interactive console where you can try scripting commands and run scripts on your working image layer or your current selection of the image. The scripting language is Scheme, which is based on LISP. There is also support for Python and a Python-Fu entry in the Filters menu. The GIMP doc has a tutorial on Basic Scheme.

It turned out that the simple example given in that Batch Mode tutorial was a great template for what I wanted to do. Here's the Scheme code. I've made some slight modifications to the indenting and line breaks to make it a little clearer, at least to me.

(define (simple-unsharp-mask
                filename
                radius
                amount
                threshold)
    (let* ( (image (car (gimp-file-load RUN-NONINTERACTIVE filename filename)))
            (drawable (car (gimp-image-get-active-layer image))))
        (plug-in-unsharp-mask RUN-NONINTERACTIVE
                image drawable radius amount threshold)
        (gimp-file-save RUN-NONINTERACTIVE image drawable filename filename)
        (gimp-image-delete image)
    )
)

I'll walk you through it, after I put it into the context of how it gets run.

How GIMP Scripts Get Run

This code defines a "filter script" and there are two specific locations where GIMP (on Macs) looks for script definitions when it starts up, a system folder and a user folder.

  • The system folder is /Applications/GIMP2.10.34.app/Contents/Resources/share/gimp/2.0/scripts — this is full of useful sample scripts, many of which are in the GIMP UI menus.
  • The user folder is ~/Library/Application Support/GIMP/2.10/scripts — this is where your personal scripts go.

GIMP loads all files in those locations that have the .scm Scheme file extension. When you save the above sample script in the above user folder, in a *.scm file, your script definitions get read when GIMP starts up.

Note that it doesn't matter what the file name is in that folder. Gimp looks in every .scm file. When I tried it with the tutorial sample code, I used TutorialSample1.scm.

To use batch mode with any filter you have defined or any of the pre-defined functions, you give the command as an argument to the -b option of the gimp command. In the GIMP tutorial, to apply the simple-unsharp-mask to an image, for instance foo.png, you are told to run the command:

gimp -i -b '(simple-unsharp-mask "foo.png" 5.0 0.5 0)' -b '(gimp-quit 0)'

How the Simple Filter Works

In the definition above, simple-unsharp-mask has four arguments: filename, radius, amount, and threshold, which are here as values in the command line. In this form, this gimp command needs to be run in the directory that contains foo.png because it's a relative path name.

The -i option says to not open an interactive GUI. We want this to run invisibly. The -b options is a batch command and in this case there are two of them.

Note that my saved filename, TutorialSample1.scm, isn't mentioned in this command. When you start GIMP, it reads all the files that are in the two script folders. By saving TutorialSample1.scm in ~/Library/Application Support/GIMP/2.10/scripts, my definition of simple-unsharp-mask is available to GIMP.

The first -b option contains the command to run simple-unsharp-mask with appropriate parameters. Let's walk through what that does.

The simple-unsharp-mask function uses gimp-file-load to set the image variable from the filename and then uses gimp-image-get-active-layer to define the drawable variable from the image. Then it runs the plug-in-unsharp-mask function using the image and drawable that it created plus the other parameters that we gave it in the -b option on the command line. It saves the transformed image and drawable back where it got them and deletes the image from GIMP's memory.

The second -b option quits GIMP.

When I tried running the above command, I ran into a minor snag. I couldn't just run gimp on a Terminal command line. The command is not in my search path.

I could create a shell alias for the command, or I could add a symbolic link into a directory that is in my $PATH, but since my goal is to use this within a KBM macro, I decided to just use the full path to the gimp command:

/Applications/Gimp-2.10.34.app/Contents/MacOS/gimp

With that, I had a shell command that would run the Unsharp Mask plugin filter on any filename that I specified. Here's the KBM version:

image

To modify that simple-unsharp-mask script to do the negative-image inversion that I needed in the first place was then a matter of changing the function name, the parameters, and the predefined filter function.

Walking Through the Code in Deep Detail

Of course that took being able to read the Scheme definition code in word-by-word detail. So let me give you a hand with that. Here's that code again:

(define (simple-unsharp-mask
                filename
                radius
                amount
                threshold)
    (let* ( (image (car (gimp-file-load RUN-NONINTERACTIVE filename filename)))
            (drawable (car (gimp-image-get-active-layer image))))
        (plug-in-unsharp-mask RUN-NONINTERACTIVE
                image drawable radius amount threshold)
        (gimp-file-save RUN-NONINTERACTIVE image drawable filename filename)
        (gimp-image-delete image)
    )
)

Looking at the sample code, the entire thing is inside a pair of parentheses. You can have more than one of these define structures in your file, defining a whole library of functions, if you want, each within its own parentheses pair. Here we have just one.

A function definition has three parts, the magic word define and two parenthesized blocks, the first with the names of the function and its arguments and the second that does the work.

For my simple invert filter, I only need a filename, so that first block can be simplified to:

(define (simple-invert filename) ... )

The definition of simple-unsharp-mask follow a pattern I've seen frequently in *.scm scripts, it lays out the function's parameters one per line. Personally, I like that style; it makes it very easy to read the rest of the script and easily tell what's a provided parameter and what's not.

The second block of the definition has four steps. In the first, the let* keyword initiates variable definitions for image and drawable.

Both of the variable definitions use the LISP magic word, car. The GIMP Basic Scheme tutorial has a section describing "car, cdr and friends" which is helpful. It includes the note:

For the Script-Fu, writer one of the most important uses of the car function is to access the returned values from the built-in GIMP functions. All GIMP functions return a list and even if the list contains only one element it must be accessed by car.

That term, car, made no sense to me and that confusion made it hard to make sense of the Scheme code. I eventually found out that it originally stood for "Contents of Address Register" which was based on the now-ancient IBM hardware that LISP was originally written for. It's companion command, cdr, derives from "Contents of Data Register". Knowing that has made it easier for me to hold on the concepts that car gives you the head of a list and cdr gives you the rest of it.

When the let* keyword defines image, it runs car on the result of running the gimp-file-load function.

So what does gimp-file-load do? if you open the GIMP UI and go to Filters > Script-Fu > Console it will pop up a small Script-Fu Console window like this: image
The text entry field at the bottom has the Browse button at the right. If you click that button, it opens the Script-Fu Procedure Browser (SPB).


In the above Procedure Browser window, I have searched for the term "load" and scrolled down to gimp-file-load to see documentation on this procedure, including why the filename parameter must be given twice.

After the image is defined, based on the loaded contents of filename, then another car operation gets the result of giving the image to the gimp-image-get-active-layer function, and that defines the drawable variable. In my simple-invert function, these operations, loading the file and getting the active layer, can be used exactly as they are in the example from the tutorial.

The first command after the let* variable definitions is the core of the operation. The plug-in-unsharp-mask function gets the same arguments as the simple-unsharp-mask that is defined here, except that it gets the image and the drawable instead of the filename.

You can see documentation for plug-in-unsharp-mask in the Script-Fu Procedure Browser.


This is the procedure I wanted to change in making my simple-invert function. I searched the SPB for "invert" and got four results. One inverted the color but not the brightness, one was for inverting a selection mask, one was deprecated, and the fourth, gimp-drawable-invert, seemed like exactly what I wanted. I changed that line of code to:

 (gimp-drawable-invert drawable 0)

The last two steps in the second part of the define function are gimp-file-save and gimp-image-delete. That saves the transformed file and then deletes the image from GIMP's internal memory.

I'm not sure, but I think that if GIMP is not otherwise open then the gimp-image-delete may not be necessary because the second batch command on the shell command line closes this instance of GIMP. If you want to be able to use the script on a file, whether or not you're also using the GIMP GUI, then you might need this. It was part of the sample script that I simply kept because it made sense that it was there and didn't seem like it could hurt anything. The description of gimp-image-delete in the Script-Fu Procedure Browser was not really helpful in understanding why this command is needed.

Beyond This Example

I hope this walk-through of using GIMP from a KBM Execute Shell Script command gives you ideas for other script-based manipulations you might do to image files. Watermarking is an obvious possibility. Adding text to an image based on the filename or image metadata is another.

And please keep in mind that Scheme is a full scripting language. It can present dialogs for the user to interact with, do calculations and have conditional decisions. There a lots of example functions in the system scripts folder, /Applications/GIMP−2.10.34.app/Contents/Resources/share/gimp/2.0/scripts.

5 Likes

Watermarking is possibly much more easily done wholly within KBM by using the Composite Onto Image action.

A text watermark can be easily created by saving text to a clipboard. The text can then be styled using the Apply Style to Clipboard action.

The Apply Style action can set a transparent background and can apply different fonts to different parts of the text.

I discovered Composite Onto Image and Apply Style only after I had attempted to do the same kind of think with GIMP. While I'm sure GIMP could do it, for my specific needs it was much easier in KBM so I abandoned trying to make that work for that particular problem.