Search notes:

GIMP Script-Fu: simple demonstration scripts (to be executed in batch mode from the command line)

The following simple examples try to demonstrate how it is possible to create an image with GIMP from scratch in batch mode, that is: in a console/shell.

Setting the background

The following script is probably one of the most simple one possible. It creates 250 pixel by 100 pixel image with a blue background and saves the image as jpg file into the directory f:\img:
(let* (
   (tq84_img (car (
         gimp-image-new
         250                         ; width
         100                         ; height
         RGB                         ; Image type (RGB, GRAY or INDEXED)
    )))

   (tq84_lyr (car (
         gimp-layer-new
         tq84_img
         250                         ; width
         100                         ; height
         RGB-IMAGE                   ; Layer type (RGB-IMAGE, RGBA-IMAGE, GRAY-IMAGE, GRAYA-IMAGE, INDEXED-IMAGE or INDEXEDA-IMAGE)
        "tq84"                       ; Name of the layer
         100                         ; Opacity (0 … 100, 0 = transparent, 100 = opaque)
         LAYER-MODE-NORMAL-LEGACY    ; Layer combination mode
    ))))

    (gimp-context-set-background     ; Set background color
       '(000 000 255)                ; blue
    )

    (gimp-drawable-fill              ; The purpose of gimp-drawable-fill is to fill a newly created
         tq84_lyr                    ; drawable before adding it to the image (Compare with gimp-edit-fill)
         FILL-BACKGROUND             ; Use current background color
    )

    (gimp-image-insert-layer
       tq84_img
       tq84_lyr
       0                             ; parent   (0 -> layer is added inside the main stack, outside of any group.)
       0                             ; position (location of the layer inside the stack, 0: top)
    )

    (gimp-file-save                  ; Invoke correct save handler according to file extension
         RUN-NONINTERACTIVE
         tq84_img
         tq84_lyr                    ; I don't understand why gimp-file-save requires a drawable - I just want to save an image.
        "f:\\img\\background.jpg"    ; File name
        ""                           ; File name as entered by user, apparently useless in scripts
    )

    (gimp-image-delete tq84_img)     ; Prevent memory leak
)

(gimp-quit TRUE)
Github repository GIMP-Fu-Scripts, path: /simple-demonstrations/background.scm
The resulting image is:
In cmd.exe, this script is executed like so
P:\ath\to\source> type background.scm | "C:\Program Files\GIMP 2\bin\gimp-2.10.exe" -d -i -f -s -b -

Line

