Skip to content

Instantly share code, notes, and snippets.

@mon
Created January 12, 2020 11:42
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mon/df669357a299e38ff282e08e3d8e5b7d to your computer and use it in GitHub Desktop.
Save mon/df669357a299e38ff282e08e3d8e5b7d to your computer and use it in GitHub Desktop.
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