I have entered this year’s Gifitup with an animation based on a Japanese woodblock print Shinobugaoka no tsuki-Gyokuensai, partly generated using Pillow and NumPy. Animating with Python allowed me to experiment quickly with different framerates and step sizes.

Samurai avoiding falling cherry blossom

Manual editing in GIMP

The animation is derived from three images. The cherry blossom, the background, and a mask to show where the blossom should not fall.

The first step was to separate the layers using GIMP. The blossom on the relatively plain background could be selected using a separate layer, edge detect and posterize. On the busier backgrounds, I used a combination of intelligent scissors and the eraser.

After this, I deleted the blossom from the background using the clone tool. Finally, I created a mask covering the border and labels.

cherry blossom on a transparent background Samurai avoiding absent cherry blossomBlack and white mask, covering the border and labels

Animating

In order to make an animated gif, you need an image for the first frame, and an iterable of the remaining frames, thus:

frames[0].save('output.gif', format='GIF', append_images=frames[1:], save_all=True, duration=150, loop=0)

The frames are generated by this function. fgarray is a numpy array representation of the foreground:

def create_frame(offsets, offset):
    pixel_offset = int(fg.size[1] * offset / offsets)
    frame_bg = bg.copy()
    frame_fg = Image.fromarray(numpy.roll(fgarray, pixel_offset, axis=0))
    frame_fg_positioned = Image.new('RGBA', size)
    frame_fg_positioned.paste(frame_fg, mask=mask)
    frame_bg.alpha_composite(frame_fg_positioned)
    return frame_bg

numpy.roll does the work of moving the blossom across the scene, by shifting rows off the bottom and putting them back in at the top.

The use of a separate image to paste the foreground onto allows masking of a layer with transparency. You cannot paste a transparent layer onto the background directly, the transparency does not work. Instead, you have to use alpha_composite, but that lacks the features of paste (masking, positioning). To get around this, you can paste the layer with transparency onto an empty image, then alpha_composite that onto the background.

Further work

This is the whole script I used to generate the animated gif. It’s not pretty, but it did the job. One day, I may come back to turn it into something a bit more professional.

A better effect might come from animating along a zigzag path, animating the petals individually.

import sys
from PIL import Image
import numpy


def format_for_gifitup(img):
    """
    Make it small enough to conform to the gifitup rules (450px wide, < 2MB)
    """
    output_width = 400
    wpercent = (output_width/float(img.size[0]))
    hsize = int((float(img.size[1])*float(wpercent)))
    return img.resize((output_width, hsize), Image.ANTIALIAS)


bg = format_for_gifitup(Image.open(sys.argv[1]))
fg = format_for_gifitup(Image.open(sys.argv[2]))
mask = format_for_gifitup(Image.open(sys.argv[3]).convert('1')) if len(sys.argv) > 3 else None
fgarray = numpy.array(fg)
size = bg.size


def create_frame(offsets, offset):
    pixel_offset = int(fg.size[1] * offset / offsets)
    frame_bg = bg.copy()
    frame_fg = Image.fromarray(numpy.roll(fgarray, pixel_offset, axis=0))
    frame_fg_positioned = Image.new('RGBA', size)
    frame_fg_positioned.paste(frame_fg, mask=mask)
    frame_bg.alpha_composite(frame_fg_positioned)
    return frame_bg


frames = [create_frame(10, offset) for offset in range(10)]

frames[0].save('snowfall.gif', format='GIF', append_images=frames[1:], save_all=True, duration=150, loop=0)