177 lines
5.8 KiB
Python
177 lines
5.8 KiB
Python
|
|
#!/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!')
|