montana/macOS/MontanaPresence/generate_icon.py

177 lines
5.8 KiB
Python
Raw Permalink Normal View History

#!/usr/bin/env python3
"""Generate Montana .icns from СИМВОЛ_ВРЕМЕНИ.PNG — exact colors, 3D volume, rounded corners."""
from PIL import Image, ImageDraw, ImageFilter
import subprocess, os, shutil
SIZE = 1024
CORNER = 228 # macOS squircle ~22%
def rounded_rect_mask(size, radius):
scale = 4
big = Image.new('L', (size[0]*scale, size[1]*scale), 0)
d = ImageDraw.Draw(big)
d.rounded_rectangle([(0, 0), (size[0]*scale-1, size[1]*scale-1)], radius=radius*scale, fill=255)
return big.resize(size, Image.LANCZOS)
def extract_symbol(src):
"""Extract only the gold symbol as RGBA with transparent background."""
w, h = src.size
px = src.load()
# Find bbox of non-black pixels
threshold = 25
min_x, min_y, max_x, max_y = w, h, 0, 0
for y in range(h):
for x in range(w):
r, g, b = px[x, y][:3]
if r > threshold or g > threshold or b > threshold:
min_x = min(min_x, x)
min_y = min(min_y, y)
max_x = max(max_x, x)
max_y = max(max_y, y)
print(f"Symbol bbox: ({min_x}, {min_y}, {max_x+1}, {max_y+1})")
# Extract just the symbol with transparency
sw = max_x - min_x + 1
sh = max_y - min_y + 1
symbol = Image.new('RGBA', (sw, sh), (0, 0, 0, 0))
spx = symbol.load()
for y in range(sh):
for x in range(sw):
r, g, b = px[min_x + x, min_y + y][:3]
brightness = max(r, g, b)
if brightness > threshold:
spx[x, y] = (r, g, b, 255)
else:
spx[x, y] = (0, 0, 0, 0)
return symbol
def create_icon(source_path):
src = Image.open(source_path).convert('RGBA')
symbol = extract_symbol(src)
cw, ch = symbol.size
print(f"Symbol size: {cw}x{ch}")
# Scale symbol to ~55% of icon
target = int(SIZE * 0.55)
scale = target / max(cw, ch)
new_w = int(cw * scale)
new_h = int(ch * scale)
symbol = symbol.resize((new_w, new_h), Image.LANCZOS)
# === BACKGROUND: pure black with subtle depth gradient ===
canvas = Image.new('RGBA', (SIZE, SIZE), (0, 0, 0, 255))
# Subtle radial gradient from center (very subtle warmth)
for y in range(SIZE):
for x in range(SIZE):
dx = (x - SIZE * 0.45) / SIZE
dy = (y - SIZE * 0.4) / SIZE
dist = (dx*dx + dy*dy) ** 0.5
v = max(0, int(10 * (1 - dist * 1.5)))
canvas.putpixel((x, y), (v, v, v + 1, 255))
# === SYMBOL PLACEMENT ===
sx = (SIZE - new_w) // 2
sy = (SIZE - new_h) // 2
# Drop shadow
shadow = Image.new('RGBA', (SIZE, SIZE), (0, 0, 0, 0))
shadow.paste(symbol, (sx + 6, sy + 8), symbol)
# Darken shadow
shpx = shadow.load()
for y in range(SIZE):
for x in range(SIZE):
r, g, b, a = shpx[x, y]
if a > 0:
shpx[x, y] = (0, 0, 0, min(a, 90))
shadow = shadow.filter(ImageFilter.GaussianBlur(radius=18))
canvas = Image.alpha_composite(canvas, shadow)
# Main symbol
canvas.paste(symbol, (sx, sy), symbol)
# Top highlight (lighter gold on upper part of symbol for 3D)
hl = Image.new('RGBA', (SIZE, SIZE), (0, 0, 0, 0))
hl.paste(symbol, (sx, sy), symbol)
hlpx = hl.load()
for y in range(SIZE):
for x in range(SIZE):
r, g, b, a = hlpx[x, y]
if a > 0:
local_y = y - sy
progress = local_y / new_h if new_h > 0 else 1
if progress < 0.45:
alpha = int(40 * (1 - progress / 0.45))
hlpx[x, y] = (255, 245, 220, alpha)
else:
hlpx[x, y] = (0, 0, 0, 0)
else:
hlpx[x, y] = (0, 0, 0, 0)
canvas = Image.alpha_composite(canvas, hl)
# === INNER BEVEL (3D container like Developer icon) ===
bevel = Image.new('RGBA', (SIZE, SIZE), (0, 0, 0, 0))
bd = ImageDraw.Draw(bevel)
# Top-left light edge
bd.rounded_rectangle([(2, 2), (SIZE-3, SIZE-3)], radius=CORNER-2,
outline=(255, 255, 255, 15), width=1)
canvas = Image.alpha_composite(canvas, bevel)
# Bottom-right shadow edge
bevel2 = Image.new('RGBA', (SIZE, SIZE), (0, 0, 0, 0))
bd2 = ImageDraw.Draw(bevel2)
bd2.rounded_rectangle([(4, 4), (SIZE-1, SIZE-1)], radius=CORNER-2,
outline=(0, 0, 0, 30), width=1)
canvas = Image.alpha_composite(canvas, bevel2)
# === ROUNDED CORNERS ===
mask = rounded_rect_mask((SIZE, SIZE), CORNER)
final = Image.new('RGBA', (SIZE, SIZE), (0, 0, 0, 0))
final.paste(canvas, (0, 0), mask)
return final
def make_icns(img, output_dir):
iconset = os.path.join(output_dir, 'Montana.iconset')
if os.path.exists(iconset):
shutil.rmtree(iconset)
os.makedirs(iconset)
sizes = [16, 32, 64, 128, 256, 512, 1024]
for s in sizes:
resized = img.resize((s, s), Image.LANCZOS)
resized.save(os.path.join(iconset, f'icon_{s}x{s}.png'))
if s <= 512:
resized2x = img.resize((s * 2, s * 2), Image.LANCZOS)
resized2x.save(os.path.join(iconset, f'icon_{s}x{s}@2x.png'))
icns_path = os.path.join(output_dir, 'Montana.icns')
subprocess.run(['iconutil', '-c', 'icns', iconset, '-o', icns_path], check=True)
shutil.rmtree(iconset)
print(f'Created: {icns_path}')
if __name__ == '__main__':
script_dir = os.path.dirname(os.path.abspath(__file__))
source = os.path.join(script_dir, '..', '..', 'Русский', 'Генезис', 'СИМВОЛ_ВРЕМЕНИ.PNG')
source = os.path.normpath(source)
print(f'Source: {source}')
icon = create_icon(source)
png_path = os.path.join(script_dir, 'Montana_icon_1024.png')
icon.save(png_path)
print(f'Preview: {png_path}')
make_icns(icon, script_dir)
print('Done!')