agent-foundation/tools/build_jump_rope_assets.py
2026-07-28 15:03:21 +08:00

163 lines
7.1 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

from collections import deque
from pathlib import Path
from PIL import Image, ImageDraw, ImageEnhance, ImageFilter, ImageFont
OUT = Path(r"C:\Users\tangl\Desktop\Git\agent-foundation\output\jump_rope_video")
OUT.mkdir(parents=True, exist_ok=True)
W, H = 1920, 1080
FONT_PATH = r"C:\Windows\Fonts\msyh.ttc"
FONT_BOLD_PATH = r"C:\Windows\Fonts\msyhbd.ttc"
def font(size: int, bold: bool = False):
return ImageFont.truetype(FONT_BOLD_PATH if bold else FONT_PATH, size)
def cover(img: Image.Image, size=(W, H)) -> Image.Image:
img = img.convert("RGB")
ratio = max(size[0] / img.width, size[1] / img.height)
resized = img.resize((round(img.width * ratio), round(img.height * ratio)), Image.Resampling.LANCZOS)
x = (resized.width - size[0]) // 2
y = (resized.height - size[1]) // 2
return resized.crop((x, y, x + size[0], y + size[1]))
def fit(img: Image.Image, size, bg=(0, 0, 0)) -> Image.Image:
img = img.convert("RGBA")
ratio = min(size[0] / img.width, size[1] / img.height)
resized = img.resize((round(img.width * ratio), round(img.height * ratio)), Image.Resampling.LANCZOS)
canvas = Image.new("RGBA", size, bg + (255,))
canvas.alpha_composite(resized, ((size[0] - resized.width) // 2, (size[1] - resized.height) // 2))
return canvas
def vignette(img: Image.Image, strength=0.42) -> Image.Image:
img = img.convert("RGB")
mask = Image.new("L", (W, H), 255)
px = mask.load()
cx, cy = W / 2, H / 2
maxd = (cx * cx + cy * cy) ** 0.5
for y in range(H):
for x in range(W):
d = (((x - cx) ** 2 + (y - cy) ** 2) ** 0.5) / maxd
px[x, y] = int(255 * max(1 - strength * d * d, 0.45))
black = Image.new("RGB", (W, H), "black")
return Image.composite(img, black, mask)
def add_caption(img: Image.Image, headline: str, subline: str, align="left") -> Image.Image:
img = img.convert("RGBA")
overlay = Image.new("RGBA", (W, H), (0, 0, 0, 0))
d = ImageDraw.Draw(overlay)
if align == "left":
x, anchor = 118, "la"
else:
x, anchor = W - 118, "ra"
y = 735
d.rounded_rectangle((72, 690, W - 72, 970), radius=28, fill=(0, 13, 30, 92))
d.text((x, y), headline, font=font(64, True), fill=(232, 241, 62, 255), anchor=anchor)
d.text((x, y + 88), subline, font=font(36), fill=(242, 247, 250, 245), anchor=anchor)
d.line((x if align == "left" else x - 480, y - 30, x + 480 if align == "left" else x, y - 30), fill=(232, 241, 62, 220), width=4)
return Image.alpha_composite(img, overlay).convert("RGB")
def remove_connected_background(img: Image.Image) -> Image.Image:
rgba = img.convert("RGBA")
rgb = rgba.convert("RGB")
pix = rgb.load()
alpha = Image.new("L", rgb.size, 255)
apix = alpha.load()
seen = bytearray(rgb.width * rgb.height)
q = deque()
def bg_like(x, y):
r, g, b = pix[x, y]
return min(r, g, b) > 150 and max(r, g, b) - min(r, g, b) < 34
for x in range(rgb.width):
q.append((x, 0)); q.append((x, rgb.height - 1))
for y in range(rgb.height):
q.append((0, y)); q.append((rgb.width - 1, y))
while q:
x, y = q.popleft()
idx = y * rgb.width + x
if seen[idx]:
continue
seen[idx] = 1
if not bg_like(x, y):
continue
apix[x, y] = 0
if x: q.append((x - 1, y))
if x + 1 < rgb.width: q.append((x + 1, y))
if y: q.append((x, y - 1))
if y + 1 < rgb.height: q.append((x, y + 1))
alpha = alpha.filter(ImageFilter.GaussianBlur(1.2))
rgba.putalpha(alpha)
bbox = alpha.getbbox()
return rgba.crop(bbox) if bbox else rgba
landing = Image.open(r"C:\Users\tangl\.codex\generated_images\019f88f8-7dd3-7c53-8d9a-b3544a768a7e\exec-872cf44f-8972-429c-92a9-9a1946daa114.png")
bend = Image.open(r"C:\Users\tangl\.codex\generated_images\019f88f8-7dd3-7c53-8d9a-b3544a768a7e\exec-38051cbd-14da-49fc-a139-d5699968df0a.png")
side = Image.open(r"C:\Users\tangl\xwechat_files\wxid_tjrokx4gscnl21_4d57\temp\RWTemp\2026-07\afdb9db85f27cdd1cc9425d3ab16ec83.jpg")
front_l = Image.open(r"C:\Users\tangl\xwechat_files\wxid_tjrokx4gscnl21_4d57\temp\RWTemp\2026-07\a9fb3a735b799f5909ae993223228a9b.jpg")
front_r = Image.open(r"C:\Users\tangl\xwechat_files\wxid_tjrokx4gscnl21_4d57\temp\RWTemp\2026-07\b0479771abcc3bc2b77bb4953e6d6b7e.jpg")
sole = Image.open(r"C:\Users\tangl\xwechat_files\wxid_tjrokx4gscnl21_4d57\temp\RWTemp\2026-07\b2717ca770f0c7cfa0110af900a8a371.jpg")
hold_ref = Image.open(r"C:\Users\tangl\AppData\Local\Temp\codex-clipboard-bde03ba2-b96f-4712-99a2-c9807418603d.png")
end_ref = Image.open(r"C:\Users\tangl\AppData\Local\Temp\codex-clipboard-6858372d-d6d7-47b9-ac51-73f718e298eb.png")
frame1 = add_caption(vignette(cover(landing)), "轻盈落地 快速回弹", "前掌触地,蓄力即发")
frame1.save(OUT / "01_landing.jpg", quality=95)
frame2 = add_caption(vignette(cover(bend)), "混合飞织 超强透气", "超薄前掌,轻盈如风", "right")
frame2.save(OUT / "02_flex.jpg", quality=95)
# Dual-shoe product composition that reads as a synchronized bounce close-up.
bg = Image.new("RGB", (W, H), (10, 44, 69))
d = ImageDraw.Draw(bg)
for y in range(H):
t = y / H
color = (int(16 - 8 * t), int(75 - 25 * t), int(106 - 34 * t))
d.line((0, y, W, y), fill=color)
for x in (310, 790, 1270, 1750):
d.line((x, 0, x - 420, H), fill=(89, 142, 166), width=6)
left_shoe = fit(front_l, (820, 650), (232, 233, 229)).convert("RGB")
right_shoe = fit(front_r, (820, 650), (232, 233, 229)).convert("RGB")
bg.paste(left_shoe, (105, 150))
bg.paste(right_shoe, (995, 150))
frame3 = add_caption(vignette(bg, 0.32), "弹跳力 MAX", "Y型支撑结构|减震灵活")
frame3.save(OUT / "03_bounce.jpg", quality=95)
frame4 = add_caption(vignette(cover(side)), "为速度而生", "跳绳竞速鞋|轻、弹、稳", "right")
frame4.save(OUT / "04_side.jpg", quality=95)
frame5 = add_caption(vignette(cover(sole)), "全掌抓地", "紫色橡胶外底|快速启停")
frame5.save(OUT / "05_outsole.jpg", quality=95)
# Recreate the hand-held hero composition as a clean floating product panel over the supplied athlete backdrop.
hold_bg = ImageEnhance.Brightness(cover(hold_ref)).enhance(0.72).filter(ImageFilter.GaussianBlur(4))
hero = hold_bg.convert("RGBA")
panel_size = (1240, 620)
panel = fit(side, panel_size, (238, 238, 235))
mask = Image.new("L", panel_size, 0)
ImageDraw.Draw(mask).rounded_rectangle((0, 0, panel_size[0] - 1, panel_size[1] - 1), radius=34, fill=238)
panel.putalpha(mask)
shadow_mask = mask.filter(ImageFilter.GaussianBlur(24))
shadow = Image.new("RGBA", panel_size, (0, 0, 0, 0))
shadow.putalpha(shadow_mask.point(lambda a: int(a * 0.42)))
pos = ((W - panel_size[0]) // 2, 205)
hero.alpha_composite(shadow, (pos[0] + 16, pos[1] + 25))
hero.alpha_composite(panel, pos)
draw = ImageDraw.Draw(hero)
draw.text((W // 2, 880), "专业跳绳竞速鞋", font=font(52, True), fill="white", anchor="mm")
draw.text((W // 2, 952), "LIGHT · REBOUND · STABILITY", font=font(25), fill=(225, 234, 238), anchor="mm")
hero.convert("RGB").save(OUT / "06_hero.jpg", quality=95)
frame7 = fit(end_ref, (W, H), (0, 0, 0)).convert("RGB")
frame7.save(OUT / "07_end.jpg", quality=98)
print(OUT)