186 lines
7.0 KiB
Python
186 lines
7.0 KiB
Python
#!/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()
|