# 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_RED = "#E9142E" 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_RED), 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") master = reference.resize((MASTER_SIZE, MASTER_SIZE), RESAMPLE) mark = wordmark(reference) foreground = mark.resize((MASTER_SIZE, MASTER_SIZE), RESAMPLE) save(master, 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()