Desactiva la interpolación al escalar un <canvas>

126

NOTA : Esto tiene que ver con cómo se representan los elementos de lienzo existentes cuando se amplía , no con cómo se representan las líneas o gráficos en una superficie de lienzo . En otras palabras, esto tiene todo que ver con la interpolación de elementos escalados , y nada que ver con antialiasing de gráficos dibujados en un lienzo. No me preocupa cómo el navegador dibuja líneas; Me importa cómo el navegador representa el elemento del lienzo en sí mismo cuando se amplía.


¿Existe una propiedad de lienzo o una configuración del navegador que pueda cambiar mediante programación para deshabilitar la interpolación al escalar <canvas>elementos? Una solución de navegador cruzado es ideal pero no esencial; Los navegadores basados ​​en webkit son mi objetivo principal. El rendimiento es muy importante.

Esta pregunta es muy similar pero no ilustra suficientemente el problema. Por lo que vale, he tratado image-rendering: -webkit-optimize-contrasten vano.

La aplicación será un juego de estilo "retro" de 8 bits escrito en HTML5 + JS para dejar en claro lo que necesito.


Para ilustrar, aquí hay un ejemplo. ( versión en vivo )

Supongamos que tengo un lienzo de 21x21 ...

<canvas id='b' width='21' height='21'></canvas>

... que tiene CSS que hace que el elemento sea 5 veces más grande (105x105):

canvas { border: 5px solid #ddd; }
canvas#b { width: 105px; height: 105px; } /* 5 * 21 = 105 */

Dibujo una simple 'X' en el lienzo así:

$('canvas').each(function () {
    var ctx = this.getContext("2d");
    ctx.moveTo(0,0);
    ctx.lineTo(21,21);
    ctx.moveTo(0,21);
    ctx.lineTo(21,0);
    ctx.stroke();
});

La imagen de la izquierda es lo que representa Chromium (14.0). La imagen de la derecha es lo que quiero (dibujado a mano con fines ilustrativos).

Chrome interpola elementos de lienzo escalados Una versión no interpolada

namuol
fuente
1
Algo relacionado, aunque no idéntico: stackoverflow.com/questions/195262/…
HostileFork dice que no confíes en SE
1
Creo que esa pregunta se refiere a las funciones de dibujo de líneas que dibujan líneas suavizadas. Me refiero a cómo los elementos de lienzo redimensionados se representan con interpolación de forma predeterminada.
namuol
Sí, lo siento ... Primero pensé que las preguntas eran las mismas e inmediatamente noté mi error. : - / ¿Qué pasaría si intentaras usar el filtro de pixelación de jsmanipulate? joelb.me/jsmanipulate
HostileFork dice que no confíes en SE
1
Eso es más un filtro de imagen destinado a fotografías.
namuol
Sí, pero si transforma tu imagen izquierda en una versión suficientemente buena de tu imagen derecha para hacerte feliz, podría proporcionar una solución de navegador cruzado (teniendo en cuenta que no parece demasiado prometedor encontrar una bandera para este modo de dibujo, universal o de otro tipo) , en WebKit). Si es un sustituto viable depende del problema que intente resolver ... si realmente necesita un dibujo de píxeles discreto o si solo está tratando de asegurarse de que el resultado esté pixelado con una cierta granularidad.
HostileFork dice que no confíes en SE

Respuestas:

126

Última actualización: 2014-09-12

¿Existe una propiedad de lienzo o una configuración del navegador que pueda cambiar mediante programación para deshabilitar la interpolación al escalar elementos?

La respuesta es quizás algún día . Por ahora, tendrás que recurrir a hackear para obtener lo que quieres.


image-rendering

El borrador de trabajo de CSS3 describe una nueva propiedad,image-rendering que debería hacer lo que quiero:

La propiedad de representación de imágenes proporciona una pista al agente de usuario sobre qué aspectos de una imagen son más importantes para preservar cuando la imagen se escala, para ayudar al agente de usuario a elegir un algoritmo de escala apropiado.

La especificación describe tres valores aceptados: auto, crisp-edges, y pixelated.

pixelado:

Al escalar la imagen, se debe usar el "vecino más cercano" o un algoritmo similar, de modo que la imagen parezca estar compuesta simplemente por píxeles muy grandes. Al reducir, esto es lo mismo que auto.

¿Estándar? Cross-browser?

Dado que esto es simplemente un borrador de trabajo , no hay garantía de que se convierta en estándar. El soporte del navegador es actualmente irregular, en el mejor de los casos.

La red de desarrolladores de Mozilla tiene una página bastante completa dedicada al estado actual de la técnica que recomiendo leer.

Los desarrolladores de Webkit inicialmente eligieron implementar esto tentativamente como-webkit-optimize-contrast , pero Chromium / Chrome no parecen estar usando una versión de Webkit que implemente esto.

Actualización: 2014-09-12

¡Chrome 38 ahora es compatibleimage-rendering: pixelated !

Firefox tiene un informe de error abierto para image-rendering: pixelatedimplementarse, pero -moz-crisp-edgesfunciona por ahora.

¿Solución?

