|
| 1 | +import math |
| 2 | +from datetime import datetime |
| 3 | +from pathlib import Path |
| 4 | + |
| 5 | +from pil_utils import BuildImage |
| 6 | + |
| 7 | +from meme_generator import add_meme |
| 8 | +from meme_generator.utils import FrameAlignPolicy, Maker, make_gif_or_combined_gif |
| 9 | + |
| 10 | +img_dir = Path(__file__).parent / "images" |
| 11 | + |
| 12 | + |
| 13 | +def backflip(images: list[BuildImage], texts, args): |
| 14 | + img_w, img_h = images[0].size |
| 15 | + length = math.sqrt(img_w * img_w + img_h * img_h) |
| 16 | + frame_w = int(length * 1.3) |
| 17 | + bounce_h = img_h * 1.2 |
| 18 | + frame_h = int(bounce_h + length / 2 + img_h * 0.6) |
| 19 | + center_x = frame_w / 2 |
| 20 | + ground_y = frame_h - img_h / 2 |
| 21 | + |
| 22 | + total_frames = 30 |
| 23 | + bounce1_range = (0.0, 0.3) |
| 24 | + bounce2_range = (0.3, 0.6) |
| 25 | + rise_range = (0.6, 0.65) |
| 26 | + flip_range = (0.65, 0.95) |
| 27 | + land_range = (0.95, 1.0) |
| 28 | + |
| 29 | + def maker(i: int) -> Maker: |
| 30 | + def make(imgs: list[BuildImage]) -> BuildImage: |
| 31 | + t = i / total_frames |
| 32 | + |
| 33 | + if bounce1_range[0] <= t < bounce1_range[1]: |
| 34 | + local_t = (t - bounce1_range[0]) / (bounce1_range[1] - bounce1_range[0]) |
| 35 | + y = -4 * bounce_h * (local_t - 0.5) ** 2 + bounce_h |
| 36 | + rot = 45 * (1 - 2 * abs(local_t - 0.5)) |
| 37 | + elif bounce2_range[0] <= t < bounce2_range[1]: |
| 38 | + local_t = (t - bounce2_range[0]) / (bounce2_range[1] - bounce2_range[0]) |
| 39 | + y = -4 * bounce_h * (local_t - 0.5) ** 2 + bounce_h |
| 40 | + rot = -45 * (1 - 2 * abs(local_t - 0.5)) |
| 41 | + elif rise_range[0] <= t < rise_range[1]: |
| 42 | + local_t = (t - rise_range[0]) / (rise_range[1] - rise_range[0]) |
| 43 | + y = bounce_h - bounce_h * (1 - local_t) ** 2 |
| 44 | + rot = 0.0 |
| 45 | + elif flip_range[0] <= t < flip_range[1]: |
| 46 | + local_t = (t - flip_range[0]) / (flip_range[1] - flip_range[0]) |
| 47 | + y = bounce_h |
| 48 | + rot = 360 * local_t |
| 49 | + elif land_range[0] <= t < land_range[1]: |
| 50 | + local_t = (t - land_range[0]) / (land_range[1] - land_range[0]) |
| 51 | + y = bounce_h - bounce_h * local_t**2 |
| 52 | + rot = 0.0 |
| 53 | + else: |
| 54 | + y = ground_y |
| 55 | + rot = 0.0 |
| 56 | + |
| 57 | + frame = BuildImage.new("RGBA", (frame_w, frame_h)) |
| 58 | + img = imgs[0].convert("RGBA").rotate(-rot, expand=True) |
| 59 | + frame.paste( |
| 60 | + img, |
| 61 | + (int(center_x - img.width / 2), int(ground_y - y - img.height / 2)), |
| 62 | + alpha=True, |
| 63 | + ) |
| 64 | + return frame |
| 65 | + |
| 66 | + return make |
| 67 | + |
| 68 | + return make_gif_or_combined_gif( |
| 69 | + images, maker, total_frames, 0.05, FrameAlignPolicy.extend_loop |
| 70 | + ) |
| 71 | + |
| 72 | + |
| 73 | +add_meme( |
| 74 | + "backflip", |
| 75 | + backflip, |
| 76 | + min_images=1, |
| 77 | + max_images=1, |
| 78 | + keywords=["后空翻"], |
| 79 | + date_created=datetime(2025, 6, 29), |
| 80 | + date_modified=datetime(2025, 6, 29), |
| 81 | +) |
0 commit comments