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

From Second Life Wiki
Jump to navigation Jump to search
(Update to 2.0pre1 (all features intended for this version were added))
(Added reading of the layer name to process Combine-mode layers; it should process all kinds of GIFs now without the need for Unoptimize.)
Line 2: Line 2:


<scheme>
<scheme>
; Layers to SL Texture Animation, version 2.0pre1
; Layers to SL Texture Animation, version 2.0pre2
;
;
; Written by Pedro Oval, 2010-12-04.
; Written by Pedro Oval, 2010-12-04.
Line 9: Line 9:


(define (script-fu-layers-to-sl-anim curimg curdrawable xframes yframes showscript fps)
(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.
   ; Read the layer IDs and the total number of layers from the original image.
Line 38: Line 54:
           (define palette (gimp-image-get-colormap curimg))
           (define palette (gimp-image-get-colormap curimg))
           (gimp-image-set-colormap img (car palette) (cadr palette))))
           (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.
       ; Loop betwen 0 and numframes - 1.
Line 94: Line 113:
           ; Force it visible.
           ; Force it visible.
           (gimp-drawable-set-visible newlayer TRUE)
           (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.
           ; All done. Next loop iteration.
Line 131: Line 183:
       ; Show the script if requested
       ; Show the script if requested
       (if (= showscript TRUE)
       (if (= showscript TRUE)
         (gimp-message (string-append "default\n{\n    state_entry()\n    {\n"
         (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, "
                                     "        llSetTextureAnim(ANIM_ON | LOOP, "
                                     (number->string xframes)
                                     (number->string xframes)
Line 172: Line 225:
* Generate the LSL script.
* Generate the LSL script.
* 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.


== TODO list ==
== TODO list ==
Line 177: Line 231:
* Perhaps change the strategy to use gimp-image-duplicate to simplify the process.
* 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.
* Add the opposite conversion: given an animated texture, split it into layers.
* Read the layer name to see if it includes the word "(combine)", and if so, combine it, as the Unoptimize filter does.

Revision as of 20:24, 11 December 2010

Development version

<scheme>

Layers to SL Texture Animation, version 2.0pre2
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)
     ; 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, "
                                    (number->string fps)
                                    ");\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.

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.