One of the great things about the GIMP is that it lets its users add to it through the use of plug-ins and scripts. Plug-ins are written in the C language (the same as the GIMP itself) and need to be compiled before they can be added. Scripts are merely text files that contain a sequence of commands which are to be executed.
Scripts can be written in a variety of languages (Perl, Python, GLUAS, and Scheme) so long as the language is supported by the GIMP (this usually means that there has to be some C code added to the GIMP which will read the script's text file and execute the commands).
The primary scripting language of the GIMP is Scheme and support for it is included in the GIMP "out-of-the-box" regardless of whether you are using Linux, Solaris, Windows, or MacOS. Since Scheme was the first scripting language available in the GIMP, it is also known as "Script-fu" (the name was an homage to the Chinese martial art, "kung-fu"). By learning to write your own Script-fu programs, you will be able to not only automate repetitive, time-consuming, and complicated tasks but also add functions to the GIMP which you may find useful (and which you feel the developers may have "forgotten").
-----
GETTING STARTED
[*] Start a new session in the GIMP and open up the Script-fu console (go to "Xtns->Script-fu->Script-fu Console").
The welcome message refers to "SIOD, Scheme In One Defun". SIOD is a particular implementation of the Scheme programming language (just as MS Word is a particular implementation of a word processor). The "defun" refers to the original "define function" command which has since been replaced by the less cryptic "define" command.
[*] Type in the following command into the input line near the bottom of the window:
Quote:
2
In the log window, the result will appear as follows:
Quote:
=> 2
2
The first line (the one starting with "
=>") is merely an echo of your command. The second line is the generated result. Throughout this tutorial, console input will be described in this form (do not type in the "=>" or the output).
What you have done is enter a
value and Script-fu has
evaluated it to produce the result.
-----
Type "*pi*" into the command line:
Quote:
=> *pi*
3.1415927
What you have done is enter a
symbol and Script-fu has
evaluated it to produce the result. This symbol's value has been defined already (to be the ratio of a circle's circumference to its diameter). Later, we will show how you can define your own symbols. It should be noted that, unlike most other programming languages, Script-fu permits symbols to contain any characters except for spaces, tabs, newlines, and parentheses.
-----
Type "(sqrt 2)" into the command line:
In the log window, this will appear as follows:
Quote:
=> (sqrt 2)
1.41421
In addition to values and symbols, Script-fu understands
lists composed of values and symbols, which are separated by spaces and surrounded by a pair of parentheses. When Script-fu evaluates a list of items, it expects the first item to be a name of a function that is to be executed. (In this case, 'sqrt' is the "square root" function and what you have just done was to calculate the "square root of 2".
-----
Now try entering "(2 + 3)":
Quote:
=> (2 + 3)
ERROR: bad function (see errobj)
What happened here is that Script-fu tried to "evaluate" the list you gave it, looked for "2" (the first item in the list) in its catalog of defined functions, and generated an error when it couldn't find it. Again (this is important), Script-fu will treat the first item of a list as a function to be called. In order to make our code work, we have to rearrange the parameters so that the addition symbol is the first item.
Quote:
=> (+ 2 3)
5
Since our list is treated as a function call and the first item is the function name, all of the items that follow it in the list are called "arguments". In the "sqrt" example, the function expected one argument and in the addition example, we passed two arguments to the function. Different functions can expect different numbers of arguments (some functions expect no arguments: type "(realtime)" into the console and you will be shown the number of seconds that have transpired since the beginning of 1970).
-----
The question then arises, if Script-fu tries to evaluate every list of items you give it (as a function call followed by some arguments) then how do you create a list of items that isn't treated as a function call?
In order to enter a list such as "(1 2 3)" you must "turn off" the normal evaluation mode using the 'quote' function.
Quote:
=>(quote (1 2 3))
(1 2 3)
The output, "(1 2 3)", is our "non-evaluated" list. In fact, we could even pass a "normal" function call list to 'quote' and it won't be evaluated either:
Quote:
=> (quote (+ 2 3))
(+ 2 3)
Instead of this producing the result, "5", the log window displays "(+ 2 3)". This is not surprising because we now know that a function call is merely a list of items.
Whenever you need to have a list which is not evaluated (i.e., not treated as a function), the 'quote' function is needed. This is such a common occurence that there is a shorthand notation for it: the apostrophe.
Quote:
=> '(+ 2 3)
(+ 2 3)
This is precisely the same thing as the preceding example, merely a more convenient syntax.
-----
As stated earlier, Script-fu allows a
symbol string to be used in place of a corresponding item. These symbols can be created using the 'define' function.
Quote:
=> (define b 2)
2
The 'define' function equates its first argument with its second. Typing the variable name in the entry window
Quote:
=> b
2
The item "b" is evaluated and its numeric value is returned (just like *pi* was evaluated earlier). If you were to not evaluate "b" by using "(quote b)", what would you expect the output to be? Give it a try.
Everything so far is fairly simple, though perhaps a bit unusual. What I have described is basically the entire language: composed entirely of items and lists of items which are either "evaluated" or not. Everything else is built upon this simple concept. Items can also be strings; which doesn't really complicate things at all, as strings are treated just like the other items. (Though I haven't mentioned it, items can also be "arrays", but that won't be covered here).
There are only a couple of things that need to be mentioned to complete our description of Script-fu. First is the fact that a list can contain zero items. Such a list is called an "empty list" (or "null list") and will prove important when we look at the "testing" functions such as 'if' and 'while'.
The second detail that I haven't mentioned is the fact that items in a list do not have to be the same type; we can mix numbers with variable with strings all in the same list. In fact (and this is IMPORTANT),
items in a list can themselves be lists. This fact is the one piece we need to complete the puzzle. Not only does this mean that the result of a function (which until now was treated as an item) can be a list, but that a list of items can actually be a list of items where each item is itself a list. The importance of this will become apparent later on but for now, let's examine how lists can be accessed.
Quote:
=> (define items (quote (1 2 (3 4) 5)))
(1 2 (3 4) 5)
To fetch the first item of a list, we use the 'car' function.
Quote:
=> (car items)
1
To return the rest of the list (except for the first item), we use the 'cdr' function.
Quote:
=> (cdr items)
(2 (3 4) 5)
Note: 'cdr' is an example of a function returning a list.
To fetch the second item from the original list of items, we should notice that it is the first item in the list returned in the 'cdr' example (i.e., the second item is the first item in the rest of the list).
Quote:
(car (cdr items))
2
The result, "2", is as expected. It should be noted that the "inner" function ('cdr') is evaluated before the "outer" function ('car'). In Script-fu, all functions are evaluated "inside-to-outside": the innermost group of parentheses are evaluated before the outer ones.
Now try the following in order to fetch the third item from the original list:
Quote:
(car (cdr (cdr items)))
(3 4)
The result should not come as a surprise (if we follow the logic of the preceding examples); the only thing notable about the result is that it is a
list. The third item in the original list was itself a list, and that list is what was returned.
The "car + cdr" notation may seem a little strange and perhaps awkward. It is derived from a historical background and some newer versions of Scheme provide for use of the synonym "first" in place of "car" and "rest" in place of "cdr". Script-fu does not do this (though you could easily do so yourself) but there are advantages to the "car + cdr" notation.
The two functions that we used to fetch the second item from the list
(car (cdr items))
can be abbreviated to the single function
(cadr items)
Likewise, the command
(car (cdr (cdr items)))
can be shortened to the single function
(caddr items)
This abbreviation proves handy for making your code more readable.
There are other sources on the web that go into greater detail but what we have here should provide enough of an introduction for our purpose.
-----
PART II: SPECIAL FUNCTIONS
At this point, you should have all of the basic knowledge needed to write scripts in the Script-fu language. It can be summarized in two fundamental rules:
1) All input consists of either an item or a list of items -- an "item" being either a core object (such as an number, string, or array) or itself a list.
2) Lists are treated as function calls, with the first item in the list being the called function; though in a few special cases, this treatment can be disabled (such as when the 'quote' function is used).
To illustrate the second point, type the following command into the console:
Quote:
=> (car (5 6 7))
ERROR: bad function (see errobj)
The "bad function" in this case is "5"; since the standard behavior is to treat "(5 6 7)" as a function call. It is necessary to "disable" the standard treatment in order for the code to work as expected.
Quote:
(car (quote (5 6 7)))
5
The 'quote' function is "special" in that its argument is treated
literally (i.e., as it appears) and it is not evaluated. There are only a few of these "special functions" but they play a critical role in the language.
Another special function is the 'define' that we used earlier.
Quote:
=> (define f (+ 2 3))
5
The first argument, "f", to 'define' is NOT evaluated, but is instead interpreted as a name of a variable to be assigned a value. Note: in this case, the second argument, "(+ 2 3)", IS evaluated.
The 'define' function also has an even more "special" form:
Quote:
=> (define (f) (+ 2 3))
#<CLOSURE () (+ 2 3)>
When the first argument to 'define' is wrapped within parentheses, it is still not evaluated and it is still treated as a symbol to be defined (though the symbol is a name of a function). In this situation, the subsequent arguments are treated as if they were 'quoted' and are
not evaluated (they are stored for later execution when 'f' is called). The "#<CLOSURE>" output displays the function as it is stored.
If any variable names appear after the function name in the first argument, they are treated as arguments to the function being defined (and will be replaced when the function is eventually evaluated).
Quote:
=> (define (f x) (+ x 3))
#<CLOSURE (x) (+ x 3)>
=> (f 2)
5
Quote:
=> (define (f x y) (+ x y))
#<CLOSURE (x) (+ x y)>
=> (f 2 3)
5
If there is more than one item appearing in the definition of the function, it is the value of the
last item evaluated that is returned by the function.
-----
The Script-fu 'if' function is also treated specially; and has the form
(if test-item TRUE-item FALSE-item)
When an 'if' is encountered, 'test-item' is evaluated and if it is true then 'TRUE-item' is evaluated. If 'test-item' evaluates to false then 'FALSE-item' is evaluated (if 'FALSE-item' is not present, nothing is evaluated). The value returned by the 'if' function is the value of either 'TRUE-item' or 'FALSE-item'; whichever is evaluated.
In order to call more than one function for either 'true-item' or 'false-item' (remember, they are only single items), it is necessary to combine all of the functions so that they are treated as a single item. This can be done using the 'begin' function.
(begin item1 item2 item3 ... itemX)
The 'begin' function will evaluate all of the items, returning the value of the last one.
-----
The Script-fu 'while' function is also special and has the form
(while test-item item1 item2 item3 ... itemX)
As long as 'test-item' evaluates to true, all of the items ('item1' through 'itemX') will be evaluated. It is necessary that one of the items being evaluated eventually change the result of the test condition so that the 'while' function stops looping.
Type the following into the console:
Quote:
=> (define b 5)
5
Quote:
=> (while (> b 0) (print b) (define b (- b 1)))
5
4
3
2
1
()
The 'test-item' here is "(> b 0)" -- in most programming languages this would be written "b > 0" but in Script-fu the function comes first -- and, as long as 'b' is greater than zero the other items will be executed. The last item, "(define b (- b 1))", subtracts one from the value of 'b'.
-----
That is pretty much it as far as the basics go, there are some other "special" functions (and several "normal" functions); but the ones covered are enough to write most any program you wish.
There are two issues that I will address in closing, and they concern the 'define' function.
Firstly, the use of 'define' in the WHILE loop above is not ideal. Whenever it is encountered, a new variable is 'defined' and the old one is abandoned. As you saw, this caused no problem with the program's execution but, nonetheless, it is a waste of memory (and eventually the "garbage collector" will have to come along and discard all of the abandoned variables).
For this reason, there is a special command, called 'set!', which will reassign the value of a variable (without creating a new one).
Quote:
=> (define b 5)
5
Quote:
=> (while (> b 0) (print b) (set! b (- b 1)))
5
4
3
2
1
()
The code executes the same, it is just more efficient and more clear what is happening.
Secondly, when symbols are
defined, they are assigned that value
throughout your program (and throughout all of Script-fu). If your program uses a variable named 'b' and calls another program which 'define's a variable 'b' also, then your variable is changed by the other program. In order to avoid this, Script-fu provides the 'let' function which limits your 'definition' to your program.
The format of the 'let' function is one of the most complicated that Script-fu has, so I will present it in its simplest form first:
Code:
(let (
(x)
(y)
(z)
)
item1
item2
...
itemX
)
The 'x', 'y', and 'z' symbols are created similar to if they had been 'define'd, but they only exist until the last right parenthesis (while all of the items are being evaluated). If one of them exists already as a symbol (for example, in another function that calls yours) then that pre-existing value is ignored and the "local" value is used. At the end of the 'let' statement (after 'itemX'), the original meaning is restored.
Quote:
=> *pi*
3.14159
Quote:
=> (let ((*pi*)) (set! *pi* 2) (print *pi*))
2
()
Quote:
=> *pi*
3.14159
The above commands demonstrates how '*pi*' is only temporarily defined as "2". Outside of the 'let' statement, '*pi*' has its normal value.
Note: you can both declare
and assign the variables when using the 'let' statement. For example, the preceding example could be written as:
Quote:
=> (let ((*pi* 2)) (print *pi*))
2
()
A variation of the 'let' function is the 'let*' function:
Code:
(let* (
(x 2)
(y 3)
(z (+ x y))
)
item1
item2
...
itemX
)
The only difference between 'let*' and 'let' is that the variables ('x', 'y', and 'z') are
guaranteed to be evaluated in the order they appear. If the 'let' function were used in the above example, it might fail because 'z' could be evaluated before 'x' and 'y'. (For this reason, it is more common to just use 'let*' in all cases.)
NOTE: Regardless of whether a variable is created with 'define' or with 'let*', the 'set!' function is still used to change its value.
-----
While I haven't shown anything about how the language is used in the GIMP (we didn't even have an image open), having an understanding of the philosophy of the basic Script-fu function and data elements is an important part of being able to use it effectively. Hopefully, this introduction will have helped developed such an understanding.