script-fu: exporting the same region for different layers

Intro

Gimp offers the ability to run scripts and by that create, manipulate and save images automatically. The language used normally is script-fu, though one could also use perl or (more commonly) python.

In my current project, I have to take pictures of different samples which are partly manipulated (using surface processing). In those pictures I'm mostly interested in three regions:

  • the reflection of light on an untreated surface,
  • the reflection of light on a treated surface,
  • the histogram of the image.

A sample image that i took looks like this:

Sample image for script-fu

The regions I'm interested in are as follows:

rund_1_16_markiert.jpg

I took about 30 pictures (with more to come) and i needed an automated method to copy those regions and save them as new images with an appropriate filename.

Preparations

First thing i did was include all the files into one big layered xcf-image (this part could be automated too..). In a next step I created three channels in which the three regions were represented. This way I didn't have to remember specific coordinates, but only the names of the channels in question: "unbehandelt" (untreated), "behandelt" (treated) and "Histogramm" (histogram).

Script-Fu

The easiest way to try out a script in Gimp is to open the "Script-Fu-Console" (Xtns->Script-Fu->Script-Fu-Console) There one can type in any Command and see the result live. Later on, we can create a complete function out of the commands.. Also from to time we will need the "procedure browser" which lists all the available functions and explains them. This is a very good starting point, when trying to find out how to solve a specific problem.

Script-Fu-commands are enclosed in brackets and are separated by spaces from their parameters. A normal command looks like this:

CODE:
  1. (command param1 param2)

Any parameter can be substituted by a function itself:

CODE:
  1. (command1 (command2 param3 param4) param2)

Sometimes it gets confusing with all those brackets, so be careful.

Finding an image

The first main task is to 'find' the appropiate image - the one with the many layers.. I must say, I had my problems doing that and the only method I found outside a function is to go through the image list and decide manually which image is the one we need. This is very easy when we have only one image opened. To get all the image handlers we can type this command:

CODE:
  1. (gimp-image-list)

The function doesn't accept parameters and returns a list containing the number of images and their respective handlers. The output could look something like this:

