GIMP Script-fu

Artifact [85d281c611]
Login

Artifact [85d281c611]

Artifact 85d281c611c110f19ca6889fc8b888447217943d:


This document is intended for developers who might wish to make changes to Schemey-fu, or those who just wish to understand how it works. It is not necessary to understand the contents of this document to use Schemey-fu.

WHAT IS SCHEMEY-FU?

Schemey-fu is wrapper for the GIMP Procedural DataBase (PDB) that is intended to allow coding of Script-fu scripts in a more Scheme-like manner. GIMP's PDB is intended to interface with a wide range of programming languages (C, Scheme, Python, Perl, Ruby, to name a few) and thus the arguments passed to PDB procedures, and the values returned by the procedures, are specified using low level data objects such as simple numbers, strings, and uncounted arrays. All plug-in languages --except C itself -- provide a wrapper interface that will convert arguments from the native formats to the format expected by the PDB; likewise converting the return values of the procedure into the corresponding native format.

In some cases, the conversion between PDB format and the native language format may be quite simple. While the PDB uses an integer value of "1" for logical true, and "0" for logical false, Python uses special boolean objects True and False, respectively. 

As a more complicated example, GIMP's Python plug-in is largely object-oriented and so an 'image' is an object with certain values (e.g., width, height, list of layers) and methods (e.g., duplicate, rotate, get width) associated with it, while to the PDB an 'image' is just a unique integer identifier. Python-fu has an extensive wrapper that converts such objects while interacting with the PDB.

Script-fu's interface to the PDB is performed at a lower level. Boolean arguments passed to the PDB must be converted (by the script) to the numbers "1" and "0", while images, paths, and drawables are passed as simple integer identifiers. Lists and vectors need to be converted to counted vectors -- in other words, while the length of a Scheme list or vector is inherently known, that length has to be passed to the PDB as a separate argument.

In addition, Script-fu procedures only return a single value, though that value may be a list (of values). Multiple assignment such as Python's "x,y = pdb.gimp_layer_get_offset(layer)" is not possible in Script-fu. Whenever a PDB procedure returns more than one value, it is necessary that those returns be provided as a list. 

Furthermore, to simplify maintainability of Script-fu and provide consistency between GIMP-supplied and third-party procedures, even PDB procedures that return only a single value have that value placed inside a list. This is why it is typically necessary to use 'car' on the return value of PDB procedures such as 'gimp-image-get-active-layer'.

Schemey-fu attempts to address some of these issues and allow script coding to benefit from the conventions of the Scheme language. 

IS SCHEMEY-FU BETTER THAN SCRIPT-FU?

No, it is not. 

SCHEMEY-FU INTERNALS

The PDB procedures are divided up into different namespaces, with the namespace supplied as a prefix in the procedure name. The main namespaces are gimp, file, script-fu, and plug-in. Schemey-fu is mostly limited to the procedures within the gimp namespace. The gimp namespace generally follows the naming convention of gimp-object-action, where the object might be an image, a layer, a tool, or somesuch.

In order to maintain a consistent mapping with the PDB names, Schemey-fu procedures are generally divided into packages based upon the object being accessed. For example, the Schemey-fu call for gimp-layer-add-alpha becomes Layer::add-alpha, the Schemey-fu call for gimp-image-select-rectangle becomes Image::select-rectangle. gimp-selection-invert becomes Selection::invert, gimp-brush-set-radius becomes Brush::set-radius, et cetera. Schemey-fu package names begin with a capital letter.

The trick to implementing this mapping has to do with how Script-fu handles variable names containing two parts separate by double colons. Given an expression such as prefix::function, Script-fu converts that into (eval function prefix), where the 'prefix' specifies the environment in which the expression is to be evaluated. This allows us to define a function (or variable) within a localized environment and then easily access that function from a different environment. 

A local environment is created whenever a new lambda is introduced. To define our functions locally (so that they are not found when invoked from a different environment), we do the following:

(lambda ()
  (define (base-type image)
    (car (gimp-image-base-type image)))
  (define (clean-all image)
    (gimp-image-clean-all image))
  <body>)
  
We can then access the two functions 'base-type' and 'clean-all' from within <body>, but they are not accessible (by those names) from outside the lambda. In order to have access from outside the lambda, we return the local environment by having the last (in fact, only) thing <body> does is call the function 'current-environment'.

Of course, we have to actually execute the lambda to create the definitions and return the environment. We do that by applying the lambda (to no arguments). We then just need to assign that returned environment to a variable (that will become our 'prefix'). 
    
(define Image
  (apply (lambda ()
           (define (base-type image)
             (car (gimp-image-base-type image)))
           (define (clean-all image)
             (gimp-image-clean-all image))
           ; more definitions can go here
           (current-environment))))

After this is done, it is possible to call 'base-type' using eval and passing the environment:

