GIMP Script-Fu First Dan. Extensions to Script-fu
If we compare an extensible application to a box, then plugins are useful things that can be filled with this box, giving new content to our application. In this row there is also such a thing as Script-fu. But what if I say that Script-fu can also be the same “box” and can have extensions, let’s figure it out. But I’ll tell you right away, not everyone will be happy.
Plugins for tinyscheme.
The original interpreter on the basis of which Script-fu was created allows you to load plugins specially written for it. Not many plugins have been written for tinischem. Yes, there is not much there, once or twice I miscalculated. Therefore, I will look at the plugin supplied with Gimp, tsx, it stands for tinischem extension, i.e. extension.
Loading the plugin into Script-fu.
Let's assume you have placed the tinyscheme plugin in your home directory “~/work/scheme/tiny/tsx/”. Let's try to issue the command in the console:
(getenv "HOME") ;;...../work/scheme/tiny
(define p-ext (string-append (getenv "HOME") "/work/scheme/tiny/"))
(define p-ext-tsx (string-append p-ext "tsx/tsx"))
(load-extension p-ext-tsx) ;;Error: eval: unbound variable: load-extension
To my GREAT regret, dear friends, in standard GIMP assemblies, from the tinischeme on the basis of which script-fu is made, deleted module dynload.c. It simply is not even in the archives of source code assemblies of GIMP distributions. Namely, he is responsible for loading extensions in tinischeme. I don’t know why this was done, but I know how this module works add to your GIMP.
But I’ll tell you how I did this process on Linux, in principle it can be done on Windows, but this is not my native system, and I don’t have any working tools there (MinGW has a cropped version, I can compile the usual tinyscheme and plugins for it , but hardly a whole GIMP).
Adding the ability to load plugins into Script-fu under Linux
So, we need to rebuild Script-fu so that it supports loading extensions. The first thing you need is a source archive of your installed GIMP build. It can be downloaded for example here: https://download.gimp.org/gimp/v2.10/ . Download it to us and unpack it. Let's launch ./configure
. This procedure will generate a Makefile in each subdirectory of the project, at the same time checking whether everything is there to build the GIMP project. We don’t need to assemble the ENTIRE gimp (but it’s advisable, otherwise assembling script-fu will turn into “an exciting activity”!!
Next we go to the directory: “gimp-2.10.30/plug-ins/script-fu” look for the line “-DUSE_INTERFACE=1” in the makefile and add two lines under it:
```
-DSUN_DL=1 \
-DUSE_DL=1 \
```
so there would be a line below:
```
-DUSE_STRLWR=0
```
In general, these are the compilation parameters of the script-fu plugin (SUN_DL is not required in this directory).
Go to the tinyscheme directory, look for the same line in the makefile and add again:
```
-DSUN_DL=1 \
-DUSE_DL=1 \
```
For Windows systems, you need to write _WIN32 instead of SUN_DL.
Next we go to GitHub https://github.com/GNOME/gimp/tree/master/plug-ins/script-fu/libscriptfu/tinyscheme – a mirror of GIMP sources, and download two files dynload.c and dynload.h from there and place them in the tinyscheme directory. One more edit needs to be done in the makefile in the same directory. We are looking for the line:
```
m_libtinyscheme_a_OBJECTS = scheme.$(OBJEXT)
```
и добавляем компиляцию dynload
```
m_libtinyscheme_a_OBJECTS = scheme.$(OBJEXT) dynload.$(OBJEXT)`
```
Ops. and I almost forgot, I need to add a link for the dynamic library loader dl to the makefile that generates the script-fu
LDADD = \
-ldl \
$(libgimpui) \
....
All! Next, go to the directory above and build the script-fu plugin using the make command.
Unfortunately, building this small plugin requires a lot of generated and compiled files from the gimp distribution, such as libgimpui-2.0.la, libgimpmodule-2.0.la, etc. They can be generated manually by jumping from directory to directory and giving the make command with the desired file, but I advise you to first compile the entire gimp with the make command from the top directory of the distribution, it’s long but easier, and after that move to the script-fu directory and there give the make command.
The compiled plugin itself is located in the directory ./.libs
. Let’s check, just in case, whether the assembled plugin contains the line: load-extension
if not, then something went wrong with you.
We copy this plugin to the place where the original plugin was located, for me this is a directory /usr/lib/gimp/2.0/plug-ins/script-fu
having previously saved the original version of the plugin, ALWAYS in another directory (gimp can also load the old renamed version of the plugin)!
Now we can load gimp and the script-fu plugin, if it loads, congratulations, everything is fine.
You can even give the command in the console scrip-fu:
load-extension
;;#
We did it!
tinischem extension libraries.
Actually, the libraries for expanding the tinischeme, hmm .. once or twice I miscalculated what I already said, this is what I found:
https://github.com/mherasg/tsx – this is the extension circuit.
https://github.com/ignorabimus/tinyscheme-ffi – I haven’t tried it, but according to the description, this library allows you to load any dynamic library from tinischeme, and not just its native extension, and work with the functions of this library as with ordinary tinischeme functions.
https://sourceforge.net/projects/tinyscheme/files/ – in addition to the tinischema itself, here is a library for working with regular expressions from the tinischeme.
http://www.geonius.com/software/tsion/ – working with the network, directories, etc.
I don’t think anyone would want to build a network web server in the Gimp console, but really, who the hell isn’t joking? ))) But these libraries can be used to study the principles of constructing tinischeme extensions.
So, if you have an already compiled version of the tinischem extensions, and you try to load it from the script-fu console using the load-extension command, your attempt will inevitably end in script-fu crash. Why is this happening? The fact is that the developers of scrip-fu significantly changed the tinischeme used with gimp, and all extensions must be compiled taking into account the new header files supplied with the gimp sources.
We compile the tinischeme extension:
Let's take the tsx project and unpack it.
Editing the makefile
#SCHEME_H_DIR=..
SCHEME_H_DIR=<ВАШ ПУТЬ ДО ИСХОДНИКОВ ГИМП>/gimp-2.10.30/plug-ins/script-fu/tinyscheme
GLIB_H=pkg-config --cflags glib-2.0
GLIB_L=pkg-config --libs glib-2.0
CC=gcc
CFLAGS=-DUSE_DL=1 -I $(SCHEME_H_DIR) $(GLIB_H)
give the build command:
make
If the build completes successfully, we will receive the file: tsx.so
which can be loaded as a tinischem extension:
(define p-ext (string-append (getenv "HOME") "/work/scheme/tiny/gimp/"))
(define p-ext-tsx (string-append p-ext "tsx/tsx"))
(load-extension p-ext-tsx)
Now we have an interesting team available to work with:
(system "pwd")
(system (string-append "ls " (getenv "HOME") "/work/gimp"))
But you can see the results of its work if you launched Gimp from some console, and they will be in it! The command will not return any data except the command return code, well, these are questions for the developer of this extension, why it works this way. But the fact that now we can execute system commands from the tinischeme is already good.
We write our own extension of the tinischeme.
To write a tinischeme extension, we need a little knowledge of C. As examples, I chose several functions of bit operations that were missing in the original tinischeme, an operation for obtaining random numbers, and an alternative to the system function from tsx, which allows you to receive console output of a command.
Place the extension in a separate directory and load it using the commands:
(define p-ext (string-append (getenv "HOME") "/work/scheme/tiny/gimp/"))
(define p-ext-my (string-append p-ext "my-ext/myext"))
(load-extension p-ext-my)
Let's start with the simplest function, getting a random number:
pointer foreign_rand(scheme * sc, pointer args)
{
pointer ret;
unsigned long rez;
rez = rand();
ret = sc->vptr->mk_integer(sc,rez);
return ret;
}
We don't need to pass anything to the function, but simply get a random number using the function rand
and generate a value in the circuit based on this number, this can be done using a function call sc->vptr->mk_integer(sc,rez)
.
In order for our function to be available in tinischeme, we need to write an initialization function that the dynload module executes when loading the extension's dynamic library. It performs schema definition functions that associate the function code with the function name in the schema. We also initialize the random number generator there: srand(time(NULL));
We will add definitions for the circuit and other extension functions to this same function.
void init_myext (scheme * sc)
{
printf("in run init_myext"); //просто печать для отладки
......
srand(time(NULL));
sc->vptr->scheme_define(sc,sc->global_env,
sc->vptr->mk_symbol(sc,"rand"),
sc->vptr->mk_foreign_func(sc, foreign_rand));
.....
printf("exit from init_myext");
}
Now let's look at transferring values from the circuit to the C function, using the example of writing a bit negation operation. To perform this operation, we need to pass one integer value to the C function. But after experimenting with this function, I decided that it was better to pass another value, a bitmask of our interest, meaning how many (and in fact which) significant bits we want to get from the function.
pointer foreign_bitwise_not(scheme * sc, pointer args)
{
pointer first_arg;
unsigned long var;
pointer ret;
pointer interest_arg;
unsigned long interest;
unsigned long rez;
if(args == sc->NIL)
{
return sc->F;
}
first_arg = sc->vptr->pair_car(args);//получаем первое значение
if(!sc->vptr->is_integer(first_arg))
{
return sc->F;
}
var = sc->vptr->ivalue(first_arg); //получаем первое значение
args = sc->vptr->pair_cdr(args); //получаем второе значение
interest_arg = sc->vptr->pair_car(args);
if(!sc->vptr->is_number(interest_arg)) {
return sc->F;
}
interest = sc->vptr->ivalue(interest_arg); //получаем второе значение
rez = (~var) & interest; //выполняем операцию
ret = sc->vptr->mk_integer(sc,rez); //возвращаем значение
return ret;
}
The arguments passed through args are a list from the tinischeme, we just need to parse it and convert the schema values into C values.
Next, we also need to register our function and make it available to the circuit in the function init_myext
sc->vptr->scheme_define(sc,sc->global_env,
sc->vptr->mk_symbol(sc,"bitwise-not"),
sc->vptr->mk_foreign_func(sc, foreign_bitwise_not));
Let's try an example:
(bitwise-not #x0 #xFF)
;;255
(bitwise-not #x01 #xFF)
;;254
(bitwise-not #x01 #xFFFF)
;;65534
(number->string 5 2)
;;"101"
(number->string (bitwise-not 5 #xF) 2)
;;"1010"
We examined the unary bitwise negation function. And the following functions will be binary (having two operands) and one more additional argument – a mask of the bits of interest to us. I will give a complete example of the bitwise or operation.
pointer foreign_bitwise_or(scheme * sc, pointer args)
{
pointer first_arg; pointer second_arg; unsigned long var1, var2;
pointer ret;
pointer interest_arg; unsigned long interest;
unsigned long rez;
if(args == sc->NIL)
{
return sc->F;
}
first_arg = sc->vptr->pair_car(args); //первый аргумент
if(!sc->vptr->is_number(first_arg))
{
return sc->F;
}
var1 = sc->vptr->ivalue(first_arg); //первый аргумент
args = sc->vptr->pair_cdr(args); //второй аргумент
second_arg = sc->vptr->pair_car(args);
if(!sc->vptr->is_number(second_arg)) {
return sc->F;
}
var2 = sc->vptr->ivalue(second_arg); //второй аргумент
args = sc->vptr->pair_cdr(args); //третий аргумент
interest_arg = sc->vptr->pair_car(args);
if(!sc->vptr->is_number(interest_arg)) {
return sc->F;
}
interest = sc->vptr->ivalue(interest_arg);//третий аргумент
rez = (var1 | var2) & interest; //операция побитового или и наложение маски
ret = sc->vptr->mk_integer(sc,rez); //возврат значения
return ret;
}
The bitwise and operation differs from or only in the operation performed on the arguments.
rez = (var1 & var2) & interest;
The bitwise XOR operation is similar:
rez = (var1 ^ var2) & interest;
You also need to include these functions in the extension initialization operation:
sc->vptr->scheme_define(sc,sc->global_env,
sc->vptr->mk_symbol(sc,"bitwise-or"),
sc->vptr->mk_foreign_func(sc, foreign_bitwise_or));
sc->vptr->scheme_define(sc,sc->global_env,
sc->vptr->mk_symbol(sc,"bitwise-and"),
sc->vptr->mk_foreign_func(sc, foreign_bitwise_and));
sc->vptr->scheme_define(sc,sc->global_env,
sc->vptr->mk_symbol(sc,"bitwise-xor"),
sc->vptr->mk_foreign_func(sc, foreign_bitwise_xor));
Let's check the operations:
(number->string (bitwise-or #b010101
#b000111
#b111111) 2)
;;"10111"
(number->string (bitwise-and #b010101
#b000111
#b111111) 2)
;;"101"
(number->string (bitwise-xor #b010101
#b000111
#b111111) 2)
;;"10010"
Well, now all that remains is to write an analogue of the function system
from tsx
pointer foreign_cmd_run(scheme * sc, pointer args)
{
pointer first_arg;
char* command;
pointer ret;
FILE* fp;
char arr[LINESIZE];
if(args == sc->NIL)
return sc->F;
first_arg = sc->vptr->pair_car(args); //принимаем первый аргумент
if(!sc->vptr->is_string(first_arg))
return sc->F;
command = sc->vptr->string_value(first_arg);
if(0 == command)
return sc->F; //принимаем первый аргумент
fp = popen(command,"r"); //запускаем на исполнение комманду, создавая канал.
//чтение простое, не обрабатывает слишком длинные строки, надо бы проверять сколько прочитали
//и если строка слишком длинная складывать её с остатком.
ret = sc->NIL;
while (fgets(arr, LINESIZE, fp) != NULL) {
printf("read: %s", arr);
ret = sc->vptr->cons(sc, sc->vptr->mk_string(sc, arr), ret);
}
pclose(fp);
// возвращаем перевёрнутый список вывода, вертеть обратно надо уже в схеме
return ret;
}
And this function also needs to be registered with the schema in the extension initialization function.
sc->vptr->scheme_define(sc,sc->global_env,
sc->vptr->mk_symbol(sc,"cmd-run"),
sc->vptr->mk_foreign_func(sc, foreign_cmd_run));
Let's try:
(cmd-run "pwd") ;;("/usr/bin\n")#
(cmd-run (string-append "ls " (getenv "HOME")))
;;("work\n" "video\n" "tmp\n" "quicklisp\n" "inst\n"
;; "game\n" "doc\n" "distr\n" "bin\n" "basedraw01.png\n" "asdf\n"
;; "Videos\n" "Templates\n" "Public\n" "Pictures\n" "Photo\n" "Music\n"
;; "Downloads\n" "Documents\n" "Desktop\n")
(reverse (cmd-run (string-append "/sbin/ifconfig" " -a")))
;;("eth1: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500\n"
;;" inet ...
;;...
;;" TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0\n"
;;"\n")
If we place the macros that we wrote earlier in the file util.scm
.
I will demonstrate a session of working with the script fu:
;;определяем пути и загружаем библиотеку и расширение my-ext
(define path-home (getenv "HOME"))
(define path-lib (string-append path-home "/work/gimp/lib/"))
(define path-work (string-append path-home "/work/gimp/"))
(load (string-append path-lib "util.scm"))
(define p-ext (string-append path-home "/work/scheme/tiny/gimp/"))
(define p-ext-my (string-append p-ext "my-ext/myext"))
(load-extension p-ext-my)
;;определяем функию получения случайного числа на основе функции из my-ext
(define (random i)
(modulo (rand) i))
;;определяем функцию получения списка случайных чисел заданной длины и огр. m
(define (rand-lst m len)
(let ((rez '()))
(for (i 1 len)
(set! rez (cons (random m)
rez)))
rez))
(rand-lst 21 30)
;;(15 10 18 15 13 0 5 3 13 8 14 2 12 3 7 3 8 1 19 10 12 13 17 18 3 9 16 17 18 13)
(rand-lst 21 30)
;;(7 17 15 18 1 0 4 1 16 3 11 9 10 11 18 9 4 17 3 2 0 13 11 8 19 16 6 12 2 10)
So, easily and “at ease,” we added several functions to Script-fu, or rather, we wrote an extension, when loaded, several functions are added to Script-fu.
Thus, tinischem's support for loading extensions is a powerful means of extending the functionality of script-fu.