(2 #( 2 1 ))

For starters I will decide manually which one is the right image. Later on, I can find the right image by letting Gimp pass it's handler as an argument to the function.

Defining some variables

There is more than one way to define variables in Script-Fu. The best way is to define them locally, which means they will only be available in a specific context:

CODE:
  1. (let*
  2.     (
  3.     (var1 val1)
  4.     (var2 val2)
  5.     (var3 val3)
  6.     )
  7. ..commands..
  8. )

In this example the variables var1, var2 and var3 will only be available inside the "let*"-brackets. For the purpose of testing a script, this isn't very practical, since I need to know all the commands beforehand. To define variables without contextual limitations I can use this command:

CODE:
  1. (define var1 val1)

Note, that the variable var1 is now available to any script-fu-command running afterwards, so this could mean that it could have a negative effect on a totally different function. Use with caution.

For my purposes i will first define a variable representing the img, one representing the Channelname and one containing the outputDirectory:

CODE:
  1. (define myimg 1)
  2. (define ChannelName "bearbeitet")
  3. (define outDir "C:\\")

The 1 stands for the image handler.

Lists of layers and channels

The next vital thing that I will need are the lists of available layers and channels in the selected image:

CODE:
  1. (gimp-image-get-layers  myimg)
  2. (gimp-image-get-channels myimg)

Each one of those commands will output a list starting with the number of elements and then the element handlers. A typical output would like this:

(32 #( 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 ))

Here, there are 32 layers with numbers from 9 to 40. To save those handlers i use these commands:

CODE:
  1. (layers (gimp-image-get-layers  myimg))
  2. (channels (gimp-image-get-channels myimg))

Loops and Hoops

Now that I've got all the information I need, I will have to find the Channel I'm searching for first, then loop through the layers. So the first loop I will have to create is the loop through the list of channels. We know, that the first element of 'channels' represents the number of elements inside the rest of it:

CODE:
  1. (define i 0)
  2. (while
  3.     (<i (car channels))
  4.     (if (equal? (car (gimp-drawable-get-name (aref (cadr channels) i))) inChannelName)
  5.         (write "found it")
  6.     )
  7.     (set! i (+ i 1))
  8. )

First I defined the loop-variable 'i' and set it to 0. Next, the syntax of the while-loop (the only loop we can use in script-fu) is as follows:

CODE:
  1. (while
  2.     (condition)
  3.     (command)
  4.     (command)
  5. )

Next we need the if-statement:

CODE:
  1. (if
  2.     (condition)
  3.     (then)
  4.     (else)
  5. )

Lists

Lists are very important in script-fu, since every return value is mainly a list, even if it contains only one value. There are two main functions to read a list: 'car' returns the first element of a list, 'cdr' returns the rest of the list. To define a list, we can use the 'define'-function:

CODE:
  1. > (define xlist '(1 2 3))
  2. > (car xlist)
  3. 1
  4. > (cdr xlist)
  5. (2 3)

So, to read the second element in the list

CODE:
  1. (car (cdr xlist))

or a simple

CODE:
  1. (cadr xlist)

can be used. Some functions return arrays rather than lists. They can be distinguished through the use of '#':

CODE:
  1. > (define xlist #(1 2 3))
  2. > (aref xlist 1)
  3. 2

Channels, Layers and Copy and Paste-as-New

The last part of the script involves copying the part of the image and pasting it as a new image. When we've found the appropriate channel, we have to create a selection using it:

CODE:
  1. (while
  2.     (<i (car channels))
  3.     (if (equal? (car (gimp-drawable-get-name (aref (cadr channels) i))) inChannelName)
  4.         (gimp-selection-load (aref (cadr channels) i))
  5.     )
  6.     (set! i (+ i 1))
  7. )

Next, we'd have to go through all the layers one-by-one and copy the content of the selection, paste-as-new and save the file:

CODE:
  1. (define i 0)
  2. (while
  3.     (<i (car layers))
  4.     (gimp-image-set-active-layer myimg (aref (cadr layers) i))
  5.     (gimp-edit-copy (aref (cadr layers) i))
  6.     (let*
  7.         (
  8.         (newimg (car (gimp-edit-paste-as-new)))
  9.         (theLayer (car (gimp-image-flatten newimg)))
  10.         (outFile (string-append outDir "\\" (car (gimp-drawable-get-name (aref (cadr layers) i))) "_" inChannelName ".bmp"))
  11.         )
  12.         (file-bmp-save RUN-NONINTERACTIVE newimg theLayer outFile outFile)
  13.         (gimp-image-delete newimg)
  14.     )
  15.     (set! i (+ i 1))
  16. )

Et voilà! The images are saved. What's missing is the wrapper to make this script a function that can be executed using a menu item:

Complete Solution

CODE:
  1. (define (script-fu-oan-copy-masked-layers inImg inChannelName outDir)
  2. (let*
  3.     (
  4.     (myimg (aref (cadr (gimp-image-list)) 0))
  5.     (myimg inImg)
  6.     (mylayer (gimp-image-active-drawable myimg))
  7.     (layers (gimp-image-get-layers  myimg))
  8.     (channels (gimp-image-get-channels myimg))
  9.     )
  10. (define i 0)
  11. (while
  12.     (<i (car channels))
  13.     (if (equal? (car (gimp-drawable-get-name (aref (cadr channels) i))) inChannelName)
  14.         (gimp-selection-load (aref (cadr channels) i))
  15. ;      (write (car (gimp-drawable-get-name (aref (cadr channels) i))))
  16.     )
  17.     (set! i (+ i 1))
  18. )
  19. (define i 0)
  20. (while
  21.     (<i (car layers))
  22.     (gimp-image-set-active-layer myimg (aref (cadr layers) i))
  23.     (gimp-edit-copy (aref (cadr layers) i))
  24.     (let*
  25.         (
  26.         (newimg (car (gimp-edit-paste-as-new)))
  27.         (theLayer (car (gimp-image-flatten newimg)))
  28.         (outFile (string-append outDir "\\" (car (gimp-drawable-get-name (aref (cadr layers) i))) "_" inChannelName ".bmp"))
  29.         )
  30.         (file-bmp-save RUN-NONINTERACTIVE newimg theLayer outFile outFile)
  31.         (gimp-image-delete newimg)
  32.     )
  33.     (set! i (+ i 1))
  34. )
  35. )
  36. )

Conclusion and Literature

It's possible to automate a great number of things using Script-Fu. At this time, it doesn't seem to be very well documented but I hope I've made it clear how to go about thinking Fu-ish. Here are some readings for the weekends:

Verwandte Beiträge

Comments

Noch keine Kommentare.

RSS-Feed für Kommentare zu diesem Beitrag. TrackBack URI

Einen Kommentar schreiben