maneshtrader/scripts/generate_app_icon.py

130 lines
4.0 KiB
Python

from __future__ import annotations
from pathlib import Path
from PIL import Image, ImageDraw, ImageFilter
def lerp(a: float, b: float, t: float) -> float:
return a + (b - a) * t
def make_gradient(size: int) -> Image.Image:
img = Image.new("RGBA", (size, size), (0, 0, 0, 255))
px = img.load()
top = (8, 17, 40)
bottom = (17, 54, 95)
for y in range(size):
t = y / (size - 1)
color = tuple(int(lerp(top[i], bottom[i], t)) for i in range(3)) + (255,)
for x in range(size):
px[x, y] = color
return img
def rounded_rect_mask(size: int, radius: int) -> Image.Image:
m = Image.new("L", (size, size), 0)
d = ImageDraw.Draw(m)
d.rounded_rectangle((0, 0, size - 1, size - 1), radius=radius, fill=255)
return m
def draw_icon(size: int = 1024) -> Image.Image:
base = make_gradient(size)
draw = ImageDraw.Draw(base)
# Soft vignette
vignette = Image.new("RGBA", (size, size), (0, 0, 0, 0))
vd = ImageDraw.Draw(vignette)
vd.ellipse((-size * 0.25, -size * 0.15, size * 1.25, size * 1.15), fill=(255, 255, 255, 26))
vd.ellipse((-size * 0.1, size * 0.55, size * 1.1, size * 1.5), fill=(0, 0, 0, 70))
vignette = vignette.filter(ImageFilter.GaussianBlur(radius=size * 0.06))
base = Image.alpha_composite(base, vignette)
draw = ImageDraw.Draw(base)
# Grid lines
grid_color = (190, 215, 255, 34)
margin = int(size * 0.16)
for i in range(1, 5):
y = int(lerp(margin, size - margin, i / 5))
draw.line((margin, y, size - margin, y), fill=grid_color, width=max(1, size // 512))
# Candlestick bodies/wicks
center_x = size // 2
widths = int(size * 0.09)
gap = int(size * 0.05)
candles = [
(center_x - widths - gap, 0.62, 0.33, 0.57, 0.36, (18, 214, 130, 255)),
(center_x, 0.72, 0.40, 0.45, 0.65, (255, 88, 88, 255)),
(center_x + widths + gap, 0.58, 0.30, 0.52, 0.34, (18, 214, 130, 255)),
]
for x, low, high, body_top, body_bottom, color in candles:
x = int(x)
y_low = int(size * low)
y_high = int(size * high)
y_a = int(size * body_top)
y_b = int(size * body_bottom)
y_top = min(y_a, y_b)
y_bottom = max(y_a, y_b)
wick_w = max(3, size // 180)
draw.line((x, y_high, x, y_low), fill=(220, 235, 255, 220), width=wick_w)
bw = widths
draw.rounded_rectangle(
(x - bw // 2, y_top, x + bw // 2, y_bottom),
radius=max(6, size // 64),
fill=color,
)
# Trend arrows
arrow_green = (45, 237, 147, 255)
arrow_red = (255, 77, 77, 255)
up = [
(int(size * 0.20), int(size * 0.70)),
(int(size * 0.29), int(size * 0.61)),
(int(size * 0.25), int(size * 0.61)),
(int(size * 0.25), int(size * 0.52)),
(int(size * 0.15), int(size * 0.52)),
(int(size * 0.15), int(size * 0.61)),
(int(size * 0.11), int(size * 0.61)),
]
down = [
(int(size * 0.80), int(size * 0.34)),
(int(size * 0.71), int(size * 0.43)),
(int(size * 0.75), int(size * 0.43)),
(int(size * 0.75), int(size * 0.52)),
(int(size * 0.85), int(size * 0.52)),
(int(size * 0.85), int(size * 0.43)),
(int(size * 0.89), int(size * 0.43)),
]
draw.polygon(up, fill=arrow_green)
draw.polygon(down, fill=arrow_red)
# Rounded-square icon mask
mask = rounded_rect_mask(size, radius=int(size * 0.23))
out = Image.new("RGBA", (size, size), (0, 0, 0, 0))
out.paste(base, (0, 0), mask)
# Subtle border
bd = ImageDraw.Draw(out)
bd.rounded_rectangle(
(2, 2, size - 3, size - 3),
radius=int(size * 0.23),
outline=(255, 255, 255, 44),
width=max(2, size // 256),
)
return out
def main() -> None:
out_path = Path("assets/icon/ManeshTrader.png")
out_path.parent.mkdir(parents=True, exist_ok=True)
img = draw_icon(1024)
img.save(out_path)
print(f"Wrote {out_path}")
if __name__ == "__main__":
main()