ScriptFu – scripting with Gimp

I wrote on Sunday about my first attempt to use Gimp to script some image manipulation stuff I wanted to do – specifically, combining multiple images into a single, multi-layered image.

A few people have asked me for more info about how to do this, so I thought I’d share my script here. Gimp is an open-source image editor, but it also comes with a batch mode where you can run it’s functions from a script. The scripting language for Gimp is called ScriptFu, and is a Lisp-type language. I’ve not done anything in Lisp since learning Scheme at University… so it felt a little odd at first.

This was my first attempt at writing in ScriptFu, so it’s worth pointing that I’m not an expert, and what I’ve written might not be elegant or the “right” way to do it. But I did manage to get something working in a few hours of playing with it.

To start with, a few tips from how I got started:

The doc – The documentation on scripting at docs.gimp.org is fantastic, and got me off to a quick start. It includes enough snippets and samples that I could see the sort of thing I’d need to do.

Gimp’s PDB – If you launch Gimp, go to Help -> Procedure Browser. It starts up a very neat searchable API doc for all of the Gimp functions. Even when I didn’t know the name of functions I needed, typing a quick guess (e.g. “layer”) into here would show me a few sensibly-named options, and show me the full API info for them all.

Photobucket

Sample scripts – Gimp comes installed with a set of sample scripts that can give useful pointers when you get stuck. On my Windows machine, I found them in C:\Program Files\GIMP-2.0\share\gimp\2.0\scripts

Running the script – To run my script, I used:

gimp-2.6.exe -i --no-data --no-fonts -b "(my-function-name my-arg1 my-arg2 \"C:\\img.png\")" -b "(gimp-quit 0)"

A few things worth pointing out:

-i
This lets me run Gimp in a “headless” way without starting the GUI, which I didn’t want.

--no-data --no-fonts
This starts Gimp without loading stuff like brushes, gradients, patterns, fonts, and a ton of other stuff I didn’t need for my script. This does make a performance difference.

-b
The batch command to run. Notice that you write this as a Lisp expression, too – function name followed by arguments, all surrounded in parentheses. On my Windows machine, I also needed to escape the double-quotes and slashes where I used file paths as arguments.

-b "(gimp-quit 0)"
This runs a command to quit Gimp, so I don’t leave it running.

Installing
I saved my script in a file ending with .scm, and put it in C:\Documents and Settings\MyUserName\.gimp-2.6\scripts

The finished script

I’ve copied my script below. Hopefully I’ve added enough comments that some of it might be useful to someone trying to write a script of their own.

Note that the main input function for my script was combine-images-into-layers (as import-my-layer is an internal function).

