; "Falling snow" animation generator (falling-snow-anim.scm)
; Jean-Marc Chatain (jimcha369@gmail.com)
; 2020/05/04
;
; Makes a copy of your image and creates a multi frames
; animation with a falling snow effect in all the image
; or in the selected part.
; Parameters allow to choose :
; - frames number in the animation		: 5 to 50
; - delay between two frames			: 40 to 100 ms
; - number of flakes layers				: 1 to 10
; - first size of snow flakes			: 1 to 10
; - size difference between two layers	: 1 or 2
; - brush rotation						: left no right
; - direction and force of wind			: -3 to 3
; - blur level applied on snow layer	: 0 to 5
; - rainy blur effect level				: 0 to 20
; - opacity of snow flakes				: 1 to 100
; - snow flakes layer display mode		: 28 modes
; The animation may be saved with the gif-plug-in.

(define (script-fu-falling-snow-anim img		; Image to use for animation
								drawable		; Area of the image to draw, all image or selected parts
								step			; Frames number, brush size and distance between two brushes
								time			; Adjust delay in millisecond between two animated frames
								layers-number	; Adjust the number of flakes layers from smallest to biggest
								first-size		; The first size of flakes 1 = smallest to 6 = biggest
								difference		; Difference of flakes size between two layer of flakes
								rotation		; Brush rotation : -1 = left ... 0 = no ... 1 = right
								wind-force		; -3 strong left wind ... 0 no wind ... 3 strong right wind
								blur-level		; Set the blur level applied on snow flakes layer
								rainy-blur		; Set the kinetic blur level for rainy effect on layer
								opacity			; Adjust the opacity of the snow layer from 1 to 100
								mode			; Snow layer mode seen in Gimp choices
	)
	(let* (	(actual-frame 1)											; Number of the frame to build
			(difference (* difference step))							; Set difference by step increment
			(points (cons-array 4 'double))								; Array to set coordinates of points
			(saved-brush (car (gimp-context-get-brush)))				; Save the brush used previously
			(saved-color (car (gimp-context-get-foreground)))			; Save the color used previously
			(saved-brush-size (car (gimp-context-get-brush-size)))		; Save the brush size used previously
			(image (car (gimp-image-duplicate img)))					; Duplicate the image for a new one
			(source-layer (car (gimp-image-get-active-layer image)))	; Get the active layer of the image
			(height (car (gimp-image-height image)))					; Get the height of the image
			(width (car (gimp-image-width image)))						; Get the width of the image
		)
		(define (rotate-brush brush deviation)			; Change the brush angle adding deviation angle
			(let* ((sum (+ brush deviation)))
				(if (> sum 180) (- sum 360)				; Brush angle value is between -180° and 180°
					(if (< sum -180) (+ sum 360) sum))))
		(gimp-image-undo-disable image)									; Disable modifications of the image
		(gimp-context-set-brush "Bristles 03")							; Set "Bristles 03" as used brush  
		(gimp-palette-set-foreground '(255 255 255))					; Set foreground color to White
		
		(while (<= actual-frame step)									; For 25 frames
			(let* (
				(actual-layer 0)										; Set actual-layer number to 0
				(actual-size (* first-size step))						; Set actual-size = first-size x step
				(brush-angle 0)									; Set brush angle = 0
				(base-layer (car (gimp-layer-copy source-layer TRUE)))	; Set base layer copy of the source layer
				(base-name (string-append "Base "						; Create a base layer name like "Base 1 (40 ms)(replace)"
										(number->string actual-frame) " ("
										(number->string time) " ms)(replace)"))
				(snow-name (string-append "Snow "						; Create a snow layer name like "Snow 1"
										(number->string actual-frame)))
				(snow-layer (car (gimp-layer-new image width height RGBA-IMAGE snow-name opacity mode))) ; Create a snow layer
				)
				(gimp-layer-set-lock-alpha base-layer FALSE)			; Allow transparency of base layer
				(gimp-image-insert-layer image base-layer 0 -1)			; Insert a base layer in the image
				(gimp-item-set-name base-layer base-name)				; Set the name of the base layer
				(gimp-layer-set-lock-alpha snow-layer FALSE)			; Allow transparency of snow layer
				(gimp-image-insert-layer image snow-layer 0 -1)			; Insert a snow layer in the image
		
				(while (< actual-layer layers-number)					; While actual layer number is not max
					(let* (
						(nb-row (+ (truncate (/ height actual-size)) 2))					; Set number of paintbrush made in the image height
						(actual-row 0)														; Start row of paintbrush up the image
						(mid-size (truncate (/ actual-size 2)))								; Set mid-size as half the actual flakes size
						(ypos (- (* actual-frame (/ actual-size step)) mid-size))			; Set y position of paintbrush more than up the image
						(nb-brush (+ (truncate (/ width actual-size)) 1 (truncate (+ (/ (* (abs wind-force) 25) step) 2))))	; Set number of paintbrush made in the image width
						(brush-deviation 0)													; Initiate brush deviation to 0 to avoid "division by zero" error
						)
						(if (<> (+ nb-brush wind-force) 0)
						(set! brush-deviation (* (/ 360 (+ nb-brush wind-force)) rotation))	; Set brush deviation 360° / paintbrush number
						)
						(gimp-context-set-brush-size actual-size)							; Set brush size = actual-size
						
						(while (< actual-row nb-row)										; While row is in the image
							(let* (
								(actual-pos 0)												; Set x first position on left of the image with wind-force
								(xpos (truncate (/ (* (+ actual-frame actual-row) actual-size wind-force) (- step 1))))
								)
								(if (> wind-force 0)
								 (set! xpos (- xpos (/ (* actual-size wind-force 25) step)))	; Change x position to left only for right angle wind force value
								)
								(while (< actual-pos nb-brush)								; While x position of the paintbrush is not right edge
									(let* ()
										(aset points 0 xpos)								; Set (x0, y0) coordinates
										(aset points 1 ypos)
										(aset points 2 xpos)								; Set (x1, y1) coordinates
										(aset points 3 ypos)
										(gimp-context-set-brush-angle brush-angle)			; Set the paintbrush angle
										(gimp-paintbrush-default snow-layer 4 points)		; Draw a little cloud of snowflakes
										(set! brush-angle (rotate-brush brush-angle brush-deviation))	; Change brush angle for better snow look
										(set! xpos (+ xpos actual-size))					; Increase x position for next paintbrush
										(set! actual-pos (+ actual-pos 1))					; Increase number of actual x position of paintbrush
									)
								)
								(set! ypos (+ ypos actual-size))							; Increase y position for next row of paintbrush
								(set! actual-row (+ actual-row 1))							; Increase number of actual row of paintbrush
							)
						)
						(set! brush-angle (rotate-brush brush-angle brush-deviation))		; Change brush angle for better snow look
						(set! actual-size (+ actual-size difference))						; Add difference to actual size of flakes for the next layer of snow flakes
						(set! actual-layer (+ actual-layer 1))								; Increase flakes layer number for the next layer of flakes
					)
				)
				(plug-in-gauss RUN-NONINTERACTIVE image snow-layer blur-level blur-level 1)	; Apply the blur effect on snow flakes layer
				(plug-in-mblur RUN-NONINTERACTIVE image snow-layer 0 rainy-blur (- 90 (* 24 wind-force)) 0 0)	; Apply kinetic blur for rainy effect
				(gimp-image-merge-down image snow-layer EXPAND-AS-NECESSARY)				; Merge down snow layer on base layer
				
				(set! actual-frame (+ actual-frame 1))										; Increase frame number for the next stage
			)
		)
		(gimp-image-remove-layer image source-layer)	; Remove the original layer
		(gimp-selection-none image)						; If drawable was a selection remove it
		(gimp-context-set-brush saved-brush)			; Restore the previous brush
		(gimp-context-set-brush-size saved-brush-size)	; Restore the previous brush size
		(gimp-context-set-foreground saved-color)		; Restore the previous foreground
		(gimp-image-undo-enable image)					; Enable image modifications
		(gimp-display-new image)						; Display the new animated image
		(plug-in-animationplay RUN-NONINTERACTIVE image drawable)	; Display the preview of the animated image
	)
)

(script-fu-register "script-fu-falling-snow-anim"
  _"_Falling snow..."
  _"Create a multi-frames image with a falling snow effect in all image or selected parts."
  "Jean-Marc Chatain (jimcha369@gmail.com)"
  "Jean-Marc Chatain"
  "2020"
  "RGB* GRAY*"
  SF-IMAGE       "Image" 0
  SF-DRAWABLE    "Drawable" 0
  SF-ADJUSTMENT _"Frames number"	'(25 5 50 1 1 0 0)		; Set frames number, brush size and distance between two brushes
  SF-ADJUSTMENT _"Delay in ms"		'(50 40 100 10 10 0 0)	; Set the delay in millisecond between two frames
  SF-ADJUSTMENT _"Layers number"	'(5 1 10 1 1 0 0)		; Set layers-number of flakes : from 1 to 7 layers of flakes
  SF-ADJUSTMENT _"First flake size"	'(3 1 10 1 1 0 0)		; Set first size of flakes : from 1 smallest to 6 biggest
  SF-ADJUSTMENT _"Size difference"	'(1 1 2 1 1 0 0)		; Set difference of flake size between two layers of flakes
  SF-ADJUSTMENT _"Brush rotation"	'(1 -1 1 1 1 0 0)		; Set brush rotation : -1 = left ... 0 = no ... 1 = right
  SF-ADJUSTMENT _"Wind force"		'(0 -3 3 1 1 0 0)		; Set the wind force like an angle of falling effect
  SF-ADJUSTMENT _"Blur level"		'(0 0 5 1 1 0 0)		; Set the blur level applied on snow flakes layer
  SF-ADJUSTMENT _"Rainy blur"		'(0 0 20 1 1 0 0)		; Set kinetic blur level to give rainy effect
  SF-ADJUSTMENT _"Opacity"			'(100 1 100 10 10 0 0)	; Set the opacity of the snow layer from 1 to 100
  SF-OPTION _"Layer mode"
  '("Normal" "Dissolve" "Behind" "Multiply" "Screen" "Overlay"
  "Difference" "Addition" "Substract" "Darken only" "Lighten only"
  "HSV Hue" "HSV Saturation" "HSL Color" "HSV Value" "Divide" "Dodge"
  "Burn" "Hardlight" "Softlight" "Grain extract" "Grain merge"
  "Color erase" "Overlay" "LCH Hue" "LCH Chroma" "LCH Color" "LCH Lightness")	; Set the layer mode in the long list of different displays
)

(script-fu-menu-register "script-fu-falling-snow-anim"		; Locate the script in menu Filters -> Animation
                         "<Image>/Filters/Animation/Animators")
