Difference between revisions of "User:Pedro Oval/GIMP Layers to SL Animated Texture"

From Second Life Wiki
Jump to navigation Jump to search
(Added reading of the layer name to process Combine-mode layers; it should process all kinds of GIFs now without the need for Unoptimize.)
(Fix decimals for fps)
Line 2: Line 2:


<scheme>
<scheme>
; Layers to SL Texture Animation, version 2.0pre2
; Layers to SL Texture Animation, version 2.0pre3
;
;
; Written by Pedro Oval, 2010-12-04.
; Written by Pedro Oval, 2010-12-04.
Line 180: Line 180:
       (gimp-image-undo-group-end img)
       (gimp-image-undo-group-end img)
       (gimp-display-new img)
       (gimp-display-new img)
      ; Prepare a version of fps ending in .0 if it doesn't.
      (define fpsstr (number->string fps))
      (if (not (contains-ci fpsstr "."))
        (set! fpsstr (string-append fpsstr ".0")))


       ; Show the script if requested
       ; Show the script if requested
Line 192: Line 197:
                                     (number->string (* xframes yframes))
                                     (number->string (* xframes yframes))
                                     ".0, "
                                     ".0, "
                                     (number->string fps)
                                     fpsstr ");\n    }\n}\n"))))
                                    ");\n    }\n}\n"))))


     ; If the frame counts didn't match...
     ; If the frame counts didn't match...
Line 226: Line 230:
* Automatically remove alpha from the final image if not needed.
* Automatically remove alpha from the final image if not needed.
* Read the layer name to see if it includes the word "(combine)", and if so, combine it, as the ''Unoptimize'' filter does. With this change, ''Unoptimize'' should be unnecessary.
* Read the layer name to see if it includes the word "(combine)", and if so, combine it, as the ''Unoptimize'' filter does. With this change, ''Unoptimize'' should be unnecessary.
* The fps argument is always shown with decimals, to avoid forcing an integer-to-float conversion at runtime.


== TODO list ==
== TODO list ==

Revision as of 05:31, 12 December 2010

Development version

<scheme>

Layers to SL Texture Animation, version 2.0pre3
Written by Pedro Oval, 2010-12-04.
Donated to the public domain.
Thanks to Digital Dharma for the suggestions.

