feat(brand): 应用图标、开屏资源与品牌字体切换为 ymb
This commit is contained in:
1 parent
26321e3348
commit
22cc669bbe
71 files changed
+489
-114
No files matched your search
@@ -0,0 +1,142 @@
|
||||
# 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()
|
||||
Reference in new issue
Block a user