130 lines
4.0 KiB
Python
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()
|