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.
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 (https://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: barcamplondon6, combine, gimp, image, layers, lisp, scheme, script-fu, scriptfu
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!
@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.
The function name is specified in the post:
The arguments for this function are shown in the post:
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
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. ๐
@Marth
Here is an example of how I run it:
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
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.
@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
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)”
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.
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.
[…] ScriptFu รขโฌโ scripting with Gimp รยซ dale lane. Filed under: Stuff […]
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.
Dunno – interesting point though ๐