;
; photo-stack
;
; This script turns a simple image into a rendition of a pile of
; photographs, with the original image at the top.  Each picture in
; the stack has a white border added, and a shadow behind it (to
; delimit it from the one below).  All the underlying pictures are, of
; course, copies of the top one.
;
; The following options are supported (with the given defaults):
;   Border Color (white):
;	The color of the border added to the image.
;   Border Width (10):
;	The width in pixels of the border added to the image.
;   Pictures in Stack (3):
;	The number of pictures in the final stack, including the
;	original image.
;   Rotate Sloppiness (100):
;	Amount of rotational sloppiness to add to each picture.  0
;	means don't rotate them.
;   Move Sloppiness (10):
;	Amount of sideways sloppiness to add to each picture.  0 means
;	don't move them.
;   Top Straight (TRUE):
;	If TRUE, keep the top image straight; otherwise, skew it like
;	the others.
;   Add Shadow (TRUE):
;	If TRUE, add a shadow to each layer (recommended).
;   Shadow Radius (12.0):
;	Blur radius in pixels used to generate each layer's shadow.
;   Shadow Weight (100%):
;	Opacity for the shadow layers: 0 - 100.
;   Work on Copy (TRUE):
;	If TRUE, everything is done on a copy of the original image
;	(highly recommended).
;   Flatten Layers (FALSE):
;	If TRUE, the image is merged into a single layer when all's
;	done, and autocrops it.
;
; Issues:
;   *	Unless the user elects to flatten the resulting image, I don't
;	autocrop it, and you're left with a huge image.  I need an
;	autocrop which takes into account the sizes and extents of
;	*all* the layers, not just the current one, to fix this.
;   *	With very large move sloppiness values, you could get a
;    	clipped image.
;   *	Using this on a multi-layered source will discard the layers.
;   *	Undo doesn't work, I think largely because of the "Add Border"
;    	script.  I could do the border adding within the script
;    	easily enough and fix that.
;
; Thoughts:
;   *	Currently all the pictures in the stack are the same size.  I
;    	could add some random cropping and/or resizing, and even 90°
;    	rotations...
;   *	Could have the ability to make multiple images, from separate
;    	layers, into a stack...  And then maybe add a "permute"
;    	option, to generate multiple stacks, one with each image on
;    	top, to make a whole photo album.


; Main function:

(define (script-fu-photo-stack
	 inImage 
	 inLayer 
	 inColor 
	 inSize 
	 inNum
	 inSlop
	 inSpill
	 inTopStraight
	 inShadow 
	 inShadRadius
	 inShadWeight 
	 inCopy
	 inFlatten
	 )

  (let* (
	 theImage theLayer width height newLayer angle
	 )

    ; Figure out what image and layer we're working on.  If the user
    ; wants to work on a copy, then copy the image; the passed-in
    ; layer handle (inLayer) then becomes invalid.
    (set! theImage
	  (if (= inCopy TRUE)
	      (car (gimp-channel-ops-duplicate inImage))
	      inImage
	  ))
    (set! theLayer (car (gimp-image-get-active-layer theImage)))

    (gimp-undo-push-group-start theImage)
    (srand (realtime))

    ; Stick a border on the original image; then flatten it, so we've
    ; got a single layer to work on.
    (script-fu-addborder theImage theLayer inSize inSize inColor 0)
    (set! theLayer (car (gimp-image-flatten theImage)))
    (gimp-layer-add-alpha theLayer)

    ; Double the width and height of the image.  I can't think of a
    ; better way to figure out how big it needs to be, to handle all
    ; the random resizing and skewing we're going to do.
    (set! width (car (gimp-image-width theImage)))
    (set! height (car (gimp-image-height theImage)))
    (gimp-image-resize theImage
		       (* width 2) (* height 2)
		       (/ width 2) (/ height 2))
    (gimp-layer-resize theLayer
		       (* width 2) (* height 2)
		       (/ width 2) (/ height 2))

    ; For every *additional* copy we need, add it.
    (while (> inNum 1)
	   ; Copy the original to make the new copy, just under the
	   ; original.
	   (set! newLayer (car (gimp-layer-copy theLayer TRUE)))
	   (gimp-image-add-layer theImage newLayer 1)

	   ; Skew it, and add a shadow if required.
	   (clint-skew-layer theImage newLayer inSlop inSpill)
	   (if (= inShadow TRUE)
	       (clint-add-shadow theImage newLayer 1 inShadRadius inShadWeight))

	   (set! inNum (- inNum 1))
	   )

    ; If we don't need the original (top) image kept straight, skew
    ; it; add a shadow to the original if required.
    (if (= inTopStraight FALSE)
	(clint-skew-layer theImage theLayer inSlop inSpill))
    (if (= inShadow TRUE)
	(clint-add-shadow theImage theLayer 0 inShadRadius inShadWeight))

    ; Flatten the image if requested.  Having done this, we can safely
    ; autocrop it.
    (if (= inFlatten TRUE)
	(begin
	  (set! theLayer (car (gimp-image-merge-visible-layers theImage 1)))
	  (plug-in-autocrop 1 theImage theLayer)
	  ))

    ; Tidy up.
    (if (= inCopy TRUE)
	(begin 	(gimp-image-clean-all theImage) 
		(gimp-display-new theImage)
		))

    (gimp-undo-push-group-end theImage)
    )
  )


