Immagini con DPI elevato per densità di pixel variabili

Boris Smus
Boris Smus

Una delle caratteristiche del complesso panorama dei dispositivi odierni è che è disponibile una gamma molto ampia di densità dei pixel dello schermo. Alcuni dispositivi sono dotati di display ad altissima risoluzione, mentre altri sono indietro. Gli sviluppatori di applicazioni devono supportare una gamma di densità di pixel, Nel web mobile, le sfide sono combinate da diversi fattori:

  • Ampia varietà di dispositivi con fattori di forma diversi.
  • Larghezza di banda di rete e durata della batteria limitate.

Per quanto riguarda le immagini, l'obiettivo degli sviluppatori di app web è pubblicare le immagini della migliore qualità nel modo più efficiente possibile. Questo articolo illustra alcune tecniche utili per farlo oggi e nel prossimo futuro.

Se possibile, evita le immagini

Prima di aprire la scatola di worm, ricorda che il web ha molte tecnologie potenti che sono in gran parte indipendenti dalla risoluzione e dai valori DPI. In particolare, il testo, le immagini SVG e gran parte del codice CSS "funzioneranno" per via della funzionalità di ridimensionamento automatico dei pixel sul web (tramite devicePixelRatio).

Tuttavia, non è sempre possibile evitare le immagini raster. Ad esempio, potresti ricevere asset che sarebbero abbastanza difficili da replicare in formato SVG/CSS puro o che hai a che fare con una fotografia. Anche se puoi convertire automaticamente l'immagine in formato SVG, la vettorializzazione delle fotografie non ha senso perché le versioni ingrandite di solito non hanno un bell'aspetto.

Contesto

Una cronologia molto breve della densità del display

All'inizio, i display dei computer avevano una densità di pixel di 72 o 96 dpi (punti per pollice).

La densità dei pixel dei display è migliorata gradualmente, in gran parte dovuta al caso d'uso dei dispositivi mobili, in cui gli utenti in genere avvicinano lo smartphone ai volti, rendendo i pixel più visibili. Nel 2008, i telefoni a 150 dpi erano la nuova norma. La tendenza all'aumento della densità del display è continuata e oggi i nuovi telefoni sfoggiano display a 300 dpi (con il brand "Retina" di Apple).

Il Santo Graal, ovviamente, è un display in cui i pixel sono completamente invisibili. Per il fattore di forma dello smartphone, l'attuale generazione di display Retina/HiDPI potrebbe essere simile a quella ideale. Tuttavia, nuove classi di hardware e indossabili come Project Glass continueranno probabilmente a generare una maggiore densità dei pixel.

In pratica, le immagini a bassa densità dovrebbero avere lo stesso aspetto sui nuovi schermi di quelle vecchie, ma rispetto alle immagini nitide e ad alta densità che gli utenti sono abituati a vedere, le immagini a bassa densità appaiono scioccanti e pixelate. Di seguito è riportata una simulazione approssimativa di come apparirà un'immagine 1x su un display 2x. Al contrario, l'immagine 2x ha un aspetto piuttosto buono.

Babbuino 1x
Babbuino 2x
Babon! a densità di pixel diverse.

Pixel sul web

Al momento della progettazione del web, il 99% dei display aveva una risoluzione di 96 dpi (o si prevedeva) e sono state fatte poche disposizioni per la variazione su questo fronte. A causa delle grandi variazioni di dimensioni e densità degli schermi, abbiamo necessario un metodo standard per fare in modo che le immagini avessero un aspetto soddisfacente su schermi di diverse densità e dimensioni.

La specifica HTML ha recentemente affrontato questo problema definendo un pixel di riferimento che i produttori utilizzano per determinare le dimensioni di un pixel CSS.

Utilizzando il pixel di riferimento, un produttore può determinare la dimensione del pixel fisico del dispositivo rispetto al pixel standard o ideale. Questo rapporto è chiamato rapporto pixel del dispositivo.

Calcolo del rapporto pixel del dispositivo

Supponiamo che uno smartphone abbia uno schermo con una dimensione in pixel fisica di 180 pixel per pollice (ppi). Il calcolo del rapporto pixel del dispositivo richiede tre passaggi:

  1. Confronta la distanza effettiva a cui è tenuto il dispositivo alla distanza del pixel di riferimento.

    Secondo le specifiche, sappiamo che a 28 pollici l'ideale è 96 pixel per pollice. Tuttavia, poiché si tratta di uno smartphone, le persone tengono il dispositivo più vicino al volto rispetto a un laptop. Supponiamo che questa distanza sia di 18 pollici.

  2. Moltiplica il rapporto di distanza con la densità standard (96 ppi) per ottenere la densità di pixel ideale per la distanza specificata.

    DensitàPixel ideale = (28/18) * 96 = 150 pixel per pollice (circa)

  3. Prendi il rapporto tra la densità dei pixel fisici e la densità dei pixel ideale per ottenere il rapporto pixel del dispositivo.

    devicePixelRatio = 180/150 = 1,2

Modalità di calcolo del RapportoPixel del dispositivo.
Diagramma che mostra un pixel angolare di riferimento, per illustrare meglio come viene calcolato il rapportoPixel del dispositivo.

Pertanto, quando un browser deve sapere come ridimensionare un'immagine per adattarla allo schermo in base alla risoluzione ideale o standard, il browser fa riferimento al rapporto pixel del dispositivo pari a 1,2, il che significa che per ogni pixel ideale questo dispositivo ha 1,2 pixel fisici. La formula per passare tra i pixel ideali (come definito dalle specifiche web) e fisici (puntini sullo schermo del dispositivo) è la seguente:

physicalPixels = window.devicePixelRatio * idealPixels

In passato, i fornitori di dispositivi tendono ad arrotondare gli devicePixelRatios (RPD). iPhone e iPad di Apple riportano una DPR pari a 1 e gli equivalenti Retina indicano 2. La specifica CSS consiglia di

l'unità pixel fa riferimento al numero intero di pixel del dispositivo che meglio si avvicina al pixel di riferimento.

Un motivo per cui le proporzioni rotonde possono essere migliori è perché possono portare a meno artefatti di pixel secondari.

Tuttavia, la realtà del panorama dei dispositivi è molto più varia e gli smartphone Android hanno spesso un DPR pari a 1, 5. Il tablet Nexus 7 ha un DPR di circa 1,33, ottenuto con un calcolo simile a quello di cui sopra. In futuro dovresti vedere altri dispositivi con DPR variabili. Per questo motivo, non devi mai dare per scontato che i tuoi clienti abbiano DPR interi.

Panoramica delle tecniche per immagini HiDPI

Esistono molte tecniche per risolvere il problema di mostrare le immagini di migliore qualità il più rapidamente possibile, in linea di massima due categorie:

  1. Ottimizzare le singole immagini
  2. Ottimizzazione della selezione tra più immagini.

Approccio basato su una singola immagine: utilizza una sola immagine, ma utilizzala in modo intelligente. Questi approcci presentano lo svantaggio di sacrificare inevitabilmente le prestazioni, poiché scaricherai immagini HiDPI anche su dispositivi meno recenti con un DPI inferiore. Ecco alcuni approcci per il caso con immagine singola:

  • Immagine HiDPI fortemente compressa
  • Formato dell'immagine assolutamente eccezionale
  • Formato dell'immagine progressivo

Vari approcci alle immagini: usa più immagini, ma scegli con intelligenza quale caricare. Questi approcci hanno un costo intrinseco per lo sviluppatore, che deve creare più versioni dello stesso asset e poi definire una strategia decisionale. Di seguito sono riportate le opzioni:

  • JavaScript
  • Distribuzione lato server
  • Query multimediali CSS
  • Funzionalità del browser integrate (image-set(), <img srcset>)

Immagine HiDPI fortemente compressa

Le immagini già costituiscono già il 60% della larghezza di banda dedicata al download di un sito web medio. Fornendo immagini HiDPI a tutti i client, aumenteremo questo numero. Di quanto crescerà?

Ho eseguito alcuni test che hanno generato frammenti di immagine 1x e 2x con qualità JPEG a 90, 50 e 20. Ecco lo script shell che ho utilizzato (utilizzando ImageMagick) per generarli:

Esempio di riquadri 1. Esempio di riquadri 2. Esempio di riquadri 3.
Campioni di immagini con diverse compressione e densità di pixel.

Da questo piccolo campione non scientifico, sembra che la compressione di immagini di grandi dimensioni offra un buon compromesso tra qualità e dimensione. Dal mio punto di vista, le immagini 2x molto compresse hanno un aspetto migliore rispetto a quelle 1x non compresse.

Naturalmente, fornire immagini 2x di bassa qualità e altamente compresse a dispositivi 2x è peggiore rispetto a pubblicare immagini di qualità superiore e l'approccio descritto sopra comporta sanzioni relative alla qualità delle immagini. Se confronti la qualità (90 immagini rispetto alla qualità: 20 immagini), noterai un calo della nitidezza e una maggiore granularità. Questi elementi potrebbero non essere accettabili nei casi in cui le immagini di alta qualità siano fondamentali (ad esempio in un'applicazione per la visualizzazione di foto) o per gli sviluppatori di app che non sono disposti a scendere a compromessi.

Il confronto riportato sopra è stato effettuato interamente con JPEG compressi. Vale la pena notare che esistono molti compromessi tra i formati dell'immagine ampiamente implementati (JPEG, PNG, GIF) e questo ci porta a...

Formato dell'immagine assolutamente eccezionale

WebP è un formato delle immagini piuttosto avvincente che si comprime molto bene, mantenendo un'elevata fedeltà delle immagini. Ovviamente, non è ancora implementato ovunque.

Un modo consiste nel verificare il supporto WebP tramite JavaScript. Carica un'immagine da 1 px tramite data-uri, attendi che siano stati attivati gli eventi caricati o di errore, quindi verifica che le dimensioni siano corrette. Modernizr viene fornito con uno script di rilevamento delle funzionalità, disponibile tramite Modernizr.webp.

Tuttavia, un modo migliore per farlo è direttamente in CSS usando la funzione image(). Quindi, se hai un'immagine WebP e JPEG di riserva, puoi scrivere quanto segue:

#pic {
  background: image("foo.webp", "foo.jpg");
}

Questo approccio presenta alcuni problemi. Innanzitutto, image() non è completamente implementato. In secondo luogo, anche se la compressione WebP soffia il formato JPEG dall'acqua, è comunque un miglioramento relativamente incrementale, circa il 30% più piccolo secondo questa galleria WebP. Quindi, da solo WebP non è sufficiente per risolvere il problema dei DPI elevati.

Formati dell'immagine progressivi

I formati dell'immagine progressivi come JPEG 2000, JPEG progressivo, PNG progressivo e GIF offrono il vantaggio (un po' discusso) di vedere l'immagine applicata prima che venga caricata completamente. Possono comportare un overhead maggiore, sebbene ci siano prove contrastanti. Jeff Atwood ha affermato che la modalità progressiva "aggiunge circa il 20% alla dimensione delle immagini PNG e circa il 10% alle dimensioni delle immagini JPEG e GIF". Tuttavia, Stoyan Stefanov ha affermato che per i file di grandi dimensioni, la modalità progressiva è più efficiente (nella maggior parte dei casi).

A prima vista, le immagini progressive sono molto promettenti se vengono pubblicate il più rapidamente possibile le immagini con la migliore qualità possibile. L'idea è che il browser possa interrompere il download e la decodifica di un'immagine quando sa che i dati aggiuntivi non aumentano la qualità dell'immagine (ovvero che tutti i miglioramenti della fedeltà sono sottopixel).

Sebbene le connessioni siano facili da terminare, spesso sono costose da riavviare. Per un sito con molte immagini, l'approccio più efficace consiste nel mantenere attiva una singola connessione HTTP, riutilizzandola il più a lungo possibile. Se la connessione viene interrotta prematuramente perché un'immagine è stata scaricata abbastanza, il browser deve quindi creare una nuova connessione, che può essere molto lenta in ambienti a bassa latenza.

Una soluzione alternativa a questo problema è utilizzare la richiesta Intervallo HTTP, che consente ai browser di specificare un intervallo di byte da recuperare. Un browser intelligente potrebbe fare una richiesta HEAD per ottenere l'intestazione, elaborarla, decidere quanta immagine è effettivamente necessaria e quindi recuperare. Sfortunatamente, l'intervallo HTTP è supportato pocomente nei server web, pertanto questo approccio non è attuabile.

Infine, un ovvio limite di questo approccio è che non puoi scegliere l'immagine da caricare, ma solo le differenze di fedeltà della stessa immagine. Di conseguenza, questa soluzione non fa riferimento al caso d'uso "art director".

Utilizza JavaScript per decidere quale immagine caricare

Il primo e più ovvio approccio per decidere quale immagine caricare è utilizzare JavaScript nel client. Questo approccio ti consente di scoprire ogni aspetto del tuo user agent e di agire nel modo giusto. Puoi determinare il rapporto pixel del dispositivo tramite window.devicePixelRatio, ottenere la larghezza e l'altezza dello schermo e persino eseguire lo sniffing della connessione di rete tramite navigator.connection o inviando una richiesta falsa, come fa la libreria foresight.js. Dopo aver raccolto tutte queste informazioni, puoi decidere quale immagine caricare.

Esistono circa un milione di librerie JavaScript che funzionano in modo simile, ma nessuna di queste è particolarmente straordinaria.

Un grande svantaggio di questo approccio è che l'utilizzo di JavaScript comporta il ritardo del caricamento dell'immagine fino al termine dell'analizzatore sintattico. Ciò significa che il download delle immagini non inizierà nemmeno dopo l'attivazione dell'evento pageload. Scopri di più nell'articolo di Jason Grigsby.

Decidi quale immagine caricare sul server

Puoi rimandare la decisione al lato server scrivendo gestori di richieste personalizzati per ogni immagine pubblicata. Un gestore di questo tipo verifica la presenza del supporto Retina in base allo user agent (l'unica informazione inoltrata al server). Quindi, a seconda che la logica lato server voglia pubblicare asset HiDPI, caricherai l'asset appropriato (denominato secondo una convenzione nota).

Purtroppo, lo user agent non fornisce necessariamente informazioni sufficienti per decidere se un dispositivo deve ricevere immagini di alta o bassa qualità. Inoltre, è inutile dire che qualsiasi cosa relativa allo User-Agent è un attacco di pirateria informatica e, se possibile, dovrebbe essere evitato.

Utilizzare query supporti CSS

Essendo dichiarative, le query multimediali CSS ti consentono di indicare la tua intenzione e lasciare che il browser faccia la cosa giusta per tuo conto. Oltre all'utilizzo più comune di query supporti (corrispondenti alle dimensioni del dispositivo), puoi anche far corrispondere devicePixelRatio. La query supporti associata è device-pixel-ratio e ha varianti minime e massime associate, come puoi aspettarti. Se vuoi caricare immagini con DPI elevato e la proporzione pixel del dispositivo supera una soglia, ecco cosa puoi fare:

#my-image { background: (low.png); }

@media only screen and (min-device-pixel-ratio: 1.5) {
  #my-image { background: (high.png); }
}

Diventa un po' più complicato con tutti i prefissi dei fornitori combinati, soprattutto a causa di folli differenze nel posizionamento dei prefissi "min" e "max":

@media only screen and (min--moz-device-pixel-ratio: 1.5),
    (-o-min-device-pixel-ratio: 3/2),
    (-webkit-min-device-pixel-ratio: 1.5),
    (min-device-pixel-ratio: 1.5) {

  #my-image {
    background:url(high.png);
  }
}

Con questo approccio, puoi nuovamente usufruire dei vantaggi dell'analisi look-ahead, che è stato perso con la soluzione JS. Puoi anche scegliere in modo flessibile i punti di interruzione adattabili (ad esempio, puoi utilizzare immagini con DPI basso, medio e alto), che non si verificava con l'approccio lato server.

Sfortunatamente, è ancora un po' ingombrante e genera un codice CSS dall'aspetto strano (o richiede una pre-elaborazione). Inoltre, questo approccio è limitato alle proprietà CSS, quindi non è possibile impostare un <img src> e le immagini devono essere tutte elementi con uno sfondo. Infine, utilizzando rigorosamente le proporzioni pixel del dispositivo, potresti ritrovarti in situazioni in cui lo smartphone ad alta DPI finisse per scaricare un enorme asset immagine 2x su una connessione EDGE. Non si tratta della migliore esperienza utente.

Usare le nuove funzionalità del browser

Di recente abbiamo discusso molto del supporto della piattaforma web per il problema delle immagini con DPI elevato. Apple ha recentemente fatto ingresso nel settore, portando la funzione CSS image-set() su WebKit. Di conseguenza, è supportato sia da Safari sia da Chrome. Poiché è una funzione CSS, image-set() non risolve il problema dei tag <img>. Inserisci @srcset per risolvere il problema, ma al momento della stesura di questo documento non ha alcuna implementazione di riferimento. La prossima sezione approfondisce image-set e srcset.

Funzionalità del browser per il supporto di una risoluzione elevata DPI

In definitiva, la decisione sull'approccio da adottare dipende dalle tue specifiche esigenze. Detto questo, tutti gli approcci sopra descritti presentano degli svantaggi. Guardando al futuro, tuttavia, una volta che image-set e srcset saranno ampiamente supportati, si tratteranno delle soluzioni appropriate a questo problema. Per il momento, parliamo di alcune best practice che possono avvicinarci il più possibile al futuro ideale.

Innanzitutto, in cosa differiscono questi due aspetti? image-set() è una funzione CSS, appropriata per l'uso come valore della proprietà CSS di sfondo. srcset è un attributo specifico per gli elementi <img>, con una sintassi simile. Entrambi questi tag consentono di specificare dichiarazioni delle immagini, ma l'attributo srcset consente anche di configurare l'immagine da caricare in base alle dimensioni dell'area visibile.

Best practice per il set di immagini

La funzione CSS image-set() è disponibile con prefisso -webkit-image-set(). La sintassi è piuttosto semplice, prendendo una o più dichiarazioni delle immagini separate da virgole, costituite da una stringa URL o da una funzione url() seguita dalla risoluzione associata. Ad esempio:

background-image:  -webkit-image-set(
  url(icon1x.jpg) 1x,
  url(icon2x.jpg) 2x
);

Questo indica al browser che è possibile scegliere tra due immagini. Uno è ottimizzato per display 1x e l'altro per display 2x. Il browser sceglie quindi quale caricare in base a una serie di fattori, tra cui la velocità della rete, se il browser è abbastanza intelligente (attualmente non implementato a seconda che ne so).

Oltre a caricare l'immagine corretta, il browser la scala di conseguenza. In altre parole, il browser presuppone che due immagini siano due volte più grandi di quelle 1x e quindi ridurrà l'immagine 2x di un fattore di 2, in modo che l'immagine appaia delle stesse dimensioni sulla pagina.

Anziché specificare 1x, 1,5x o Nx, puoi anche indicare una determinata densità di pixel del dispositivo in dpi.

Questo comando funziona bene, tranne nei browser che non supportano la proprietà image-set, per i quali non viene mostrata alcuna immagine. È chiaramente un problema, quindi devi utilizzare una serie di fallback (o una serie di elementi di riserva) per risolvere il problema:

background-image: url(icon1x.jpg);
background-image: -webkit-image-set(
  url(icon1x.jpg) 1x,
  url(icon2x.jpg) 2x
);
/* This will be useful if image-set gets into the platform, unprefixed.
    Also include other prefixed versions of this */
background-image: image-set(
  url(icon1x.jpg) 1x,
  url(icon2x.jpg) 2x
);

Quanto riportato sopra caricherà l'asset appropriato nei browser che supportano il set di immagini e altrimenti utilizzerà l'asset 1x. Un avvertimento ovvio è che, sebbene il supporto del browser image-set() sia basso, la maggior parte degli user agent riceverà l'asset 1x.

Questa demo utilizza image-set() per caricare l'immagine corretta, utilizzando l'asset 1x se questa funzione CSS non è supportata.

A questo punto, ti starai chiedendo perché non semplicemente eseguire il polyfill, ovvero creare uno shim JavaScript per image-set(), e chiamarlo un giorno. È piuttosto difficile implementare polyfill efficienti per le funzioni CSS. Per una spiegazione dettagliata del motivo, consulta questa Discussione in stile www.

srcset immagine

Ecco un esempio di srcset:

<img alt="my awesome image"
  src="banner.jpeg"
  srcset="banner-HD.jpeg 2x, banner-phone.jpeg 640w, banner-phone-HD.jpeg 640w 2x">

Come puoi vedere, oltre alle dichiarazioni x fornite da image-set, l'elemento srcset accetta anche i valori w e h che corrispondono alle dimensioni dell'area visibile, per tentare di pubblicare la versione più pertinente. Quanto riportato sopra consente di pubblicare banner-phone.jpeg per dispositivi con larghezza dell'area visibile inferiore a 640 px, banner-phone-HD.jpeg per dispositivi con schermo piccolo ad alto DPI, banner-HD.jpeg ai dispositivi con DPI elevato con schermi superiori a 640 px e banner.jpeg per tutto il resto.

Utilizzare il set di immagini per gli elementi immagine

Poiché l'attributo srcset negli elementi img non è implementato nella maggior parte dei browser, si potrebbe avere la tentazione di sostituire gli elementi img con <div> con sfondi e utilizzare l'approccio image-set. Funziona, con le avvertenze. Lo svantaggio è che il tag <img> ha un valore semantico lungo. In pratica, questo è importante soprattutto per i web crawler e per motivi di accessibilità.

Se finissi per utilizzare -webkit-image-set, potresti essere tentato di utilizzare la proprietà CSS di sfondo. Lo svantaggio di questo approccio è che devi specificare le dimensioni dell'immagine, che non sono note se utilizzi un'immagine non 1x. Invece di farlo, puoi utilizzare la proprietà CSS dei contenuti nel seguente modo:

<div id="my-content-image"
  style="content: -webkit-image-set(
    url(icon1x.jpg) 1x,
    url(icon2x.jpg) 2x);">
</div>

L'immagine verrà scalata automaticamente in base alle proporzioni pixel del dispositivo. Consulta questo esempio della tecnica precedente in azione, con un ulteriore fallback a url() per i browser che non supportano image-set.

srcset di policompilazione

Una funzionalità utile di srcset è che ha un fallback naturale. Nel caso in cui l'attributo srcset non sia implementato, tutti i browser sanno di dover elaborare l'attributo src. Inoltre, poiché si tratta solo di un attributo HTML, è possibile creare polyfill con JavaScript.

Questo polyfill viene fornito con i test delle unità per garantire che sia il più vicino possibile alla specifica. Inoltre, sono in atto dei controlli che impediscono al polyfill di eseguire qualsiasi codice se srcset è implementato in modo nativo.

Ecco una demo del polyfill.

Conclusione

Non esiste un punto magico per risolvere il problema delle immagini con DPI elevato.

La soluzione più semplice è evitare del tutto le immagini, optando invece per SVG e CSS. Tuttavia, ciò non è sempre realistico, soprattutto se il tuo sito include immagini di alta qualità.

Gli approcci in JS, CSS e l'utilizzo del lato server hanno tutti i loro punti di forza e di debolezza. L'approccio più promettente, tuttavia, consiste nello sfruttare le nuove funzionalità del browser. Sebbene il supporto dei browser per image-set e srcset sia ancora incompleto, oggi esistono opzioni di riserva ragionevoli da utilizzare.

Per riepilogare, i miei consigli sono i seguenti:

  • Per le immagini di sfondo, utilizza image-set con i video di riserva appropriati per i browser che non lo supportano.
  • Per le immagini di contenuti, utilizza un polyfill srcset oppure di riserva all'utilizzo di image-set (vedi sopra).
  • Per le situazioni in cui sei disposto a sacrificare la qualità delle immagini, valuta la possibilità di utilizzare immagini 2x molto compresse.