Tuesday, December 18, 2012

A Little Script-Fu To Help With Image Organization

One of the easy things about iOS app development (and I haven't found many!) is creating a splash screen. You just create an image named something like Default.png and Adobe AIR or PhoneGap do the right thing.

Things get trickier when you realize that you need to maintain a whole bunch of images, and have to have them follow a specific naming convention.

Before you know it, you've got a whole slew of files named something like Default-PortraitUpsideDown@2~ipad that you have to keep track of.

I was wrestling with just such a set of images today, when I decided I could maintain them all a lot easier if they lived in a single Gimp xcf. My goal then, became as follows:

Write some Script-Fu that takes each layer of an image and saves it off to its own file, and names it per the layer name. With a single click, a whole batch of images can be created from one source.

And here's the code I came up with to accomplish this:

;; Code It
(define (bs-script-fu-save-individual-layers img)
  (let ((layers (vector->list (second (gimp-image-get-layers img))))
        (orig-file-name (car (gimp-image-get-filename img))))
    (for-each (lambda (layer)
                (let ((name (car (gimp-drawable-get-name layer)))
                       (h    (car (gimp-drawable-height layer)))
                       (w    (car (gimp-drawable-width layer))))
                  (let* ((target (car (gimp-image-new w h RGB)))
                         (layer  (car (gimp-layer-new-from-drawable layer target)))
                         (file-name (string-append (filename-dirname orig-file-name) "\\" name ".png")))
                    (gimp-image-insert-layer target layer 0 -1)
                    (gimp-layer-set-offsets layer 0 0)
                    (file-png-save-defaults RUN-NONINTERACTIVE target layer file-name file-name))))

;; Register It
 "Save Layers"
 "Save out all the layers of this image as separate files."
 "Ben Simon"
 "copyright 2012, Ideas2Executables"
 SF-IMAGE      "Image to split into layers and save" 0
(script-fu-menu-register "bs-script-fu-save-individual-layers" "<Image>/Filters/Util")

;; Utility Functions

(define (last-string-position haystack needle)
  (define (go haystack needle position)
    (if (< (string-length haystack) (string-length needle))
      (if (equal? (substring haystack
                             (- (string-length haystack) (string-length needle))
                             (string-length haystack))
        (go (substring haystack 0 (- (string-length haystack) 1))
            (- position 1)))))
  (go haystack needle (- (string-length haystack) (string-length needle))))
(define (filename-dirname f)
  (let ((pivot (last-string-position f "\\")))
    (if pivot
      (substring f 0 pivot)

Most of my effort went into writing filename-dirname and last-string-position, two utility functions. Especially last-string-position - coding that in terms of equals? and substring felt more like an interview question than my usual daily hacking.

I continue to believe in the power of The Gimp and Script-Fu. There's just something invaluable about being able to script your way out of tedious jobs.


  1. Anonymous3:21 AM

    Script-fu retains the original 'strbreakup' and 'unbreakupstr' procedures from George Carrette's SIOD interpreter. Though these procedures are not part of the R#RS standard, I find them useful for parsing filenames and paths.

    For example:
    (define (filename-dirname f)
    (let ((parts (butlast (strbreakup f DIR-SEPARATOR))))
    (if (null? parts)
    (unbreakupstr parts DIR-SEPARATOR) )))

    The DIR-SEPARATOR constant is used instead of "\\" so the script will work on platforms that use forward slashes in pathnames (e.g., Unix).

  2. saulgoode -

    That's awesome to know, thanks! I poked around the source code for script-fu, but didn't stumble upon this. Thanks for sharing.