Files
ReelShort/tools/generate_brand_assets.py
T

146 lines
5.3 KiB
Python

# Author: fruit.com.cn
"""使用确认的 ymb 原图生成图标、启动标识及原生平台尺寸。"""
import json
from pathlib import Path
from PIL import Image, ImageChops, ImageDraw
ROOT = Path(__file__).resolve().parents[1]
ASSETS = ROOT / "assets/icon"
IOS = ROOT / "ios/Runner/Assets.xcassets"
ANDROID = ROOT / "android/app/src/main/res"
REFERENCE = ROOT / "assets/branding/ymb-reference.png"
LAUNCH_REFERENCE = ROOT / "assets/branding/ymb-launch-reference.png"
BRAND_BACKGROUND = "#000000"
WHITE = "#FFFFFF"
CLEAR = (0, 0, 0, 0)
MASTER_SIZE = 1024
SPLASH_SIZE = 80
SPLASH_RADIUS = 18
ANDROID12_SIZE = 288
FOREGROUND_SIZE = 108
LEGACY_SIZE = 48
PLACEHOLDER_SIZE = (600, 360)
LAUNCH_SIZE = (390, 844)
WHITE_THRESHOLD = 100
WHITE_RANGE = 145
DENSITIES = {"mdpi": 1, "hdpi": 1.5, "xhdpi": 2, "xxhdpi": 3, "xxxhdpi": 4}
RESAMPLE = Image.Resampling.LANCZOS
def wordmark(reference):
"""从原图白色字标提取透明前景,不重新绘制或改变字形。"""
channels = reference.convert("RGB").split()
lightness = ImageChops.darker(ImageChops.darker(*channels[:2]), channels[2])
alpha = lightness.point(
lambda value: max(0, min(255, round(
(value - WHITE_THRESHOLD) * 255 / WHITE_RANGE
)))
)
image = Image.new("RGBA", reference.size, WHITE)
image.putalpha(alpha)
return image
def centered(canvas, mark):
"""将标识按可见边界居中,不包含字体基线额外留白。"""
origin = ((canvas.width - mark.width) // 2, (canvas.height - mark.height) // 2)
canvas.alpha_composite(mark, origin)
return canvas
def scaled_wordmark(mark, width):
"""等比缩放字标,所有输出共用同一字形。"""
height = round(mark.height * width / mark.width)
return mark.resize((width, height), RESAMPLE)
def splash_icon(master, size):
"""仅启动图烘焙圆角;桌面图标保留系统负责的裁切。"""
image = master.resize((size, size), RESAMPLE).convert("RGBA")
mask = Image.new("L", (MASTER_SIZE, MASTER_SIZE))
radius = MASTER_SIZE * SPLASH_RADIUS / SPLASH_SIZE
ImageDraw.Draw(mask).rounded_rectangle(
(0, 0, MASTER_SIZE - 1, MASTER_SIZE - 1), radius=radius, fill=255
)
image.putalpha(mask.resize((size, size), RESAMPLE))
return image
def save(image, target):
"""写入明确路径的生成资源,不清理或删除目录。"""
target.parent.mkdir(parents=True, exist_ok=True)
image.save(target, optimize=True)
def ios_assets(master):
"""严格按资源目录声明导出图标;开屏保留确认的完整参考图。"""
app_icons = IOS / "AppIcon.appiconset"
manifest = json.loads((app_icons / "Contents.json").read_text())
for entry in manifest["images"]:
points = float(entry["size"].split("x")[0])
scale = int(entry["scale"].removesuffix("x"))
pixels = round(points * scale)
save(master.resize((pixels, pixels), RESAMPLE), app_icons / entry["filename"])
for scale in (1, 2, 3):
suffix = "" if scale == 1 else f"@{scale}x"
size = tuple(value * scale for value in LAUNCH_SIZE)
image = Image.open(LAUNCH_REFERENCE).convert("RGB").resize(size, RESAMPLE)
save(image, IOS / f"LaunchImage.imageset/LaunchImage{suffix}.png")
def android_assets(master, foreground):
"""覆盖现有密度资源,并为 Android 12 提供独立安全区标识。"""
for density, scale in DENSITIES.items():
pixels = round(LEGACY_SIZE * scale)
save(
master.resize((pixels, pixels), RESAMPLE),
ANDROID / f"mipmap-{density}/ic_launcher.png",
)
pixels = round(FOREGROUND_SIZE * scale)
save(
foreground.resize((pixels, pixels), RESAMPLE),
ANDROID / f"drawable-{density}/ic_launcher_foreground.png",
)
save(
Image.new("RGB", (pixels, pixels), BRAND_BACKGROUND),
ANDROID / f"mipmap-{density}/ic_launcher_background.png",
)
mark = splash_icon(master, round(SPLASH_SIZE * scale))
save(mark, ANDROID / f"drawable-{density}/splash_screen.png")
size = round(ANDROID12_SIZE * scale)
image = centered(Image.new("RGBA", (size, size), CLEAR), mark)
save(image, ANDROID / f"drawable-{density}/android12splash.png")
def main():
"""直接复用确认的原始图标和开屏,输出平台所需尺寸。"""
reference = Image.open(REFERENCE).convert("RGB")
mark = wordmark(reference)
master = centered(
Image.new("RGBA", (MASTER_SIZE, MASTER_SIZE), (0, 0, 0, 255)),
mark.resize((MASTER_SIZE, MASTER_SIZE), RESAMPLE),
)
foreground = mark.resize((MASTER_SIZE, MASTER_SIZE), RESAMPLE)
save(master.convert("RGB"), ASSETS / "ymb.png")
save(foreground, ASSETS / "ymb_foreground.png")
placeholder = centered(
Image.new("RGBA", PLACEHOLDER_SIZE, CLEAR),
scaled_wordmark(mark.crop(mark.getbbox()), PLACEHOLDER_SIZE[0]),
)
save(placeholder, ASSETS / "splash_logo.png")
icon = splash_icon(master, SPLASH_SIZE * 4)
size = ANDROID12_SIZE * 4
save(
centered(Image.new("RGBA", (size, size), CLEAR), icon),
ASSETS / "ymb_android12.png",
)
ios_assets(master)
android_assets(master, foreground)
print("Generated ymb assets: master 1024, iOS catalog, Android 5 densities.")
if __name__ == "__main__":
main()