Imágenes con valores altos de DPI para densidades de píxeles variables

Fecha de publicación: 22 de agosto de 2012; Última actualización: 14 de abril de 2025

Con tantos dispositivos en el mercado, hay una gama muy amplia de densidades de píxeles de pantalla disponibles. Los desarrolladores de aplicaciones deben admitir una variedad de densidades de píxeles, lo que puede ser bastante desafiante. En la Web móvil, los desafíos se multiplican por varios factores:

  • Gran variedad de dispositivos con diferentes factores de forma.
  • Ancho de banda de red y duración de batería limitados.

En términos de imágenes, el objetivo de los desarrolladores web es entregar las imágenes de mejor calidad de la manera más eficiente posible. Aquí, presentamos algunas técnicas útiles para hacerlo ahora y en el futuro.

Evita las imágenes siempre que sea posible

Antes de suponer que necesitas incluir una imagen, recuerda que la Web tiene varias tecnologías potentes que son independientes de la resolución y el DPI. Específicamente, el texto, SVG y gran parte de CSS “funcionan” gracias a la función de escalamiento automático de píxeles de la Web con devicePixelRatio.

Dicho esto, no siempre puedes evitar las imágenes rasterizadas. Por ejemplo, es posible que se te proporcionen recursos que serían bastante difíciles de replicar en SVG o CSS puros. Es posible que estés tratando con una fotografía. Si bien puedes convertir una imagen en un SVG automáticamente, vectorizar fotografías no tiene mucho sentido, ya que las versiones ampliadas suelen no verse bien.

Historia de la densidad de píxeles

En los primeros días, las pantallas de computadoras tenían una densidad de píxeles de 72 o 96 puntos por pulgada (DPI).

Las pantallas mejoraron gradualmente en densidad de píxeles, en gran medida gracias al avance de los dispositivos móviles, ya que los usuarios suelen sostener sus teléfonos más cerca de sus rostros, lo que hace que los píxeles sean más visibles. En 2008, los teléfonos de 150 dpi se convirtieron en la nueva norma. Se siguió aumentando la densidad de la pantalla, y los teléfonos actuales tienen pantallas de 300 ppp.

En la práctica, las imágenes de baja densidad deberían verse igual en las pantallas nuevas que en las antiguas, pero en comparación con las imágenes nítidas de alta densidad que los usuarios están acostumbrados a ver, las imágenes de baja densidad se ven irregulares y pixeladas. La siguiente es una simulación aproximada de cómo se ve una imagen de 1x en una pantalla de 2x. En cambio, la imagen del doble de tamaño se ve bastante bien.

1x píxeles 2 veces los píxeles
Densidad de píxeles de Baboon 1x. Densidad de píxeles 2 veces mayor que la de Baboon.
Babuinos con diferentes densidades de píxeles.

Píxeles en la Web

Cuando se diseñó la Web, el 99% de las pantallas tenía 96 ppp y se hicieron pocas provisiones para las variaciones. Ahora que tenemos una gran variación en los tamaños y las densidades de pantalla, necesitamos una forma estándar de hacer que las imágenes se vean bien en todas ellas.

La especificación HTML abordó este problema definiendo un píxel de referencia que los fabricantes usan para determinar el tamaño de un píxel CSS.

Con el píxel de referencia, un fabricante puede determinar el tamaño del píxel físico del dispositivo en relación con el píxel estándar o ideal. Esta proporción se denomina proporción de píxeles del dispositivo.

Cómo calcular la proporción de píxeles del dispositivo

Supongamos que un teléfono celular tiene una pantalla con un tamaño de píxeles físico de 180 píxeles por pulgada (ppi). El cálculo de la proporción de píxeles del dispositivo requiere tres pasos:

  1. Compara la distancia real a la que se sostiene el dispositivo con la distancia del píxel de referencia.

    Según las especificaciones, sabemos que, a 28 pulgadas, lo ideal es 96 píxeles por pulgada. Con un teléfono celular, sabemos que los rostros de las personas están más cerca de sus dispositivos que con las laptops y las computadoras de escritorio. En las siguientes fórmulas, estimamos esa distancia en 45 cm.

  2. Multiplica la relación de distancia por la densidad estándar (96 ppi) para obtener la densidad de píxeles ideal para la distancia determinada.

    idealPixelDensity = (28/18) × 96 = 150 píxeles por pulgada (aproximadamente)

  3. Toma la proporción de la densidad de píxeles física a la densidad de píxeles ideal para obtener la proporción de píxeles del dispositivo.

    devicePixelRatio = 180/150 = 1.2

