Created
June 2, 2023 18:04
-
-
Save nitori/3257fa1fac5e007afa1a30af91f4036a to your computer and use it in GitHub Desktop.
This file contains hidden or 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
| 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