La solución más multiplataforma, solo CSS hasta ahora es así:

canvas {
  image-rendering: optimizeSpeed;             /* Older versions of FF          */
  image-rendering: -moz-crisp-edges;          /* FF 6.0+                       */
  image-rendering: -webkit-optimize-contrast; /* Safari                        */
  image-rendering: -o-crisp-edges;            /* OS X & Windows Opera (12.02+) */
  image-rendering: pixelated;                 /* Awesome future-browsers       */
  -ms-interpolation-mode: nearest-neighbor;   /* IE                            */
}

Lamentablemente, esto todavía no funcionará en todas las plataformas HTML5 principales (Chrome, en particular).

Por supuesto, uno podría escalar manualmente las imágenes usando la interpolación del vecino más cercano en superficies de lienzo de alta resolución en javascript, o incluso preescalar imágenes del lado del servidor, pero en mi caso esto será increíblemente costoso, por lo que no es una opción viable.

ImpactJS utiliza una técnica de preescalado de textura para sortear todo este FUD. El desarrollador de Impact, Dominic Szablewski, escribió un artículo muy profundo sobre esto (incluso terminó citando esta pregunta en su investigación).

Vea la respuesta de Simon para una solución basada en lienzo que se basa en la imageSmoothingEnabledpropiedad (no disponible en navegadores más antiguos, pero más simple que el preescalado y con un soporte bastante amplio).

Demo en vivo

Si desea probar las propiedades CSS discutidas en el artículo de MDN sobre canvaselementos, he creado este violín que debería mostrar algo así, borroso o no, dependiendo de su navegador: una imagen 4: 1 (64x64 a 256x256) de un televisor isométrico de estilo pixel art

namuol
fuente
La representación de imágenes parece funcionar en otros lugares, pero en los navegadores webkit. Realmente espero que el personal de Chrome / Chromium / Safari lea bien lo que significa "cambiar a interpolación EPX en situaciones de baja carga". Cuando leí la oración por primera vez, pensé que optimizar el contraste permite nuevos colores creados con poca carga, pero NO: en.wikipedia.org/wiki/…
Timo Kähkönen
2
Opera 12.02 en Mac y Windows admite la representación de imágenes: -o-crisp- edge ( jsfiddle.net/VAXrL/21 ), para que pueda agregarlo en su respuesta.
Timo Kähkönen
¿Se supone que funciona cuando estoy aumentando el zoom del navegador? He probado el dibujo con FF 22, Chrome 27 e IE 10 en Windows 7 cuando el zoom del navegador es del 150% y el resultado es borroso en todos los navegadores. Cuando doblo el ancho y el alto del lienzo y lo vuelvo a ajustar al ancho y alto originales con CSS, dibuja líneas definidas.
pablo
Buena pregunta. No estoy seguro de si hay una especificación para el comportamiento de escala especificado por el usuario.
namuol
1
Estoy (desde el futuro presente!) En Chrome 78. Estoy viendo "renderizado de imágenes: pixelado"; trabajando. Parece que esto se agregó en 2015 a Chrome 41: developers.google.com/web/updates/2015/01/pixelated
MrColes
61

Nueva respuesta 31/07/2012

¡Esto finalmente está en las especificaciones del lienzo!

La especificación ha agregado recientemente una propiedad llamada imageSmoothingEnabled, que por defecto truedetermina y determina si las imágenes dibujadas en coordenadas no enteras o dibujadas a escala utilizarán un algoritmo más uniforme. Si se establece en el falsevecino más cercano, se usa, produciendo una imagen menos uniforme y, en su lugar, creando píxeles más grandes.

El suavizado de imágenes se ha agregado recientemente a la especificación del lienzo y no es compatible con todos los navegadores, pero algunos navegadores han implementado versiones prefijadas por el proveedor de esta propiedad. En el contexto, existe mozImageSmoothingEnableden Firefox y webkitImageSmoothingEnableden Chrome y Safari, y establecerlos en falso detendrá el suavizado. Desafortunadamente, al momento de escribir este artículo, IE9 y Opera no han implementado esta propiedad, con el prefijo del proveedor o de otra manera.


Vista previa: JSFiddle

Resultado:

ingrese la descripción de la imagen aquí

Simon Sarris
fuente
1
Desafortunadamente, parece que esto tampoco funciona todavía. Tal vez me estoy perdiendo algo? Aquí hay una prueba: jsfiddle.net/VAXrL/187
namuol
12
tienes que escalar usando la API de lienzo para que funcione, nunca puedes escalar con CSS y hacer que la API de lienzo lo afecte. Entonces algo como esto: jsfiddle.net/VAXrL/190
Simon Sarris
1
Pero la naturaleza de este problema no se trata realmente del lienzo. Se trata de cómo el navegador representa imágenes escaladas, incluidos los elementos <canvas>.
namuol
44
Si escala usando CSS, la solución de namuol funcionará. Si escala la imagen manualmente en el lienzo, la solución de Simon funciona. Es genial que haya soluciones para ambos casos, ¡así que gracias a los dos!
Shaun Lebron
Para IE 11: msImageSmoothingEnabled funciona para mí. msdn.microsoft.com/en-us/library/dn265062(v=vs.85).aspx
steve
11