Un píxel angular de referencia para ilustrar cómo se calcula la proporción de píxeles del dispositivo.

Por lo tanto, cuando un navegador necesita saber cómo cambiar el tamaño de una imagen para que se ajuste a la pantalla, según la resolución ideal o estándar, el navegador se refiere a la relación de píxeles del dispositivo de 1.2. Esto significa que, por cada píxel ideal, este dispositivo tiene 1.2 píxeles físicos. La fórmula para pasar de los píxeles ideales (según se define en las especificaciones web) a los físicos (puntos en la pantalla del dispositivo) es la siguiente:

physicalPixels = window.devicePixelRatio * idealPixels

Históricamente, los proveedores de dispositivos suelen redondear los devicePixelRatios (DPR). Los iPhone y los iPad de Apple registran una DPR de 1, y sus equivalentes Retina registran 2. La especificación de CSS recomienda que

La unidad de píxeles hace referencia al número entero de píxeles del dispositivo que se aproxima mejor al píxel de referencia.

Una razón por la que las relaciones redondas pueden ser mejores es porque pueden generar menos artefactos de subpíxeles.

Sin embargo, la realidad del panorama de dispositivos es mucho más variada, y los teléfonos Android suelen tener DPR de 1.5. La tablet Nexus 7 tiene un DPR de aproximadamente 1.33, que se obtuvo mediante un cálculo similar al ejemplo anterior. Se espera que en el futuro haya más dispositivos con DPR variables. Por lo tanto, nunca debes suponer que tus clientes tienen DPR de números enteros.

Técnicas de imágenes HiDPI

Existen muchas técnicas para resolver el problema de mostrar las imágenes de mejor calidad lo más rápido posible, que se dividen en dos categorías:

  1. Optimiza imágenes individuales.
  2. Optimización de la selección entre varias imágenes

Enfoques de una sola imagen: usa una imagen, pero haz algo inteligente con ella. Estos enfoques tienen el inconveniente de que, inevitablemente, debes sacrificar el rendimiento, ya que debes descargar imágenes HiDPI, incluso en dispositivos más antiguos con DPI más bajos. Estos son algunos enfoques para el caso de una sola imagen:

  • Imagen HiDPI muy comprimida
  • Formato de imagen totalmente genial
  • Formato de imagen progresiva

Enfoques de varias imágenes: Usa varias imágenes, pero haz algo inteligente para elegir cuál cargar. Estos enfoques tienen una sobrecarga inherente para que el desarrollador cree varias versiones del mismo recurso y, luego, determine una estrategia de decisión. Dispone de las siguientes opciones:

  • JavaScript
  • Publicación del servidor
  • Consultas de medios de CSS
  • Funciones integradas del navegador (image-set(), <img srcset>)

Imagen HiDPI muy comprimida

Las imágenes ya representan un asombroso 60% del ancho de banda que se usa para descargar un sitio web promedio. Cuando entregamos imágenes HiDPI a todos los clientes, aumentamos esta cantidad. ¿Cuánto más puede crecer?

Ejecuté algunas pruebas que generaron fragmentos de imagen de 1x y 2x con calidad JPEG de 90, 50 y 20.

Seis versiones de una imagen, con diferentes niveles de compresión y densidad de píxeles. Seis versiones de una imagen, con diferentes niveles de compresión y densidad de píxeles. Seis versiones de una imagen, con diferentes niveles de compresión y densidad de píxeles.

A partir de este pequeño muestreo no científico, parece que comprimir imágenes grandes proporciona una buena relación calidad-tamaño. En mi opinión, las imágenes de 2x muy comprimidas se ven mejor que las imágenes de 1x sin comprimir.

Dicho esto, entregar imágenes de baja calidad y muy comprimidas a dispositivos de 2x es peor que entregar imágenes de mayor calidad, y ese enfoque generaría penalizaciones de calidad de imagen. Si comparas las imágenes quality: 90 con las imágenes quality: 20, verás que la imagen es menos nítida y tiene más grano. Es posible que los artefactos con quality:20 no sean aceptables cuando las imágenes de alta calidad son importantes (por ejemplo, una aplicación de visor de fotos) o para los desarrolladores de apps que no están dispuestos a aceptar compromisos.

Esta comparación se realizó por completo con archivos JPEG comprimidos. Vale la pena señalar que existen muchos inconvenientes entre los formatos de imagen ampliamente implementados (JPEG, PNG y GIF), lo que nos lleva a…

WebP: Formato de imagen totalmente asombroso

WebP es un formato de imagen atractivo que se comprime muy bien y, al mismo tiempo, mantiene una alta fidelidad de imagen.

Una forma de verificar la compatibilidad con WebP es usar JavaScript. Carga una imagen de 1 píxel con data-uri, espera a que se cargue o se activen eventos de error y, luego, verifica que el tamaño sea correcto. Modernizr se envía con una secuencia de comandos de detección de funciones, que está disponible con Modernizr.webp.

Sin embargo, una mejor manera de hacerlo es directamente en CSS con la función image(). Por lo tanto, si tienes una imagen WebP y un resguardo JPEG, puedes escribir lo siguiente:

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

Este enfoque tiene algunos problemas. En primer lugar, image() no se implementa de forma generalizada. En segundo lugar, si bien la compresión de WebP supera a JPEG, sigue siendo una mejora relativamente incremental, aproximadamente un 30% más pequeña según esta galería de WebP. Por lo tanto, WebP por sí solo no es suficiente para abordar el problema de DPI alto.

Formatos de imagen progresiva

Los formatos de imagen progresivos, como JPEG 2000, JPEG progresivo, PNG progresivo y GIF, tienen el beneficio (algo debatido) de ver la imagen antes de que se cargue por completo. Pueden generar una sobrecarga de tamaño, aunque hay evidencia contradictoria sobre esto. Jeff Atwood afirmó que el modo progresivo “agrega alrededor de un 20% al tamaño de las imágenes PNG y un 10% al tamaño de las imágenes JPEG y GIF”. Sin embargo, Stoyan Stefanov afirmó que, para los archivos grandes, el modo progresivo es más eficiente (en la mayoría de los casos).

A primera vista, las imágenes progresivas parecen ser muy prometedoras en el contexto de la publicación de imágenes de la mejor calidad lo más rápido posible. La idea es que el navegador pueda dejar de descargar y decodificar una imagen una vez que sepa que los datos adicionales no aumentarán la calidad de la imagen (es decir, todas las mejoras de fidelidad son subpíxeles).

Si bien las conexiones se finalizan rápidamente, a menudo es costoso reiniciarlas. Para un sitio con muchas imágenes, el enfoque más eficiente es mantener activa una sola conexión HTTP y reutilizarla durante el mayor tiempo posible. Si la conexión se cierra de forma prematura porque se descargó una imagen suficiente, el navegador debe crear una conexión nueva, lo que puede ser muy lento en entornos de baja latencia.

Una solución alternativa a esto es usar la solicitud de rango HTTP, que permite que los navegadores especifiquen un rango de bytes para recuperar. Un navegador inteligente podría realizar una solicitud HEAD para obtener el encabezado, procesarlo, decidir qué parte de la imagen se necesita y, luego, recuperarla. Lamentablemente, el rango HTTP no es compatible con los servidores web, lo que hace que este enfoque no sea práctico.

Por último, una limitación obvia de este enfoque es que no puedes elegir qué imagen cargar, solo puedes variar las fidelidades de la misma imagen. Como resultado, no se aborda el caso de uso de "dirección de arte".

Usa JavaScript para decidir qué imagen cargar

El primer y más obvio enfoque para decidir qué imagen cargar es usar JavaScript en el cliente. Este enfoque te permite obtener información sobre tu usuario-agente y hacer lo correcto. Puedes determinar la relación de píxeles del dispositivo con window.devicePixelRatio, obtener el ancho y la altura de la pantalla, y hasta realizar un espionaje de la conexión de red con navigator.connection o emitir una solicitud falsa, como lo hace la biblioteca foresight.js. Una vez que hayas recopilado toda esta información, podrás decidir qué imagen cargar.

Existen aproximadamente un millón de bibliotecas de JavaScript que usan esta técnica. Lamentablemente, ninguno de ellos se destaca.

Una gran desventaja es que retrasas la carga de imágenes hasta que finaliza el analizador de anticipaciones. Esto significa, en esencia, que las imágenes ni siquiera comenzarán a descargarse hasta que se active el evento pageload. Obtén más información en el artículo de Jason Grigsby.

Decide qué imagen cargar en el servidor

Puedes aplazar la decisión al servidor escribiendo controladores de solicitudes personalizados para cada imagen que publiques. Un controlador de este tipo verificaría la compatibilidad con Retina en función del usuario-agente (la única información que se comparte con el servidor). Luego, según si la lógica del servidor quiere entregar recursos HiDPI, cargas el recurso adecuado (con un nombre según alguna convención conocida).

Lamentablemente, el usuario-agente no siempre proporciona información suficiente para decidir si un dispositivo debe recibir imágenes de alta o baja calidad. Además, se debe evitar cualquier solución que use User-Agent para tomar decisiones de diseño.

Usa consultas de medios de CSS

Como son declarativas, las consultas de medios de CSS te permiten indicar tu intención y permitir que el navegador haga lo correcto en tu nombre. Además del uso más común de las consultas de medios (coincidencia de tamaño del dispositivo), también puedes hacer coincidir devicePixelRatio. La consulta de medios asociada es device-pixel-ratio y tiene variantes mínimas y máximas asociadas, como es de esperar.

Si quieres cargar imágenes de alta densidad de píxeles y la relación de píxeles del dispositivo supera un umbral, puedes hacer lo siguiente:

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

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

Se vuelve un poco más complicado con todos los prefijos de proveedores mezclados, en especial debido a las grandes diferencias en la posición de los prefijos "min" y "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 este enfoque, recuperas los beneficios del análisis anticipado, que se perdieron con la solución de JavaScript. También obtienes la flexibilidad de elegir tus puntos de inflexión responsivos (por ejemplo, puedes tener imágenes de DPI bajas, medias y altas), que se perdían con el enfoque del servidor.

Lamentablemente, aún es un poco incómodo y genera CSS con un aspecto extraño o requiere procesamiento previo. Además, este enfoque se limita a las propiedades CSS, por lo que no hay forma de establecer un <img src>, y todas tus imágenes deben ser elementos con un fondo. Por último, si dependes estrictamente de la relación de píxeles del dispositivo, puedes terminar en situaciones en las que tu teléfono celular de alta densidad de píxeles termine descargando un recurso de imagen masivo de 2 veces mientras está en una conexión EDGE. Esta no es la mejor experiencia del usuario.

Como image-set() es una función de CSS, no aborda el problema de las etiquetas <img>. Ingresa @srcset, que soluciona este problema. En la siguiente sección, se profundiza en image-set y srcset.

Funciones del navegador para admitir alta densidad de píxeles

En última instancia, la forma en que abordes la compatibilidad con DPI altos depende de tus requisitos específicos. Todos los enfoques mencionados tienen inconvenientes.

Ahora que image-set y srcset son ampliamente compatibles, son las mejores soluciones. Existen prácticas recomendadas adicionales que pueden acercarnos más a los navegadores más antiguos.

¿En qué se diferencian? Bueno, image-set() es una función de CSS, adecuada para usarse como valor de la propiedad CSS de fondo. srcset es un atributo específico de los elementos <img>, con una sintaxis similar. Ambas etiquetas te permiten especificar declaraciones de imágenes, pero el atributo srcset también te permite configurar qué imagen cargar según el tamaño del viewport.

Prácticas recomendadas para el conjunto de imágenes

