πŸ“œ ⬆️ ⬇️

GIMP Script-fu: Quickly learning and writing simple scripts on Scheme (+ batch processing for free)

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:
  1. What do we need?
  2. Syntax briefly
  3. Variables
  4. Functions
  5. Lists
  6. Register the script in GIMP
  7. Code writing
  8. 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

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.Example: β€œ (+ 1 2) ” is the correct code, β€œ (+1 2) ” is not one.

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: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.

Source: https://habr.com/ru/post/111387/


All Articles