from pathlib import Path
from PIL import Image, ImageDraw, ImageFilter
HERE = Path(__file__).resolve().parent
FONT = (HERE.parent / "fonts" / "vga_8x16.bin").read_bytes()
SIZES = [16, 24, 32, 48, 64, 128, 256, 512, 1024]
GREEN = (51, 255, 102)
GLOW = (20, 200, 80)
GLASS_TOP = (6, 34, 16)
GLASS_BOTTOM = (2, 14, 6)
BEZEL = (46, 48, 52)
BEZEL_EDGE = (24, 25, 28)
HIGHLIGHT = (86, 90, 96)
def glyph(ch):
return FONT[ch * 16 : ch * 16 + 16]
def text_mask(text, scale):
img = Image.new("L", (8 * len(text) * scale, 16 * scale), 0)
d = ImageDraw.Draw(img)
for i, ch in enumerate(text.encode("cp437")):
for y, bits in enumerate(glyph(ch)):
for x in range(8):
if bits & (0x80 >> x):
x0, y0 = (i * 8 + x) * scale, y * scale
d.rectangle([x0, y0, x0 + scale - 1, y0 + scale - 1], fill=255)
return img
def vertical_gradient(size, top, bottom):
w, h = size
img = Image.new("RGB", size)
for y in range(h):
t = y / max(h - 1, 1)
img.paste(tuple(round(a + (b - a) * t) for a, b in zip(top, bottom)), (0, y, w, y + 1))
return img
def draw(n):
img = Image.new("RGBA", (n, n), (0, 0, 0, 0))
d = ImageDraw.Draw(img)
m = round(n * 0.06)
r = round(n * 0.2)
case = Image.new("L", (n, n), 0)
ImageDraw.Draw(case).rounded_rectangle([m, m, n - 1 - m, n - 1 - m], r, fill=255)
shade = vertical_gradient((n, n), HIGHLIGHT, BEZEL_EDGE)
img.paste(shade, (0, 0), case)
inner = Image.new("L", (n, n), 0)
e = max(n // 96, 1)
ImageDraw.Draw(inner).rounded_rectangle([m + e, m + 2 * e, n - 1 - m - e, n - 1 - m - 2 * e], r - e, fill=255)
img.paste(vertical_gradient((n, n), BEZEL, BEZEL), (0, 0), inner)
g0 = round(n * 0.17)
box = (g0, g0, n - g0, n - round(n * 0.2))
gw, gh = box[2] - box[0], box[3] - box[1]
glass = vertical_gradient((gw, gh), GLASS_TOP, GLASS_BOTTOM)
lines = ImageDraw.Draw(glass)
step = max(n // 128, 2)
for y in range(0, gh, step * 2):
lines.rectangle([0, y, gw, y + step - 1], fill=tuple(max(c - 4, 0) for c in GLASS_BOTTOM))
shape = Image.new("L", (gw, gh), 0)
ImageDraw.Draw(shape).rounded_rectangle([0, 0, gw - 1, gh - 1], round(n * 0.07), fill=255)
img.paste(glass, box[:2], shape)
scale = max(round(gw / 2.1 / 16), 1)
mask = text_mask(">_", scale)
tx = box[0] + (gw - mask.width) // 2
ty = box[1] + (gh - mask.height) // 2
full = Image.new("L", (n, n), 0)
full.paste(mask, (tx, ty))
for radius, strength in ((n / 22, 150), (n / 60, 220)):
glow = full.filter(ImageFilter.GaussianBlur(radius)).point(lambda v, s=strength: min(255, v * s // 100))
img.paste(Image.new("RGBA", (n, n), GLOW + (255,)), (0, 0), Image.composite(glow, Image.new("L", (n, n)), shape_at(shape, box, n)))
img.paste(Image.new("RGBA", (n, n), GREEN + (255,)), (0, 0), full)
sheen = Image.new("L", (gw, gh), 0)
ImageDraw.Draw(sheen).ellipse([-gw // 2, -gh, gw + gw // 2, gh // 4], fill=10)
sheen = Image.composite(sheen, Image.new("L", (gw, gh)), shape)
white = Image.new("RGBA", (gw, gh), (255, 255, 255, 255))
img.paste(white, box[:2], sheen)
return img
def shape_at(shape, box, n):
m = Image.new("L", (n, n), 0)
m.paste(shape, box[:2])
return m
def draw_small(n):
s = n / 16
img = Image.new("RGBA", (n, n), (0, 0, 0, 0))
d = ImageDraw.Draw(img)
def px(x0, y0, x1, y1, color):
d.rectangle([round(x0 * s), round(y0 * s), round((x1 + 1) * s) - 1, round((y1 + 1) * s) - 1], fill=color)
px(1, 1, 14, 14, BEZEL_EDGE)
px(1, 1, 14, 13, BEZEL)
px(2, 1, 13, 1, HIGHLIGHT)
px(3, 3, 12, 11, GLASS_BOTTOM)
px(3, 3, 12, 5, GLASS_TOP)
for x, y in ((4, 5), (5, 6), (6, 7), (5, 8), (4, 9)):
px(x, y, x, y, GREEN)
px(8, 9, 11, 9, GREEN)
for x, y in ((1, 1), (14, 1), (1, 14), (14, 14)):
px(x, y, x, y, (0, 0, 0, 0))
return img
def main():
images = {}
for n in SIZES:
img = draw_small(n) if n <= 48 else draw(n) if n >= 256 else draw(1024).resize((n, n), Image.LANCZOS)
img.save(HERE / f"badterm-{n}.png")
images[n] = img
(HERE / "badterm-64.rgba").write_bytes(images[64].convert("RGBA").tobytes())
images[256].save(HERE / "badterm.ico", sizes=[(n, n) for n in (16, 24, 32, 48, 64, 128, 256)],
append_images=[images[n] for n in (16, 24, 32, 48, 64, 128)])
images[1024].save(HERE / "badterm.icns", append_images=[images[n] for n in (16, 32, 64, 128, 256, 512)])
if __name__ == "__main__":
main()