Making the Most of Script-Fu Server

For all the great things that can be said about the GIMP, one thing that you can't say is that it's a model Unix application. You can't send anything into its standard input, nor get anything from its standard output. The GIMP is a universe unto itself, and this flies in the face of the Unix philosophy, which shuns interactive interfaces and encourages developers to make every program a filter.

For all the great things that can be said about the GIMP, one thing that you can’t say is that it’s a model Unix application. You can’t send anything into its standard input, nor get anything from its standard output. The GIMP is a universe unto itself, and this flies in the face of the Unix philosophy, which shuns interactive interfaces and encourages developers to make every program a filter.

There’s nothing intrinsically wrong with being interactive; shells are interactive, and so is vi, but interactive programs have a tendency to not play well with others. Unfortunately, the GIMP is one such program, but there is hope.

The Script-Fu Server

The Script-Fu Server is like a back door into the GIMP. By connecting to a TCP socket (usually on port 10008), a program from outside the GIMP can communicate with it by sending arbitrary Scheme code. (For more information about Scheme, see the January 2002 Power Tools column.) However, letting untrusted code run wild on your system is a recipe for disaster, so the developers of the GIMP made things more secure by “crippling” the interpreter. This reduced functionality makes things difficult.

The protocol for communicating with the Script-Fu Server is not documented anywhere, but examination of the GIMP source file plug-ins/script-fu/ script-fu-server.c shows that the protocol is very simple. Table One describes the format of a Script-Fu request.




Table One: A Request Header







PositionValue
0×00 “G”
0×01 length_of_script_fu (high byte)
0×02 length_of_script_fu (low byte)

Note: The rest of the request after the header should be Scheme code. The length bytes do not include the three bytes in the header.

After the header, you simply send raw Scheme over the socket. However, you can only send one top-level s-expression per request. That means this won’t work:


(set! x 9)
(set! y 7)
(number->string (+ x y))

There are three s-expressions, so only the first one will be processed; the rest will be ignored. To make this just one s-expression, wrap the whole thing in a begin:


(begin
(set! x 9)
(set! y 7)
(number->string (+ x y)))

After writing a request to the socket, you can read from the same socket and get back a response in the format described in Table Two.




Table Two: The Response Header








PositionValue
0×00 “G”
0×01 error_code
0×02 length_of_response (high byte)
0×03 length_of_response (low byte)

Note: The rest of the response is a string. The length of the response does not include the four bytes of the response header.

If the s-expression you send evaluates to a string, the error code will be zero and the string will be returned in the response. In our example, the string 16 would be returned, because 9+7= 16. If the return value is not a string, it just returns the string “Success.”

If there is a failure, the error code will be set to a non-zero value. Often times, there will also be an error message that comes back in the response — but good luck trying to make sense out of it.

Scheme Meets Perl

The Script-Fu Server can be exciting at first, but the feeling wears off as you realize how limited it is. The Scheme interpreter is literally incapable of talking to anything outside of the GIMP, and this makes it very hard to accomplish tasks that should be simple. Within the interpreter, there are no command-line arguments, you can’t see environment variables, nor can you even list the contents of a directory (to make a list of image files, for instance). On the other hand, Perl knows how to do all these things and more.