The following example draws two lines, one with gimp-pencil and once with gimp-paintbrush.
GIMP's user manual says the following about the difference between a pencil and a paintbrush:
The pencil and paintbrush are similar tools. The main difference between the two tools is that although both use the same type of brush, the pencil tool will not produce fuzzy edges, even with a very fuzzy brush. It does not even do anti-aliasing.
(let* (
     (img (car (gimp-image-new     250 250 RGB                                              )))
     (lyr (car (gimp-layer-new img 250 250 RGB-IMAGE "my layer" 100 LAYER-MODE-NORMAL-LEGACY)))
     (brs (car (gimp-brush-new "TQ84 Brush")))

     (coords (make-vector 4)) ; Array of 4 elements (to be set to doubles)
   )

   (gimp-context-set-background '(000 000 255))
   (gimp-context-set-foreground '(255 000 000))

   (gimp-drawable-fill          lyr FILL-BACKGROUND)
   (gimp-image-insert-layer img lyr 0 0)
 ;
 ;  Assign coordinates for first line to be drawn:
 ;
   (vector-set! coords 0  20.0) ; x1
   (vector-set! coords 1  20.0) ; y1
   (vector-set! coords 2 230.0) ; x2
   (vector-set! coords 3 230.0) ; y2

   (gimp-context-set-brush            brs   )
   (gimp-context-set-brush-size       19.0  )
   (gimp-context-set-brush-hardness    0.5  )
;  (gimp-context-set-brush-spacing    0.01  ) ; Is this sometimes necessary?

   (gimp-pencil lyr 4 coords)

 ;
 ;  Assign coordinates for second line to be drawn:
 ;
   (vector-set! coords 0 230.0) ; x1
   (vector-set! coords 1  20.0) ; y1
   (vector-set! coords 2  20.0) ; x2
   (vector-set! coords 3 230.0) ; y2

   (gimp-paintbrush lyr
         0              ; fade-out
         4              ; number of coordinates (not coordinate pairs)
         coords         ; strokes
         PAINT-CONSTANT ; method (PAINT-CONSTANT / PAINT-INCREMENTAL)
         0              ; gradient length
   )

   (gimp-file-save RUN-NONINTERACTIVE img lyr "f:\\img\\line.jpg" "")
   (gimp-brush-delete brs)
   (gimp-image-delete img)
)
(gimp-quit TRUE)
Github repository GIMP-Fu-Scripts, path: /simple-demonstrations/line.scm
The line that goes from top left to bottom right is drawn with a pencil while the other one is drawn with a paintbrush. Imho, the difference of smoothness can be clearly seen:
P:\ath\to\source> type line.scm | "C:\Program Files\GIMP 2\bin\gimp-2.10.exe"    -i    -s -b -

Circle

(let* (
     (img (car (gimp-image-new     250 250 RGB                                              )))
     (lyr (car (gimp-layer-new img 250 250 RGB-IMAGE "my layer" 100 LAYER-MODE-NORMAL-LEGACY)))
   )

   (gimp-context-set-background '(250 230 240))
   (gimp-context-set-foreground '( 30  20  90))

   (gimp-drawable-fill lyr FILL-BACKGROUND)

   (gimp-image-insert-layer img lyr 0 0)

;  (gimp-context-set-antialias TRUE)

   (gimp-image-select-ellipse
          img                ;
          CHANNEL-OP-REPLACE ; operation (CHANNEL-OP-{ADD,SUBTRACT,REPLACE,INTERSECXT} )
           25  25            ; x and y coordinate of the upper-left  corner of the ellipse bounding box
          200 200            ; width and height of the ellipse
   )
;  (gimp-edit-stroke lyr)
   ;
   ; 2020-03-08
   ;    The following gimp-context-set-brush is apparently needed on Linux or
   ;    older/newer(?) versions for this example to run on David's
   ;    GIMP installation.
   :
   (gimp-context-set-brush (car (gimp-brush-new "non-existent brush")))

   (gimp-drawable-edit-stroke-selection lyr)

   (gimp-file-save RUN-NONINTERACTIVE img lyr "f:\\img\\circle.jpg" ""      )
;  (gimp-xcf-save RUN-NONINTERACTIVE  img lyr "f:\\img\\circle.xcf" "circle")

   (gimp-image-delete img)
)
(gimp-quit TRUE)
Github repository GIMP-Fu-Scripts, path: /simple-demonstrations/circle.scm
P:\ath\to\source> type circle.scm | "C:\Program Files\GIMP 2\bin\gimp-2.10.exe"    -i    -s -b -
Update 2020-03-08: as David C. notified me that the (gimp-context-set-brush (car (gimp-brush-new "non-existent brush"))) statement was needed on his GIMP installation (Ubuntu 19.10, GMIP version 2.10.8) for the example to work. Interestingly, on my Windows/GIMP 2.10.18 installation, the example worked without this statement.

Filled circle

This snippet is basically the same as the previous circle. However, it uses gimp-drawable-edit-fill rather than gimp-drawable-edit-stroke-selection.
(let* (
     (img (car (gimp-image-new     250 250 RGB                                              )))
     (lyr (car (gimp-layer-new img 250 250 RGB-IMAGE "my layer" 100 LAYER-MODE-NORMAL-LEGACY)))
   )

   (gimp-context-set-background '( 10  40  30))
   (gimp-context-set-foreground '(255 240 230))

   (gimp-drawable-fill lyr FILL-BACKGROUND)

;  (gimp-image-add-layer img lyr 100)
   (gimp-image-insert-layer img lyr 0 0)

   (gimp-image-select-ellipse
          img                ;
          CHANNEL-OP-REPLACE ; operation (CHANNEL-OP-{ADD,SUBTRACT,REPLACE,INTERSECXT} )
           25  25            ; x and y coordinate of the upper-left  corner of the ellipse bounding box
          200 200            ; width and height of the ellipse
   )

   (gimp-drawable-edit-fill lyr FILL-FOREGROUND)

   (gimp-file-save RUN-NONINTERACTIVE img lyr "f:\\img\\filled-circle.jpg" ""             )
;  (gimp-xcf-save RUN-NONINTERACTIVE  img lyr "f:\\img\\filled-circle.xcf" "filled-circle")

   (gimp-image-delete img)
)
(gimp-quit TRUE)
Github repository GIMP-Fu-Scripts, path: /simple-demonstrations/filled-circle.scm
P:\ath\to\source> type filled-circle.scm | "C:\Program Files\GIMP 2\bin\gimp-2.10.exe" -i -s    -b -
TODO: This example is similar to this one, but uses gimp-drawable-edit-fill while the other one uses gimp-edit-bucket-fill.

Text

Write Hello world:
(let* (
        (img (car (gimp-image-new     250 100 RGB                                              )))
        (lyr (car (gimp-layer-new img 250 100 RGB-IMAGE "my layer" 100 LAYER-MODE-NORMAL-LEGACY)))
      )

   (gimp-context-set-background '(  0 063 127))
   (gimp-context-set-foreground '( 63 127   0))
   (gimp-drawable-fill lyr FILL-BACKGROUND)

   (gimp-image-insert-layer img lyr 0 0)

   (gimp-floating-sel-anchor (car ; Anchor specified floating selection to its associated drawable
      (gimp-text-fontname
          img
          lyr
          10           ; x-coordinate for left of bounding box
          10           ; y-coordinate for top  of bounding box
         "Hello World" ; Text (UTF-8)
          1            ; Border
          TRUE         ; Antialias (true / false)
          30  PIXELS   ; size and size type (PIXELS/POINTS)
         "Arial"       ; Font name
       )
    ))

   (gimp-file-save RUN-NONINTERACTIVE img lyr "f:\\img\\text.jpg" "")
   (gimp-image-delete img)
)

(gimp-quit TRUE)
Github repository GIMP-Fu-Scripts, path: /simple-demonstrations/text.scm
P:\ath\to\source> type text.scm | "C:\Program Files\GIMP 2\bin\gimp-2.10.exe" -d -i -s    -b -

Brush spacing

Using gimp-context-set-brush-spacing allows to set a distance between «points» where the brush is applied on a line, thus creating a dotted line.
(let* (
     (img (car (gimp-image-new     250 100 RGB                                              )))
     (lyr (car (gimp-layer-new img 250 100 RGB-IMAGE "my layer" 100 LAYER-MODE-NORMAL-LEGACY)))
     (brs (car (gimp-brush-new                       "TQ84 Brush"                           )))

     (lin-1 (make-vector 4))
     (lin-2 (make-vector 4))
     (lin-3 (make-vector 4))
     (lin-4 (make-vector 4))
     (lin-5 (make-vector 4))
   )

   (vector-set! lin-1 0  25) ; x1
   (vector-set! lin-1 1  10) ; y1
   (vector-set! lin-1 2 225) ; x2
   (vector-set! lin-1 3  10) ; y2

   (vector-set! lin-2 0  25) ; x1
   (vector-set! lin-2 1  30) ; y1
   (vector-set! lin-2 2 225) ; x2
   (vector-set! lin-2 3  30) ; y2

   (vector-set! lin-3 0  25) ; x1
   (vector-set! lin-3 1  50) ; y1
   (vector-set! lin-3 2 225) ; x2
   (vector-set! lin-3 3  50) ; y2

   (vector-set! lin-4 0  25) ; x1
   (vector-set! lin-4 1  70) ; y1
   (vector-set! lin-4 2 225) ; x2
   (vector-set! lin-4 3  70) ; y2

   (vector-set! lin-5 0  25) ; x1
   (vector-set! lin-5 1  90) ; y1
   (vector-set! lin-5 2 225) ; x2
   (vector-set! lin-5 3  90) ; y2


   (gimp-context-set-background '(240 220 230))
   (gimp-context-set-foreground '(255 100  30))

   (gimp-drawable-fill lyr FILL-BACKGROUND)

   (gimp-image-insert-layer img lyr 0 0)

   (gimp-brush-set-shape           brs BRUSH-GENERATED-CIRCLE)
   (gimp-context-set-brush         brs)
   (gimp-context-set-brush-size       9.0 )
   (gimp-context-set-brush-spacing    3.5 )

   (gimp-paintbrush lyr 0 4 lin-1 PAINT-CONSTANT 0)

   (gimp-context-set-brush-spacing    2.0 )
   (gimp-paintbrush lyr 0 4 lin-2 PAINT-CONSTANT 0)

   (gimp-context-set-brush-spacing    1.5 )
   (gimp-paintbrush lyr 0 4 lin-3 PAINT-CONSTANT 0)

   (gimp-context-set-brush-spacing    8.0 )
   (gimp-paintbrush lyr 0 4 lin-4 PAINT-CONSTANT 0)

   (gimp-context-set-brush-spacing    1.0 )
   (gimp-paintbrush lyr 0 4 lin-5 PAINT-CONSTANT 0)

   (gimp-file-save RUN-NONINTERACTIVE img lyr "f:\\img\\brush-spacing.jpg" "")
   (gimp-brush-delete brs)
   (gimp-image-delete img)
)
(gimp-quit TRUE)
Github repository GIMP-Fu-Scripts, path: /simple-demonstrations/brush-spacing.scm
P:\ath\to\source> type brush-spacing.scm | "C:\Program Files\GIMP 2\bin\gimp-2.10.exe"    -i    -s -b -

Using a function to draw a star

The following example defines a function define draw-line… to make it (a bit) easier to draw a line.
This function is called repeatedly to draw a star:
(define (draw-line layer x-from y-from x-to y-to)

  (let* (
        (points (make-vector 4 'double))
      )

      (vector-set! points 0 x-from)
      (vector-set! points 1 y-from)
      (vector-set! points 2 x-to  )
      (vector-set! points 3 y-to  )

      (gimp-paintbrush layer
            0              ; fade-out
            4              ; number of coordinates (not coordinate pairs)
            points         ; strokes
            PAINT-CONSTANT ; method (PAINT-CONSTANT / PAINT-INCREMENTAL
            0              ; gradient length
      )
  )
)

(let* (
         (img (car (gimp-image-new     250 250 RGB)))
         (lyr (car (gimp-layer-new img 250 250 RGB-IMAGE "my layer" 100 LAYER-MODE-NORMAL-LEGACY)))
         (brs (car (gimp-brush-new                       "TQ84 Brush"                           )))
         (nof-points  10)
         (jump-points  3)
         (i nof-points)
      )

     (gimp-image-add-layer img lyr 0)

     (gimp-context-set-background '( 30  50 200))
     (gimp-context-set-foreground '(240 110  30))

     (gimp-context-set-brush         brs)
     (gimp-brush-set-shape           brs BRUSH-GENERATED-CIRCLE)
     (gimp-brush-set-hardness        brs 0.3)

     (gimp-drawable-fill lyr FILL-BACKGROUND)

     (gimp-context-set-brush-size       3.0 )
     (gimp-context-set-brush-spacing    2.0 )

     (while (> i 0)

       (draw-line
          lyr
        ;
        ;  Note π is represented by *pi* (with the stars):
        ;
          (+ 125 (* 100 (sin (* 2 *pi* jump-points    i    (/ 1 nof-points)))))
          (+ 125 (* 100 (cos (* 2 *pi* jump-points    i    (/ 1 nof-points)))))
          (+ 125 (* 100 (sin (* 2 *pi* jump-points (+ i 1) (/ 1 nof-points)))))
          (+ 125 (* 100 (cos (* 2 *pi* jump-points (+ i 1) (/ 1 nof-points)))))
       )

;      (gimp-message (number->string i))
       (set! i (- i 1))
     )

     (gimp-file-save RUN-NONINTERACTIVE img lyr "F:\\img\\function-star.jpg" "")

     (gimp-brush-delete brs)
     (gimp-image-delete img)

)
(gimp-quit TRUE)
Github repository GIMP-Fu-Scripts, path: /simple-demonstrations/function-star.scm
P:\ath\to\source> type function-star.scm | "C:\Program Files\GIMP 2\bin\gimp-2.10.exe"    -i    -s -b -

Writing (debug) messages with (gimp-message)

(gimp-message …) writes messages, however with the prefix warning.
By default, these messages seem to go to a message box. This is of course less than desirable. Apparently, in order to redirect output to the console, the -s (or also -c?) command line argument must be used.
Alternatively, the message stream can also be redirected with (gimp-message-set-handler 1).
In cmd.exe, the string needs to be quoted with four apostrophes or \" (see Splitting command line into arguments when executing an exe).
C:\> "C:\Program Files\GIMP 2\bin\gimp-2.10.exe" -i -s -b "(gimp-message """"foo"""")(gimp-quit TRUE)"
script-fu.exe-Warning: foo
"C:\Program Files\GIMP 2\bin\gimp-2.10.exe" -i -b "(begin (gimp-message-set-handler 1) (gimp-message \"hello world\") (gimp-message (number->string 42)) (gimp-quit 0) )"

Iterate over files

The following script tries to present a template to demonstrate how it is possible to iterate over files in a directory.
(define (iterate-files pattern)

  (let* (
      (filelist (cadr (file-glob pattern 1)))
  )

  (gimp-message pattern)

  (while (not (null? filelist))
        (let* (
               (filename (car filelist                                             ))
   ;
   ;  Load file, create drawable ...
   ;           (image    (car (gimp-file-load RUN-NONINTERACTIVE filename filename)))
   ;           (drawable (car (gimp-image-get-active-layer       image            )))             
              )

   ;     (gimp-message filename)


   ;     (gimp-file-save RUN-NONINTERACTIVE image drawable filename filename)
   ;     (gimp-image-delete image)
         )
     (set! filelist (cdr filelist)))
  )
; (gimp-quit TRUE)
)
With PowerShell, this script might be copied into the user's GIMP-script directory:
copy-item .\gimp-glob_iterate-over-files.scm $env:appdata\GIMP\2.10\scripts 
Executing the script, for example, with one of the following two PowerShell commands:
echo "(iterate-files ""$curDirEscapedBackslash\\*.jpg"")" | & 'C:\Program Files\GIMP 2\bin\gimp-2.10.exe'  -i -s -b -
For a reason I really don't understand, the string with the glob-pattern needs to be enclosed with six(!) apostrophes on either side:
& 'C:\Program Files\GIMP 2\bin\gimp-2.10.exe'  -i  -b "(iterate-files """"""*.jpg"""""")"

Thanks

Thanks to David in New Zealand for his valuable feedback and improvments on these scripts.

TODO

The following examples from my old website www.adp-gmbh.ch should be moved here, too:
An example that demonstrates how multiple layers are merged into one image and then saved to a file. I once even asked a corresponding question on StackOverflow.

Index