#!/usr/bin/env python3 """Genera la locandina dell'In-Sanity Hour in PDF vettoriale, con il QR code vero. Perché non si parte da un sorgente grafico: l'unico file disponibile è un JPEG da 612x751 px, che stampato su A4 darebbe 70 dpi. La locandina però è fatta di tre soli colori piatti, quindi si può ricalcare: ogni forma — testi, calligrafia, bicchieri — diventa un tracciato vettoriale che si stampa nitido a qualsiasi formato, senza dover reinstallare i font originali (che non abbiamo) né riprodurli a occhio. Del JPEG resta solo la geometria. Il QR di prova viene coperto e ridisegnato con l'URL vero della pagina evento, anch'esso vettoriale. Uso: python3 scripts/locandina-evento.py Serve: pip install --user potracer qrcode cairosvg """ from pathlib import Path import cairosvg import numpy as np import potrace import qrcode from PIL import Image URL_EVENTO = 'https://insanitylab.it/promo-eventi/eventi' RADICE = Path(__file__).resolve().parent.parent SORGENTE = RADICE.parent / 'Scaricati' / 'Locandina.jpeg' USCITA_PDF = RADICE / 'public' / 'locandina-insanity-hour.pdf' USCITA_PNG = RADICE / 'public' / 'img' / 'locandina-insanity-hour.png' USCITA_TITOLO = RADICE / 'public' / 'img' / 'insanity-hour-titolo.png' # Riquadro stampato dentro il JPEG, senza il bordo bianco dello screenshot. RIQUADRO = (22, 13, 597, 732) # Blocco "SAVE THE DATE / IN-SANITY / Hour" usato come immagine nella pagina web. TITOLO_WEB = (23, 24, 596, 275) # Quadrato del QR di prova da sostituire, in coordinate del JPEG originale. QR_PROVA = (387, 632, 475, 720) # Bordo superiore della fascia beige: la toppa sul QR non deve sconfinare nel marrone. FASCIA_Y = 627 FONDO, BEIGE, CREMA = '#4F3126', '#D0B49E', '#F6F1E9' CENTRI = [(79, 49, 38), (208, 180, 158), (246, 241, 233)] UPSCALE = 4 # si traccia sull'immagine ingrandita: bordi piu' morbidi, curve piu' fedeli MODULI_QUIET = 4 # margine muto richiesto dallo standard QR, in moduli def classifica(immagine: Image.Image) -> np.ndarray: """Assegna a ogni pixel il piu' vicino dei tre colori piatti della locandina.""" grande = immagine.resize((immagine.width * UPSCALE, immagine.height * UPSCALE), Image.LANCZOS) px = np.asarray(grande).astype(np.int16) distanze = np.stack([((px - np.array(c)) ** 2).sum(axis=2) for c in CENTRI]) return np.argmin(distanze, axis=0) def traccia(maschera: np.ndarray) -> str: """Contorni della maschera come path SVG. potracer prende per foreground i pixel falsi, quindi la maschera va negata.""" percorso = potrace.Bitmap(~maschera).trace(turdsize=6, alphamax=1.0, opticurve=True, opttolerance=0.2) d = [] for curva in percorso.curves: avvio = curva.start_point d.append(f'M{avvio.x / UPSCALE:.2f},{avvio.y / UPSCALE:.2f}') for seg in curva.segments: fine = seg.end_point if seg.is_corner: d.append(f'L{seg.c.x / UPSCALE:.2f},{seg.c.y / UPSCALE:.2f}L{fine.x / UPSCALE:.2f},{fine.y / UPSCALE:.2f}') else: d.append( f'C{seg.c1.x / UPSCALE:.2f},{seg.c1.y / UPSCALE:.2f}' f' {seg.c2.x / UPSCALE:.2f},{seg.c2.y / UPSCALE:.2f}' f' {fine.x / UPSCALE:.2f},{fine.y / UPSCALE:.2f}' ) d.append('Z') return ''.join(d) def qr_svg(x0: float, y0: float, x1: float, y1: float) -> str: """QR dell'URL evento, disegnato a rettangoli dentro il riquadro dato. Il QR di prova sfiorava il bordo marrone: qui il modulo viene calcolato includendo il margine muto, cosi' il codice resta leggibile anche stampato in piccolo. """ codice = qrcode.QRCode(error_correction=qrcode.constants.ERROR_CORRECT_M, box_size=1, border=0) codice.add_data(URL_EVENTO) codice.make(fit=True) matrice = codice.get_matrix() n = len(matrice) lato = min(x1 - x0, y1 - y0) modulo = lato / (n + 2 * MODULI_QUIET) off_x = x0 + (x1 - x0 - lato) / 2 + MODULI_QUIET * modulo off_y = y0 + (y1 - y0 - lato) / 2 + MODULI_QUIET * modulo quadrati = [ f'' for r, riga in enumerate(matrice) for c, pieno in enumerate(riga) if pieno ] return f'{"".join(quadrati)}', n def main() -> None: originale = Image.open(SORGENTE).convert('RGB') locandina = originale.crop(RIQUADRO) larghezza, altezza = locandina.size etichette = classifica(locandina) dx, dy = RIQUADRO[0], RIQUADRO[1] qx0, qy0, qx1, qy1 = QR_PROVA[0] - dx, QR_PROVA[1] - dy, QR_PROVA[2] - dx, QR_PROVA[3] - dy qr, moduli = qr_svg(qx0, qy0, qx1, qy1) toppa_y = max(qy0 - 6, FASCIA_Y - dy + 1) svg = f''' {qr} ''' USCITA_PDF.parent.mkdir(parents=True, exist_ok=True) # Proporzioni native (575x719). cairosvg legge i px a 96 dpi: per una base di 210 mm # (la larghezza di un A4) la pagina va chiesta in px, non in punti. px_per_mm = 96 / 25.4 scala = 210 * px_per_mm / larghezza cairosvg.svg2pdf(bytestring=svg.encode(), write_to=str(USCITA_PDF), output_width=larghezza * scala, output_height=altezza * scala) cairosvg.svg2png(bytestring=svg.encode(), write_to=str(USCITA_PNG), output_width=larghezza * 3, output_height=altezza * 3) originale.crop(TITOLO_WEB).save(USCITA_TITOLO) print(f'locandina {larghezza}x{altezza} px -> {USCITA_PDF.relative_to(RADICE)} ' f'({210:.0f}x{210 * altezza / larghezza:.0f} mm)') print(f'anteprima {larghezza * 3}x{altezza * 3} -> {USCITA_PNG.relative_to(RADICE)}') print(f'QR {moduli}x{moduli} moduli -> {URL_EVENTO}') if __name__ == '__main__': main()