; "Falling snow or other brush..." animation generator (falling-snow-or-other-brush-anim.scm)
; Jean-Marc Chatain (jimcha369@gmail.com)
; 2020/04/13
;
; Makes a copy of your image and creates a multi frames
; animation with a falling snow (or other brush) effect
; in all the image or in the selected part.
; The animation may be saved with the gif-plug-in.

(define (script-fu-falling-snow-or-other-brush-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
								difference		; Difference of flakes size between two layer of flakes
								first-size		; The first size of flakes : 1 = smallest to 15 = biggest
								first-angle		; Set the first angle of brush : -180 to 180
								brush-name		; Enter the brush name in the text box
								brush-color		; Set the brush color in the color selector
								rotation		; Brush rotation : -1 = left, 0 = no, 1 = right
								wind-force		; -3 strong left, 0 no wind, 3 strong right
								blur-level		; Set the blur level applied on snow flakes layer
								rainy-blur		; Set the kinetic blur level for rainy effect on layer
								rainy-angle		; Set the angle of the rainy blur effect
								opacity			; Adjust the opacity of the snow layer from 1 to 100
								mode			; Snow layer mode seen in Gimp choices
								harden-flakes	; Set to duplicate the flakes layers to harden them
								reverse-anim	; Set to reverse the animation during the building
								open-preview	; Set if animation window open or no in the end of script
	)
	(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))))

	(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
			(saved-brush-angle (car(gimp-context-get-brush-angle)))		; Save the brush angle ased 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
		)
		(gimp-image-undo-disable image)									; Disable modifications of the image
		(gimp-context-set-brush (car brush-name))						; Set "Bristles 03" as used brush  
		(gimp-palette-set-foreground brush-color)						; Set foreground color to used brush
		
		(while (<= actual-frame step)									; For number of frames choosen
			(let* (
				(actual-layer 0)										; Set actual-layer number to 0
				(actual-size (* first-size step))						; Set actual-size = first-size x step
				(brush-angle first-angle)								; Set brush angle = 0 or slider choice
				(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 (40ms)(replace)"
										(number->string actual-frame) " ("
										(number->string time) "ms) (replace)"))
				(snow-layer (car (gimp-layer-new image width height RGBA-IMAGE "Snow" opacity mode))) ; Create a snow layer
				(double-layer 0)
				)
				(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* (
						(actual-row 0)														; Start row of paintbrush up the image
						(mid-size (/ actual-size 2))										; Set mid-size as half the actual flakes size
						(scrolling (round (/ actual-size step)))							; Set scrolling number of pixel between two layers
						(nb-row (+ (truncate (/ height actual-size)) 3))					; Set number of paintbrush made in the image height
						(nb-brush (+ (truncate (/ width actual-size)) 2 (abs wind-force)))	; Set number of paintbrush made in the image width
						(ypos (- (* (+ actual-frame actual-layer -1) scrolling) mid-size actual-size))	; Set y position of paintbrush more than up the image
						(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 (+ (* (+ actual-frame actual-layer -1) scrolling wind-force) mid-size))
								)
								(if (> wind-force 0)
								 (set! xpos (- xpos (* actual-size wind-force) actual-size))		; 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
									(and (> ypos (- 0 mid-size)) (< ypos (+ height mid-size))		; Draw only if the brush is in image
										(and  (> xpos (- 0 mid-size)) (< xpos (+ width mid-size))
											(begin
												(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 rainy-angle) 0 0)	; Apply kinetic blur for rainy effect
				(if (= harden-flakes TRUE)
					(begin																	; Harden flakes layer duplicate himself
						(set! double-layer (car (gimp-layer-copy snow-layer TRUE)))				; Create double layer as copy of the snow layer
						(gimp-image-insert-layer image double-layer 0 -1)						; Insert the double layer in the image
						(set! snow-layer (car (gimp-image-merge-down image double-layer 0)))	; Merge down the double layer on snow layer
					)
				)
				(set! base-layer (car (gimp-image-merge-down image snow-layer 0)))			; Merge down snow layer on base layer
				(if (= reverse-anim TRUE)													; Send base layer to bottom to reverse the animation
					(gimp-image-lower-item-to-bottom image 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-brush-angle saved-brush-angle); Restore the previous brush angle
		(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
		(if (= open-preview TRUE)
			(plug-in-animationplay RUN-NONINTERACTIVE image drawable))	; Display the preview of the animated image
	)
)

(script-fu-register "script-fu-falling-snow-or-other-brush-anim"
  _"_Falling snow or other brush..."
  _"Create a multi-frames image with a falling snow (or other brush) effect in all image or selected parts."
  "Jean-Marc Chatain (jimcha369@gmail.com)"
  "Jean-Marc Chatain"
  "April 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 15 1 1 0 0)		; Set layers-number of flakes : from 1 to 15 layers of flakes
  SF-ADJUSTMENT _"Size difference"		'(1 1 2 1 1 0 0)		; Set difference of flake size between two layers of flakes
  SF-ADJUSTMENT _"First brush size"		'(3 1 15 1 1 0 0)		; Set first size of flakes : from 1 smallest to 15 biggest
  SF-ADJUSTMENT _"First brush angle"	'(0 -180 180 1 1 0 0)	; Set first angle of brush : from -180 to 180
  SF-BRUSH		_"Brush name"			'("Bristles 03" 100 3 0); Set the brush name in the text box
  SF-COLOR		_"Brush color"			'(255 255 255)			; Set the brush color in the color selector
  SF-ADJUSTMENT _"Brush rotation\n-1=left 0=no 1=right"		'(-1 -1 1 1 1 0 0)		; Set brush rotation : -1 = left, 0 = no, 1 = right
  SF-ADJUSTMENT _"Wind force\n-3-2-1=left 0=no 1 2 3=right"	'(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 30 1 1 0 0)		; Set kinetic blur level to give rainy effect
  SF-ADJUSTMENT _"Rainy angle"			'(0 -90 90 1 1 0 0)		; Set the angle of the rainy blur 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"	; Set the layer mode in the long list of different displays
  "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")
  SF-TOGGLE _"Harden the layers of flakes"		FALSE		; Set if duplicate the flakes layers to harden them
  SF-TOGGLE _"Reverse the animation"			FALSE		; Set to reverse the animation after build it
  SF-TOGGLE _"Open animation window at end"		TRUE		; Set if animation window open or no in the end of script
)

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