Created
January 12, 2020 11:42
-
-
Save mon/df669357a299e38ff282e08e3d8e5b7d to your computer and use it in GitHub Desktop.
Very slow and very messy floodfill animation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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