saicore/tools/gen_all_icons.py

419 lines
34 KiB
Python

"""SaiCore Tool Palette, Extra Palette, Erase Palette, Background Palette, Cursors, and misc icons."""
import os
from PIL import Image, ImageDraw, ImageFont
BASE = os.path.join(os.path.dirname(__file__), "..", "resources", "images")
DARK = (250, 250, 249) # cream-white for dark bg
INDIGO = (220, 38, 38) # red-600
INDIGO_LT = (248, 113, 113) # red-400
WHITE = (255, 255, 255)
RED = (239, 68, 68)
GREEN = (16, 185, 129)
AMBER = (245, 158, 11)
GRAY = (82, 82, 91)
LIGHT_GRAY = (229, 231, 235)
T = (0, 0, 0, 0)
def font(sz):
for n in ['C:/Windows/Fonts/segoeuib.ttf', 'C:/Windows/Fonts/arialbd.ttf']:
try: return ImageFont.truetype(n, sz)
except: pass
return ImageFont.load_default()
def new(sz=42):
img = Image.new('RGBA', (sz, sz), T)
return img, ImageDraw.Draw(img)
def save(path, img):
img.save(path, 'PNG')
print(f" {os.path.basename(path)}")
def svg(path, body, w=42, h=42):
with open(path, 'w', encoding='utf-8') as f:
f.write(f'<?xml version="1.0"?>\n<svg width="{w}" height="{h}" viewBox="0 0 {w} {h}" xmlns="http://www.w3.org/2000/svg">\n{body}</svg>\n')
print(f" {os.path.basename(path)}")
def hx(c): return f'#{c[0]:02x}{c[1]:02x}{c[2]:02x}'
def gen_tool_palette():
print("=== Tool Palette ===")
d = os.path.join(BASE, "toolPalette")
sz = 42
# Compass
img, dr = new(sz)
dr.line([21,8,12,34], fill=DARK, width=2)
dr.line([21,8,30,34], fill=DARK, width=2)
dr.ellipse([18,5,24,11], outline=DARK, width=2)
dr.ellipse([9,31,15,37], fill=DARK)
dr.line([12,34,30,34], fill=DARK, width=1)
save(os.path.join(d, "compassTool.png"), img)
svg(os.path.join(d, "compassTool.svg"), '<path d="M21 8 L12 34 M21 8 L30 34" stroke="#fafaf9" stroke-width="2"/><circle cx="21" cy="8" r="3" fill="none" stroke="#fafaf9" stroke-width="2"/><circle cx="12" cy="34" r="3" fill="#fafaf9"/><path d="M12 34 L30 34" stroke="#fafaf9" stroke-width="1"/>')
# Ruler
img, dr = new(sz)
dr.polygon([(6,28),(34,6),(36,10),(8,32)], fill=DARK)
for i in range(5):
x1, y1 = 10+i*5, 30-i*4
dr.line([(x1,y1),(x1+2,y1-3)], fill=WHITE, width=1)
save(os.path.join(d, "rulerTool.png"), img)
# Protractor
img, dr = new(sz)
dr.arc([6,6,36,36], 0, 180, fill=DARK, width=2)
dr.line([6,21,36,21], fill=DARK, width=2)
dr.arc([12,12,30,30], 0, 180, fill=DARK, width=1)
save(os.path.join(d, "protractorTool.png"), img)
# Triangle
img, dr = new(sz)
dr.polygon([(6,34),(34,34),(6,6)], outline=DARK, width=2)
save(os.path.join(d, "triangleTool.png"), img)
# Axes
img, dr = new(sz)
dr.line([21,6,21,36], fill=DARK, width=2)
dr.line([6,21,36,21], fill=DARK, width=2)
dr.polygon([(21,6),(18,10),(24,10)], fill=DARK)
dr.polygon([(36,21),(32,18),(32,24)], fill=DARK)
save(os.path.join(d, "axesTool.png"), img)
# Aristo (L-shape)
img, dr = new(sz)
dr.polygon([(6,6),(6,34),(34,34)], outline=DARK, width=2)
save(os.path.join(d, "aristoTool.png"), img)
# Magnifier
img, dr = new(sz)
dr.ellipse([8,8,28,28], outline=DARK, width=3)
dr.line([26,26,36,36], fill=DARK, width=3)
save(os.path.join(d, "magnifierTool.png"), img)
# Cache
img, dr = new(sz)
dr.rounded_rectangle([8,8,34,34], radius=4, outline=DARK, width=2)
dr.line([8,8,34,34], fill=DARK, width=1)
save(os.path.join(d, "cacheTool.png"), img)
# Mask
img, dr = new(sz)
dr.rounded_rectangle([8,8,34,34], radius=4, fill=DARK)
dr.rounded_rectangle([14,14,28,28], radius=2, fill=T[:3]+(0,))
save(os.path.join(d, "maskTool.png"), img)
# Podcast
svg(os.path.join(d, "podcast.svg"), '<circle cx="21" cy="21" r="12" fill="none" stroke="#fafaf9" stroke-width="2"/><circle cx="21" cy="21" r="4" fill="#dc2626"/><path d="M21 9 Q14 9 14 21" stroke="#fafaf9" stroke-width="2" fill="none"/>')
svg(os.path.join(d, "podcastOn.svg"), '<circle cx="21" cy="21" r="12" fill="none" stroke="#dc2626" stroke-width="2"/><circle cx="21" cy="21" r="4" fill="#dc2626"/><path d="M21 9 Q14 9 14 21" stroke="#dc2626" stroke-width="2" fill="none"/>')
def gen_extra_palette():
print("=== Extra Palette ===")
d = os.path.join(BASE, "extraPalette")
sz = 42
for name, suffix, color, fill_c in [
("blackout","",DARK,None),("blackoutOn","",INDIGO,None),
("compass","",DARK,None),("compassOn","",INDIGO,None),
("keyboard","",DARK,None),("keyboardOn","",INDIGO,None),
("mask","",DARK,None),("maskOn","",INDIGO,None),
("oEmbed","",DARK,None),
("podcast","",DARK,None),("podcastOn","",INDIGO,None),
("protractor","",DARK,None),("protractorOn","",INDIGO,None),
("ruler","",DARK,None),("rulerOn","",INDIGO,None),
("trapFlash","",DARK,None),
("webTrap","",DARK,None),("webTrapEnabled","",INDIGO,None),
]:
img, dr = new(sz)
if "blackout" in name:
dr.rounded_rectangle([8,8,34,34], radius=4, fill=color)
elif "compass" in name:
dr.line([21,8,12,34], fill=color, width=2)
dr.line([21,8,30,34], fill=color, width=2)
dr.ellipse([18,5,24,11], outline=color, width=2)
elif "keyboard" in name:
dr.rounded_rectangle([6,14,36,28], radius=2, outline=color, width=2)
for i in range(5):
for j in range(2):
dr.rectangle([10+j*5+i*5, 16, 13+j*5+i*5, 19], fill=color)
elif "mask" in name:
dr.rounded_rectangle([8,8,34,34], radius=4, fill=color)
elif "podcast" in name:
dr.ellipse([9,9,33,33], outline=color, width=2)
dr.ellipse([17,17,25,25], fill=color)
elif "protractor" in name:
dr.arc([6,6,36,36], 0, 180, fill=color, width=2)
dr.line([6,21,36,21], fill=color, width=2)
elif "ruler" in name:
dr.polygon([(6,28),(34,6),(36,10),(8,32)], fill=color)
elif "oEmbed" in name:
dr.rounded_rectangle([8,14,34,28], radius=2, outline=color, width=2)
dr.polygon([(18,18),(18,24),(24,21)], fill=color)
elif "trapFlash" in name:
dr.ellipse([8,8,34,34], outline=color, width=2)
dr.line([8,8,34,34], fill=color, width=1)
elif "webTrap" in name:
dr.ellipse([8,8,34,34], outline=color, width=2)
dr.line([8,8,34,34], fill=color, width=1)
dr.line([21,8,21,34], fill=color, width=1)
dr.line([8,21,34,21], fill=color, width=1)
save(os.path.join(d, f"{name}{suffix}.png"), img)
def gen_erase_palette():
print("=== Erase Palette ===")
d = os.path.join(BASE, "erasePalette")
for name, color in [("eraseAll",DARK),("eraseAnnotation",DARK),("eraseBackground",DARK),("eraseItem",DARK)]:
svg(os.path.join(d, f"{name}.svg"), f'<path d="M12 12 L30 30 M30 12 L12 30" stroke="{hx(color)}" stroke-width="3" stroke-linecap="round"/>')
# eraseAll - X with circle
svg(os.path.join(d, "eraseAll.svg"), '<circle cx="21" cy="21" r="14" fill="none" stroke="#fafaf9" stroke-width="2"/><path d="M15 15 L27 27 M27 15 L15 27" stroke="#ef4444" stroke-width="3" stroke-linecap="round"/>')
# eraseAnnotation - eraser with lines
svg(os.path.join(d, "eraseAnnotation.svg"), '<rect x="10" y="14" width="22" height="14" rx="3" fill="#fafaf9"/><rect x="10" y="24" width="22" height="4" rx="2" fill="#dc2626"/><path d="M4 8 L38 8 M4 38 L38 38" stroke="#fafaf9" stroke-width="1" stroke-dasharray="2,2"/>')
# eraseBackground - grid with X
svg(os.path.join(d, "eraseBackground.svg"), '<rect x="8" y="8" width="26" height="26" fill="none" stroke="#fafaf9" stroke-width="1"/><path d="M8 16 L34 16 M8 24 L34 24 M16 8 L16 34 M24 8 L24 34" stroke="#fafaf9" stroke-width="1"/><path d="M14 14 L28 28 M28 14 L14 28" stroke="#ef4444" stroke-width="2"/>')
# eraseItem - box with X
svg(os.path.join(d, "eraseItem.svg"), '<rect x="8" y="8" width="26" height="26" rx="3" fill="none" stroke="#fafaf9" stroke-width="2"/><path d="M14 14 L28 28 M28 14 L14 28" stroke="#ef4444" stroke-width="2"/>')
def gen_background_palette():
print("=== Background Palette ===")
d = os.path.join(BASE, "backgroundPalette")
bgs = [
("background1", "plain"), ("background2", "grid"), ("background3", "lines"),
("background4", "dots"), ("background5", "cross"), ("background6", "ruler"),
("background7", "dark"), ("background8", "light"),
]
for name, bg_type in bgs:
for suffix, color in [("", DARK), ("On", INDIGO)]:
c = hx(color)
if bg_type == "plain":
body = f'<rect x="6" y="6" width="30" height="30" rx="3" fill="none" stroke="{c}" stroke-width="2"/>'
elif bg_type == "grid":
body = f'<rect x="6" y="6" width="30" height="30" rx="3" fill="none" stroke="{c}" stroke-width="2"/><path d="M12 6 L12 36 M18 6 L18 36 M24 6 L24 36 M30 6 L30 36 M6 12 L36 12 M6 18 L36 18 M6 24 L36 24 M6 30 L36 30" stroke="{c}" stroke-width="0.5"/>'
elif bg_type == "lines":
body = f'<rect x="6" y="6" width="30" height="30" rx="3" fill="none" stroke="{c}" stroke-width="2"/><path d="M6 12 L36 12 M6 18 L36 18 M6 24 L36 24 M6 30 L36 30" stroke="{c}" stroke-width="0.5"/>'
elif bg_type == "dots":
body = f'<rect x="6" y="6" width="30" height="30" rx="3" fill="none" stroke="{c}" stroke-width="2"/>' + ''.join(f'<circle cx="{x}" cy="{y}" r="1" fill="{c}"/>' for x in range(12,36,6) for y in range(12,36,6))
elif bg_type == "cross":
body = f'<rect x="6" y="6" width="30" height="30" rx="3" fill="none" stroke="{c}" stroke-width="2"/>' + ''.join(f'<path d="M{x-2} {y} L{x+2} {y} M{x} {y-2} L{x} {y+2}" stroke="{c}" stroke-width="0.5"/>' for x in range(12,36,6) for y in range(12,36,6))
elif bg_type == "ruler":
body = f'<rect x="6" y="6" width="30" height="30" rx="3" fill="none" stroke="{c}" stroke-width="2"/><path d="M6 21 L36 21" stroke="{c}" stroke-width="1"/>' + ''.join(f'<path d="M{x} 18 L{x} 24" stroke="{c}" stroke-width="0.5"/>' for x in range(12,36,3))
elif bg_type == "dark":
body = f'<rect x="6" y="6" width="30" height="30" rx="3" fill="{c}"/>'
elif bg_type == "light":
body = f'<rect x="6" y="6" width="30" height="30" rx="3" fill="none" stroke="{c}" stroke-width="2"/><rect x="8" y="8" width="26" height="26" rx="2" fill="{c}" opacity="0.1"/>'
svg(os.path.join(d, f"{name}{suffix}.svg"), body)
# Reset grid size
svg(os.path.join(d, "resetDefaultGridSize.svg"), '<path d="M21 8 L21 34 M8 21 L34 21" stroke="#fafaf9" stroke-width="2" stroke-dasharray="3,3"/><circle cx="21" cy="21" r="4" fill="none" stroke="#dc2626" stroke-width="2"/>')
def gen_cursors():
print("=== Cursors ===")
d = os.path.join(BASE, "cursors")
sz = 32
for name, fn in [
("drawCompass", lambda dr: (dr.line([16,4,8,28], fill=DARK, width=2), dr.line([16,4,24,28], fill=DARK, width=2), dr.ellipse([13,1,19,7], outline=DARK, width=2))),
("drawRulerLine", lambda dr: (dr.line([4,28,28,4], fill=DARK, width=2), dr.ellipse([2,26,8,32], fill=DARK), dr.ellipse([26,2,32,8], fill=DARK))),
("eraser", lambda dr: dr.rounded_rectangle([6,10,26,22], radius=3, fill=DARK)),
("laser", lambda dr: (dr.ellipse([12,12,20,20], fill=RED), dr.line([16,16,28,28], fill=RED, width=2))),
("marker", lambda dr: (dr.line([6,26,26,6], fill=DARK, width=5), dr.rounded_rectangle([23,4,29,10], radius=2, fill=DARK))),
("play", lambda dr: dr.polygon([(8,6),(8,26),(26,16)], fill=DARK)),
("resize", lambda dr: (dr.line([4,16,28,16], fill=DARK, width=2), dr.polygon([(4,16),(8,12),(8,20)], fill=DARK), dr.polygon([(28,16),(24,12),(24,20)], fill=DARK))),
("rotate", lambda dr: (dr.arc([6,6,26,26], 0, 270, fill=DARK, width=2), dr.polygon([(26,6),(22,6),(26,10)], fill=DARK))),
("zoomIn", lambda dr: (dr.ellipse([6,6,22,22], outline=DARK, width=2), dr.line([20,20,28,28], fill=DARK, width=2), dr.line([10,14,18,14], fill=DARK, width=2), dr.line([14,10,14,18], fill=DARK, width=2))),
("zoomOut", lambda dr: (dr.ellipse([6,6,22,22], outline=DARK, width=2), dr.line([20,20,28,28], fill=DARK, width=2), dr.line([10,14,18,14], fill=DARK, width=2))),
]:
img, dr = new(sz)
fn(dr)
save(os.path.join(d, f"{name}.png"), img)
def gen_misc():
print("=== Misc Icons ===")
sz = 42
# Main SVG icons
d = BASE
for name, body in [
("close", '<path d="M10 10 L32 32 M32 10 L10 32" stroke="#fafaf9" stroke-width="3" stroke-linecap="round"/>'),
("closeDisabled", '<path d="M10 10 L32 32 M32 10 L10 32" stroke="#9ca3af" stroke-width="3" stroke-linecap="round"/>'),
("closeTool", '<path d="M10 10 L32 32 M32 10 L10 32" stroke="#fafaf9" stroke-width="2" stroke-linecap="round"/><circle cx="21" cy="21" r="16" fill="none" stroke="#fafaf9" stroke-width="1"/>'),
("color", '<circle cx="21" cy="21" r="10" fill="#dc2626"/>'),
("circle", '<circle cx="21" cy="21" r="12" fill="none" stroke="#fafaf9" stroke-width="2"/>'),
("duplicate", '<rect x="8" y="12" width="18" height="22" rx="2" fill="none" stroke="#fafaf9" stroke-width="2"/><rect x="16" y="8" width="18" height="22" rx="2" fill="none" stroke="#fafaf9" stroke-width="2"/>'),
("duplicateDisabled", '<rect x="8" y="12" width="18" height="22" rx="2" fill="none" stroke="#9ca3af" stroke-width="2"/><rect x="16" y="8" width="18" height="22" rx="2" fill="none" stroke="#9ca3af" stroke-width="2"/>'),
("eyeClosed", '<path d="M6 16 Q21 6 36 16" fill="none" stroke="#fafaf9" stroke-width="2"/><path d="M6 16 Q21 26 36 16" fill="none" stroke="#fafaf9" stroke-width="2"/><line x1="6" y1="26" x2="36" y2="10" stroke="#ef4444" stroke-width="2"/>'),
("eyeOpened", '<path d="M6 16 Q21 6 36 16" fill="none" stroke="#fafaf9" stroke-width="2"/><path d="M6 16 Q21 26 36 16" fill="none" stroke="#fafaf9" stroke-width="2"/><circle cx="21" cy="16" r="4" fill="#fafaf9"/>'),
("flash", '<path d="M22 6 L12 24 L20 24 L18 36 L30 18 L22 18 Z" fill="#dc2626"/>'),
("font", '<text x="21" y="28" font-family="Arial" font-size="20" font-weight="bold" text-anchor="middle" fill="#fafaf9">A</text>'),
("frozen", '<path d="M21 6 L21 36 M6 21 L36 21 M9 9 L33 33 M33 9 L9 33" stroke="#dc2626" stroke-width="2" stroke-linecap="round"/>'),
("unfrozen", '<path d="M21 6 L21 36 M6 21 L36 21" stroke="#9ca3af" stroke-width="2" stroke-linecap="round"/>'),
("groupItems", '<rect x="6" y="6" width="12" height="12" rx="2" fill="none" stroke="#fafaf9" stroke-width="2"/><rect x="24" y="6" width="12" height="12" rx="2" fill="none" stroke="#fafaf9" stroke-width="2"/><rect x="6" y="24" width="12" height="12" rx="2" fill="none" stroke="#fafaf9" stroke-width="2"/><rect x="24" y="24" width="12" height="12" rx="2" fill="none" stroke="#fafaf9" stroke-width="2"/>'),
("ungroupItems", '<rect x="6" y="6" width="12" height="12" rx="2" fill="none" stroke="#fafaf9" stroke-width="2"/><rect x="24" y="6" width="12" height="12" rx="2" fill="none" stroke="#fafaf9" stroke-width="2"/><rect x="6" y="24" width="12" height="12" rx="2" fill="none" stroke="#fafaf9" stroke-width="2"/><rect x="24" y="24" width="12" height="12" rx="2" fill="none" stroke="#fafaf9" stroke-width="2"/><path d="M18 18 L24 18" stroke="#ef4444" stroke-width="2"/>'),
("home", '<path d="M21 6 L36 20 L30 20 L30 36 L12 36 L12 20 L6 20 Z" fill="#fafaf9"/>'),
("locked", '<rect x="10" y="20" width="22" height="16" rx="2" fill="#fafaf9"/><path d="M14 20 L14 14 Q14 8 21 8 Q28 8 28 14 L28 20" fill="none" stroke="#fafaf9" stroke-width="2"/>'),
("unlocked", '<rect x="10" y="20" width="22" height="16" rx="2" fill="#fafaf9"/><path d="M14 20 L14 14 Q14 8 21 8 Q28 8 28 14" fill="none" stroke="#fafaf9" stroke-width="2"/>'),
("menu", '<path d="M8 14 L34 14 M8 21 L34 21 M8 28 L34 28" stroke="#fafaf9" stroke-width="2" stroke-linecap="round"/>'),
("menuDisabled", '<path d="M8 14 L34 14 M8 21 L34 21 M8 28 L34 28" stroke="#9ca3af" stroke-width="2" stroke-linecap="round"/>'),
("minus", '<path d="M8 21 L34 21" stroke="#fafaf9" stroke-width="3" stroke-linecap="round"/>'),
("plus", '<path d="M21 8 L21 34 M8 21 L34 21" stroke="#fafaf9" stroke-width="3" stroke-linecap="round"/>'),
("pin", '<path d="M16 6 L26 6 L24 18 L30 24 L12 24 L18 18 Z" fill="#dc2626"/><path d="M21 24 L21 36" stroke="#fafaf9" stroke-width="2"/>'),
("unpin", '<path d="M16 6 L26 6 L24 18 L30 24 L12 24 L18 18 Z" fill="#9ca3af"/><path d="M21 24 L21 36" stroke="#9ca3af" stroke-width="2"/>'),
("play", '<polygon points="12,8 12,34 34,21" fill="#fafaf9"/>'),
("pause", '<rect x="12" y="8" width="6" height="26" fill="#fafaf9"/><rect x="24" y="8" width="6" height="26" fill="#fafaf9"/>'),
("stop", '<rect x="10" y="10" width="22" height="22" fill="#fafaf9"/>'),
("reload", '<path d="M21 8 A13 13 0 1 1 8 21" fill="none" stroke="#fafaf9" stroke-width="2"/><polygon points="21,4 25,10 17,10" fill="#fafaf9"/>'),
("rotate", '<path d="M21 8 A13 13 0 1 1 8 21" fill="none" stroke="#fafaf9" stroke-width="2"/><polygon points="21,4 25,10 17,10" fill="#fafaf9"/>'),
("rotateProtractor", '<path d="M21 6 A15 15 0 0 1 36 21" fill="none" stroke="#fafaf9" stroke-width="2"/><polygon points="36,16 40,21 32,21" fill="#fafaf9"/>'),
("roundeRrectangle", '<rect x="8" y="12" width="26" height="18" rx="6" fill="none" stroke="#fafaf9" stroke-width="2"/>'),
("save", '<path d="M8 8 L34 8 L34 34 L8 34 Z M14 8 L14 20 L28 20 L28 8" fill="none" stroke="#fafaf9" stroke-width="2"/>'),
("setAsBackground", '<rect x="6" y="6" width="30" height="30" rx="3" fill="none" stroke="#fafaf9" stroke-width="2"/><rect x="9" y="9" width="24" height="12" fill="#dc2626" opacity="0.3"/>'),
("soundOff", '<path d="M8 16 L8 26 L16 26 L24 34 L24 8 L16 16 Z" fill="#fafaf9"/><path d="M28 12 L36 28 M36 12 L28 28" stroke="#ef4444" stroke-width="2"/>'),
("soundOn", '<path d="M8 16 L8 26 L16 26 L24 34 L24 8 L16 16 Z" fill="#fafaf9"/><path d="M28 14 Q32 21 28 28" fill="none" stroke="#fafaf9" stroke-width="2"/>'),
("hflipTool", '<path d="M21 6 L21 36 M10 12 L10 30 L18 21 Z M32 12 L32 30 L24 21 Z" fill="#fafaf9"/><path d="M21 6 L21 36" stroke="#dc2626" stroke-width="1" stroke-dasharray="2,2"/>'),
("vflipTool", '<path d="M6 21 L36 21 M12 10 L30 10 L21 18 Z M12 32 L30 32 L21 24 Z" fill="#fafaf9"/>'),
("moveDown", '<path d="M21 8 L21 34 M12 26 L21 34 L30 26" stroke="#fafaf9" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/>'),
("moveDownDisabled", '<path d="M21 8 L21 34 M12 26 L21 34 L30 26" stroke="#9ca3af" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/>'),
("moveUp", '<path d="M21 34 L21 8 M12 16 L21 8 L30 16" stroke="#fafaf9" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/>'),
("moveUpDisabled", '<path d="M21 34 L21 8 M12 16 L21 8 L30 16" stroke="#9ca3af" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/>'),
("moveTool", '<path d="M21 6 L36 21 L21 36 L6 21 Z" fill="none" stroke="#fafaf9" stroke-width="2"/><circle cx="21" cy="21" r="3" fill="#dc2626"/>'),
("resetAngle", '<path d="M21 8 A13 13 0 1 1 8 21" fill="none" stroke="#fafaf9" stroke-width="2"/><path d="M21 21 L21 8" stroke="#dc2626" stroke-width="2"/>'),
("resetTool", '<path d="M21 8 A13 13 0 1 0 34 21" fill="none" stroke="#fafaf9" stroke-width="2"/><polygon points="34,16 38,21 30,21" fill="#fafaf9"/>'),
("resizeTool", '<path d="M8 8 L34 34 M8 16 L16 8 M34 26 L26 34" stroke="#fafaf9" stroke-width="2" stroke-linecap="round"/>'),
("resizeBottom", '<path d="M21 6 L21 34 M14 27 L21 34 L28 27" stroke="#fafaf9" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/>'),
("resizeLeft", '<path d="M34 21 L6 21 M13 14 L6 21 L13 28" stroke="#fafaf9" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/>'),
("resizeRight", '<path d="M6 21 L34 21 M27 14 L34 21 L27 28" stroke="#fafaf9" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/>'),
("resizeTop", '<path d="M21 34 L21 6 M14 13 L21 6 L28 13" stroke="#fafaf9" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/>'),
("resizeButton", '<circle cx="21" cy="21" r="12" fill="#dc2626"/><path d="M21 15 L21 27 M15 21 L27 21" stroke="white" stroke-width="2"/>'),
("resizeCompass", '<path d="M21 6 L14 30 M21 6 L28 30" stroke="#fafaf9" stroke-width="2"/><circle cx="21" cy="6" r="3" fill="none" stroke="#fafaf9" stroke-width="2"/>'),
("resizeRuler", '<path d="M6 28 L34 6 L36 10 L8 32 Z" fill="none" stroke="#fafaf9" stroke-width="2"/>'),
("numbersTool", '<text x="21" y="27" font-family="Arial" font-size="18" font-weight="bold" text-anchor="middle" fill="#fafaf9">123</text>'),
("angleMarker", '<path d="M6 34 L36 34 L36 6" fill="none" stroke="#fafaf9" stroke-width="2"/><path d="M6 34 A14 14 0 0 0 20 20" fill="none" stroke="#dc2626" stroke-width="2"/>'),
("centerAligned", '<path d="M8 14 L34 14 M12 21 L30 21 M8 28 L34 28" stroke="#fafaf9" stroke-width="2" stroke-linecap="round"/>'),
("leftAligned", '<path d="M8 14 L34 14 M8 21 L30 21 M8 28 L34 28" stroke="#fafaf9" stroke-width="2" stroke-linecap="round"/>'),
("rightAligned", '<path d="M8 14 L34 14 M12 21 L34 21 M8 28 L34 28" stroke="#fafaf9" stroke-width="2" stroke-linecap="round"/>'),
("notAligned", '<path d="M8 14 L34 14 M8 21 L34 21 M8 28 L34 28" stroke="#fafaf9" stroke-width="2" stroke-linecap="round"/><path d="M6 6 L36 36" stroke="#ef4444" stroke-width="1"/>'),
("z_layer_up", '<rect x="10" y="20" width="22" height="16" rx="2" fill="none" stroke="#fafaf9" stroke-width="2"/><rect x="10" y="10" width="22" height="10" rx="2" fill="#dc2626" opacity="0.3"/><path d="M21 6 L21 14 M16 10 L21 6 L26 10" stroke="#fafaf9" stroke-width="2" fill="none"/>'),
("z_layer_down", '<rect x="10" y="6" width="22" height="16" rx="2" fill="none" stroke="#fafaf9" stroke-width="2"/><rect x="10" y="22" width="22" height="10" rx="2" fill="#dc2626" opacity="0.3"/><path d="M21 28 L21 36 M16 32 L21 36 L26 32" stroke="#fafaf9" stroke-width="2" fill="none"/>'),
("increase", '<path d="M21 8 L36 30 L6 30 Z" fill="#fafaf9"/>'),
("decrease", '<path d="M21 34 L6 12 L36 12 Z" fill="#fafaf9"/>'),
("resize", '<path d="M8 8 L34 34 M8 16 L16 8" stroke="#fafaf9" stroke-width="2" stroke-linecap="round"/>'),
("rotate", '<path d="M21 8 A13 13 0 1 0 34 21" fill="none" stroke="#fafaf9" stroke-width="2"/><polygon points="34,16 38,21 30,21" fill="#fafaf9"/>'),
("addItemToCurrentPage", '<rect x="6" y="6" width="30" height="30" rx="3" fill="none" stroke="#fafaf9" stroke-width="2"/><path d="M21 12 L21 30 M12 21 L30 21" stroke="#dc2626" stroke-width="2"/>'),
("addItemToLibrary", '<rect x="6" y="6" width="30" height="30" rx="3" fill="none" stroke="#fafaf9" stroke-width="2"/><path d="M21 14 L21 28 M14 21 L28 21" stroke="#dc2626" stroke-width="2"/>'),
("addItemToNewPage", '<rect x="6" y="6" width="30" height="30" rx="3" fill="none" stroke="#fafaf9" stroke-width="2"/><line x1="21" y1="6" x2="21" y2="36" stroke="#fafaf9" stroke-width="1" stroke-dasharray="2,2"/><path d="M28 21 L34 21 M31 18 L34 21 L31 24" stroke="#dc2626" stroke-width="2" fill="none"/>'),
("addToFavorites", '<polygon points="21,6 26,16 37,17 28,25 31,36 21,30 11,36 14,25 5,17 16,16" fill="none" stroke="#fafaf9" stroke-width="2"/>'),
("removeFromFavorites", '<polygon points="21,6 26,16 37,17 28,25 31,36 21,30 11,36 14,25 5,17 16,16" fill="#dc2626"/>'),
("folder", '<path d="M6 12 L16 12 L20 8 L28 8 L28 12 L36 12 L36 34 L6 34 Z" fill="#dc2626" fill-opacity="0.15" stroke="#fafaf9" stroke-width="2" stroke-linejoin="round"/><path d="M6 10 L16 10 L20 6 L28 6 L28 10" fill="#dc2626" fill-opacity="0.3" stroke="#fafaf9" stroke-width="2" stroke-linejoin="round"/>'),
("myFolder", '<path d="M6 12 L16 12 L20 8 L28 8 L28 12 L36 12 L36 34 L6 34 Z" fill="#dc2626" fill-opacity="0.15" stroke="#fafaf9" stroke-width="2" stroke-linejoin="round"/><path d="M6 10 L16 10 L20 6 L28 6 L28 10" fill="#dc2626" fill-opacity="0.3" stroke="#fafaf9" stroke-width="2" stroke-linejoin="round"/>'),
]:
svg(os.path.join(d, f"{name}.svg"), body)
# PNG-only misc icons
for name, fn in [
("addToFavorites", lambda dr: dr.polygon([(21,6),(26,16),(37,17),(28,25),(31,36),(21,30),(11,36),(14,25),(5,17),(16,16)], outline=DARK, width=2)),
("removeFromFavorites", lambda dr: dr.polygon([(21,6),(26,16),(37,17),(28,25),(31,36),(21,30),(11,36),(14,25),(5,17),(16,16)], fill=INDIGO)),
("asc", lambda dr: (dr.line([8,30,34,30], fill=DARK, width=2), dr.line([8,20,34,20], fill=DARK, width=2), dr.line([8,10,34,10], fill=DARK, width=2), dr.polygon([(21,4),(16,10),(26,10)], fill=DARK))),
("desc", lambda dr: (dr.line([8,30,34,30], fill=DARK, width=2), dr.line([8,20,34,20], fill=DARK, width=2), dr.line([8,10,34,10], fill=DARK, width=2), dr.polygon([(21,36),(16,30),(26,30)], fill=DARK))),
("cache_circle", lambda dr: dr.ellipse([6,6,36,36], outline=DARK, width=2)),
("cache_close", lambda dr: (dr.ellipse([6,6,36,36], outline=DARK, width=2), dr.line([14,14,28,28], fill=RED, width=2), dr.line([28,14,14,28], fill=RED, width=2))),
("cache_open", lambda dr: (dr.ellipse([6,6,36,36], outline=DARK, width=2), dr.line([14,14,28,28], fill=GREEN, width=2))),
("cache_square", lambda dr: dr.rounded_rectangle([8,8,34,34], radius=2, outline=DARK, width=2)),
("collapse-all", lambda dr: (dr.line([6,10,36,10], fill=DARK, width=2), dr.polygon([(10,6),(6,10),(10,14)], fill=DARK), dr.line([6,21,36,21], fill=DARK, width=2), dr.polygon([(10,17),(6,21),(10,25)], fill=DARK), dr.line([6,32,36,32], fill=DARK, width=2), dr.polygon([(10,28),(6,32),(10,36)], fill=DARK))),
("expand-all", lambda dr: (dr.line([6,10,36,10], fill=DARK, width=2), dr.polygon([(6,6),(10,10),(6,14)], fill=DARK), dr.line([6,21,36,21], fill=DARK, width=2), dr.polygon([(6,17),(10,21),(6,25)], fill=DARK), dr.line([6,32,36,32], fill=DARK, width=2), dr.polygon([(6,28),(10,32),(6,36)], fill=DARK))),
("currentDocument", lambda dr: (dr.rounded_rectangle([8,6,34,36], radius=2, outline=DARK, width=2), dr.line([12,12,30,12], fill=DARK, width=1), dr.line([12,16,30,16], fill=DARK, width=1), dr.line([12,20,24,20], fill=DARK, width=1))),
("defaultWidgetIcon", lambda dr: (dr.rounded_rectangle([8,8,34,34], radius=4, outline=DARK, width=2), dr.ellipse([16,16,26,26], fill=INDIGO))),
("download_close", lambda dr: (dr.line([8,8,34,34], fill=RED, width=2), dr.line([34,8,8,34], fill=RED, width=2))),
("download_open", lambda dr: (dr.line([8,21,34,21], fill=GREEN, width=2), dr.polygon([(14,16),(14,26),(8,21)], fill=GREEN), dr.polygon([(28,16),(28,26),(34,21)], fill=GREEN))),
("edit-mode", lambda dr: (dr.line([8,34,28,14], fill=DARK, width=3), dr.ellipse([25,11,33,19], outline=DARK, width=2))),
("favorites", lambda dr: dr.polygon([(21,6),(26,16),(37,17),(28,25),(31,36),(21,30),(11,36),(14,25),(5,17),(16,16)], outline=DARK, width=2)),
("folder", lambda dr: (dr.rounded_rectangle([6,12,36,34], radius=3, fill=(220,38,38,40), outline=DARK, width=2), dr.polygon([(6,12),(16,12),(20,8),(28,8)], fill=DARK, outline=DARK, width=1), dr.rounded_rectangle([6,10,28,14], radius=2, fill=(220,38,38,60), outline=DARK, width=1))),
("library_close", lambda dr: (dr.rounded_rectangle([6,6,36,36], radius=4, outline=DARK, width=2), dr.line([14,14,28,28], fill=RED, width=2))),
("library_open", lambda dr: (dr.rounded_rectangle([6,6,36,36], radius=4, outline=DARK, width=2), dr.line([14,21,28,21], fill=GREEN, width=2))),
("myContent", lambda dr: (dr.rounded_rectangle([6,8,36,34], radius=2, outline=DARK, width=2), dr.line([6,16,36,16], fill=DARK, width=1))),
("myFolder", lambda dr: (dr.rounded_rectangle([6,12,36,34], radius=3, fill=(220,38,38,40), outline=DARK, width=2), dr.polygon([(6,12),(16,12),(20,8),(28,8)], fill=DARK, outline=DARK, width=1), dr.rounded_rectangle([6,10,28,14], radius=2, fill=(220,38,38,60), outline=DARK, width=1))),
("navig_arrow", lambda dr: dr.polygon([(8,8),(34,21),(8,34)], fill=DARK)),
("online", lambda dr: (dr.ellipse([8,8,34,34], outline=GREEN, width=2), dr.line([14,21,19,26], fill=GREEN, width=3), dr.line([19,26,30,15], fill=GREEN, width=3))),
("pages_close", lambda dr: (dr.rounded_rectangle([6,6,36,36], radius=4, outline=DARK, width=2), dr.line([14,14,28,28], fill=DARK, width=2), dr.line([28,14,14,28], fill=DARK, width=2))),
("pages_open", lambda dr: (dr.rounded_rectangle([6,6,36,36], radius=4, outline=DARK, width=2), dr.line([14,18,28,18], fill=DARK, width=2), dr.line([14,24,28,24], fill=DARK, width=2))),
("paletteLibrary", lambda dr: (dr.rounded_rectangle([6,6,36,36], radius=4, outline=DARK, width=2), dr.line([6,16,36,16], fill=DARK, width=1), dr.line([16,6,16,36], fill=DARK, width=1))),
("paletteNavigator", lambda dr: (dr.rounded_rectangle([6,6,36,36], radius=4, outline=DARK, width=2), dr.ellipse([14,14,28,28], outline=INDIGO, width=2))),
("save", lambda dr: (dr.rounded_rectangle([8,6,34,36], radius=2, outline=DARK, width=2), dr.rectangle([14,6,28,14], fill=DARK), dr.rectangle([14,22,28,34], outline=DARK, width=1))),
("shape", lambda dr: (dr.ellipse([6,6,24,24], outline=DARK, width=2), dr.polygon([(20,18),(36,18),(28,36)], outline=DARK, width=2))),
("tab_mask", lambda dr: dr.rounded_rectangle([2,8,40,34], radius=2, outline=DARK, width=2)),
("tools", lambda dr: (dr.line([8,34,24,18], fill=DARK, width=3), dr.ellipse([22,14,32,24], outline=DARK, width=2))),
("trash", lambda dr: (dr.line([6,8,36,8], fill=DARK, width=2), dr.rounded_rectangle([10,8,32,36], radius=2, outline=DARK, width=2), dr.line([16,4,26,4], fill=DARK, width=2))),
("trash-delete-document", lambda dr: (dr.line([6,8,36,8], fill=DARK, width=2), dr.rounded_rectangle([10,8,32,36], radius=2, outline=DARK, width=2), dr.rounded_rectangle([16,14,26,30], radius=2, outline=RED, width=1))),
("trash-delete-folder", lambda dr: (dr.line([6,8,36,8], fill=DARK, width=2), dr.rounded_rectangle([10,8,32,36], radius=2, outline=DARK, width=2), dr.rounded_rectangle([14,14,28,28], radius=2, fill=(220,38,38,40), outline=RED, width=1), dr.polygon([(14,14),(20,14),(22,10),(26,10)], fill=RED), dr.line([16,18,26,26], fill=RED, width=2), dr.line([26,18,16,26], fill=RED, width=2))),
("trash-document-page", lambda dr: (dr.line([6,8,36,8], fill=DARK, width=2), dr.rounded_rectangle([10,8,32,36], radius=2, outline=DARK, width=2), dr.line([16,14,26,14], fill=RED, width=1), dr.line([16,20,26,20], fill=RED, width=1))),
("trash-document", lambda dr: (dr.line([6,8,36,8], fill=DARK, width=2), dr.rounded_rectangle([10,8,32,36], radius=2, outline=DARK, width=2), dr.rounded_rectangle([16,14,26,30], radius=2, outline=DARK, width=1))),
("trash-empty", lambda dr: (dr.line([6,8,36,8], fill=DARK, width=2), dr.rounded_rectangle([10,8,32,36], radius=2, outline=DARK, width=2))),
("trash-folder", lambda dr: (dr.line([6,8,36,8], fill=DARK, width=2), dr.rounded_rectangle([10,8,32,36], radius=2, outline=DARK, width=2), dr.rounded_rectangle([14,14,28,28], radius=2, fill=(220,38,38,40), outline=RED, width=1), dr.polygon([(14,14),(20,14),(22,10),(26,10)], fill=RED))),
("trash-my-documents", lambda dr: (dr.line([6,8,36,8], fill=DARK, width=2), dr.rounded_rectangle([10,8,32,36], radius=2, outline=DARK, width=2), dr.ellipse([16,14,22,20], outline=DARK, width=1), dr.ellipse([24,14,30,20], outline=DARK, width=1))),
("up_arrow", lambda dr: dr.polygon([(21,6),(8,30),(34,30)], fill=DARK)),
("down_arrow", lambda dr: dr.polygon([(21,36),(8,12),(34,12)], fill=DARK)),
("left_arrow", lambda dr: dr.polygon([(6,21),(30,8),(30,34)], fill=DARK)),
("right_arrow", lambda dr: dr.polygon([(36,21),(12,8),(12,34)], fill=DARK)),
("edit-mode", lambda dr: (dr.line([8,34,28,14], fill=DARK, width=3), dr.ellipse([25,11,33,19], outline=DARK, width=2))),
("banner", lambda dr: dr.rounded_rectangle([0,0,42,42], radius=0, fill=INDIGO)),
("banner_linear", lambda dr: dr.rounded_rectangle([0,0,42,42], radius=0, fill=INDIGO)),
]:
img, dr = new(sz)
fn(dr)
save(os.path.join(d, f"{name}.png"), img)
# Ch.SaiCore.SaiCore.svg
svg(os.path.join(d, "ch.SaiCore.SaiCore.svg"), '<circle cx="21" cy="21" r="18" fill="#dc2626"/><text x="21" y="28" font-family="Arial" font-size="20" font-weight="bold" text-anchor="middle" fill="white">S</text>')
def gen_libpalette():
print("=== Lib Palette ===")
d = os.path.join(BASE, "libpalette")
if not os.path.exists(d): os.makedirs(d)
# Check what's there and regenerate
for f in os.listdir(d):
path = os.path.join(d, f)
if f.endswith('.png'):
try:
img = Image.open(path)
img = img.convert('RGBA')
# Recolor: replace green/colored with red, dark pixels with cream
pixels = img.load()
for y in range(img.height):
for x in range(img.width):
r, g, b, a = pixels[x, y]
if a > 0 and g > r and g > b:
pixels[x, y] = (INDIGO[0], INDIGO[1], INDIGO[2], a)
elif a > 0 and r == g == b and r < 128:
pixels[x, y] = (DARK[0], DARK[1], DARK[2], a)
elif a > 0 and r > 100 and g < 100 and b < 100:
pixels[x, y] = (INDIGO[0], INDIGO[1], INDIGO[2], a)
img.save(path, 'PNG')
print(f" recolored: {f}")
except: pass
def gen_print():
print("=== Print ===")
d = os.path.join(BASE, "print")
if not os.path.exists(d): os.makedirs(d)
for f in os.listdir(d):
path = os.path.join(d, f)
if f.endswith('.png'):
try:
img = Image.open(path).convert('RGBA')
pixels = img.load()
for y in range(img.height):
for x in range(img.width):
r, g, b, a = pixels[x, y]
if a > 0 and g > r and g > b:
pixels[x, y] = (INDIGO[0], INDIGO[1], INDIGO[2], a)
img.save(path, 'PNG')
print(f" recolored: {f}")
except: pass
def run():
gen_tool_palette()
gen_extra_palette()
gen_erase_palette()
gen_background_palette()
gen_cursors()
gen_misc()
gen_libpalette()
gen_print()
if __name__ == "__main__":
run()