wandres.dev
RASTER Y VECTOR · Los dos modelos de imagen

Escalado: qué pasa exactamente cuando amplías

Analizar los algoritmos de interpolación que entran en juego al escalar un raster, y contrastarlo con la reproyección de un vector, para saber qué se degrada y cuánto.

⏱ 17 min

“El vector escala y el raster no” es una simplificación que oculta lo interesante. El raster escala perfectamente en algunos casos y fatal en otros, según el factor y el algoritmo; el vector no escala sin coste, sino que se vuelve a rasterizar. Saber qué ocurre en cada caso te permite decidir, con datos, a qué resolución generar un recurso y cuándo un escalado es aceptable.

🎯 Al terminar esta lección sabrás
  • Explicar qué hace un algoritmo de interpolación al ampliar y al reducir un raster.
  • Distinguir los casos de escalado que no pierden calidad de los que sí.
  • Controlar el suavizado en el canvas y elegirlo según el tipo de imagen.
  • Aplicar la técnica de reducción por pasos y decir por qué mejora el resultado.

Ampliar: inventar muestras que no existen

Al ampliar un raster, el destino tiene más píxeles que el origen. Para cada píxel de destino se calcula la posición correspondiente en el origen, que casi nunca cae sobre una muestra existente, y hay que estimar un valor. Los tres esquemas habituales son:

Vecino más próximo. Se coge el valor de la muestra más cercana. Produce bloques duros. Es exactamente lo que quieres para pixel art y para inspeccionar píxeles individuales, y exactamente lo que no quieres para una fotografía.

Bilineal. Se interpolan las cuatro muestras que rodean el punto, ponderando por la distancia. Suaviza, y es lo que hace por defecto casi todo. Su defecto es que también suaviza los bordes que deberían ser nítidos.

Bicúbica y variantes. Se usan dieciséis muestras y un polinomio de tercer grado, lo que conserva mejor los bordes a costa de introducir un ligero realce alrededor de las transiciones fuertes. Es lo que suelen usar los navegadores en la calidad alta.

Ninguno añade información. Todos reparten la que había sobre más muestras. Por eso una ampliación al doble se ve tolerable, al cuádruple se ve mal, y al óctuple se ve como lo que es: una imagen pequeña estirada.

Reducir: descartar información, y el aliasing

Reducir parece más benigno y en realidad es donde están los peores artefactos, porque aparece el aliasing. Si el destino tiene la mitad de píxeles que el origen y el algoritmo se limita a tomar una de cada dos muestras, cualquier detalle con periodo similar al muestreo produce patrones falsos: los famosos moirés en tejidos, rejillas y texto pequeño.

La solución teórica es filtrar antes de muestrear: promediar las muestras que caen dentro de cada píxel de destino. En la práctica, los navegadores hacen algo intermedio, y cuando la reducción es fuerte —más de un factor dos o tres en una sola operación— el filtrado se queda corto y el resultado sale sucio.

De ahí viene la técnica de reducción por pasos: en lugar de ir de 2000 a 200 píxeles de golpe, se hace en varias etapas de un factor dos, cada una de las cuales sí está bien filtrada por la interpolación bilineal.

// Reduccion por pasos: cada iteracion divide entre dos como mucho
function reducir(origen, anchoFinal, altoFinal) {
  let w = origen.width, h = origen.height;
  let actual = origen;
  while (w * 0.5 > anchoFinal) {
    w = Math.max(anchoFinal, Math.round(w * 0.5));
    h = Math.max(altoFinal, Math.round(h * 0.5));
    const paso = document.createElement('canvas');
    paso.width = w; paso.height = h;
    const c = paso.getContext('2d');
    c.imageSmoothingEnabled = true;
    c.imageSmoothingQuality = 'high';
    c.drawImage(actual, 0, 0, w, h);
    actual = paso;
  }
  const fin = document.createElement('canvas');
  fin.width = anchoFinal; fin.height = altoFinal;
  const c = fin.getContext('2d');
  c.imageSmoothingEnabled = true;
  c.imageSmoothingQuality = 'high';
  c.drawImage(actual, 0, 0, anchoFinal, altoFinal);
  return fin;
}

La diferencia entre esa función y un drawImage directo es visible a simple vista en cualquier fotografía con detalle fino. Es la técnica que usan las bibliotecas de miniaturas serias, y no está en ningún tutorial de introducción.

El control del suavizado en el canvas