We can leverage the GIMP with a script called gimp-request (see Listing Two, which is available online at http://www.linux-mag.com/downloads/2002-02/). This Perl script bridges the gap between Unix and the GIMP. It takes Scheme code from a file or STDIN, sends it to the Script-Fu server, and prints out the response on STDOUT. It also does a lot more.




Listing Two: gimp-request


#!/usr/bin/perl -w

use strict;
use IO::Socket;
use Getopt::Long;
use Text::Template;

sub sexp_from_list {
“(” . join(” “, map { qq(“$_”) } @_) . “)”;
}

sub set_argv {
“(set! argv ‘” . sexp_from_list(@ARGV) . “)”;
}

# defaults
my $verbose = 0;
my $peer_host = “localhost”;
my $peer_port = 10008;

GetOptions (
“server|s=s” => \$peer_host,
“port|p=i” => \$peer_port,
);

# connect to the gimp
my $gimp = IO::Socket::INET->new (
Proto => “tcp”,
PeerHost => $peer_host,
PeerPort => $peer_port,
);

# preprocess scheme code using perl
my $template;
if (@ARGV) {
$template = Text::Template->new (
TYPE => “FILE”,
SOURCE => shift
);
} else {
$template = Text::Template->new (
TYPE => “FILEHANDLE”,
SOURCE => \*STDIN
);
}
my $script_fu = $template->fill_in();
$script_fu =~ s/^#.*$//m;

# request
my $length = length($script_fu) & 0xffff;
my $lo_byte = ($length & 0x00ff);
my $hi_byte = ($length & 0xff00) >> 8;
my $header = “G “;
vec($header, 1, 8) = $hi_byte;
vec($header, 2, 8) = $lo_byte;
syswrite($gimp, $_) for ($header, $script_fu);

# response
sysread($gimp, $header, 4);
my @byte = map { ord } split(”, $header);
$length = ($byte[2] << 8) | $byte[3];
read($gimp, my $response, $length);
print
“error | $byte[1]\n”,
“length | $length\n”
if ($verbose);
print $response, “\n”;

exit $byte[1];

__END__

=head1 NAME

gimp-request – send a request to GIMP’s Script-Fu server

=head1 SYNOPSIS

Syntax:

$ gimp-request \
[--server=HOST][--port=PORT] \
[SCHEME_FILE] [ARGS]…

Bang Notation:

#!/usr/bin/env gimp-request

(define beppu ‘(“just” “another” “script-fu” “hacker”))

=head1 DESCRIPTION

This is a script for sending a Scheme
request to a Script-Fu server. It can
be given a Scheme file as a command-line
argument, or Scheme code can be given to
it on STDIN.

=head1 AUTHOR

John BEPPU – beppu@cpan.org

=cut

Not only does gimp-request let us send Scheme code to the Script-Fu server, but we can also write a Scheme “script” that calls other parts of gimp-request to dynamically create Scheme code, which then gets passed to the Script-Fu server. This effectively turns Perl into a Scheme preprocessor.

Let’s look at a common example. A question often asked on the gimp-user mailing list (http://www.gimp.org/ mailing_list.html) is how to take a group of images and make smaller thumbnail images of them. The usual response is to use ImageMagick’s convert program instead of the GIMP. This is because it’s so difficult to get a list of files from inside the Scheme interpreter. Otherwise, someone would have surely whipped up a Script-Fu solution. It’s a silly thing to get stuck on, but that’s the way it’s been.

Listing One shows a Scheme script called make-thumbnail.scm that has some Perl embedded inside it.




Listing One: make-thumbnail.scm


#!/usr/bin/env gimp-request

(begin

; This is a helper function that puts
; the contents of @ARGV into the
; Scheme variable, argv.
{set_argv}

; This uses perl’s regexes to create
; filenames for the thumbnails. Doing
; this in Scheme would be harder.
(set!
outfiles ‘{
sexp_from_list(
map { s/\..*$/-thumbnail.png/; $_ } @ARGV)})

; configure your scaling factor
(set! scale 0.33)

; This is a function for resizing an image
(define (resize filename)
(let*
((image (car (gimp-file-load 0 filename filename)))
(drawable nil)
(wd (car (gimp-image-width image)))
(hi (car (gimp-image-height image)))
(_wd (* wd scale))
(_hi (* hi scale))
(new-filename nil))
(gimp-image-scale image _wd _hi)
(set! drawable (car (gimp-image-flatten image)))
(set! new-filename (car outfiles))
(set! outfiles (cdr outfiles))
(gimp-file-save 1 image drawable new-filename new-filename)
(gimp-image-delete image)
))

; Finally, make a thumbnail out of every file in argv
(for-each resize argv))

Before running this script, there are a few things that must be done:








Power Tools
Figure One: You can start the Script-Fu server through the GIMP’s menu. Logging is a useful function when debugging.


  • Install the Text::Template Perl module. If your CPAN shell is properly set up, it’s a simple matter of executing the following command:

perl -MCPAN -e ‘install Text::Template’


  • Put the gimp-request script somewhere in your PATH so that /usr/ bin/env can see it. (Warning: There is a bug in many versions of env that could bite you if you have something like ~/bin in your PATH, because env doesn’t do tilde expansion. The solution is to do the expansion yourself: /home/beppu/bin in your PATH.)

  • Be sure that make-thumbnail.scm is an executable file. This can be done by doing chmod +x make-thumbnail.scm.

  • Start the Script-Fu server. You can do this by going to (Xtns | Script-Fu | Server…) in the GIMP’s menu. See Figure One . (Notice that the server can have a log file. It’s very useful to have a tail-f running on this file while you are debugging.)

Once the prerequisites have been satisfied, you can try running this script on a batch of images by doing the following:

./make-thumbnail.scm> /directory/full/of/images/*

where /directory/full/of/images/* is replaced with something appropriate for your system. The resultant thumbnails will be stored in files with the string “-thumbnail.png” suffix appended to each image file name.

The script cannot call gimp-request directly (because gimp-request is itself a script), so it calls env to invoke gimp-request. The contents of the make-thumbnail.scm script can now be read by gimp-request from STDIN. It consists of Scheme code, except for the sections between curly braces. Everything between the curly braces is treated as a tiny Perl script and is evaluated via the Text::Template module.

The resulting string (which will be Scheme code) is put in the place of the curly-braced section. There are two such calls in make-thumbnail.scm — one to the set_ argv subroutine and the other to the sexp_from_ list subroutine.

The remaining Scheme code creates thumbnails from the list of files passed as arguments to make-thumbnail. scm (which the Perl code has stored in the variable argv) and stores them in the file name that is contained in the variable outfile.

The gimp-request script itself concerns itself mostly with communicating with the Script-Fu server. The subroutines set_argv and sexp_from_list set the Scheme variable argv to the list of command-line arguments and create a string literal for use in an s-expression.

Making Expectations Meet Reality

This was just a simple example of the new dimension that gimp-request has introduced to Script-Fu. When people hear that the GIMP is scriptable, most assume that this means you can use the GIMP to do large batch operations. Before gimp-request, this was not true. Now it is.

Thanks to Perl, Script-Fu code can have access to information that was previously unavailable to it. Command-line arguments and environment variables are just the beginning. If you want, you can do a lot more preprocessing and bring in all kinds of information from whatever external sources Perl has access to. Perl is used as a preprocessor, and the only thing that gets sent to the Script-Fu Server is pure Scheme code.

Now the GIMP can truly be considered “civic-minded” and is a good citizen of the Unix universe.




Resources

The Unix Philosophy by Mike Gancarz

Digital Press. ISBN: 1-55558-123-4

Text::Template

SIOD: Scheme In One Defun



John Beppu is addicted to Perl. He can be reached at beppu @cpan.org.

Fatal error: Call to undefined function aa_author_bios() in /opt/apache/dms/b2b/linux-mag.com/site/www/htdocs/wp-content/themes/linuxmag/single.php on line 62