116 lines
4.5 KiB
Python
116 lines
4.5 KiB
Python
#!/usr/bin/env python3
|
|
"""Generate the 5 missing UI slices to match existing style-board exports."""
|
|
from __future__ import annotations
|
|
|
|
from pathlib import Path
|
|
|
|
import numpy as np
|
|
from PIL import Image, ImageDraw, ImageEnhance, ImageFilter
|
|
|
|
SLICES = Path(r"d:\UD\AI\AIC#Project\Doc\design\UI\slices")
|
|
|
|
|
|
def fit_canvas(im: Image.Image, tw: int, th: int) -> Image.Image:
|
|
im = im.convert("RGBA")
|
|
bbox = im.getbbox()
|
|
if bbox:
|
|
im = im.crop(bbox)
|
|
out = Image.new("RGBA", (tw, th), (0, 0, 0, 0))
|
|
scale = min(tw / max(im.width, 1), th / max(im.height, 1))
|
|
nw = max(1, int(im.width * scale))
|
|
nh = max(1, int(im.height * scale))
|
|
resized = im.resize((nw, nh), Image.Resampling.LANCZOS)
|
|
out.paste(resized, ((tw - nw) // 2, (th - nh) // 2), resized)
|
|
return out
|
|
|
|
|
|
def make_hover(src_name: str, dst_name: str, size: tuple[int, int], brightness=1.18, color=1.12, contrast=1.05):
|
|
src = Image.open(SLICES / src_name).convert("RGBA")
|
|
rgb = src.convert("RGB")
|
|
rgb = ImageEnhance.Brightness(rgb).enhance(brightness)
|
|
rgb = ImageEnhance.Color(rgb).enhance(color)
|
|
rgb = ImageEnhance.Contrast(rgb).enhance(contrast)
|
|
# stronger top highlight: blend a light gradient on opaque pixels
|
|
arr = np.array(rgb)
|
|
alpha = np.array(src.split()[-1])
|
|
h, w = alpha.shape
|
|
# lift luminance slightly more on top half of opaque region
|
|
ys = np.linspace(1.08, 0.98, h).reshape(h, 1, 1)
|
|
arr = np.clip(arr.astype(np.float32) * ys, 0, 255).astype(np.uint8)
|
|
out = Image.fromarray(arr, "RGB").convert("RGBA")
|
|
out.putalpha(Image.fromarray(alpha, "L"))
|
|
# soft extra rim glow
|
|
glow = out.filter(ImageFilter.GaussianBlur(1))
|
|
composed = Image.alpha_composite(Image.new("RGBA", out.size, (0, 0, 0, 0)), glow)
|
|
composed = Image.alpha_composite(composed, out)
|
|
fit_canvas(composed, *size).save(SLICES / dst_name, "PNG")
|
|
print("wrote", dst_name)
|
|
|
|
|
|
def make_caret():
|
|
# 8x64 blue caret matching focus input border blue
|
|
tw, th = 8, 64
|
|
im = Image.new("RGBA", (tw, th), (0, 0, 0, 0))
|
|
d = ImageDraw.Draw(im)
|
|
# soft glow
|
|
d.rectangle((2, 4, 5, th - 5), fill=(120, 180, 240, 90))
|
|
d.rectangle((3, 6, 4, th - 7), fill=(70, 140, 220, 255))
|
|
# tiny top/bottom caps
|
|
d.rectangle((2, 6, 5, 9), fill=(180, 220, 255, 255))
|
|
d.rectangle((2, th - 10, 5, th - 7), fill=(50, 110, 190, 255))
|
|
im.save(SLICES / "input_caret.png", "PNG")
|
|
print("wrote input_caret.png")
|
|
|
|
|
|
def make_sound_off():
|
|
src = Image.open(SLICES / "ico_sound_on.png").convert("RGBA")
|
|
# work on larger canvas then fit back to 64
|
|
big = fit_canvas(src, 128, 128)
|
|
d = ImageDraw.Draw(big)
|
|
# red mute slash with dark outline
|
|
# from top-right to bottom-left across icon
|
|
pts = [(98, 28), (108, 36), (38, 108), (28, 100)]
|
|
d.polygon(pts, fill=(160, 40, 40, 255))
|
|
pts_inner = [(96, 32), (104, 38), (40, 104), (32, 98)]
|
|
d.polygon(pts_inner, fill=(230, 70, 70, 255))
|
|
# slight highlight on slash
|
|
d.line([(92, 34), (42, 92)], fill=(255, 160, 160, 200), width=2)
|
|
fit_canvas(big, 64, 64).save(SLICES / "ico_sound_off.png", "PNG")
|
|
print("wrote ico_sound_off.png")
|
|
|
|
|
|
def make_item_placeholder_then_wait():
|
|
"""Fallback potion/item icon if AI gen unavailable — stylized potion vial."""
|
|
tw, th = 64, 64
|
|
im = Image.new("RGBA", (tw, th), (0, 0, 0, 0))
|
|
d = ImageDraw.Draw(im)
|
|
# cork
|
|
d.rounded_rectangle((26, 6, 38, 14), 2, fill=(180, 120, 60, 255), outline=(100, 60, 30, 255))
|
|
# neck
|
|
d.rectangle((28, 14, 36, 22), fill=(220, 230, 240, 220), outline=(90, 70, 50, 255))
|
|
# bottle body
|
|
d.rounded_rectangle((16, 20, 48, 58), 10, fill=(90, 200, 140, 255), outline=(40, 100, 70, 255), width=2)
|
|
# liquid highlight
|
|
d.ellipse((20, 26, 30, 36), fill=(200, 255, 220, 180))
|
|
# sparkle
|
|
d.ellipse((40, 28, 44, 32), fill=(255, 255, 255, 220))
|
|
# label band
|
|
d.rectangle((18, 38, 46, 46), fill=(255, 245, 210, 230))
|
|
d.ellipse((28, 39, 36, 45), fill=(230, 90, 90, 255))
|
|
im.save(SLICES / "ico_item.png", "PNG")
|
|
print("wrote ico_item.png (procedural fallback)")
|
|
|
|
|
|
def main():
|
|
make_hover("btn_primary_normal.png", "btn_primary_hover.png", (256, 96), 1.20, 1.15, 1.06)
|
|
make_hover("btn_small_normal.png", "btn_small_hover.png", (192, 72), 1.18, 1.14, 1.05)
|
|
make_caret()
|
|
make_sound_off()
|
|
# ico_item is produced by AI gen + postprocess; keep procedural only as fallback
|
|
if not (SLICES / "ico_item.png").exists():
|
|
make_item_placeholder_then_wait()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|