La sintaxis de image-set() toma una o más declaraciones de imagen separadas por comas, que consisten en una cadena de URL o una función url() seguida de la resolución adecuada. Por ejemplo:

image-set(
  url("image1.jpg") 1x,
  url("image2.jpg") 2x
);

/* You can also include image-set without `url()` */
image-set(
  "image1.jpg" 1x,
  "image2.jpg" 2x
);

Esto le indica al navegador que hay dos imágenes para elegir. Una imagen está optimizada para pantallas de 1x y la otra para pantallas de 2x. Luego, el navegador puede elegir cuál cargar en función de una variedad de factores, que incluso pueden incluir la velocidad de la red, si el navegador es lo suficientemente inteligente.

Además de cargar la imagen correcta, el navegador la ajusta según corresponda. En otras palabras, el navegador supone que 2 imágenes son el doble de grandes que las de 1x, por lo que reduce la imagen de 2x en un factor de 2, de modo que la imagen parezca tener el mismo tamaño en la página.

En lugar de especificar 1x, 1.5x o Nx, también puedes especificar una densidad de píxeles del dispositivo en DPI.

Si te preocupa que los navegadores más antiguos no admitan la propiedad image-set, puedes agregar un resguardo para asegurarte de que se muestre una imagen. Por ejemplo:

/* Fallback support. */
background-image: url(icon1x.jpg);
background-image: image-set(
  url(icon1x.jpg) 1x,
  url(icon2x.jpg) 2x
);

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

Este código de ejemplo carga el recurso adecuado en los navegadores que admiten set de imágenes y, de lo contrario, recurre al recurso de 1x.

En este punto, es posible que te preguntes por qué no solo se usa un polyfill (es decir, se crea un shim de JavaScript para) image-set() y se da por terminado el día. Resulta que es bastante difícil implementar polyfills eficientes para las funciones de CSS. (para obtener una explicación detallada del motivo, consulta esta discusión de estilo www).

Srcset de imagen

Además de las declaraciones que proporciona image-set, el elemento srcset también toma valores de ancho y alto que corresponden al tamaño del viewport y, de esta manera, intenta entregar la versión más relevante.

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

En este ejemplo, se entrega banner-phone.jpeg a dispositivos con un ancho de viewport inferior a 640 px, banner-phone-HD.jpeg a dispositivos con alta densidad de píxeles y pantalla pequeña, banner-HD.jpeg a dispositivos con alta densidad de píxeles y pantallas superiores a 640 px, y banner.jpeg a todo lo demás.

Usa image-set para los elementos de imagen

Puede ser tentador reemplazar los elementos img por <div> con fondos y usar el enfoque de conjunto de imágenes. Esto funciona, pero con algunas salvedades. El inconveniente aquí es que la etiqueta <img> tiene un valor semántico a largo plazo. En la práctica, esto es importante para la accesibilidad y los rastreadores web.

Puedes usar la propiedad CSS de contenido, que escala automáticamente la imagen según devicePixelRation. Por ejemplo:

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

Polyfill srcset

Una función útil de srcset es que incluye un resguardo natural. En el caso de que no se implemente el atributo srcset, todos los navegadores saben procesar el atributo src. Además, como es solo un atributo HTML, es posible crear polyfills con JavaScript.

Este polyfill incluye pruebas de unidades para garantizar que sea lo más cercano posible a la especificación. Además, existen verificaciones que impiden que el polyfill ejecute código si srcset se implementa de forma nativa.

Conclusión

La mejor solución para las imágenes de alta DPI es optar por SVG y CSS. Sin embargo, esta no siempre es una solución realista, en especial para los sitios web con muchas imágenes.

Los enfoques de JavaScript, CSS y las soluciones del servidor tienen sus fortalezas y debilidades. El enfoque más prometedor es usar image-set y srcset.

En resumen, mis recomendaciones son las siguientes:

  • Para las imágenes de fondo, usa image-set con los resguardos adecuados para los navegadores que no lo admiten.
  • Para las imágenes de contenido, usa un polyfill de srcset o recurre a usar image-set (consulta más arriba).
  • En situaciones en las que estés dispuesto a sacrificar la calidad de la imagen, considera usar imágenes 2 veces comprimidas.