
Introduction
The article will tell you how to quickly get acquainted with the basics of scripting in GIMP in the
Scheme language and proceed directly to solving simple practical problems. This material is intended only for those who are going to automate routine processing here and now, not much going into subtleties and not sacrificing precious time. Also, the
article is not recommended for use as a manual on Scheme separately from Script-fu. This is due to the simplified programming style in this material and the lack of coverage of other important facts that we are now concerned about much less than the speed of development.
Content:- What do we need?
- Syntax briefly
- Variables
- Functions
- Lists
- Register the script in GIMP
- Code writing
- Conclusion
What do we need?
English interface : to do this, it is enough to create the βLANGβ environment variable with the value βenβ. Why do you need it? First, it will be easier to look for matching procedures to interface objects. Secondly, I do not have to bring commands in two languages. Thirdly, in English on the Internet the most information.
Script-fu console : Filters β Script-fu β Console. Here we will be able to test small pieces of code, - that is necessary when mastering the language.
Procedure Browser: Help β Procedure Browser. Here you can easily find the function that performs the required action and read its full description (everything is well documented).
Code editor with highlighting and / or paired brace counting . I will leave for your taste. Notepad ++ was enough for me. But remember, there will be a lot of brackets!
The next few sections contain excerpts from the first four pages of the
Script-fu documentation and some ad-lib. It is strongly recommended to try running the examples below in the console.
')
Syntax briefly
- All expressions in Scheme must be surrounded by brackets.
- The name of the function always comes first in brackets, and then its parameters.
- Mathematical operators are also functions.
It's time to give an example:
(* (+ 1 2) (sqrt (- 13 4)) 10)
The last will be calculated multiplication result. As can be seen, three arguments are passed to the multiplication function: the result of addition, the result of extracting the root from the difference, and the number. Pay attention to the number of brackets: they are required everywhere. This may interfere, but it is always clear what is being calculated.
- The function and each of the arguments must be separated from each other by spaces.
Example: β
(+ 1 2)
β is the correct code, β
(+1 2)
β is not one.
- Anything that follows the symbol β
;
, Is a comment and is ignored.
Variables
Variables in Scheme are defined using the
let*
construct. General form:
(let* ( ( ) ... ( ) ) () ... () )
When compared with imperative languages, it is something like a local variable declaration. In other words, after the bracket closing the
let*
construction, variables cease to exist.
Example:
(let* ( (a 1) (b (+ a 2)) ) (+ ab) )
Another example:
(let* ( (x 9) ) (sqrt x) )
Please note that even in the case when we define only one variable, the external brackets for the list of variables are
not omitted !
The new value of the variable can be assigned using the
set!
:
(set! )
Example:
(let* ( (a 42) (b 21) (x 0) ) (set! x (/ ab)) )
Functions
You can define your functions using the
define
construct:
(define (_ ) _)
The value of the function is the result of the last command in the function code.
Implement the function of calculating the difference of the modules (

). This can be done with
abs
, but we will make it a little harder:
(define (difference xy) (if (< x 0) (set! x (- x))) (if (< y 0) (set! y (- y))) (if (> xy) (- xy) (- yx)) )
Here we used the
if
construct, which checks the truth of its first argument and, depending on this, it performs either the second or the third argument (and the last one, as you can see, is optional).
Note that a function can treat its arguments as variables, but it only changes copies of them. This can be seen as follows:
(let* ((a 3) (b -4)) (list (difference ab) ab))
(The
list
function is used here to display several results β the value of the function, the variable
a
and the variable
b
β and weβll discuss more about the lists below). Run in the console and check that the values ββof the variables have not changed.
Lists
To define a list, just write (no commas):
'(0 1 1 2 3 5 8 13)
An empty list can be specified either through "
'()
" or through "
()
". Lists can contain both atomic values ββand other lists:
(let* ( (x '("GIMP" (1 2 3) ("is" ("great" () ) ) ) ) ) x )
Since we have already written one apostrophe, it is not necessary to preface them with internal lists.
To add one more element to the top of the list, you need to use the
cons
function concatenation:
(cons 1 '(2 3 4) )
It works equally well with empty lists (β
(cons 1 () )
β gives a list of one element).
To create a list containing the values ββof previously declared variables, you need the
list
function:
(let* ( (a 1) (b 2) (c 3) ) (list abc 4 5) )
To understand the difference with the definition of a list through an apostrophe, replace "
(list abc 4 5)
" with "
'(abc 4 5)
" and compare the output.
It's all good, but how to get the contents of the list? There are two functions for this. The first,
car
, returns the head of the list, that is, the first item. The second,
cdr
, returns the tail of the list, that is, a list containing all elements except the first. Both functions assume the list is non-empty. Examples:
(car '(1 2 3 4) )
(cdr '(1 2 3 4) )
(car '(1) )
(cdr '(1) )
Instead of calling
car
and
cdr
succession, it is useful to use functions like
caadr
,
cddr
, etc. For example, to get the second list item, write the following:
(cadr '("first" "second") )
which is equivalent to
(car (cdr '("first" "second") ) )
In the following example, try to get to element 3 using only two calls of such functions:
(let* ( ( x '( (1 2 (3 4 5) 6) 7 8 (9 10) ) ) )
If you succeed, then you are almost ready to write your first script.
Register the script in GIMP
Before we sit down to write code, we will ensure convenient conditions for this.
For user scripts, GIMP creates a
.gimp-2.6/scripts
folder in its home directory. In order for the script to pick up, it is enough to place an scm file into it and select Filters β Script-fu β Refresh Scripts in the GIMP menu (this is if GIMP is already running, otherwise it will load everything at startup).
In the file, obviously, you need to put the functions we wrote. It can contain as many functions as you like, but it would be nice to split logically different functions across different files and name the files after the contents. Another recommendation, even an agreement: the functions we create should be referred to as
script-fu-functionname
.
By and large, this is already enough so that we can call our functions from the console.
But if we want the script to have its own menu, and when it is called, a window with parameter settings opens, then we need to add two functions responsible for registration. And there is nothing difficult, just look at the example.
Suppose we want to write a function that improves the quality of the text in the picture with uneven illumination (in fact, I have already written it, but this does not prevent us from doing it again). Here is its definition:
(define (script-fu-readability inImage inLayer inRadius inHigh-input))
I know, I know, here is just a function declaration, and it does nothing. Useful code will be later. This is quite enough for us now. Registration takes place like this:
(script-fu-register "script-fu-readability" "Readability" "Improves text readability on the photos. It's needed only when there is a non-uniform illumination" "Dragonizer" "Copyleft, use it at your own sweet will" "January 7, 2011" "RGB* GRAY* INDEXED*" SF-IMAGE "The image" 0 SF-DRAWABLE "The layer" 0 SF-ADJUSTMENT "Median blur: radius" '(15 1 20 1 5 0 SF-SLIDER) SF-ADJUSTMENT "Levels: intensity of highest input" '(235 0 255 1 10 0 SF-SPINNER) ) (script-fu-menu-register "script-fu-readability" "<Image>/Filters/User's scripts")
The following function is passed to the first function. The first argument is the name of our function, the second is the display name, the third is the description, the fourth is the author, the fifth is copyright information, the sixth is the date of creation. Seventh - types of supported images (RGB, RGBA, GRAY, GRAYA, INDEXED, INDEXEDA).
Subsequent arguments are optional. They (except for
SF-IMAGE
and
SF-DRAWABLE
) allow you to create widgets in the script window, such as stitches, daws, sliders, spinners, color choices, fonts, and more, to convey the user's choice to a function. The mentioned
SF-IMAGE
will give us a link to the current open image, and the
SF-DRAWABLE
to the selected layer. I will not describe all these
SF-*
, their parameters you can see in the tables
here (the rest is not necessary to read, for it is summarized in this article). And I also advise you to have a look at
this picture in order to understand what you need from this (taken
from here ).
The window is ready, it remains to add its call to the GIMP menu, which is what the second function of the code above does. Two arguments: again, the function name and the menu path. The path starts with
<Image>
, if some branches did not exist before, GIMP will add them.

Another example: if we wanted to write a script that
creates an image with the specified properties, we would remove the
SF-IMAGE
and
SF-DRAWABLE
from the first function, instead of
"RGB* GRAY* INDEXED*"
use the empty string
""
(we donβt you need an open image, we will create it), and in the second function would change the path to something like
"<Image>/File/Create/Something"
.
To admire the result, let's save our creativity in β
script-fu-readability.scm
β and update the scripts. Now open / create some image and select our script from the menu.
Code writing
Here it is, the desired moment! But I hasten to upset: there is nothing complicated here. Totally. Functions you already know how to write. And everything that you may need from the editor is easy to find in the procedure browser. Need some kind of operation with layers? Search for β
layer
β. Invert image? You need something that contains "
invert
". And so on.
I will only make two comments:
- It would be nice to conclude all actions performed by the script between the functions
gimp-image-undo-group-start
and gimp-image-undo-group-end
, as is done below, so that the user does not have to cancel each action separately. - All GIMP functions return lists, regardless of the amount of data in the result. It is easy to pierce, waiting, for example,
layer
, and receiving (layer)
. So do not forget to do car
in such cases.
And now an example of a working code. I borrowed the algorithm from
here (thanks to
Killy ).
(define (script-fu-readability inImage inLayer inRadius inHigh-input) (let* ( (layer2 0) ) (gimp-image-undo-group-start inImage) (if (not (= (car (gimp-image-base-type inImage)) GRAY)) (gimp-image-convert-grayscale inImage)) (set! layer2 (car (gimp-layer-copy inLayer FALSE))) (gimp-image-add-layer inImage layer2 -1) (plug-in-despeckle RUN-NONINTERACTIVE inImage layer2 inRadius 0 -1 256) (gimp-layer-set-mode layer2 DIFFERENCE-MODE) (set! inLayer (car (gimp-image-flatten inImage))) (gimp-invert inLayer) (gimp-levels inLayer HISTOGRAM-VALUE 0 inHigh-input 0.1 0 255) (gimp-image-undo-group-end inImage) ) )
Having a browser of procedures at hand, it's easy to find out if it's interesting.

Batch processing
Where where? That's not all. Do you think we have done so much to write some unfortunate scriptwriter processing one picture? Yes, it would be faster hands! So let's make GIMP open all files from a given folder, process it, and save it to another folder.
The most pleasant thing is to adapt the code below to do something else, it is enough to replace the function it calls with the necessary one, everything else will not change (well, unless you want to save the file to another extension).
The code is partially borrowed from
this topic (thanks to
Apostol ), but there it saves files, overwriting the original ones. The
morph-filename
function is taken
from here .
(define (morph-filename orig-name new-extension) (let* ((buffer (vector "" "" ""))) (if (re-match "^(.*)[.]([^.]+)$" orig-name buffer) (string-append (substring orig-name 0 (car (vector-ref buffer 2))) new-extension) ) ) ) (define (script-fu-batch-readability inInFolder inOutFolder inRadius inHigh-input) (let* ((filelist (cadr (file-glob (string-append inInFolder DIR-SEPARATOR "*") 1)))) (while (not (null? filelist)) (let* ((filename (car filelist)) (image (car (gimp-file-load RUN-NONINTERACTIVE filename filename))) (layer (car (gimp-image-get-active-layer image))) ) (script-fu-readability image layer inRadius inHigh-input) (set! layer (car (gimp-image-get-active-layer image))) (set! filename (string-append inOutFolder DIR-SEPARATOR (morph-filename (car (gimp-image-get-name image)) "png"))) (file-png-save2 RUN-NONINTERACTIVE image layer filename filename 0 9 0 0 0 1 0 0 0) (gimp-image-delete image) ) (set! filelist (cdr filelist)) ) ) )
Conclusion
Readability script with the batch version can be downloaded here ( mirror ). The code is commented, even somewhat unnecessarily.Let me remind you once again that the article does not pretend to be complete in a broad sense and is intended only so that the reader can sit down, read thoughtfully, practice at the same time, and start creating scripts that solve his tasks. So that it does not take as much time as is required for more or less high-quality language learning.
If you read the article to the end, now you can Script-fu as good as me.