; Skew a layer by the given slop (rotation) and spill (vert/horiz
; shift) factors, randomized.

(define (clint-skew-layer
	 image
	 layer
	 slop
	 spill)
  (let* (dx dy angle)
    ; X/Y shift.
    (if (> spill 0)
	(begin
	  (set! dx (+ (rand spill) 3))
	  (set! dy (+ (rand spill) 3))
	  (if (= (rand 2) 1) (set! dx (- 0 dx)))
	  (if (= (rand 2) 1) (set! dy (- 0 dy)))
	  (gimp-layer-translate layer dx dy)
	  ))

    ; Rotational skew.
    (if (> slop 0)
	(begin
	  (set! angle (+ (/ (rand slop) 1000) 0.02))
	  (if (= (rand 2) 1) (set! angle (- 0 angle)))
	  (gimp-rotate image layer TRUE angle)
	  ))
    )
  )


; Add a shadow layer.

(define (clint-add-shadow
	image
	layer
	depth
	radius
	opacity)
  (set! layer (car (gimp-layer-copy layer TRUE)))
  (gimp-image-add-layer image layer (+ depth 1))
  (gimp-selection-layer-alpha image layer)
  (gimp-palette-set-foreground '(0 0 0))
  (gimp-bucket-fill image layer 0 0 100 0 FALSE 0 0)
  (gimp-selection-none image)
  (plug-in-gauss-iir 1 image layer radius TRUE TRUE)
  (gimp-layer-set-opacity layer opacity)
  )


; Register the function with the GIMP:

(script-fu-register
 "script-fu-photo-stack"
 "<Image>/Script-Fu/Modify/Photo Stack"
 "foo"
 "Ian Cameron Smith"
 "1998, Ian Cameron Smith, http://www.hermit.org/"
 "27 May 1998"
 "RGB RGBA GRAY GRAYA"
 SF-IMAGE "The Image" 0
 SF-DRAWABLE "The Layer" 0
 SF-COLOR "Border Color:" '(255 255 255)
 SF-VALUE "Border Width:" "10"
 SF-VALUE "Pictures in Stack:" "3"
 SF-VALUE "Rotate Sloppiness:" "100"
 SF-VALUE "Move Sloppiness:" "10"
 SF-TOGGLE "Top Straight?" TRUE
 SF-TOGGLE "Add Shadow?" TRUE
 SF-VALUE "Shadow Radius:" "12.0"
 SF-VALUE "Shadow Weight (%):" "100"
 SF-TOGGLE "Work on Copy?" TRUE
 SF-TOGGLE "Flatten Layers?" FALSE
 )

