Skip to content

Instantly share code, notes, and snippets.

@nitori
Created June 2, 2023 18:04
Show Gist options
  • Select an option

  • Save nitori/3257fa1fac5e007afa1a30af91f4036a to your computer and use it in GitHub Desktop.

Select an option

Save nitori/3257fa1fac5e007afa1a30af91f4036a to your computer and use it in GitHub Desktop.
from __future__ import annotations
from PIL import Image
import numpy as np
def ease_in_out_quad(t):
return t * t * (3.0 - 2.0 * t)
def find_coeffs(pa, pb):
matrix = []
for p1, p2 in zip(pa, pb):
matrix.append([p1[0], p1[1], 1, 0, 0, 0, -p2[0] * p1[0], -p2[0] * p1[1]])
matrix.append([0, 0, 0, p1[0], p1[1], 1, -p2[1] * p1[0], -p2[1] * p1[1]])
A = np.matrix(matrix, dtype=np.float64)
B = np.array(pb).reshape(8)
res = np.dot(np.linalg.inv(A.T * A) * A.T, B)
return np.array(res).reshape(8)
def transform(startpoints, endpoints, im):
width, height = im.size
coeffs = find_coeffs(endpoints, startpoints)
im = im.transform((width, height), Image.PERSPECTIVE, coeffs, Image.BICUBIC, fillcolor=(0, 0, 0, 0))
return im
def transition(start, stop, t: float):
start = np.array(start)
stop = np.array(stop)
return start + (stop - start) * ease_in_out_quad(t)
def main():
im = Image.open('pudding-original.webp')
im = im.convert('RGBA')
bg = Image.new('RGBA', im.size, (0, 0, 0, 0))
bg.paste(im, mask=im)
im = bg
width, height = im.size
positions = {
'normal': [(0, 0), (width, 0), (width, height), (0, height)],
'flat': [(0, height * 0.2), (width, height * 0.2), (width, height), (0, height)],
'flat-wide': [(-width * 0.1, height * 0.3), (width + width * 0.1, height * 0.3), (width, height), (0, height)],
'right': [(width * 0.3, 0), (width + width * 0.05, 0), (width, height), (0, height)],
'left': [(-width * 0.05, 0), (width - width * 0.3, 0), (width, height), (0, height)],
}
frames = []
steps = [
('flat', 4),
('flat-wide', 3),
('left', 5),
('flat', 4),
('flat-wide', 3),
('right', 5),
('flat', 4),
]
total_time = sum(step[1] for step in steps)
nb_frames = 50
acc_steps = []
acc = 0
for name, weight in steps:
acc += weight
acc_steps.append((name, acc / total_time))
print(f'{width=}\n{height=}')
print()
for key, position in positions.items():
print(f'{key:>10}:', [f'{v[0]:7.3f}, {v[1]:7.3f}' for v in position])
base_step = acc_steps[0][1]
# adjust steps to start at 0 but keep the last one at 1.0
range_values = 1.0 - base_step
acc_steps = [(a, (t - base_step) / range_values) for a, t in acc_steps]
print()
print(*acc_steps, sep='\n')
print()
for i in range(nb_frames):
t = i / nb_frames
for (a1, t_step1), (a2, t_step2) in zip(acc_steps, acc_steps[1:]):
if t_step1 <= t <= t_step2:
ratio = (t - t_step1) / (t_step2 - t_step1)
values = transition(positions[a1], positions[a2], ratio)
frame = transform(positions['normal'], values, im)
frames.append(frame)
print(
f'{a1:<10} {a2:<10} {ratio=:.4f} {t_step1=:.3f} {t_step2=:.3f} ',
[f'{v[0]:7.3f}, {v[1]:7.3f}' for v in values]
)
break
print({f.size for f in frames})
frames[0].save('pudding.gif', save_all=True, append_images=frames[1:], duration=20, loop=0, disposal=2)
if __name__ == '__main__':
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment