← Tutti gli episodi

Stagione 93  /  Episodio 9

Dietro il bancone

WebP nel codice: Go, PHP con GD e Imagick, Laravel con Intervention Image, Node.js con sharp e Python con Pillow, più le sette regole di una pipeline di conversione robusta.

Note dell'episodio

Bentornati in bottega. Fino a qui abbiamo lavorato a mano, uno strumento alla volta. Ma una bottega vera, quando arrivano le commissioni grosse, si organizza: c'è chi riceve il materiale, chi lo controlla, chi lo taglia, chi lo posa, chi lo imballa. Una catena.

Nelle nostre applicazioni la catena si chiama pipeline delle immagini. Un utente carica una foto, un redattore inserisce un'immagine in un articolo, un'email arriva con un allegato. E da lì devono uscire versioni WebP pronte per il web, nelle giuste dimensioni, senza dati sensibili e senza aprire la porta a file malevoli.

Oggi vediamo come si fa nei linguaggi che usiamo tutti i giorni, e poi mettiamo insieme le regole della catena. Gli esempi completi sono nelle note dell'episodio: qui raccontiamo le scelte e le trappole.

Quasi tutte le librerie che vedremo, sotto, usano libwebp. Cambia il modo in cui la espongono, ma i concetti sono quelli degli episodi precedenti: qualità, sforzo dell'encoder, senza perdita, qualità dell'alfa, metadati. Se riconoscete questi nomi, sapete già come configurare qualunque libreria.

E vale una seconda regola: siccome sotto c'è quasi sempre libwebp, la versione di libwebp che avete in produzione conta. Spesso è nascosta dentro un pacchetto, un'immagine Docker o una libreria precompilata. Ci torneremo nel prossimo episodio, perché è una questione di sicurezza, non solo di qualità.

In Go la situazione è particolare. La libreria standard non conosce WebP, ma il modulo ufficiale golang punto org slash x slash image include un pacchetto webp scritto interamente in Go. Si registra presso il pacchetto image, così image punto Decode riconosce automaticamente i file WebP, purché importiate il pacchetto, anche solo per il suo effetto collaterale.

Il pacchetto legge immagini con e senza perdita e con trasparenza, ma ha due limiti da conoscere. Non decodifica le animazioni. E soprattutto non codifica: sa leggere i mosaici, non sa farli.

Per scrivere WebP da Go avete tre strade. Usare binding cgo verso libwebp, che vi danno tutte le opzioni ma vi costringono a compilare con cgo e a gestire la libreria nativa. Usare govips, che si appoggia a libvips, una libreria di elaborazione immagini molto veloce e parca di memoria, ottima se dovete anche ridimensionare. Oppure invocare cwebp come processo esterno, che è meno elegante ma semplicissimo e isola il codice nativo in un processo separato. Esistono anche encoder in Go puro, ma in genere coprono solo la compressione senza perdita.

Un consiglio pratico per Go: prima di decodificare, chiamate DecodeConfig. Legge solo l'intestazione e vi restituisce larghezza e altezza. Così potete rifiutare un'immagine da trentamila per trentamila pixel prima di allocare gigabyte di memoria.

In PHP avete due estensioni. La più diffusa è GD. La funzione imagewebp scrive un'immagine WebP, con un parametro di qualità; dalla versione 8.1 di PHP potete passare come qualità la costante IMG_WEBP_LOSSLESS per ottenere la compressione senza perdita. Per leggere c'è imagecreatefromwebp.

La trappola classica è che GD deve essere compilata con il supporto WebP. Nelle immagini Docker ufficiali di PHP, per esempio, non basta installare l'estensione: bisogna configurarla con l'opzione with-webp e installare prima la libreria di sviluppo di libwebp. Se manca, l'errore arriva solo quando provate a leggere o scrivere un WebP, spesso in produzione. Controllatelo con gd_info: c'è una voce dedicata al supporto WebP. GD inoltre non gestisce le animazioni.

L'alternativa è Imagick, basata su ImageMagick. È più completa: gestisce animazioni, profili colore, opzioni avanzate dell'encoder passate come opzioni con il prefisso webp due punti, come webp due punti lossless o webp due punti method. In cambio è più pesante, ed ImageMagick ha una storia di vulnerabilità che consiglia di configurarne bene le policy, limitando formati, memoria e dimensioni.

In Laravel la scelta più comune è Intervention Image, che nella versione tre offre un'interfaccia pulita sopra GD o Imagick. Leggete l'immagine con il manager, la ridimensionate con scaleDown, che riduce solo se serve e mantiene le proporzioni, e la codificate con toWebp indicando la qualità. Poi salvate il risultato su un disco di Storage.

Due accortezze in Laravel. La prima: la conversione è un lavoro pesante, quindi va in un job in coda, non nella richiesta che riceve l'upload. L'utente riceve subito una risposta, e il worker produce le varianti con calma. La seconda: il driver si sceglie nella configurazione, e i limiti di GD che abbiamo appena visto valgono anche qui.

In Node.js lo standard di fatto è sharp, che si appoggia a libvips. È veloce, gestisce stream, e offre un metodo webp con tutte le opzioni che conosciamo: quality, effort da zero a sei che corrisponde al metodo di cwebp, lossless, nearLossless, alphaQuality, e smartSubsample, che è l'equivalente di sharp YUV.

Attenzione ai metadati: di default sharp non li conserva e converte i colori in sRGB. Per il web è quasi sempre il comportamento giusto. Se invece vi serve conservare il profilo colore o i dati EXIF, ci sono metodi appositi da chiamare esplicitamente. E per le animazioni, bisogna dire a sharp di leggere tutti i fotogrammi con l'opzione animated, altrimenti prende solo il primo.

In Python c'è Pillow. Si salva un'immagine con il formato WEBP passando quality, method da zero a sei, lossless, ed exact per i pixel trasparenti. Le animazioni si gestiscono passando save_all e la lista dei fotogrammi.

Anche Pillow non copia il profilo colore da sola: bisogna passarlo esplicitamente nel parametro icc_profile, prendendolo dalle informazioni dell'immagine di partenza. Lo stesso per l'EXIF.

E Pillow ha una protezione utile contro le bombe di decompressione: un limite al numero di pixel, oltre il quale emette un avviso, e oltre il doppio un errore. Non disattivatelo con leggerezza: piuttosto, abbassatelo alle dimensioni che la vostra applicazione accetta davvero.

Ora mettiamo insieme le regole, che valgono in qualunque linguaggio.

Prima regola: conservate l'originale. Il WebP è un prodotto finito, con perdita. Se domani cambiate qualità, dimensioni o formato, volete ripartire dalla sorgente, non ricomprimere un'immagine già compressa.

Seconda regola: controllate prima di aprire. Verificate la firma dei byte, non l'estensione né il tipo dichiarato dal client. Leggete solo le dimensioni con la funzione apposita e rifiutate ciò che supera un limite ragionevole. Mettete limiti alla dimensione del file caricato. Il decodificatore è la parte più esposta di tutta la catena.

Terza regola: lavorate lontano dalla richiesta. Code e worker, con limiti di memoria e di tempo. Un'immagine che manda in crash un worker non deve trascinarsi dietro il server web.

Quarta regola: nomi deterministici. Il nome di ogni variante può derivare dall'impronta dell'originale più i parametri, per esempio larghezza e qualità. Così due richieste uguali producono lo stesso file, le cache restano efficaci e potete servire le immagini con una scadenza molto lunga.

Quinta regola: decidete i metadati. Conservate il profilo colore o convertite in sRGB, togliete EXIF e GPS a meno di averne bisogno, correggete prima l'orientamento indicato nell'EXIF, perché una volta tolto il dato la foto deve già essere dritta.

Sesta regola: producete più larghezze. Tre o quattro varianti per immagine, da usare nello srcset dell'episodio scorso.

E settima regola, la più trascurata: tenete aggiornate le librerie native. Ed è proprio da qui che parte il prossimo episodio.

Ricapitolando: in Go si legge con x slash image slash webp e si scrive con binding, libvips o cwebp. In PHP GD va compilata con WebP, Imagick è più completa ma va configurata con cura. In Laravel Intervention Image in un job in coda. In Node.js sharp, in Python Pillow, e in entrambi attenzione ai metadati. E sopra tutto, una catena che conserva gli originali, controlla prima di aprire e lavora lontano dalla richiesta.

Nel prossimo e ultimo episodio parliamo della crepa: la vulnerabilità di libwebp del 2023, una delle più serie mai trovate in una libreria di immagini. E poi mettiamo WebP accanto ad AVIF e JPEG XL per decidere, nel 2026, quando usare cosa. Ci sentiamo lì.