Skip to content

Instantly share code, notes, and snippets.

@mon
Created January 12, 2020 11:42
Very slow and very messy floodfill animation
import os
from statistics import mean
from PIL import Image
from tqdm import tqdm
# render frames with
# ffmpeg -framerate 60 -pattern_type glob -i '*.png' -vf "pad=ceil(iw/2)*2:ceil(ih/2)*2" -c:v libx264 -pix_fmt yuv420p out.mp4
source_img = 'tfoust10.jpg'
dest_dir = "frames"
threshold = 160 # out of 255
start_x, start_y = (574, 527)
# animation specification
fps = 60
desired_secs = 30
last_frame_hold_secs = 7
frame_i = 0
skip = -1
# skip_interval is auto-calculated below
def save_frame(im):
global frame_i
global skip
skip += 1
if skip % skip_interval != 0:
return
fname = f'frame_{frame_i:04d}.png'
tqdm.write(f'Saving {fname}')
im.save(os.path.join(dest_dir, fname), compress_level=1)
frame_i += 1
def visit(x, y, width, height, pixels, orig_pixels, im, to_visit, to_fade, visited, start_avg):
global skip_interval
tqdm.write(f'visiting {x}, {y}')
if x < 0 or y < 0 or x >= width or y >= height:
return False
# drop white pixels
avg = mean(orig_pixels[x,y])
if abs(start_avg - avg) > threshold:
# restore original colour
pixels[x,y] = orig_pixels[x,y]
return False
# list so we can pass-by-ref later
to_fade[(x,y)] = [256 * skip_interval * 2]
for dx, dy in ((-1,0), (0, -1), (1, 0), (0, 1)):
new_x = x + dx
new_y = y + dy
if (new_x, new_y) not in visited:
visited.add((new_x,new_y))
to_visit.append((new_x, new_y))
return True
def update_fade(pixels, to_fade):
global skip_interval
to_drop = []
for (x,y), val in to_fade.items():
# pass-by-ref for python
if val[0] > 0:
val[0] -= 32
v = val[0] // skip_interval
if v > 255:
v //= 2
pixels[x, y] = (255-v, 255, 0)
else:
pixels[x, y] = (255, v, 0)
else:
to_drop.append((x,y))
for drop in to_drop:
to_fade.pop(drop)
if not os.path.isdir(dest_dir):
os.mkdir(dest_dir)
im = Image.open(source_img)
orig_im = im.copy()
width, height = im.size
pixels = im.load()
orig_pixels = orig_im.load()
start_colour = pixels[start_x, start_y]
start_avg = mean(start_colour)
total_frames = 0
tqdm.write('Determining frame count')
for x in tqdm(range(width)):
for y in range(height):
if abs(start_avg - mean(pixels[x,y])) < threshold:
total_frames += 1
desired_frames = fps*desired_secs
skip_interval = total_frames // desired_frames if total_frames > desired_frames else total_frames
visited = set((start_x, start_y))
to_visit = list()
to_fade = dict()
visit(start_x, start_y, width, height, pixels, orig_pixels, im, to_visit, to_fade, visited, start_avg)
with tqdm(total=total_frames) as progress:
while True:
try:
update_fade(pixels, to_fade)
next_x, next_y = to_visit.pop(0)
if visit(next_x, next_y, width, height, pixels, orig_pixels, im, to_visit, to_fade, visited, start_avg):
save_frame(im)
progress.update()
except IndexError:
break
# don't make people wait a ton of time and then blueball them on the final result
last_frame_duplicate = last_frame_hold_secs * fps * skip_interval
for _ in tqdm(range(last_frame_duplicate), desc='Duplicating final frame'):
update_fade(pixels, to_fade)
save_frame(im)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment