proposte di icona: quattro direzioni disegnate a 172 e 86 px (scripts/icon_proposals.py)
|
After Width: | Height: | Size: 6.6 KiB |
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 5.3 KiB |
|
After Width: | Height: | Size: 7.0 KiB |
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 42 KiB |
|
After Width: | Height: | Size: 4.9 KiB |
|
After Width: | Height: | Size: 2.7 KiB |
@@ -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()
|
||||