Previous Next TOC Index Feedback


Thirty-Nine

Chapter


 

Mike Terry's black belt school of Script-Fu

Author Mike Terry; Copyright 1998 by Mike Terry License, GDPCL.

 

The road to Script-Fu Mastery

So, little grasshopper, you have found Gimp, and you want to learn of its secrets?

More specifically, you wish to learn of its fantastic scripting abilities, no? You are perhaps tantalized at the prospect of automating image-editing drudgery, or maybe you seek the kind precision in your work that only a well-written script can achieve...

Well, you have come to the right place, my friend, as Mike Terry's Black Belt School of Script-Fu can train you in the not-so-ancient art of Script-Fu.

Course outline

In this training course, we'll introduce you to the fundamentals of Scheme necessary to use Script-Fu, and then build a handy script which you can add to your toolbox of scripts. The script prompts the user for some text, then creates a new image sized perfectly to the text. We will then enhance the script to allow for a buffer of space around the text.

Meet your instructor

Let me first confess that I am currently only a yellow-belt of this art, and as such, can only take you so far. However, together, we can press on and reach new heights. If I err, omit some important detail in this training, or am just plain wrong about something, please email me so I may correct it. Similarly, if you have tips or suggestions on how to improve your training, please forward them to me.

I hope you benefit from this training, and may you soon become a Master of Script-Fu!

Audience

These training sessions are intended for the beginning Script-Fu'er. When I heard that Gimp was scriptable, I got very excited, and wanted to dive right in. Unfortunately, the tutorials were scant and incomplete, especially if you knew no Scheme (like I didn't). After about two days of trying to force my square peg of C/C++ knowledge into the round hole of Scheme, I reckoned a tutorial from the ground-up, chock-full of demos, would do the new Script-Fu'er a lot of good.

Currently, then, the tutorial is really aimed at the beginner, but as I learn more, I will expand it so we can all be Script-Fu Masters! Your suggestions and complaints are welcome.

Michael Terry 
mterry@soulfry.com 

 

Lesson 1: Getting acquainted with Scheme

Let's start Scheme'ing

The first thing to learn is that every statement in Scheme is surrounded by parentheses - ().

