117 lines
4.2 KiB
Python
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()
|