Editar 31/07/2012 - ¡Esta funcionalidad ahora está en la especificación del lienzo! Ver respuesta por separado aquí:

https://stackoverflow.com/a/11751817/154112

La respuesta anterior está abajo para la posteridad.


Dependiendo de su efecto deseado, tiene esto como una opción:

var can = document.getElementById('b');
var ctx = can.getContext('2d');
ctx.scale(5,5);
$('canvas').each(function () {
    var ctx = this.getContext("2d");
    ctx.moveTo(0,0);
    ctx.lineTo(21,21);
    ctx.moveTo(0,21);
    ctx.lineTo(21,0);
    ctx.stroke();
});

http://jsfiddle.net/wa95p/

Lo que crea esto:

ingrese la descripción de la imagen aquí

Probablemente no sea lo que quieres. Pero si simplemente estaba buscando tener cero desenfoque, ese sería el boleto, así que lo ofreceré por si acaso.

Una opción más difícil es utilizar la manipulación de píxeles y escribir un algoritmo usted mismo para el trabajo. Cada píxel de la primera imagen se convierte en un bloque de píxeles de 5x5 en la nueva imagen. No sería demasiado difícil hacer con imagedata.

Pero Canvas y CSS por sí solos no lo ayudarán aquí para escalar uno al otro con el efecto exacto que desea.

Simon Sarris
fuente
Sí, eso es lo que me daba miedo. Aparentemente, image-rendering: -webkit-optimize-contrastdebería hacer el truco pero no parece hacer nada. Puedo considerar rodar una función para escalar el contenido de un lienzo utilizando la interpolación del vecino más cercano.
namuol
Originalmente acepté tu respuesta; Lo revoqué por ahora porque parece haber confusión sobre si esto es un duplicado o no.
namuol
Tengo una respuesta más completa después de investigar un poco y me gustaría volver a abrir la pregunta para proporcionar mi respuesta.
namuol
3

En google chrome, los patrones de imágenes de lienzo no se interpolan.

Aquí hay un ejemplo de trabajo editado de la respuesta namuol http://jsfiddle.net/pGs4f/

ctx.scale(4, 4);
ctx.fillStyle = ctx.createPattern(image, 'repeat');
ctx.fillRect(0, 0, 64, 64);
saviski
fuente
Esta es la solución más atractiva que he visto hasta ahora. Todavía no estoy seguro del rendimiento, pero vale la pena investigarlo. Voy a tratar de salir.
namuol
Usar repetir en lugar de no repetir es más rápido. No se porque. También es bastante rápido, three.js usa esto en CanvasRenderer
saviski el
66
actualización: ya no funciona con Chrome 21 (¿debido a que se agregó soporte para pantalla retina?) nueva versión jsfiddle.net/saviski/pGs4f/12 usando imageSmoothingEnabled señalado por Simon
saviski
Cuando la resolución de Retina se generaliza, las cosas cambian enormemente. Cuando la pantalla Retina de 326 ppp (128 ppp) de estilo iPhone4-5 llega a monitores de 24 pulgadas (52x32 cm), el tamaño de la pantalla será de 6656 x 4096 px. Entonces el antialiasing es malo, y todos los proveedores de navegadores (incluso basados ​​en Webkit) se ven obligados a permitir deshabilitar el antialiasing. El antialiasing es una operación intensiva de la CPU y el antialiasing de la imagen de 6656 x 4096 px sería demasiado lento, pero afortunadamente innecesario en dicha pantalla.
Timo Kähkönen
1
Solo como una nota al margen, aquí hay un buen punto de referencia sobre patrones vs imagen: jsperf.com/drawimage-vs-canvaspattern/9
yckart
1

La solución alternativa de Saviski explicada aquí es prometedora, porque funciona en:

  • Chrome 22.0.1229.79 Mac OS X 10.6.8
  • Chrome 22.0.1229.79 m Windows 7
  • Chromium 18.0.1025.168 (Desarrollador Build 134367 Linux) Ubuntu 11.10
  • Firefox 3.6.25 Windows 7

Pero no funciona de la siguiente manera, pero se puede lograr el mismo efecto usando la representación de imágenes CSS:

  • Firefox 15.0.1 Mac OS X 10.6.8 (la representación de imágenes: -moz-crisp-edge funciona en esto )
  • Opera 12.02 Mac OS X 10.6.8 (la representación de imágenes: -o-crisp-edge funciona en esto )
  • Opera 12.02 Windows 7 (la representación de imágenes: -o-crisp-edge funciona en esto )

Los problemáticos son estos, porque ctx.XXXImageSmoothingEnabled no funciona y el procesamiento de imágenes no funciona:

  • Safari 5.1.7 Mac OS X 10.6.8. (renderizado de imágenes: -webkit-optimizar-contraste NO funciona)
  • Safari 5.1.7 Windows 7 (la representación de imágenes: -webkit-optimizar-contraste NO funciona)
  • IE 9 Windows 7 (-ms-interpolation-mode: el vecino más cercano NO funciona)
Timo Kähkönen
fuente