Ridimensionare immagini al volo

Ridimensionamento rapido delle immagini

Come architetto web, uno dei tanti problemi riguarda la gestione delle risorse. E il problema più significativo riguarda le immagini. Un approccio ingenuo sarebbe impostare un’immagine e permettere al browser di ridimensionarla tramite CSS:

Tuttavia, ciò significa che scarichi l’immagine originale. Ciò comporta due problemi: la dimensione dell’immagine originale e il ridimensionamento tramite browser non ottimale.

In questo articolo verranno illustrare due alternative: soluzioni tradizionali e innovative.

Ridimensionamento in anticipo

La soluzione tradizionale per una singola risorsa immagine è sempre stata il ridimensionamento in anticipo. Prima del rilascio, i designer dedicavano del tempo per fornire diverse versioni di immagini a diverse risoluzioni. Su questo blog, utilizzo questa tecnica. Fornisco tre risoluzioni per visualizzare l’immagine principale dell’articolo in contesti diversi come immagini di sfondo:

  • Grande per l’articolo nella sua pagina
  • VoAGI per l’articolo nella pagina principale
  • Piccola per articoli correlati in una pagina dell’articolo

Rimovo anche i metadati JPEG per una riduzione delle dimensioni ancora maggiore.

Tuttavia, l’approccio tradizionale è quello di sfruttare l’elemento HTML picture:

L’elemento HTML <picture> contiene zero o più elementi <source> e un elemento <img> per offrire versioni alternative di un’immagine per diversi scenari di visualizzazione/dispositivi.

Il browser considererà ogni elemento figlio <source> e sceglierà la migliore corrispondenza tra di essi. Se nessuna corrispondenza viene trovata, o il browser non supporta l’elemento <picture>, viene selezionato l’URL dell’attributo src dell’elemento <img>. L’immagine selezionata viene quindi presentata nello spazio occupato dall’elemento <img>.

L’elemento Picture sul sito web di MDN

A sua volta, si può utilizzarlo come segue:

Questo metodo ha funzionato per anni, ma presenta due problemi. In primo luogo, fornire più risoluzioni per ciascuna immagine richiede molto tempo. Si potrebbe automatizzare il processo e ottenere buoni risultati con l’IA.

Tuttavia, il volume di archiviazione necessario potrebbe essere due o tre volte la dimensione dell’immagine originale, a seconda del numero di risoluzioni extra create. In un ambiente ricco di risorse, ad esempio nell’e-commerce, i costi aumenterebbero significativamente.

Ridimensionamento al volo

Di recente mi sono imbattuto in imgproxy, un componente per ridimensionare le immagini al volo:

imgproxy rende i siti web e le applicazioni rapidissimi, risparmiando costi di archiviazione e SaaS

Sito web di imgproxy

Offre un endpoint in cui è possibile inviare un URL codificato che definisce:

  • L’immagine da ridimensionare e la sua posizione, ad esempio locale, un URL HTTP, un bucket S3, ecc.
  • Diverse impostazioni di dimensionamento, ad esempio le dimensioni, se adattare o riempire, ecc.
  • Il formato. imgproxy supporta formati standard come JPEG e PNG, ma anche formati più moderni come WebP e AVIF. Può anche scegliere il formato migliore in base all’intestazione ‘Accept’.
  • Molte (molte!) altre opzioni, come filigrana, filtraggio, rotazione, ecc.

imgproxy offre sia una versione Open Source gratuita che una versione a pagamento; tutto ciò incluso in questo articolo fa parte della prima.

Una soluzione potrebbe essere che lo sviluppatore web inserisca ogni URL di imgproxy nell’HTML:

Questo rivelerebbe dettagli legati alla topologia sulla pagina web. Non è una soluzione mantenibile. Possiamo risolvere il problema con un proxy inverso o un API Gateway. Userò Apache APISIX per ovvie ragioni.

Con questo approccio, l’HTML precedente diventa molto più semplice:

Apache APISIX intercetta le richieste che iniziano con /resize, riscrive l’URL per imgproxy e inoltra l’URL riscritto a imgproxy. Ecco l’intero flusso:

La configurazione corrispondente di Apache APISIX appare come segue:

  1. Abbina le richieste precedute da /resize
  2. Riscrivi l’URL
  3. Intercetta la larghezza e l’immagine nella regolare espressione
  4. Formatta l’URL per imgproxy. http://server:3000 è il server che ospita l’immagine originale; @webp indica una preferenza per il formato WebP (se il browser lo supporta)

Con quanto sopra, /resize/200/ai-generated.jpg su Apache APISIX viene riscritto come /rs:fill/w:200/plain/http://server:3000/ai-generated.jpg@webp su imgproxy.

Testing

Possiamo configurare un piccolo campione di test con Docker Compose:

  1. Semplice server web che ospita l’HTML e l’immagine principale

Ora possiamo testare la configurazione precedente con gli strumenti per sviluppatori del browser, emulando dispositivi con schermo piccolo, ad esempio l’iPhone SE. Il risultato è il seguente:

  • A causa della risoluzione dello schermo, l’immagine richiesta è quella con una larghezza di 400px, non l’originale. Puoi vederlo nell’URL della richiesta
  • L’immagine restituita è nel formato WebP; il suo peso è di 14,4 kb
  • L’immagine JPEG originale pesa 154 kb, più di dieci volte di più. È un grande risparmio di larghezza di banda di rete!

Discussione

Ridurre i costi di archiviazione di un fattore di dieci è sicuramente un grande vantaggio. Tuttavia, non è tutto rose e fiori. Il ridimensionamento dell’immagine comporta un’intensa operazione di calcolo, che richiede tempo CPU per ogni richiesta. Inoltre, per quanto efficiente sia imgproxy, crea un’immagine richiedendo tempo. Abbiamo scambiato i costi di archiviazione con i costi CPU e ora subiamo un leggero calo delle prestazioni.

Per risolvere il problema, abbiamo bisogno di uno strato di caching di fronte, che sia personalizzato oppure, molto probabilmente, un CDN. Potresti obiettare che archivieremo nuovamente le risorse; quindi, i costi di archiviazione aumenteranno di nuovo. Tuttavia, la differenza significativa è che la cache funziona solo per le immagini utilizzate, mentre in precedenza pagavamo per archiviare tutte le immagini nella soluzione iniziale. È possibile applicare anche ricette note per la memorizzazione nella cache, come il pre-riscaldamento, quando si sa che un gruppo di immagini sarà molto richiesto, ad esempio prima di un evento.

Conclusioni

In questo post, abbiamo descritto come utilizzare Apache APISIX con imgproxy per ridurre i costi di archiviazione delle immagini in diverse risoluzioni. Con la memorizzazione nella cache in cima, si aggiungono ulteriori componenti all’architettura complessiva ma si riducono i costi di archiviazione.

Questo post è stato ispirato dalla relazione di Andreas Lehr alla StackConf.

Il codice sorgente completo di questo post può essere trovato su GitHub.

Per approfondire