File size: 2,069 Bytes
fcc874d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
"""Render the zoom levels into one continuous zoom clip.

Each segment pushes in on level i until its frame is exactly the blurry crop that level i+1
was made from, then holds still and cross-fades blurry into sharp. The hold is the point:
it is where the viewer sees what the model actually added.
"""
import tempfile

import imageio.v2 as imageio
import numpy as np
from PIL import Image

from geometry import window_rect

FPS = 30
PUSH_FRAMES = 30
FADE_FRAMES = 14
TAIL_FRAMES = 24


def _ease(t):
    return t * t * (3 - 2 * t)


def _lerp_rect(a, b, t):
    return tuple(round(p + (q - p) * t) for p, q in zip(a, b))


def _frames(levels, upscale, center):
    size = levels[0].size
    full = (0, 0, size[0], size[1])
    for i in range(len(levels) - 1):
        target = window_rect(size, upscale, center)
        for f in range(PUSH_FRAMES):
            # f / (PUSH_FRAMES - 1), not f / PUSH_FRAMES: the last frame has to land exactly on
            # `target`, which is the crop the next level was built from. Otherwise it jumps.
            rect = _lerp_rect(full, target, _ease(f / (PUSH_FRAMES - 1)))
            yield levels[i].crop(rect).resize(size, Image.BICUBIC)
        blurry = np.asarray(levels[i].crop(target).resize(size, Image.BICUBIC), dtype=np.float32)
        sharp = np.asarray(levels[i + 1], dtype=np.float32)
        for f in range(FADE_FRAMES):
            a = _ease((f + 1) / FADE_FRAMES)
            yield Image.fromarray((blurry * (1 - a) + sharp * a).astype(np.uint8))
    for _ in range(TAIL_FRAMES):
        yield levels[-1]


def render(levels, upscale=4, center=(0.5, 0.5)):
    """Write an mp4 of the zoom and return its path. Needs at least two levels."""
    if len(levels) < 2:
        return None
    path = tempfile.mkstemp(suffix=".mp4")[1]
    with imageio.get_writer(path, fps=FPS, codec="libx264", quality=8,
                            pixelformat="yuv420p", macro_block_size=1) as out:
        for frame in _frames(levels, upscale, center):
            out.append_data(np.asarray(frame.convert("RGB")))
    return path