El contexto 2D expone dos propiedades para gobernar la interpolación al dibujar imágenes:

ctx.imageSmoothingEnabled = false;      // vecino mas proximo
ctx.imageSmoothingQuality = 'low' | 'medium' | 'high';

imageSmoothingEnabled es una decisión binaria y forma parte del estado del contexto, así que save y restore la guardan. Desactivarla es obligatorio para pixel art: sin ella, un sprite de dieciséis por dieciséis ampliado a trescientos veinte se convierte en una mancha.

imageSmoothingQuality es una pista, no una garantía. El navegador puede ignorarla, y los tres valores no siempre se distinguen. Sirve para pedir más calidad en operaciones puntuales, no para confiar en un resultado exacto.

Hay un detalle que provoca bugs sutiles: estas propiedades afectan a drawImage y a los patrones, pero no a putImageData, que escribe píxeles uno a uno sin interpolación ni transformación. Si esperas que una escritura de píxeles respete la escala del contexto, no lo hará, y es a propósito.

<canvas id="p" width="320" height="160"></canvas>
<script>
  const ctx = document.getElementById('p').getContext('2d');
  const src = document.createElement('canvas');
  src.width = src.height = 8;
  const s = src.getContext('2d');
  for (let y = 0; y < 8; y++) for (let x = 0; x < 8; x++) {
    s.fillStyle = (x + y) % 2 ? '#f38ba8' : '#1e1e2e';
    s.fillRect(x, y, 1, 1);
  }
  ctx.imageSmoothingEnabled = false;
  ctx.drawImage(src, 0, 0, 160, 160);   // nitido, cuadros perfectos
  ctx.imageSmoothingEnabled = true;
  ctx.drawImage(src, 160, 0, 160, 160); // borroso, mezcla los colores
</script>
Los factores de escala que no pierden nada, y el que sí y nadie ve venir

Hay una clase de escalados que no degradan: los múltiplos enteros con el suavizado desactivado. Ampliar al doble, al triple o al cuádruple con vecino más próximo produce una imagen donde cada píxel original se convierte en un bloque exacto de n por n, sin mezclar nada. Es reversible: puedes volver al original tomando una muestra de cada n. Por eso el pixel art se ve perfecto a 200 % y a 400 %, y horrible a 250 %. Y aquí está la trampa que nadie ve venir: el factor de escala real casi nunca es el que tú pusiste. Entre tu código y la pantalla se multiplican el ratio de píxeles del dispositivo, el zoom del navegador y cualquier transform de CSS en un ancestro. Un sprite dibujado a escala 3 en un canvas con ratio 1,5 acaba en la pantalla a escala 4,5, que no es entera, y el compositor lo remuestrea con un filtrado que tú no controlas. El resultado es que el pixel art se ve sutilmente mal en dispositivos con ratios fraccionarios —muchos Android y cualquier ventana con zoom al 110 %— sin que aparezca nada en el código. La única defensa fiable es calcular la escala total real, redondearla a entero, y aceptar unos píxeles de margen en lugar de rellenar el contenedor exactamente. Ajustar el diseño al múltiplo entero más cercano es feo de escribir y es lo que hace que la imagen se vea bien.

El vector no escala: se vuelve a ejecutar

Frente a todo lo anterior, el vector no tiene ninguno de estos problemas porque no está haciendo la misma operación. No hay muestras que interpolar: hay una descripción geométrica que se multiplica por una matriz y se rasteriza de cero contra la rejilla de destino. El antialiasing se calcula a la resolución final, así que los bordes salen tan bien a 20 píxeles como a 2000.

Lo que sí paga el vector es tiempo, y de forma proporcional al área. Rasterizar el mismo trazado al cuádruple de tamaño cuesta aproximadamente dieciséis veces más, porque hay dieciséis veces más píxeles que cubrir. Un icono no lo notarás nunca; una ilustración compleja a pantalla completa, sí. Y hay un umbral de detalle por debajo del cual el vector deja de tener sentido: si el resultado va a mostrarse siempre a dieciséis píxeles, rasterizarlo una vez y reutilizar el búfer es estrictamente mejor.

La conclusión operativa para el canvas es doble. Si vas a dibujar la misma forma vectorial muchas veces al mismo tamaño, rasterízala una vez a un canvas auxiliar y copia el búfer. Si el tamaño cambia con frecuencia, mantén el vector y redibuja. La primera estrategia cambia cómputo por memoria; la segunda al revés, y la elección depende de cuál de los dos te sobra.