The second thing you need to know is that the function is always the first item in the parent and the rest of the items are parameters to the function. (However, not everything enclosed in parentheses is a function - they can also be items in a list, but we'll get to that later).

The third thing to understand is that mathematical operators are also considered functions, and thus are listed first when writing mathematical expressions. If you're familiar with post-fix notation, or own a calculator that uses Reverse Polish Notation (such as most HP calculators), you should have no problem adapting to formulating expressions in Scheme.

Now, young grasshopper, let's practice what we have just learned. Start up Gimp, if you have not already done so, and choose Xtns->Script-Fu->Console. This will start up the Script-Fu Console window, which allows us to work interactively in Scheme.

Extracted pic [1]

Extracted pic [2] In a matter of moments, the Script-Fu Console will appear:

At the bottom of this window is an entry-field entitled current command. Here, we can test out simple Scheme commands interactively.

Let's start out easy, and add some numbers.

(+ 3 5)

Typing this in and hitting Return yields the expected answer of 8 in the center window.

Now what if we wanted to add more than one number? The + function can take 2 or more arguments, so this is not a problem:

(+ 3 5 6)

This also yields the expected answer of 14.

So far, so good - we type in a Scheme statement and it's executed immediately in the Script-Fu Console window. Now for a word of caution...

Watch out for extra parens.

If you're like me, you're used to being able to use extra parentheses whenever you want to - like when you're typing a complex mathematical equation and you want to separate the parts by parentheses to make it clearer when you read it. In Scheme, you have to be careful and not insert these extra parentheses incorrectly. For example, say we wanted to add 3 to the result of adding 5 and 6 together:

3 + (5 + 6) = ?

You might be tempted to translate that into the following Scheme statement:

(+ 3 (5 6) )

However, this is incorrect - remember, every statement in Scheme starts and ends with parens, so the Scheme interpreter will think that you're trying to call a function named 5 in the second group of parens, rather than summing those numbers before adding them to 3.

The correct way to write the above statement would be:

(+ 3 (+ 5 6) )

Practice a bit with simple mathematical equations in the Script-Fu Console until you're totally comfortable with these initial concepts.

 

Lesson 2: Of Variables and Functions...

So, my student, you are curious and want to know about variables and functions? Such vigor in your training - I like it.

Variables

Now that we know that every Scheme statement is enclosed in parentheses, and that the function is listed first, we need to know how to create and use variables, and create and use functions. We'll start with the variables.

Declaring global variables with "set!"

Variables can have either local or global scope. To declare a global variable, use the set! function:

(set! myVar 5)

Now you can use myVar as you'd expect:

((+ myVar 8)

Go ahead and try declaring and using a variable in the Script-Fu Console.

Declaring local variables with "let"

Variables can also have local scope. This is achieved by using the let statement:

(let ( (x 5) (y 6) ) (...) )

You'll probably notice an abundance of parens here - all I can say is, get used to it.

In the let statement, after the let keyword, we have a list of initialized values. We haven't gotten into lists yet, but the syntax shouldn't be too hard to grasp - we open a parens to contain all the variable declarations, then we enclose each variable declaration in its own set of parens.

After all the variable declarations, we can then start using our local variables, up until the final closing parens (the statements would go where the ellipsis (...) is).

The variables declared within the let statement have scope only within the enclosing parens. An example will help clarify this.

(let
(
(x 5)
(y 6)
)
(+ x y)

)

As you might expect, this produces the answer of 11 when used within a script. However, if we were to follow this with the following statement:

(* x y)

we'd get an error because x and y are now out of scope - they only are usable within the let statement in which they're declared.

White space

If you notice above, the let statement is written across multiple lines - this is not a problem, as white space can be liberally applied to help clarify and organize the code within a script. (However, if you're working in Script-Fu's Console window, you'll need to enter everything on one line.)

Assigning a new value to a variable

Once you've initialized a variable, you'll more than likely need to change its value later on in the script. Use the set! statement for both local and global variables to change the variable's value:

(set! gCount 15)
(let ( (theNum 10) ) (set! theNum (+ gCount theNum) ) (set! gCount theNum) )
(* gCount gCount)

Try to guess what the above statements will do, then go ahead and enter them in the Script-Fu Console window.

Functions

Now that you've got the hang of variables, let's get to work with some functions.

You declare a function with the following syntax:

define (TheFunctionName param1 param2) (...) (...) )

where TheFunctionName is the function's name, and any and all param names follow it. Notice that the parameters don't have any types - Scheme is a type-less language.

The (...) characters represent the function's code. Thus, to find the square of a number, we could write the following function:

(define (square inNumber) (* inNumber inNumber) )

If you type this into Script-Fu's Console window, you'll get a message about Closure, then you'll be ready to use the function:

(square 5)

Typing this in will yield the expected result.

But how do you know what will be returned? Basically, the result of the last statement executed within the function is the result it returns.

 

Lesson 3: '(Lists Lists and More Lists)

We've trained you in variables and functions, young Script-Fu'er, and now we must enter the murky swamps of Scheme's lists. Are you ready for the challenge?

Defining a list

Before we talk more about lists, it is necessary that you know the difference between atomic values and lists.

You've already seen atomic values when we initialized variables in the previous lesson. An atomic value is a single value. So, for example, we can assign the variable x the single value of 8 in the following statement:

(set! x 8)

Try typing both statements into the Script-Fu Console and notice how it replies. When you type the first statement in, it simply replies with the result:

8

However, when you type in the other statement, it replies with the following result:

(1 3 5)

When it replies with the value 8 it is informing you that x contains the atomic value 8. However, when it replies with (1 3 5), it is then informing you that x no longer contains a single value, but a list of values. Notice that there are no commas in our declaration or assignment of the list, nor in the printed result.

The syntax to define a list is:

'(a b c)

where a, b, and c are literals. We use the apostrophe (') to indicate that what follows in the parentheses is a list of literal values, rather than a function.

An empty list can be defined as such:

'()

or simply:

()

Lists can contain atomic values, as well as other lists:

(set! x '("The GIMP" (1 2 3) ("is" ("great" ()) ) ) )

Notice that after the first apostrophe, we no longer need to use an apostrophe when defining the inner lists. Go ahead and copy the statement into the Script-Fu Console and see what it returns.

You should notice that the result returned is not a list of single, atomic values - rather, it is a list of a literal ("The GIMP"), a list of the values (1 2 3), etc.

Concatenating variables to lists

To concatenate variables to a list, use the cons function:

(cons x (cons y () ) )

This concatenates the empty list to the variable y, then concatenates that list to the variable x.

Accessing values in a list

To access the values in a list, we use the functions car and cdr, which return the first element of the list, and the rest of the list, respectively.

car

car returns the first element of the list (also known as the head of the list). The list needs to be non-null. Thus, the following returns the first element of the list:

(car '("first" 2 "third"))

(which returns:)

"first"

cdr

cdr returns the rest of the list after the first element (also known as the tail of the list). If there is only one element in the list, it returns an empty list.

(cdr '("first" 2 "third"))

returns:

(2 "third")

while the following:

(cdr '("one and only"))

returns:

()

Accessing other elements in a list

OK, great, we can get the first element in a list, as well as the rest of the list, but how do we access the second, third, or other elements of a list? Well, there exist several convenience functions to access, for example, the head of the head of the tail of a list (caadr), the tail of the tail of a list (cddr), etc.

The basic naming convention is easy - the a's and d's represent the heads and tails of lists, so

(car (cdr (car x) ) )

could be written as:

(cadar x)

To view a full list of the list functions, refer to the SIOD home page (or Appendix D) which lists the available functions for the version of Scheme used by Script-Fu.

To get some practice with list accessing functions, try typing in the following and using different variations of car and cdr to access the different elements of the list:

(set! x '( (1 2 (3 4 5) 6) 7 8 (9 10) ) )

Try accessing the number 3 in the list using only two function calls. If you can do that, you're on your way to becoming a Script-Fu Master!

 

Lesson 4: Your First Script-Fu Script

Do you not need to stop and catch your breath, little grasshopper? No? Well then, let's proceed with the 4th lesson in your training - your first Script-Fu Script.

Creating a text box script

One of the most common operations I perform in Gimp is creating a box with some text in it for a web page, a logo, or whatever. However, you never quite know how big to make the initial image when you start out - you don't know how much space the text will fill with the font and font size you want.

The Script-Fu Master (and student) will quickly realize that this problem can easily be solved and automated with Script-Fu.

We will therefore create a script, called Text Box, which creates an image correctly sized to fit snug around a line of text the user inputs. We'll also let the user choose the font, font size, and text color.

Getting started

Editing and storing your scripts

Up until now, we've been working in the Script-Fu Console. Now, however, we're going to switch to editing script text files.

Where you place your scripts is a matter of preference - if you have access to Gimp's default script directory, you can place your scripts there. However, I prefer keeping my own personal scripts in my own script directory, to keep them separate from the factory-installed scripts.

I copied the script to ~/.gimp/scripts which is the my personal script directory (made by Gimp when it was installed).

Now, whenever Gimp starts up, or I refresh the Script-Fu database (Xtns->Script-Fu->Refresh), it will add my personal scripts to the procedural database.

The bare essentials

Every Script-Fu script defines at least one function, which is the script's main function. This is where you do the work.

Every script must also register with the procedural database, so you can access it within Gimp.

We'll define the main function first:

(define (script-fu-text-box inText inFont inFontSize inTextColor))

Here we've defined a new function called script-fu-text-box which takes four parameters, which will later correspond to some text, a font, the font size, and the text's color. The function is currently empty and thus does nothing. So far, so good - nothing new, nothing fancy.

Naming conventions

Scheme's naming conventions seem to prefer lowercase letters with hyphens, which I've followed in the naming of the function. However, I've departed from the convention with the parameters - I like more descriptive names for my parameters and variables, and thus add the in prefix to the parameters so I can quickly see that they're values passed into the script, rather than created within it. I use the prefix the for variables defined within the script.

It's Gimp convention to name your script functions script-fu-abc, because then when they're listed in the procedural database, they'll all show up under script-fu when you're listing the functions. This also helps distinguish them from plug-ins when listed.

Registering the function

Now, let's register the function with Gimp - when Gimp reads in a script, it will search for this function and use it to register the script with the procedural database. These lines of code come after the function definition listed above.

(script-fu-register
   "script-fu-text-box"
   "<Toolbox>/Xtns/Script-Fu/Text/Text Box"

   "Creates a simple text box, sized to fit around the user's choice of text, font, font size, and color."
   "Michael Terry"
   "copyright 1997, Michael Terry"
   "October 27, 1997"
   ""
   SF-VALUE "Text:"       "\"Text Box\""
   SF-VALUE "Font:"       "\"Charter\""
   SF-VALUE "Font size:"  "45"
   SF-COLOR "Color:"      '(0 0 0)
)

If you save these functions in a text file with a .scm suffix in your script directory, then choose Xtns->Script-Fu->Refresh, this new script will appear as Xtns->Script-Fu->Text->Text Box:

Extracted pic [3]

If you invoke this new script, it won't do anything, of course, but you can view the prompts you created when registering the script (more information about what we did is covered next).

Extracted pic [4]

Finally, if you invoke the DB Browser (the procedural database browser - Xtns->DB Browser), you'll notice that our script now appears in the database:

Extracted pic [5]

Steps for registering the script

To register our script with Gimp, we call the function script-fu-register, fill in the 7 required parameters, and add our scripts' own parameters, along with a description and default value for each parameter.

The required parameters

Registering the script's parameters

Once we have listed the required parameters, we then need to list the parameters which correspond to the parameters our script needs. When we list these params, we give hints as to what their types are - this is for the dialog box which pops up when the user selects our script. We also provide a default value.

This section of the registration process has the following format:

Param-type "Prompt text" "default value"

The different parameter types, plus examples, are listed below:

Param type Description Examples
SF-VALUE Accepts numbers and strings. Note that
quotes must be escaped for default text.
SF-VALUE "Text:" "\"Some text\""
SF-VALUE "A number:" "34"
SF-COLOR Indicates that a color is requested in this
parameter.
SF-COLOR "Color:" '(0 0 0)
SF-TOGGLE A checkbox is displayed, to get boolean
value.
SF-TOGGLE "Resize?" TRUE
SF-IMAGE If your script operates on an open image,
this should be the first parameter after the
required parameters. The GIMP will pass in
a reference to the image in this parameter.
SF-IMAGE "The image" 0
SF-DRAWABLE If your script operates on an open image,
this should be the second parameter after the
SF-IMAGE param. It refers to the active
layer. The GIMP will pass in a reference to
the active layer in this param.
SF-DRAWABLE "The layer" 0

Now, young student, this was a lot of information, so take a break.

 

Lesson 5: Giving our script some guts

You show great dedication to your studies, my student. Let us thus continue with your training and add some functionality to our script.

Creating a new image

In the previous lesson, we created an empty function and registered it with Gimp. In this lesson, we want to provide functionality to our script - we want to create a new image, add the user's text to it, and resize the image to fit the text exactly.

Once you know how to set variables, define functions, and access list members, the rest is all downhill - all you need to do is familiarize yourself with the functions available in Gimp's procedural database and call those functions directly. So fire up the DB Browser and let's get cookin'!

Let's begin by making a new image. We'll create a new variable, theImage, set to the result of calling Gimp's built-in function gimp-image-new:

Extracted pic [6]

As you can see from the DB Browser, the function gimp-image-new takes three parameters - the image's width, height, and the type of image. Since we'll later resize the image to fit the text, we'll make a 10x10 RGB image. We'll store the image's width and sizes in some variables, too, as we'll refer to and manipulate them later in the script:

(define (script-fu-text-box inText in(set! theImageWidth 10)
   (set! theImageHeight 10)
   (set! theImage (car (gimp-image-new theImageWidth theImageHeight RGB) ) )
    )Font inFontSize inTextColor)
)

You should notice that we used the value RGB to specify that the image is an RGB image. We could have also used 0, but RGB is more descriptive when we glance at the code.

You should also notice that we took the head of the result of the function call - this may seem strange, because the database explicitly tells us that it returns only one value - the ID of the newly created image. However, all Gimp functions return a list, even if there is only one element in the list, so we need to get the head of the list.

Adding a new layer to the image

Now that we have an image, we need to add a layer to it. We'll call the gimp-layer-new function to create the layer, passing in the ID of the image we just created. (From now on, instead of listing the complete function, we'll only list the lines we're adding to it.)

(set! theLayer (car (gimp-layer-new theImage theImageWidth theImageHeight RGB_IMAGE "layer 1" 100 NORMAL) ) )

Once we have the new layer, we need to add it to the image:

(gimp-image-add-layer theImage theLayer 0)

Now just for fun, let's see the fruit of our labors up until this point, and add this line to show the new, empty, image:

(gimp-display-new theImage)

Save your work, select Xtns->Script-Fu->Refresh, run the script, and a new image should pop up. It will probably contain garbage (random colors), because we haven't erased it. We'll get to that in a second.

Adding the text

Go ahead and remove the line to display the image (or comment it out with a ; as the first character of the line).

Before we add text to the image, we need to set the background and foreground colors so that the text appears in the color the user specified. We'll use the gimp-palette-set-back/foreground functions:

(gimp-palette-set-background '(255 255 255) )
(gimp-palette-set-foreground inTextColor)

With the colors properly set, let's now clean out the garbage currently in the image. We'll select everything in the image, and call clear:

(gimp-selection-all theImage)
(gimp-edit-clear theImage theLayer)
(gimp-selection-none theImage)

With the image cleared, we're ready to add some text:

(set! theText (car (gimp-text theImage theLayer 0 0 inText 0 TRUE inFontSize PIXELS "*" inFont "*" "*" "*" "*")))

While a long function call, it's fairly straight-forward if you go over the parameters while looking at the function's entry in the DB Browser. Basically, we're creating a new text layer and assigning it to the variable theText.

Now that we have the text, we can grab its width and height and resize the image and the image's layer to the text's size:

(set! theImageWidth  (car (gimp-drawable-width theText) ) )
(set! theImageHeight (car (gimp-drawable-height theText) ) )

(gimp-image-resize theImage theImageWidth theImageHeight 0 0)
(gimp-layer-resize theLayer theImageWidth theImageHeight 0 0)

If you're like me, you're probably wondering what a drawable is when compared to a layer - A drawable is the paintable area in an image, like a layer, a background, or a selection (floating or in a layer).

With the image ready to go, we can now re-add our display line:

(gimp-display-new theImage)

Save your work, refresh the database, and give your first script a run! You should get something like the following:

Extracted pic [7]

Clearing the "dirty" flag

If you try and close the image created without first saving the file, Gimp will ask you if you want to save your work before you close the image. It asks this because the image is marked as dirty, or unsaved. In the case of our script, this is a nuisance for the times when we simply give it a test run and don't add or change anything in the resulting image - that is, our work is easily reproducible in such a simple script, so it makes sense to get rid of this dirty flag.

To do this, we can clear the dirty flag after displaying the image:

(gimp-image-clean-all theImage)

This will dirty count to 0, making it appear to be a "clean" image.

Whether to add this line or not is a matter of personal taste - I use it in scripts that produce new images, where the results are trivial, as in this case. If your script is very complicated, or if it works on an existing image, you will probably not want to use this function.

Enabling and Disabling undo

If your script works on an image, you'll probably want to call gimp-image-disable-undo at the beginning of the script, and gimp-image-enable-undo at the end of the script - these functions turn undo-recording off and on, respectively. If you are writing a complex script, a user will not want to have to hit undo a million times after invoking your script, to undo all the actions your script took to do its job.

 

Lesson 6: Extending the Text Box Script

Your will and determination are unstoppable, my eager student. So let us continue your training.

The game plan

Now that we have a very handy-dandy script to create text boxes, let's add two features to it:

Modifying the parameters and the registration function

To let the user specify the amount of buffer, we'll add a parameter to our function and the registration function:

(define (script-fu-text-box inText inFont inFontSize inTextColor inBufferAmount)
...
)
(script-fu-register
   "script-fu-text-box"
   "<Toolbox>/Xtns/Script-Fu/Text/Text Box"
   "Creates a simple text box, sized to fit around the user's choice of text, font, font size, and color."
   "Michael Terry"
   "copyright 1997, Michael Terry"
   "October 27, 1997"
   ""
   SF-VALUE "Text:"       "\"Text Box\""
   SF-VALUE "Font:"       "\"Charter\""
   SF-VALUE "Font size:"  "45"
   SF-COLOR "Color:"      '(0 0 0)
   SF-VALUE "Buffer amount (0 - 100% height of text):" "35"
)

Adding the new code

We're going to add code in two places - right before we resize the image, and at the end of the script (to return the new image, the layer, and the text).

After we get the text's height and width, we need to resize these values based on the buffer amount specified by the user. We won't do any error checking to make sure it's in the range of 0-100% because it's not life-threatening, and because there's no reason why the user can't enter a value like "200" as the percent of buffer to add.

(set! theBuffer (* theImageHeight (/ inBufferAmount 100) ) )
(set! theImageHeight (+ theImageHeight theBuffer theBuffer) )
(set! theImageWidth  (+ theImageWidth theBuffer theBuffer) )

All we're doing here is setting the buffer based on the height of the text, and adding it twice to both the height and width of our new image. (We add it twice to both dimensions because the buffer needs to be added to both sides of the text.)

Now that we have resized the image to allow for a buffer, we need to center the text within the image. This is done by moving it to the (x, y) coordinates of (theBuffer, theBuffer). I added this line after resizing the layer and the image:

(gimp-layer-set-offsets theText theBuffer theBuffer)

Go ahead and save your script, and try it out after refreshing the database. You should now get a window like the following:

Extracted pic [8]

All that is left to do is to return our image, the layer, and the text layer. After displaying the image, we add this line:

(cons theImage (cons theLayer (cons theText () ) ) )

We use the function cons to create a list of values. This is the last line of the function, making this list available to other scripts that want to use it.

To use our new text box script in another script, we could write something like the following:

(set! theResult (script-fu-text-box "Some text" "Charter" "30" '(0 0 0) "35") )

(gimp-image-flatten (car theResult) )

Congratulations, my student, you are on your way to your Black Belt of Script-Fu!

 


The Gimp User Manual
Last modified: 20 May 1998

Previous Next TOC Index Feedback