{"id":628,"date":"2009-04-01T10:17:29","date_gmt":"2009-04-01T10:17:29","guid":{"rendered":"http:\/\/dalelane.co.uk\/blog\/?p=628"},"modified":"2009-04-01T10:24:47","modified_gmt":"2009-04-01T10:24:47","slug":"scriptfu-scripting-with-gimp","status":"publish","type":"post","link":"https:\/\/dalelane.co.uk\/blog\/?p=628","title":{"rendered":"ScriptFu &#8211; scripting with Gimp"},"content":{"rendered":"<p>I <a href=\"http:\/\/dalelane.co.uk\/blog\/?p=621\">wrote on Sunday<\/a> about my first attempt to use Gimp to script some image manipulation stuff I wanted to do &#8211; specifically, combining multiple images into a single, multi-layered image.<\/p>\n<p>A few people have asked me for more info about how to do this, so I thought I&#8217;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&#8217;s functions from a script. The scripting language for Gimp is called ScriptFu, and is a <a href=\"http:\/\/en.wikipedia.org\/wiki\/Lisp_programming_language\" target=\"_blank\">Lisp<\/a>-type language. I&#8217;ve not done anything in Lisp since learning Scheme at University&#8230; so it felt a little odd at first.<\/p>\n<p>This was my first attempt at writing in ScriptFu, so it&#8217;s worth pointing that I&#8217;m not an expert, and what I&#8217;ve written might not be elegant or the &#8220;right&#8221; way to do it. But I did manage to get something working in a few hours of playing with it. <\/p>\n<p>To start with, a few tips from how I got started:<\/p>\n<p><strong>The doc<\/strong> &#8211; The documentation on scripting at <a href=\"http:\/\/docs.gimp.org\/en\/gimp-scripting.html\" target=\"_blank\">docs.gimp.org<\/a> 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&#8217;d need to do. <\/p>\n<p><strong>Gimp&#8217;s PDB<\/strong> &#8211; 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&#8217;t know the name of functions I needed, typing a quick guess (e.g. &#8220;layer&#8221;) into here would show me a few sensibly-named options, and show me the full API info for them all.<\/p>\n<p><a href=\"http:\/\/s267.photobucket.com\/albums\/ii311\/dale_lane\/?action=view&#038;current=090401-gimppdb.png\" target=\"_blank\"><img decoding=\"async\" src=\"http:\/\/i267.photobucket.com\/albums\/ii311\/dale_lane\/090401-gimppdb.png\" border=\"0\" alt=\"Photobucket\"\/><\/a><\/p>\n<p><!--more--><strong>Sample scripts<\/strong> &#8211; 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<\/p>\n<p><strong>Running the script<\/strong> &#8211; To run my script, I used:<\/p>\n<pre style=\"overflow: scroll; font-size: 1.1em; border: thin solid silver; background-color: #eeeeee; padding: 0.8em\">gimp-2.6.exe -i --no-data --no-fonts -b \"(my-function-name my-arg1 my-arg2 \\\"C:\\\\img.png\\\")\" -b \"(gimp-quit 0)\"<\/pre>\n<p>A few things worth pointing out:<\/p>\n<p><code><strong>-i<\/strong><\/code><br \/>\nThis lets me run Gimp in a &#8220;headless&#8221; way without starting the GUI, which I didn&#8217;t want.<\/p>\n<p><code><strong> --no-data --no-fonts<\/strong><\/code><br \/>\nThis starts Gimp without loading stuff like brushes, gradients, patterns, fonts, and a ton of other stuff I didn&#8217;t need for my script. This does make a performance difference.<\/p>\n<p><code><strong>-b<\/strong><\/code><br \/>\nThe batch command to run. Notice that you write this as a Lisp expression, too &#8211; 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.<\/p>\n<p><code><strong>-b \"(gimp-quit 0)\"<\/strong><\/code><br \/>\nThis runs a command to quit Gimp, so I don&#8217;t leave it running.<\/p>\n<p><strong>Installing<\/strong><br \/>\nI saved my script in a file ending with .scm, and put it in C:\\Documents and Settings\\MyUserName\\.gimp-2.6\\scripts<\/p>\n<p><strong>The finished script<\/strong><\/p>\n<p>I&#8217;ve copied my script below. Hopefully I&#8217;ve added enough comments that some of it might be useful to someone trying to write a script of their own. <\/p>\n<p>Note that the main input function for my script was combine-images-into-layers (as import-my-layer is an internal function). <\/p>\n<pre style=\"overflow: scroll; font-size: 1.1em; border: thin solid silver; background-color: #eeeeee; padding: 0.8em\">;\r\n; bLADE Screen - Script-Fu script for creating multi-layered desktop images\r\n;\r\n;  Dale Lane (http:\/\/dalelane.co.uk\/blog)\r\n;    29-Mar-2009\r\n;\r\n;    written at BarCampLondon6\r\n; \r\n\r\n; \r\n; function which takes a multi-layered image (gimp format), and a description of \r\n;  a single single-layer image (any format). the function copies the single \r\n;  layer from the single-layered image, and copies it on top of the \r\n;  multi-layered image into a new layer.\r\n;\r\n; parameters:\r\n;   destinationimage       - multi-layered image we are adding to\r\n;   newlayerimagefilepath  - full path to an image file containing the image we\r\n;                             want to add as a layer in the multi-layered \r\n;                             destination image\r\n;   newlayername           - the name to give the new layer we create\r\n;   newlayerx              - the x coordinate to move the new layer to\r\n;   newlayery              - the y coordinate to move the new layer to\r\n;   newlayervis            - if 0, the new layer should be made invisible\r\n;                            if 1, the new layer should be left visible\r\n;\r\n(define (import-my-layer destinationimage newlayerimagefilepath newlayername newlayerx newlayery newlayervis)\r\n    (let*\r\n        (\r\n            ; \r\n            ; load the new layer image from file\r\n            (newlayerimage\r\n                (car\r\n                    (gimp-file-load\r\n                            RUN-NONINTERACTIVE\r\n                            newlayerimagefilepath\r\n                            newlayerimagefilepath\r\n                    )\r\n                )\r\n            )\r\n\r\n            ;\r\n            ; get the drawable from the image file - assuming that the one \r\n            ;  active layer contains the drawable we want\r\n            (newlayerdrawable\r\n                (car\r\n                    (gimp-image-get-active-layer\r\n                            newlayerimage\r\n                    )\r\n                )\r\n            )\r\n\r\n            ;\r\n            ; a layer from one image cannot be added to another image, so we\r\n            ;  copy the drawable ready for use in the intended destination image\r\n            (newlayercopy\r\n                (car\r\n                    (gimp-layer-new-from-drawable\r\n                            newlayerdrawable\r\n                            destinationimage\r\n                    )\r\n                )\r\n            )\r\n        )\r\n\r\n        ;\r\n        ; add the copied layer to the destination image\r\n        (gimp-image-add-layer\r\n                destinationimage\r\n                newlayercopy\r\n                -1                    ' \r\n        )\r\n\r\n        ; \r\n        ; rename the layer with the provided name\r\n        (gimp-drawable-set-name\r\n                newlayercopy \r\n                newlayername\r\n        )\r\n\r\n        ;\r\n        ; move the layer to the provided coordinates\r\n        (gimp-layer-set-offsets\r\n                newlayercopy\r\n                newlayerx\r\n                newlayery\r\n        )\r\n\r\n        ;\r\n        ; set the visibility of the layer to the provided state\r\n        ;  (assuming visible = TRUE by default)\r\n        (if (= newlayervis 0)\r\n            (gimp-drawable-set-visible \r\n                newlayercopy\r\n                FALSE\r\n            )\r\n        )\r\n\r\n        ;\r\n        ; clean-up - we no longer need the image in gimp\r\n        (gimp-image-delete \r\n                newlayerimage\r\n        )\r\n    )\r\n)\r\n\r\n; \r\n; function which takes a list of image files, and combines them into a single\r\n;  gimp xcf image file made up of layers - where each original image makes up\r\n;  it's own layer in the output image\r\n;\r\n; parameters:\r\n;   fileoutname         - the full path to the output xcf file created by this \r\n;                          function\r\n;   backgroundimagename - the full path to the image to use as the background \/\r\n;                          lowest layer in the output xcf file\r\n;   fileinputcount      - the number of layers to create in the output xcf file\r\n;                          (number of layers to add on top of the background \r\n;                          layer, NOT including the background layer itself)\r\n;   fileinputlist       - a list of variables describing the layers to add\r\n;                          list should be zero or more sequences of:\r\n;                           filename - full path to the image to add\r\n;                           xcoord   - x coordinate for where to put the image\r\n;                           ycoord   - y coordinate for where to put the image\r\n;                           caption  - string to use as the name for the layer\r\n;      \r\n(define (combine-images-into-layers fileoutname backgroundimagename fileinputcount fileinputlist)\r\n    (let* \r\n        (\r\n            ;\r\n            ; define backgroundimage\r\n            ; - which we get by loading it from the background image file\r\n            (backgroundimage \r\n               (car \r\n                    (gimp-file-load \r\n                            RUN-NONINTERACTIVE \r\n                            backgroundimagename \r\n                            backgroundimagename\r\n                    )\r\n                )\r\n            )\r\n\r\n            ;\r\n            ; define a drawable element \r\n            ; - which we get by getting the active layer from our image\r\n            (drawable \r\n                (car \r\n                    (gimp-image-get-active-layer \r\n                            backgroundimage\r\n                    )\r\n                )\r\n            )\r\n\r\n            ;\r\n            ; define loop variables - variables that we will get out of the \r\n            ;  fileinputlist list\r\n            (nextImageFileName)\r\n            (nextImageX)\r\n            (nextImageY)\r\n            (nextImageCaption)\r\n            (nextImageVisible)\r\n        )\r\n\r\n        ;\r\n        ; loop through the fileinputlist list of variables\r\n        ;  - fileinputcount tells us how many times to loop\r\n        (while (> fileinputcount 0)\r\n\r\n            ;\r\n            ; get each of the variables in turn\r\n            (set! nextImageFileName (car fileinputlist))\r\n            (set! nextImageX        (car (cdr fileinputlist)))\r\n            (set! nextImageY        (car (cdr (cdr fileinputlist))))\r\n            (set! nextImageCaption  (car (cdr (cdr (cdr fileinputlist)))))\r\n            (set! nextImageVisible  (car (cdr (cdr (cdr (cdr fileinputlist))))))\r\n\r\n            ;\r\n            ; add the next image described in these variables as a new layer\r\n            ;  on top of the background image\r\n            (import-my-layer \r\n                    backgroundimage \r\n                    nextImageFileName \r\n                    nextImageCaption \r\n                    nextImageX \r\n                    nextImageY \r\n                    nextImageVisible\r\n            )\r\n\r\n            ;\r\n            ; prepare for next iteration - remove the variables just used from\r\n            ;  the top of the list and decrement the counter\r\n            (set! fileinputlist     (cdr (cdr (cdr (cdr (cdr fileinputlist))))))\r\n            (set! fileinputcount    (- fileinputcount 1))\r\n        )\r\n\r\n        ;\r\n        ; save the completed image to a file\r\n        (gimp-file-save \r\n                RUN-NONINTERACTIVE \r\n                backgroundimage \r\n                drawable \r\n                fileoutname \r\n                fileoutname\r\n        )\r\n\r\n        ;\r\n        ; clean-up - we no longer need the image in gimp\r\n        (gimp-image-delete \r\n                backgroundimage\r\n        )\r\n    )\r\n)<\/pre>\n","protected":false},"excerpt":{"rendered":"<p>I wrote on Sunday about my first attempt to use Gimp to script some image manipulation stuff I wanted to do &#8211; 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&#8217;d share my script here. Gimp is [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[7,4],"tags":[358,366,361,369,364,368,367,363,362],"class_list":["post-628","post","type-post","status-publish","format-standard","hentry","category-code","category-ibm","tag-barcamplondon6","tag-combine","tag-gimp","tag-image","tag-layers","tag-lisp","tag-scheme","tag-script-fu","tag-scriptfu"],"_links":{"self":[{"href":"https:\/\/dalelane.co.uk\/blog\/index.php?rest_route=\/wp\/v2\/posts\/628","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/dalelane.co.uk\/blog\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/dalelane.co.uk\/blog\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/dalelane.co.uk\/blog\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/dalelane.co.uk\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=628"}],"version-history":[{"count":0,"href":"https:\/\/dalelane.co.uk\/blog\/index.php?rest_route=\/wp\/v2\/posts\/628\/revisions"}],"wp:attachment":[{"href":"https:\/\/dalelane.co.uk\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=628"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/dalelane.co.uk\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=628"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/dalelane.co.uk\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=628"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}