(define (script-fu-layers-to-sl-anim curimg curdrawable xframes yframes showscript fps)

 ; Local function: check if a string contains a substring
 (define (contains-ci str substr)
   (define i 0)
   (define lensub (string-length substr))
   (define imax (- (string-length str) lensub))
   (if (negative? imax)
     #f
     (begin
       (define found #f)
       (while (and (not found) (<= i imax))
         ; We make a case-insensitive comparison.
         (if (string-ci=? (substring str i (+ i lensub)) substr)
           (set! found #t)
           (set! i (+ i 1))))
       found)))
 ; Read the layer IDs and the total number of layers from the original image.
 (define layerset (gimp-image-get-layers curimg))
 (define numframes (car layerset))
 (set! layerset (cadr layerset))
 ; The frame size X and Y is the size of the original image.
 (define framesizex (car (gimp-image-width curimg)))
 (define framesizey (car (gimp-image-height curimg)))
 ; Get the image type; indexed images need special treatment.
 (define imgtype (car (gimp-image-base-type curimg)))
 ; Check if the given number of horizontal x vertical frames matches
 ; the number of layers in the original image. If yes, proceed; if not,
 ; show an error message.
 (if (= numframes (* xframes yframes))
   (begin
     ; New image
     (define img (car (gimp-image-new (* framesizex xframes) (* framesizey yframes) imgtype)))
     ; We don't touch the original image, so we start a new undo group
     ; for the new image instead.
     (gimp-image-undo-group-start img)
     (if (= imgtype INDEXED)
       (begin
         ; Indexed images need the palette to be copied to the new image.
         (define palette (gimp-image-get-colormap curimg))
         (gimp-image-set-colormap img (car palette) (cadr palette))))
     ; Initialize previous layer info to 0, as there's no previous layer yet.
     (define prevlayerinfo 0)
     ; Loop betwen 0 and numframes - 1.
     (define frame 0)
     (while (< frame numframes)
       ; For each layer/frame:
       (begin
         ; Show progress as the current frame relative to the number of frames.
         (gimp-progress-update (/ frame numframes))
         ; Read the individual layer ID, starting from the bottom.
         (define oldlayer (vector-ref layerset (- numframes frame 1)))
         ; Create new layer as a copy of this one and add it to the image.
         (define newlayer (car (gimp-layer-new-from-drawable oldlayer img)))
         (gimp-image-add-layer img newlayer -1)
         ; Add alpha if the layer doesn't have it.
         (if (= (car (gimp-drawable-has-alpha newlayer)) FALSE)
           (gimp-layer-add-alpha newlayer))
         ; Calculate the offsets and crop size.
         (define offsets (gimp-drawable-offsets oldlayer))
         (define newsizex (car (gimp-drawable-width oldlayer)))
         (define newsizey (car (gimp-drawable-height oldlayer)))
         (define offsetx (car offsets))
         (define offsety (cadr offsets))
         (if (< offsetx 0)
           (begin
             (set! newsizex (+ newsizex offsetx))
             (set! offsetx 0)))
         (if (< offsety 0)
           (begin
             (set! newsizey (+ newsizey offsety))
             (set! offsety 0)))
         (if (> (+ offsetx newsizex) framesizex)
           (set! newsizex (- framesizex offsetx)))
         (if (> (+ offsety newsizey) framesizey)
           (set! newsizey (- framesizey offsety)))
         ; Calculate frame position X and Y.
         (define framey (truncate (/ frame xframes)))
         (define framex (- frame (* framey xframes)))
         (set! framex (* framex framesizex))
         (set! framey (* framey framesizey))
         ; Resize and place the layer.
         (gimp-layer-resize newlayer
                            newsizex
                            newsizey
                            (- (car offsets) offsetx)
                            (- (cadr offsets) offsety))
         (gimp-layer-set-offsets newlayer (+ framex offsetx) (+ framey offsety))
         ; Force it visible.
         (gimp-drawable-set-visible newlayer TRUE)
         ; Check for (combine) mode. Note: It just works
         ; in layers other than 0. Otherwise no action is
         ; done. It makes no sense for the bottom layer to
         ; use combine mode anyway.
         (if (and (> frame 0) (contains-ci (car (gimp-drawable-get-name oldlayer)) "(combine)"))
           (begin
             ; Duplicate the previous layer to serve as a base
             ; for the differencing frame being added.
             (define layercopy (car (gimp-layer-new-from-drawable (car prevlayerinfo) img)))
             ; Add the new layer below the last layer.
             (gimp-image-add-layer img layercopy 1)
             ; Set the layer position to match that of the new layer.
             (gimp-layer-set-offsets layercopy (+ framex (cadr prevlayerinfo))
                                               (+ framey (caddr prevlayerinfo)))
             ; Ensure the previous layer's copy is visible.
             (gimp-drawable-set-visible layercopy TRUE)
             ; Merge the differencing frame into the previous layer's copy.
             (set! newlayer (car (gimp-image-merge-down img newlayer EXPAND-AS-NECESSARY)))
             ; Correct the offsets. We use 'min' to match what
             ; EXPAND-AS-NECESSARY has supposedly done. Ideally
             ; we should read the new offsets and subtract
             ; framex/framey from them, but this is faster and simpler,
             (set! offsetx (min offsetx (cadr prevlayerinfo)))
             (set! offsety (min offsety (caddr prevlayerinfo)))))
         ; Previous layer = this.
         (set! prevlayerinfo (list newlayer offsetx offsety))
         ; All done. Next loop iteration.
         (set! frame (+ frame 1))))
     ; Progress 100%
     (gimp-progress-update 1.0)
     ; Merge visible layers (all are visible now).
     (define mergedlayer (car (gimp-image-merge-visible-layers img CLIP-TO-IMAGE)))
     ; Make the new layer the size of the image.
     (gimp-layer-resize-to-image-size mergedlayer)
     ; Convert the image to RGB if it is not already.
     (if (not (= imgtype RGB))
       (gimp-image-convert-rgb img))
     ; Detect alpha by converting it to a selection.
     (gimp-selection-layer-alpha mergedlayer)
     ; If there is no useful alpha, the selection should cover the whole image.
     ; Invert it, and if it becomes empty, then it means that alpha was not
     ; in use: the whole image was covered with 100% opacity.
     (gimp-selection-invert img)
     (define hasalpha (car (gimp-selection-bounds img)))
     (gimp-selection-none img)
     ; If it didn't have any alpha, remove the alpha mask from the layer.
     (if (= hasalpha FALSE)
       (gimp-layer-flatten mergedlayer))
     ; All done - close the undo group and add a display window for the new image.
     (gimp-image-undo-group-end img)
     (gimp-display-new img)
     ; Prepare a version of fps ending in .0 if it doesn't.
     (define fpsstr (number->string fps))
     (if (not (contains-ci fpsstr "."))
       (set! fpsstr (string-append fpsstr ".0")))
     ; Show the script if requested
     (if (= showscript TRUE)
       (gimp-message (string-append "Copy-paste the following fragment as a script:\n\n"
                                    "default\n{\n    state_entry()\n    {\n"
                                    "        llSetTextureAnim(ANIM_ON | LOOP, "
                                    (number->string xframes)
                                    ", "
                                    (number->string yframes)
                                    ", 0.0, "
                                    (number->string (* xframes yframes))
                                    ".0, "
                                    fpsstr ");\n    }\n}\n"))))
   ; If the frame counts didn't match...
   (gimp-message "Error: Number of layers doesn't match horizontal x vertical frames")))
Register the function.

(script-fu-register "script-fu-layers-to-sl-anim"

                   _"<Image>/Script-Fu/SecondLife/Frames to texture..."
                   _"Convert a set of layers (frames) to a texture suitable for llSetTextureAnim."
                   "Pedro Oval"
                   _"Public Domain"
                   "2010-12-04"
                   "RGB*, GRAY*, INDEXED*"
                   SF-IMAGE        "Image"        0
                   SF-DRAWABLE     "Drawable"     0
                   SF-ADJUSTMENT   _"# of horizontal frames" '(2 1 1024 1 10 0 1)
                   SF-ADJUSTMENT   _"# of vertical frames"   '(2 1 1024 1 10 0 1)
                   SF-TOGGLE       _"Generate LSL script?"   TRUE
                   SF-VALUE        _"Frames per second (for script):" "10.0")

</scheme>

New in this version with respect to the stable one:

  • Add progress indicator.
  • Clip each layer to its rectangle.
  • Respect the original offsets of layers.
  • Do the merge down in the correct order.
  • Add alpha to the layers that lack it.
  • Set the final layer's size to the image's size.
  • Extensively commented.
  • Generate the LSL script.
  • Automatically remove alpha from the final image if not needed.
  • Read the layer name to see if it includes the word "(combine)", and if so, combine it, as the Unoptimize filter does. With this change, Unoptimize should be unnecessary.
  • The fps argument is always shown with decimals, to avoid forcing an integer-to-float conversion at runtime.

TODO list

  • Perhaps change the strategy to use gimp-image-duplicate to simplify the process.
  • Add the opposite conversion: given an animated texture, split it into layers.