;
; bLADE Screen - Script-Fu script for creating multi-layered desktop images
;
;  Dale Lane (http://dalelane.co.uk/blog)
;    29-Mar-2009
;
;    written at BarCampLondon6
; 

; 
; function which takes a multi-layered image (gimp format), and a description of 
;  a single single-layer image (any format). the function copies the single 
;  layer from the single-layered image, and copies it on top of the 
;  multi-layered image into a new layer.
;
; parameters:
;   destinationimage       - multi-layered image we are adding to
;   newlayerimagefilepath  - full path to an image file containing the image we
;                             want to add as a layer in the multi-layered 
;                             destination image
;   newlayername           - the name to give the new layer we create
;   newlayerx              - the x coordinate to move the new layer to
;   newlayery              - the y coordinate to move the new layer to
;   newlayervis            - if 0, the new layer should be made invisible
;                            if 1, the new layer should be left visible
;
(define (import-my-layer destinationimage newlayerimagefilepath newlayername newlayerx newlayery newlayervis)
    (let*
        (
            ; 
            ; load the new layer image from file
            (newlayerimage
                (car
                    (gimp-file-load
                            RUN-NONINTERACTIVE
                            newlayerimagefilepath
                            newlayerimagefilepath
                    )
                )
            )

            ;
            ; get the drawable from the image file - assuming that the one 
            ;  active layer contains the drawable we want
            (newlayerdrawable
                (car
                    (gimp-image-get-active-layer
                            newlayerimage
                    )
                )
            )

            ;
            ; a layer from one image cannot be added to another image, so we
            ;  copy the drawable ready for use in the intended destination image
            (newlayercopy
                (car
                    (gimp-layer-new-from-drawable
                            newlayerdrawable
                            destinationimage
                    )
                )
            )
        )

        ;
        ; add the copied layer to the destination image
        (gimp-image-add-layer
                destinationimage
                newlayercopy
                -1                    ' 
        )

        ; 
        ; rename the layer with the provided name
        (gimp-drawable-set-name
                newlayercopy 
                newlayername
        )

        ;
        ; move the layer to the provided coordinates
        (gimp-layer-set-offsets
                newlayercopy
                newlayerx
                newlayery
        )

        ;
        ; set the visibility of the layer to the provided state
        ;  (assuming visible = TRUE by default)
        (if (= newlayervis 0)
            (gimp-drawable-set-visible 
                newlayercopy
                FALSE
            )
        )

        ;
        ; clean-up - we no longer need the image in gimp
        (gimp-image-delete 
                newlayerimage
        )
    )
)

; 
; function which takes a list of image files, and combines them into a single
;  gimp xcf image file made up of layers - where each original image makes up
;  it's own layer in the output image
;
; parameters:
;   fileoutname         - the full path to the output xcf file created by this 
;                          function
;   backgroundimagename - the full path to the image to use as the background /
;                          lowest layer in the output xcf file
;   fileinputcount      - the number of layers to create in the output xcf file
;                          (number of layers to add on top of the background 
;                          layer, NOT including the background layer itself)
;   fileinputlist       - a list of variables describing the layers to add
;                          list should be zero or more sequences of:
;                           filename - full path to the image to add
;                           xcoord   - x coordinate for where to put the image
;                           ycoord   - y coordinate for where to put the image
;                           caption  - string to use as the name for the layer
;      
(define (combine-images-into-layers fileoutname backgroundimagename fileinputcount fileinputlist)
    (let* 
        (
            ;
            ; define backgroundimage
            ; - which we get by loading it from the background image file
            (backgroundimage 
               (car 
                    (gimp-file-load 
                            RUN-NONINTERACTIVE 
                            backgroundimagename 
                            backgroundimagename
                    )
                )
            )

            ;
            ; define a drawable element 
            ; - which we get by getting the active layer from our image
            (drawable 
                (car 
                    (gimp-image-get-active-layer 
                            backgroundimage
                    )
                )
            )

            ;
            ; define loop variables - variables that we will get out of the 
            ;  fileinputlist list
            (nextImageFileName)
            (nextImageX)
            (nextImageY)
            (nextImageCaption)
            (nextImageVisible)
        )

        ;
        ; loop through the fileinputlist list of variables
        ;  - fileinputcount tells us how many times to loop
        (while (> fileinputcount 0)

            ;
            ; get each of the variables in turn
            (set! nextImageFileName (car fileinputlist))
            (set! nextImageX        (car (cdr fileinputlist)))
            (set! nextImageY        (car (cdr (cdr fileinputlist))))
            (set! nextImageCaption  (car (cdr (cdr (cdr fileinputlist)))))
            (set! nextImageVisible  (car (cdr (cdr (cdr (cdr fileinputlist))))))

            ;
            ; add the next image described in these variables as a new layer
            ;  on top of the background image
            (import-my-layer 
                    backgroundimage 
                    nextImageFileName 
                    nextImageCaption 
                    nextImageX 
                    nextImageY 
                    nextImageVisible
            )

            ;
            ; prepare for next iteration - remove the variables just used from
            ;  the top of the list and decrement the counter
            (set! fileinputlist     (cdr (cdr (cdr (cdr (cdr fileinputlist))))))
            (set! fileinputcount    (- fileinputcount 1))
        )

        ;
        ; save the completed image to a file
        (gimp-file-save 
                RUN-NONINTERACTIVE 
                backgroundimage 
                drawable 
                fileoutname 
                fileoutname
        )

        ;
        ; clean-up - we no longer need the image in gimp
        (gimp-image-delete 
                backgroundimage
        )
    )
)

Tags: , , , , , , , ,

12 Responses to “ScriptFu – scripting with Gimp”

  1. Marth Gelaude says:

    Hey, that’s a great script there. It looks really helpful for something I’m trying to do. Could you post a more specific example of how to run this particular script, please? Something a bit more specific than “my-function-name my-arg1 my-arg2”? I don’t know any Lisp or ScriptFu.

    Thanks!

  2. dale says:

    @Marth – Thanks for the comment.

    I’m not sure how to make this more specific… I’ll highlight a few points in case that helps.

    To run the script:
    gimp-2.6.exe -i --no-data --no-fonts -b "(my-function-name my-arg1 my-arg2 \"C:\\img.png\")" -b "(gimp-quit 0)"

    The function name is specified in the post:

    Note that the main input function for my script was combine-images-into-layers

    The arguments for this function are shown in the post:

    ; fileoutname – the full path to the output xcf file created by this
    ; function
    ; backgroundimagename – the full path to the image to use as the background /
    ; lowest layer in the output xcf file
    ; fileinputcount – the number of layers to create in the output xcf file
    ; (number of layers to add on top of the background
    ; layer, NOT including the background layer itself)
    ; fileinputlist – a list of variables describing the layers to add
    ; list should be zero or more sequences of:
    ; filename – full path to the image to add
    ; xcoord – x coordinate for where to put the image
    ; ycoord – y coordinate for where to put the image
    ; caption – string to use as the name for the layer

    You just need to put this together. For example:

    gimp-2.6.exe -i --no-data --no-fonts -b "(combine-images-into-layers \"C:\\imgout.png\" \"C:\\background.png\" 1 (\"C:\\file1.png\" 10 20 \"file1title\"))" -b "(gimp-quit 0)"

    Hope this helps

    D

  3. Marth Gelaude says:

    Thanks a lot! I almost have it working now. Now I get a “error: illegal function” when trying to run it from the Script-Fu Console (which I’m only using because there was an execution error using batch mode). I copied the script into the share/gimp/2.0/scripts folder. I’m pretty sure it’s something trivial, I just don’t know what it is. Also, to use more files in the fileinputlist, is it correct to do it this way: (list (\”C:\\file1.png\” 10 20 \”file1title\”) (\”C:\\file2.png\” 0 5 \”file2title\”))

    Again, I really appreciate your help and prompt response. 🙂

  4. dale says:

    @Marth

    Here is an example of how I run it:

    gimp-2.6.exe
    -i --no-data --no-fonts -b 
    "(combine-images-into-layers 
         \"C:\\destination_screenshot.xcf\" 
         \"C:\\desktop_background_img.png\" 
         4 
         '(\"C:\\screenshot_img_app0.png\" 0 60 \"App Zero\"  
           \"C:\\screenshot_img_app1.png\" 0 56 \"App One\" 
           \"C:\\screenshot_img_app2.png\" 0 56 \"App Two\" 
           \"C:\\screenshot_img_app3.png\" 0 56 \"App Three\"  
          )
     )" -b "(gimp-quit 0)"

    The line breaks are intended to make it more readable – in reality this is run all on one line.

    Note that I don’t break each list up into parentheses.

    I put the script in a file (ending in a .scm extension) in the C:\Documents and Settings\Administrator\.gimp-2.6\scripts directory.

    Hope this helps

    D

  5. Marth Gelaude says:

    Sorry to bother you again, but for some reason I’m still having problems running this. Here’s what I try to run in the console:

    > (combine-images-into-layers “C:\\Users\\Marth\\Documents\\Canvas\\imgout.xcf” “C:\\Users\\Marth\\Documents\\Canvas\\bg.png” 2 ‘(“C:\\Users\\Marth\\Documents\\Canvas\\layer1.png” 0 0 “file1title” “C:\\Users\\Marth\\Documents\\Canvas\\layer2.png” 0 0 “file2title”))

    Error: Invalid type for argument 2 to gimp-file-load

    Thanks again.

  6. dale says:

    @Marth – Sorry, without debugging it for myself, I’m not sure that I will be able to help.

    I would suggest checking out the advice at http://adrian.gimp.org/batch/batch-7.html

    In particular – adding print commands so you can work out which gimp-file-load is failing, and the fact that “‘invalid types for arguments’… is usually the result of misquoting in the console”.

    Good luck

  7. Marth Gelaude says:

    Thanks a bunch! I finally got it to work. It seemed the problem was that I was missing the argument in the fileinputlist for whether or not the image should be visible. Here’s the call:

    gimp-2.6.exe -i –no-data –no-fonts -b “(combine-images-into-layers \”C:\\Users\\Jonathan\\Documents\\Canvas\\imgout.xcf\” \”C:\\Users\\Jonathan\\Documents\\Canvas\\bg.png\” 2 ‘(\”C:\\Users\\Jonathan\\Documents\\Canvas\\layer1.png\” 0 0 \”file1title\” 1 \”C:\\Users\\Jonathan\\Documents\\Canvas\\layer2.png\” 0 0 \”file2title\” 1))” -b “(gimp-quit 0)”

  8. Darren says:

    What a lovely, polite thread! It’s like the Script Fu version of ’84 Charing Cross Road’. Proof that there are considerate, erudite people out there.

  9. R says:

    Great post! I got it to run without errors in OS X, however the layers do not stack for some reason. I am not running it with a file list since I just want to layer one image. I just omitted the loop.

  10. […] ScriptFu – scripting with Gimp « dale lane. Filed under: Stuff […]

  11. Sybs ALHABSHI says:

    Where does the name “Script Fu” or “ScriptFu” or “Script-Fu” comes from ? Script Fu as in Kung Fu or as in Script Foo ?

    Just Curious.

  12. dale says:

    Dunno – interesting point though 🙂