Hi all,
I have created a GIMP python script that creates a looped snow animation on an existing animation:
Lots of things can be customized: the number of frames, the number of flakes, the min to max speed, the min to max wind, the min to max rotation, the min to max flake size, the area of fall and the shape, color and opacity of the flakes.
You can render any type of particles like snowflakes, ash, sparks. If you set a negative fall, the particles will raise. You can apply the effect several times to create different types of particles but you have to save, quit GIMP, restart it and reopen your project.
You first have to create all the frames with the background. Otherwise, it will prompt you to do so.
Here is the Python code:
#!/usr/bin/env python
from gimpfu import *
import random
import gtk
class ModeDialog(gtk.Window):
def __init__ (self):
self.w, self.h = 0, 0
ret = gtk.Window.__init__(self)
vbox = gtk.VBox(False, 0)
self.add(vbox)
btn = gtk.Button("You are supposed to create all the layers for the animation before. Only one layer found. How many frames you want?")
btn.connect("clicked", self.disappear)
vbox.pack_start(btn, False, False, 0)
btn.show()
adjustment = gtk.Adjustment(50, 0, 10000, 1, 5, 0)
self.spinbutton = gtk.SpinButton(adjustment)
vbox.pack_start(self.spinbutton, False, False, 0)
self.spinbutton.show()
closeButton = gtk.Button("close", gtk.STOCK_CLOSE)
closeButton.connect("clicked", self.disappear)
vbox.pack_start(closeButton, False, False, 0)
closeButton.show()
vbox.show()
self.show()
return ret
def disappear(self, widget) :
gtk.main_quit()
return False
def snowflake(image, drawable, flake_number, brush_name, color, opacity, flake_size_1, flake_size_2, flake_fall_1, flake_fall_2, relation, wind_speed_1, wind_speed_2, wind_change_1, wind_change_2, angle_1, angle_2, position_1, position_2):
# Get the list of layers in the image
layers = image.layers
if len(layers) == 1:
r = ModeDialog()
gtk.main()
# Get the top-level layer
top_layer = image.layers[0]
# Duplicate the layer x times
pdb.gimp_item_set_visible(top_layer, True)
for i in range(r.spinbutton.get_value_as_int() - 1):
duplicate = pdb.gimp_layer_new_from_drawable(top_layer, image)
pdb.gimp_image_insert_layer(image, duplicate, None, (i + 1) * 2)
layers = image.layers
pdb.gimp_progress_init("Start adding snow...", None)
pdb.gimp_context_push()
# Translate parameters
flake_size_min = min(flake_size_1, flake_size_2)
flake_size_max = max(flake_size_1, flake_size_2)
flake_fall_min = min(flake_fall_1, flake_fall_2)
flake_fall_max = max(flake_fall_1, flake_fall_2)
wind_speed_min = min(wind_speed_1, wind_speed_2)
wind_speed_max = max(wind_speed_1, wind_speed_2)
wind_change_min = min(wind_change_1, wind_change_2)
wind_change_max = max(wind_change_1, wind_change_2)
angle_min = min(angle_1, angle_2)
angle_min = max(angle_min, -180)
angle_max = max(angle_1, angle_2)
angle_max = min(angle_max, 180)
position_1 = min(position_1, image.width)
position_1 = max(position_1, -image.width)
position_1 = position_1 % image.width
position_2 = min(position_2, image.width)
position_2 = max(position_2, -image.width)
position_2 = position_2 % image.width
position_min = min(position_1, position_2)
position_max = max(position_1, position_2)
if (position_min == 0) and (position_max == 0):
position_min = 0
position_max = image.width
# Group actions as a whole
pdb.gimp_image_undo_group_start(image)
# Select the paint tool
pdb.gimp_context_set_brush(str(brush_name))
pdb.gimp_context_set_foreground(color)
pdb.gimp_context_set_opacity(opacity)
# Loop through each layer and paint the point
for i in range(flake_number):
pdb.gimp_progress_update((i * 1.0) / flake_number)
pdb.gimp_progress_set_text("Rendering flake #" + str(i + 1) + " over " + str(flake_number) + "...")
flake_size = max(random.uniform(flake_size_min, flake_size_max), 1)
flake_fall = random.uniform(flake_fall_min, flake_fall_max)
# Relate to flake size
flake_fall = ((flake_fall - flake_fall_min) * (1 - relation) + ((flake_size - flake_size_min) * relation * (flake_fall_max - flake_fall_min) / (flake_size_max - flake_size_min))) + flake_fall_min
if flake_fall == 0:
flake_fall = 1
wind_speed = random.uniform(wind_speed_min, wind_speed_max)
wind_change = -1
angle = random.uniform(angle_min, angle_max)
# A flake can fall from any frame
j = random.randint(0, len(layers) - 1)
# Flake coodinates
x = position_min + (flake_size / 2 + (((i + 0.5) * ((position_max - position_min) + flake_size)) / flake_number))
if 0 < flake_fall:
y = 0 - flake_size / 2
else:
y = image.height + flake_size / 2
# Set the brush size
pdb.gimp_context_set_brush_size(flake_size)
pdb.gimp_context_set_brush_angle(angle)
# We paint a flake till the flake is out of scope
while ((0 < flake_fall) and (y < image.height + flake_size / 2)) or ((flake_fall <= 0) and (0 - flake_size / 2 < y)):
if wind_change < 0:
wind_change = random.randint(wind_change_min, wind_change_max)
new_wind_speed = random.uniform(wind_speed_min, wind_speed_max)
# The wind should not change too quickly
new_wind_speed = min(new_wind_speed, wind_speed + 1)
new_wind_speed = max(new_wind_speed, wind_speed - 1)
wind_speed = new_wind_speed
else:
wind_change -= 1
layer = layers[j]
# Make the layer active
pdb.gimp_image_set_active_layer(image, layer)
# Paint the point
pdb.gimp_paintbrush_default(layer, 2, [x, y])
new_angle = pdb.gimp_context_get_brush_angle() + angle
# More than 360 degrees is useless (and forbidden)
new_angle = ((new_angle + 180) % 360) - 180
pdb.gimp_context_set_brush_angle(new_angle)
# Flake new coodinates
x += wind_speed
if x < 0 - flake_size / 2:
x = image.width + flake_size / 2
if image.width + flake_size / 2 < x:
x = 0 - flake_size / 2
y += flake_fall
j -= 1
if j < 0:
j = len(layers) - 1
pdb.gimp_context_pop()
pdb.gimp_image_undo_group_end(image)
# Update the image display
gimp.displays_flush()
pdb.gimp_progress_end()
pdb.plug_in_animationplay(image, drawable)
# Register the GIMP Python-fu command
register(
"python_fu_snowflake",
"Adds a snowflake fall effect on an existing animation",
"Adds a snowflake fall effect on an existing animation",
"Fabrice TIERCELIN",
"Fabrice TIERCELIN",
"2023",
"<Image>/Filters/Animation/Snowflake fall on animation",
"*",
[
(PF_INT, "flake_number", "_Number of flakes that appear during the animation", 200),
(PF_BRUSH, "brush_name", "Flake _brush", None),
(PF_COLOR, "color", "Flake _color", (255, 255, 255)),
(PF_SLIDER, "opacity", "Flake _opacity (0=transparent; 100=opaque)", 65, (1, 100, 1)),
(PF_FLOAT, "flake_size_1", "Flake from this _size (in pixels)", 1.1),
(PF_FLOAT, "flake_size_2", "...to this si_ze (in pixels)", 10.5),
(PF_FLOAT, "fall_per_frame_1", "_Fall from this speed (pixels per frame)", 0.5),
(PF_FLOAT, "fall_per_frame_2", "..._to this speed (pixels per frame)", 10.5),
(PF_SLIDER, "relation", "_Relation between size and fall (0=not related; 1=analogous)", 0.85, (0, 1, 0.01)),
(PF_FLOAT, "wind_speed_1", "_Wind from this force (in pixels, negative for left, positive for right)", -2.5),
(PF_FLOAT, "wind_speed_2", "...to t_his force (in pixels, negative for left, positive for right)", 2.5),
(PF_INT, "wind_change_1", "Win_d change from this time (frame number)", 1),
(PF_INT, "wind_change_2", "...to th_is time (frame number)", 10),
(PF_FLOAT, "angle_1", "Rotation from this _angle (in degrees)", -20.5),
(PF_FLOAT, "angle_2", "...to this an_gle (in degrees)", 20.5),
(PF_FLOAT, "position_1", "Falling from this _position (in pixels)", 0),
(PF_FLOAT, "position_2", "...to this position (in pixels)", 0),
],
[],
snowflake)
main()
β¦or download this file:
snowflake.py.txt (8.1 KB)
- Rename the file
snowflake.py
- Put it on your
C:\Users\YourName\AppData\Roaming\GIMP\2.10\plug-ins
folder - Start GIMP
- Open an image
- Create as many frames as necessary for your animation
- Go on Filter β Animation β Snowflake fall on an animation
- Launch the script
The animation is done.
Successfully tested on GIMP 2.10.10 on Windows 10.