(eval '(base-type x) Image)

Or using the double-colon reader transformation:

(Image::base-type x)

Similarly for 'clean-all'. 

All that is left is to define a macro that simplifies all that. 

(macro (make-environment form)
   `(apply (lambda ()
             ,@(cdr form)
             (current-environment))))

Which allows stating the same thing as:

(define Image
  (make-environment
    (define (base-type image)
      (car (gimp-image-base-type image)))
    (define (clean-all image)
      (gimp-image-clean-all image))
    ; more definitions can go here
    ))

Note that the 'make-environment' macro is included in Script-fu's script-fu.init file.

HELPER MACROS

Schemey-fu defines a few helper routines that make defining packages a bit easier and more readable. First there is 'pdb-dehyphen' which will return a new symbol after stripping the prefixes. For example, (pdb-deyphen 2 'gimp-image-base-type) will return 'base-type (the first parameter is the number of hyphens to skip). 

There are also some macros that create definitions for the most common types of PDB functions automatically. For example, (pdb-pass 2 gimp-image-clean-all) will create the appropriate definition as described above. Likewise, (pdb-decar 2 gimp-image-base-type) will create the above defintion for 'base-type such that the car of the PDB return value is extracted.

There is also a macro for converting the PDB return values from boolean tests to Scheme's #t and #f, ready for testing with Scheme conditionals. A question mark is appended to the new name following the Scheme convention for boolean procedures. For example, (pdb-bool 3 gimp-item-get-visible) will provide a suitable definition for (Item::visible? x).

Note that not all PDB procedures are handled by the above three macros. The above macros only handle the most common transformations of return values; sometimes it is necessary to transform the arguments being passed to the procedure. Also, the data types that get transformed (booleans and counted arrays) are sometimes returned along with other values in a list (see Vectors::stroke-get-points for one such example). It would be a waste of effort to have a macro for such exceptional cases. 

TRANSFORMATIONS

In addition to "decar"ing the returns of procedures that return a single value and converting TRUE/FALSE returns to Scheme booleans, Schemey-fu converts counted PDB arrays to lists. For example, the PDB procedure, gimp-image-get-layers, returns the number of layers plus a vector of those layers; this gets converts to a simple list of the layers. 

(set! layers (Image::get-layers x))

This transformation of counted arrays applies also to parameters passed to procedures expecting counted arrays. The Schemey-fu binding will automatically insert the number of elements in the list and convert the list to the appropriate array.

(set! stroke-id (Vectors::stroke-new-from-points path 
                                                 VECTORS-STROKE-TYPE-BEZIER  
                                                 '(34 5 40 10 42 15 54 15 41 20 68 24)
                                                 1))

Note that when booleans parameters are expected by PDB procedures, they should be passed as TRUE/FALSE (1/0) rather than as Scheme's #f and #t. It is only when they are returned as values from PDB procedures that booleans get transformed. The reasoning behind adopting this inconsistency is two-fold. 

First, the majority of third-party plug-ins/scripts do not return meaningful values and hardly any return booleans. However, there are third-party scripts that accept TRUE/FALSE as parameters and using the same convention for both the Gimp namespace and third-party parameters simplifies things somewhat.

Second, if the script writer makes a mistake and passes #t or #f to a procedure that is expecting a TRUE or FALSE, an error will be produced about the wrong type being supplied. However, if Schemey-fu had chosen the opposite approach (and functions expected #t/#f parameters) then should a scripter pass, by mistake, a TRUE/FALSE to the procedure, no error would be generated and in the case of FALSE being passed, that FALSE would be treated as a TRUE. It would be much harder to track down the cause of such an unflagged error after it produces the faulty behavior.

Finally, there are basically two different forms of boolean procedures in the PDB: where the procedure name includes "is/has" (e.g., gimp-image-is-valid, gimp-drawable-has-alpha), and where the procedure name includes "get" and there is a corresponding "set" procedure (e.g., gimp-item-get-visible). For the first form, the Schemey-fu function starts with the "is-" (or "has-") while for the second form the "-get-" is dropped altogether. For both forms, a question mark is appended to the function name: e.g., Image::is-valid?, Drawable::has-alpha?, and Item::visible?.


APPENDIX

Useful means of querying the PDB while excluding deprecated procedures:
   (gimp-procedural-db-query ".*"
                             "^((?!deprecated).)*$" ; exclude deprecated
                             ".*" 
                             ".*" 
                             ".*" 
                             ".*" 
                             "Internal GIMP procedure")

The following function was used to separate the procedures based upon the number of values they return:
                      
   (define (filter-by-nb-values pdb-names nb-values)
     (let loop ((procs pdb-names)
                (result '()))
       (if (null? procs)
         (reverse result)
         (loop (cdr procs)
           (if (= (cadddr (cddddr (gimp-procedural-db-proc-info (car procs)))) nb-values)
             (cons (car procs) result)
             result)))))