proposte di icona: quattro direzioni disegnate a 172 e 86 px (scripts/icon_proposals.py)

This commit is contained in:
2026-10-11 08:01:14 +02:00
parent 4be5caea81
commit 27f97dabef
10 changed files with 185 additions and 0 deletions
+185
View File
@@ -0,0 +1,185 @@
#!/usr/bin/env python3
"""Quattro proposte di icona per harbour-aira, alla misura vera (172 e 86 px).
Si disegna a 344 px e si ridimensiona: bordi puliti a ogni taglia, come fa
scripts/make_icons.py. Nessun logo di terzi (il permesso per nome e logo Aira
non e' ancora arrivato), nessuna lettera generata.
Uso: python3 scripts/icon_proposals.py
Esito: docs/icona/proposte/*.png (172 e 86 px) + contatto.png con tutte e quattro
"""
import math
import pathlib
from PIL import Image, ImageDraw, ImageFont
BASE = 344 # si disegna grande, si ridimensiona dopo
OUT = pathlib.Path("docs/icona/proposte")
TOP = (12, 62, 130) # blu profondo (identita' attuale)
BOTTOM = (24, 148, 190) # azzurro
WARM = (255, 138, 46) # arancione
WHITE = (255, 255, 255, 240)
BLUE = (14, 78, 148) # blu del fondo, per i ritagli (negative space)
def rounded_mask(size, radius):
m = Image.new("L", (size, size), 0)
ImageDraw.Draw(m).rounded_rectangle([0, 0, size - 1, size - 1], radius=radius, fill=255)
return m
def gradient(size):
img = Image.new("RGB", (1, size))
d = ImageDraw.Draw(img)
for y in range(size):
t = y / max(1, size - 1)
d.point((0, y), tuple(int(TOP[i] + (BOTTOM[i] - TOP[i]) * t) for i in range(3)))
return img.resize((size, size))
def onda(d, x0, x1, y, ampiezza, spessore, fill, creste=2):
"""Una linea ondulata: la stessa forma in tutti i disegni che la usano."""
punti = []
passi = 120
for i in range(passi + 1):
t = i / passi
x = x0 + (x1 - x0) * t
yy = y + ampiezza * math.sin(2 * math.pi * creste * t)
punti.append((x, yy))
d.line(punti, fill=fill, width=int(spessore), joint="curve")
def base_icon():
img = gradient(BASE).convert("RGBA")
return img, ImageDraw.Draw(img)
def goccia(d, cx, cy, r, fill, fattore_punta=2.35):
"""Goccia con la punta smussata: la punta aguzza, a 86 px, sparisce."""
d.ellipse([cx - r, cy - r, cx + r, cy + r], fill=fill)
d.polygon([(cx - r * 0.11, cy - fattore_punta * r), (cx + r * 0.11, cy - fattore_punta * r),
(cx + r * 0.99, cy + r * 0.10), (cx - r * 0.99, cy + r * 0.10)], fill=fill)
# --------------------------------------------------------------------------- #
# A. Casa con l'onda calda dentro: "la casa che viene scaldata"
# --------------------------------------------------------------------------- #
def casa_onda():
img, d = base_icon()
tetto = [(BASE * 0.16, BASE * 0.50), (BASE * 0.50, BASE * 0.24), (BASE * 0.84, BASE * 0.50)]
corpo = [BASE * 0.24, BASE * 0.50, BASE * 0.76, BASE * 0.78]
d.polygon(tetto, fill=WHITE)
d.rectangle(corpo, fill=WHITE)
# onda dentro il corpo: non deve toccare il tetto (si fondeva a 86 px)
onda(d, BASE * 0.26, BASE * 0.74, BASE * 0.665, BASE * 0.040, BASE * 0.078, WARM + (255,), creste=2)
return img
# --------------------------------------------------------------------------- #
# B. Manopola: "il controllo"
# --------------------------------------------------------------------------- #
def manopola():
img, d = base_icon()
m = BASE * 0.125 # margine piu' largo: il pomello non deve
spessore = int(BASE * 0.115) # sfiorare l'angolo arrotondato
riquadro = [m, m, BASE - m, BASE - m]
# anello aperto in alto (la finestra della scala) e pomello in alto a destra
d.arc(riquadro, start=295, end=245, fill=WHITE, width=spessore)
cx = cy = BASE / 2
raggio = (BASE - 2 * m) / 2
ang = math.radians(315)
px, py = cx + raggio * math.cos(ang), cy + raggio * math.sin(ang)
r = BASE * 0.095
d.ellipse([px - r, py - r, px + r, py + r], fill=WARM + (255,))
# tacca al centro: il valore
d.rounded_rectangle([cx - BASE * 0.032, cy - BASE * 0.14,
cx + BASE * 0.032, cy + BASE * 0.14],
radius=BASE * 0.032, fill=WHITE)
return img
# --------------------------------------------------------------------------- #
# C. Goccia con la fiamma: "acqua calda" (evoluzione dell'icona attuale)
# --------------------------------------------------------------------------- #
def goccia_fiamma():
img, d = base_icon()
cx, cy = BASE * 0.50, BASE * 0.615
r = BASE * 0.225
goccia(d, cx, cy, r, WHITE)
img = img.transform(img.size, Image.AFFINE, (1, 0, 0, 0, 1, -BASE * 0.055))
d = ImageDraw.Draw(img)
# fiamma piu' piccola: il bianco attorno deve restare spesso a 86 px
cy2 = cy + r * 0.18 - BASE * 0.055
fr = r * 0.42
d.ellipse([cx - fr, cy2 - fr, cx + fr, cy2 + fr], fill=WARM + (255,))
d.polygon([(cx - fr * 0.13, cy2 - fr * 2.15), (cx + fr * 0.13, cy2 - fr * 2.15),
(cx + fr * 0.96, cy2 + fr * 0.20)], fill=WARM + (255,))
return img
# --------------------------------------------------------------------------- #
# D. Casa e goccia insieme: "riscaldamento e acqua calda"
# --------------------------------------------------------------------------- #
def casa_goccia():
img, d = base_icon()
tetto = [(BASE * 0.16, BASE * 0.50), (BASE * 0.50, BASE * 0.24), (BASE * 0.84, BASE * 0.50)]
corpo = [BASE * 0.24, BASE * 0.50, BASE * 0.76, BASE * 0.78]
d.polygon(tetto, fill=WHITE)
d.rectangle(corpo, fill=WHITE)
# goccia nel corpo della casa, in arancione: a 86 px il blu si perderebbe
goccia(d, BASE * 0.50, BASE * 0.665, BASE * 0.092, WARM + (255,), fattore_punta=2.10)
return img
PROPOSTE = [
("a-casa-onda", casa_onda, "A. Casa + onda calda"),
("b-manopola", manopola, "B. Manopola"),
("c-goccia-fiamma", goccia_fiamma, "C. Goccia + fiamma"),
("d-casa-goccia", casa_goccia, "D. Casa + goccia"),
]
def salva(img, nome, size):
fuori = img.resize((size, size), Image.LANCZOS)
fuori = fuori.convert("RGB").convert("RGBA")
fuori.putalpha(rounded_mask(size, int(size * 0.22)))
OUT.mkdir(parents=True, exist_ok=True)
percorso = OUT / f"{nome}-{size}.png"
fuori.save(percorso)
return percorso
def contatto():
"""Un'immagine sola: le quattro proposte a 172 px e, sotto, a 86 px."""
larghezza = 4 * 200
altezza = 200 + 130 + 30
tela = Image.new("RGB", (larghezza, altezza), (16, 20, 26))
d = ImageDraw.Draw(tela)
try:
piccolo = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", 14)
except OSError:
piccolo = ImageFont.load_default()
for i, (nome, disegna, descrizione) in enumerate(PROPOSTE):
img = disegna()
grande = Image.open(salva(img, nome, 172)).convert("RGBA")
piccola = Image.open(salva(img, nome, 86)).convert("RGBA")
x = i * 200 + 14
tela.paste(grande, (x, 14), grande.split()[3])
tela.paste(piccola, (x + 43, 196), piccola.split()[3])
d.text((x, 296), descrizione, font=piccolo, fill=(200, 208, 216))
percorso = OUT / "contatto.png"
tela.save(percorso)
return percorso
def main():
for nome, disegna, _ in PROPOSTE:
img = disegna()
for size in (172, 86):
print("scritto", salva(img, nome, size))
print("scritto", contatto())
if __name__ == "__main__":
main()