vita_gamepad/vita/assets/generate_assets.py

117 lines
4.2 KiB
Python

#!/usr/bin/env python3
"""Generate deterministic LiveArea artwork for the Vita VPK."""
from pathlib import Path
from PIL import Image, ImageDraw, ImageFont
ROOT = Path(__file__).resolve().parents[1]
LIVEAREA = ROOT / "sce_sys" / "livearea" / "contents"
NAVY = (18, 24, 38, 255)
NAVY_LIGHT = (29, 39, 58, 255)
GREEN = (38, 190, 125, 255)
GREEN_LIGHT = (80, 225, 155, 255)
WHITE = (245, 248, 252, 255)
MUTED = (165, 178, 197, 255)
def draw_wifi(draw: ImageDraw.ImageDraw, center: tuple[int, int], scale: float) -> None:
x, y = center
widths = (54, 38, 22)
for index, width in enumerate(widths):
inset = index * 7 * scale
box = (
x - width * scale,
y - width * scale + inset,
x + width * scale,
y + width * scale + inset,
)
draw.arc(box, 205, 335, fill=GREEN_LIGHT, width=max(2, int(5 * scale)))
radius = max(2, int(5 * scale))
draw.ellipse((x - radius, y + 31 * scale - radius, x + radius, y + 31 * scale + radius), fill=GREEN_LIGHT)
def draw_controller(
draw: ImageDraw.ImageDraw, box: tuple[int, int, int, int], line_width: int
) -> None:
x0, y0, x1, y1 = box
draw.rounded_rectangle(box, radius=(y1 - y0) // 3, outline=WHITE, width=line_width)
height = y1 - y0
cx_left = x0 + (x1 - x0) * 0.30
cy = y0 + height * 0.50
arm = height * 0.17
draw.line((cx_left - arm, cy, cx_left + arm, cy), fill=WHITE, width=line_width)
draw.line((cx_left, cy - arm, cx_left, cy + arm), fill=WHITE, width=line_width)
cx_right = x0 + (x1 - x0) * 0.71
radius = height * 0.055
for dx, dy in ((0, -0.15), (0.15, 0), (0, 0.15), (-0.15, 0)):
px, py = cx_right + height * dx, cy + height * dy
draw.ellipse((px - radius, py - radius, px + radius, py + radius), fill=GREEN_LIGHT)
def font(size: int) -> ImageFont.FreeTypeFont | ImageFont.ImageFont:
candidates = (
"/System/Library/Fonts/SFNS.ttf",
"/System/Library/Fonts/Helvetica.ttc",
"/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf",
)
for candidate in candidates:
try:
return ImageFont.truetype(candidate, size=size)
except OSError:
pass
return ImageFont.load_default()
def save(image: Image.Image, path: Path) -> None:
path.parent.mkdir(parents=True, exist_ok=True)
# Vita's package promoter rejects RGB/RGBA LiveArea PNGs with 0x8010113D.
# Quantize to an actual 8-bit indexed palette, including alpha entries.
indexed = image.convert("RGBA").quantize(
colors=256,
method=Image.Quantize.FASTOCTREE,
dither=Image.Dither.NONE,
)
indexed.save(path, "PNG", optimize=True, bits=8)
def make_icon() -> None:
image = Image.new("RGBA", (128, 128), NAVY)
draw = ImageDraw.Draw(image)
draw.rounded_rectangle((4, 4, 123, 123), radius=24, fill=NAVY_LIGHT, outline=GREEN, width=5)
draw_controller(draw, (20, 35, 108, 90), 5)
draw_wifi(draw, (64, 52), 0.36)
save(image, ROOT / "sce_sys" / "icon0.png")
def make_background() -> None:
image = Image.new("RGBA", (840, 500), NAVY)
draw = ImageDraw.Draw(image)
for index in range(7):
x = 480 + index * 72
draw.ellipse((x, -120 + index * 18, x + 330, 210 + index * 18), outline=(38, 190, 125, 25), width=4)
draw.rounded_rectangle((54, 61, 786, 439), radius=42, fill=NAVY_LIGHT, outline=(38, 190, 125, 120), width=4)
draw_controller(draw, (104, 160, 408, 350), 12)
draw_wifi(draw, (256, 212), 1.15)
draw.text((470, 184), "VITA", font=font(57), fill=WHITE)
draw.text((470, 252), "GAMEPAD", font=font(42), fill=GREEN_LIGHT)
draw.text((472, 322), "Wi-Fi controller", font=font(22), fill=MUTED)
save(image, LIVEAREA / "bg.png")
def make_startup() -> None:
image = Image.new("RGBA", (280, 158), NAVY_LIGHT)
draw = ImageDraw.Draw(image)
draw.rounded_rectangle((3, 3, 276, 154), radius=18, outline=GREEN, width=4)
draw_controller(draw, (26, 47, 128, 111), 5)
draw_wifi(draw, (77, 64), 0.39)
draw.text((151, 50), "VITA", font=font(28), fill=WHITE)
draw.text((151, 82), "GAMEPAD", font=font(19), fill=GREEN_LIGHT)
save(image, LIVEAREA / "startup.png")
if __name__ == "__main__":
make_icon()
make_background()
make_startup()