montana/macOS/MontanaPresence/generate_icon.py

177 lines
5.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/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!')