Friday, December 30, 2011

Gimp Script-Fu Hacking: Image Spinning and Sprite Generation

I've been working on a project that required some relatively simple, but time consuming image hacking. Specifically, I needed to generate a CSS sprite image from an image that has to be rotated a number of times. The result are the following two Script-Fu functions.

bs-spin-layer: This plugin takes the currently selected layer and duplicates it, then rotates it a number of degrees. It will prompt you for the starting and ending rotation, as well as the step value to use. Nothing fancy here.

bs-layers-to-sprite-ribbon: This plugin is a bit sexier, and may turn out to be a generally useful tool. Supposing you've got a file named foo.xcf, the plugin creates a brand new image named foo.sprite.xcf which consists of each of the layers in foo.xcf, though, they are laid out side by side to create a sprite ribbon. At the same time the foo.sprite.xcf is created, foo.sprite.css is created, which contains the basic CSS you'd need to power the generated sprite. The correct background-position offsets and the appropriate height and width for each layer are calculated and stored in this file.

Every time I use it, I like Script-Fu just a little bit more. I may be the only fan left, but I'm a big one.

Download the code at: utils.scm (needed by both plugins), sprite-ribbon.scm and spin-layer.scm.

And here are the functions plugins minus the required utility functions:

;;
;; The spin-layer module is responsible for spinning (rotating) a layer a number
;; of times to create new layers.
;;


(define (bs-spin-layer img drawable start end step)
  (gimp-image-undo-group-start img)
  (let loop ((angle start))
    (cond ((<= angle end)
           (let ((layer (car (gimp-layer-new-from-drawable drawable img))))
             (gimp-image-add-layer img layer -1)
             (gimp-drawable-set-name layer (number->string angle))
             (gimp-drawable-transform-rotate layer (d->r angle) TRUE 0 0 TRANSFORM-FORWARD
                                             INTERPOLATION-CUBIC FALSE 3
                                             TRANSFORM-RESIZE-ADJUST)
             (loop (+ angle step))))
          (else
           'done)))
  (gimp-image-undo-group-end img)
  (gimp-displays-flush))


(script-fu-register
 "bs-spin-layer"
 "Spin Layer"
 "Spin (rotate) a layer a bunch of times"
 "Ben Simon"
 "Copyright 2011, Ideas2Executables"
 "2011/12/29"
 "RGB* GRAY*"
 SF-IMAGE "Image to Spin" 0
 SF-DRAWABLE "Layer to Spin" 0
 SF-VALUE "Start Angle" "15"
 SF-VALUE "End Angle" "360"
 SF-VALUE "Step" "15")

(script-fu-menu-register "bs-spin-layer" "<Image>/Filters/Util")


;;
;; The sprite-ribbon is responsible for generating a CSS sprite image.
;; Inspired by: http://registry.gimp.org/node/24538
;;
(define (bs-layers-to-sprite-ribbon img drawable css-selector sprite-prefix)
  (define (sprite-css-header file-name)
    (display (&& css-selector " { "
                 "background-repeat: no-repeat; "
                 "background-image: url(" file-name "); "
                 "}\n")))
  (define (sprite-css-entry layer x-off)
    (display (&& css-selector "." sprite-prefix (car (gimp-drawable-get-name layer)) " {"
                 "width: " (car (gimp-drawable-width layer)) "px; "
                 "height: " (car (gimp-drawable-height layer)) "px; "
                 "background-position: " (* -1 x-off) "px 0px; "
                 "}\n")))
  (gimp-image-undo-group-start img)
  (let ((layers (bas-image-layer-list img))
        (file-name  (car (gimp-image-get-filename img))))
    (let ((width (foldr (lambda (w layer)
                                 (+ w (car (gimp-drawable-width layer)) 2))
                        0 layers))
          (height (foldr (lambda (h layer)
                           (max h (car (gimp-drawable-height layer))))
                         0 layers)))
      (let ((sprite (car (gimp-image-new width height RGB))))
        (with-output-to-file (morph-filename file-name "sprite.css")
          (lambda ()
            (sprite-css-header (morph-filename file-name "sprite.png"))
            (let loop ((layers (reverse layers)) (x-off 0))
              (cond ((null? layers) 'done)
                    (else
                     (let ((clone (car (gimp-layer-new-from-drawable (car layers) sprite))))
                       (gimp-image-add-layer sprite clone -1)
                       (gimp-drawable-set-name clone (car (gimp-drawable-get-name (car layers))))
                       (gimp-layer-set-offsets clone x-off 0)
                       (sprite-css-entry clone x-off)
                       (loop (cdr layers) (+ x-off (car (gimp-drawable-width (car layers))) 2))))))))
        (gimp-image-set-filename sprite (morph-filename file-name "sprite.xcf"))
        (gimp-display-new sprite)
        (gimp-image-undo-group-end img)))))


(script-fu-register
 "bs-layers-to-sprite-ribbon"
 "Layers > Sprite Ribbon"
 "Create a CSS spirte ribbon from the layers in the image"
 "Ben Simon"
 "Copyright 2011, Ideas2Executables"
 "2011/12/29"
 "RGB* GRAY*"
 SF-IMAGE "Image to process" 0
 SF-DRAWABLE "Active layer" 0
 SF-STRING "Sprite CSS Selector" ""
 SF-STRING "CSS Layer Prefix" "")


(script-fu-menu-register "bs-layers-to-sprite-ribbon" "<Image>/Filters/Util")

No comments:

Post a Comment