¿Puedo desactivar el antialiasing en un elemento HTML <canvas>?

85

Estoy jugando con el <canvas>elemento, dibujando líneas y demás.

He notado que mis líneas diagonales están suavizadas. Preferiría el aspecto irregular para lo que estoy haciendo. ¿Hay alguna forma de desactivar esta función?

Blorgbeard está fuera
fuente
Creo que está bastante relacionado con el navegador. Tal vez sería útil alguna información adicional sobre el software que utiliza.
Tomalak
Preferiría un método de
varios
Solo quería ver si ha habido algún cambio en este tema.
vternal3
¿hay alguna actualización sobre esto?
Roland

Respuestas:

60

Para imágenes hay ahora .context.imageSmoothingEnabled= false

Sin embargo, no hay nada que controle explícitamente el dibujo de líneas. Es posible que deba dibujar sus propias líneas (de la manera difícil ) usando getImageDatay putImageData.

Kornel
fuente
1
Me pregunto sobre el rendimiento de un algoritmo de línea JavaScript. Podría darle una oportunidad a Bresenham en algún momento.
Blorgbeard sale el
Los proveedores de navegadores están promocionando nuevos motores JS superrápidos últimamente, por lo que finalmente tendría un buen uso.
Kornel
1
¿Esto realmente funciona? Estoy dibujando una línea usando putImageData pero todavía hace aliasing de píxeles cercanos maldita sea.
Pacerier
si dibujo en un lienzo más pequeño (caché) y luego dibujoImagen en otro lienzo con esa configuración desactivada, ¿funcionará según lo previsto?
SparK
69

Dibuja tus 1-pixellíneas en coordenadas como ctx.lineTo(10.5, 10.5). Dibujo de una línea de un píxel sobre el punto (10, 10)medio, que este 1píxel en que alcanza la posición de 9.5a 10.5lo que resulta en dos líneas que se dibujen sobre el lienzo.

Un buen truco para no tener que agregar siempre el 0.5a la coordenada real sobre la que desea dibujar si tiene muchas líneas de un píxel, es ctx.translate(0.5, 0.5)todo el lienzo al principio.

Alano
fuente
hmm, tengo problemas para deshacerme del suavizado con esta técnica. ¿Quizás, extraño entender algo? ¿Te importaría publicar un ejemplo en algún lugar?
Xavi
7
Esto no elimina el suavizado, pero hace que las líneas suavizadas se vean mucho mejor, como deshacerse de esas líneas horizontales o verticales vergonzosas que tienen dos píxeles de grosor cuando en realidad deseaba un píxel.
David dado
1
@porneL: No, las líneas se dibujan entre las esquinas de los píxeles. Cuando su línea tiene 1 píxel de ancho, se extiende medio píxel en cualquier dirección
Eric
Agregar +0.5 funciona para mí, pero ctx.translate(0.5,0.5)no lo hizo. en FF39.0
Paulo Bueno
¡Muchas gracias! ¡No puedo creer que tenga líneas reales de 1px para variar!
Chunky Chunk
24

Se puede hacer en Mozilla Firefox. Agregue esto a su código:

contextXYZ.mozImageSmoothingEnabled = false;

En Opera, actualmente es una solicitud de función, pero con suerte se agregará pronto.

francholi
fuente
frio. +1 por tu contribución. Me pregunto si la desactivación de AA acelera el dibujo de líneas
marcusklaas
7
El OP quiere eliminar las líneas anti-alias, pero esto solo funciona en imágenes. Según la especificación , determina"whether pattern fills and the drawImage() method will attempt to smooth images if their pixels don't line up exactly with the display, when scaling images up"
rvighne
14

Debe antialias gráficos vectoriales.

El suavizado es necesario para el trazado correcto de gráficos vectoriales que involucran coordenadas no enteras (0.4, 0.4), lo que hacen todos los clientes, excepto muy pocos.

Cuando se le dan coordenadas no enteras, el lienzo tiene dos opciones:

  • Antialias : pinta los píxeles alrededor de la coordenada según la distancia entre la coordenada entera y la no entera (es decir, el error de redondeo).
  • Redondear : aplique alguna función de redondeo a la coordenada no entera (por lo que 1.4 se convertirá en 1, por ejemplo).

La última estrategia funcionará para gráficos estáticos, aunque para gráficos pequeños (un círculo con un radio de 2) las curvas mostrarán pasos claros en lugar de una curva suave.

El problema real es cuando los gráficos se traducen (mueven): los saltos entre un píxel y otro (1.6 => 2, 1.4 => 1), significan que el origen de la forma puede saltar en relación con el contenedor principal (cambiando constantemente 1 píxel arriba / abajo e izquierda / derecha).

Algunos consejos

Consejo n. ° 1 : puede suavizar (o endurecer) el antialiasing escalando el lienzo (por ejemplo, por x) y luego aplique la escala recíproca (1 / x) a las geometrías usted mismo (sin usar el lienzo).

Comparar (sin escala):

Algunos rectángulos

con (escala de lienzo: 0,75; escala manual: 1,33):

Mismos rectángulos con bordes más suaves

y (escala de lienzo: 1,33; escala manual: 0,75):

Mismos rectángulos con bordes más oscuros

Consejo n. ° 2 : si lo que buscas es un aspecto irregular, intenta dibujar cada forma varias veces (sin borrar). Con cada dibujo, los píxeles de suavizado se oscurecen.

Comparar. Después de dibujar una vez:

Algunos caminos

Después de dibujar tres veces:

Mismos caminos pero más oscuros y sin antialiasing visible.

Izhaki
fuente
@vanowm no dudes en clonar y jugar con: github.com/Izhaki/gefri . Todas las imágenes son capturas de pantalla de la carpeta / demo (con el código ligeramente modificado para la sugerencia # 2). Estoy seguro de que le resultará fácil introducir el redondeo de enteros a las figuras dibujadas (me tomó 4 minutos) y luego simplemente arrastre para ver el efecto.
Izhaki
9

Dibujaría todo usando un algoritmo de línea personalizado como el algoritmo de línea de Bresenham. Consulte esta implementación de JavaScript: http://members.chello.at/easyfilter/canvas.html

Creo que esto definitivamente resolverá tus problemas.

Jón Trausti Arason
fuente
2
Exactamente lo que necesitaba, lo único que agregaría es que necesita implementar setPixel(x, y); Usé la respuesta aceptada aquí: stackoverflow.com/questions/4899799/…
Tina Vall
8

Quiero agregar que tuve problemas al reducir el tamaño de una imagen y dibujar en el lienzo, todavía usaba suavizado, aunque no se usaba al aumentar la escala.

Resolví usando esto:

function setpixelated(context){
    context['imageSmoothingEnabled'] = false;       /* standard */
    context['mozImageSmoothingEnabled'] = false;    /* Firefox */
    context['oImageSmoothingEnabled'] = false;      /* Opera */
    context['webkitImageSmoothingEnabled'] = false; /* Safari */
    context['msImageSmoothingEnabled'] = false;     /* IE */
}

Puede utilizar esta función de esta manera:

var canvas = document.getElementById('mycanvas')
setpixelated(canvas.getContext('2d'))

Quizás esto sea útil para alguien.

eri0o
fuente
¿por qué no context.imageSmoothingEnabled = false?
Martijn Scheffer
Esto no funcionó en el momento en que escribí mi respuesta. ¿Funciona ahora?
eri0o
1
lo hizo, es EXACTAMENTE lo mismo, en javascript escribir obj ['nombre'] u obj.name siempre ha sido, y siempre será el mismo, un objeto es una colección de valores con nombre (tuplas), usando algo que se asemeja a un tabla hash, ambas notaciones se tratarán de la misma manera, no hay ninguna razón para que su código no haya funcionado antes, en el peor de los casos, asigna un valor que no tiene ningún efecto (porque está destinado a otro navegador. un ejemplo simple: escriba obj = {a: 123}; console.log (obj ['a'] === obj.a? "sí, es cierto": "no, no lo es")
Martijn Scheffer
Pensé que querías decir por qué tienen todas las otras cosas, lo que quise decir con mi comentario es que en ese momento los navegadores requerían propiedades diferentes.
eri0o
ok, sí, por supuesto :) Estaba hablando de la sintaxis, no de la validez del código en sí (funciona)
Martijn Scheffer
6
ctx.translate(0.5, 0.5);
ctx.lineWidth = .5;

Con este combo puedo dibujar bonitas líneas finas de 1px.

retepaskab
fuente
6
No necesita establecer lineWidth en .5 ... eso (o debería) solo lo hará a la mitad de opacidad.
aaaidan
4

Note un truco muy limitado. Si desea crear una imagen de 2 colores, puede dibujar cualquier forma que desee con el color # 010101 sobre un fondo con el color # 000000. Una vez hecho esto, puede probar cada píxel en imageData.data [] y establecer en 0xFF cualquier valor que no sea 0x00:

imageData = context2d.getImageData (0, 0, g.width, g.height);
for (i = 0; i != imageData.data.length; i ++) {
    if (imageData.data[i] != 0x00)
        imageData.data[i] = 0xFF;
}
context2d.putImageData (imageData, 0, 0);

El resultado será una imagen en blanco y negro sin suavizado. Esto no será perfecto, ya que se producirá algo de antialiasing, pero este antialiasing será muy limitado, siendo el color de la forma muy parecido al color del fondo.

StashOfCode
fuente
1

Para aquellos que aún buscan respuestas. aquí está mi solución.

Suponiendo que la imagen es de 1 canal gris. Acabo de establecer un umbral después de ctx.stroke ().

ctx.beginPath();
ctx.moveTo(some_x, some_y);
ctx.lineTo(some_x, some_y);
...
ctx.closePath();
ctx.fill();
ctx.stroke();

let image = ctx.getImageData(0, 0, ctx.canvas.width, ctx.canvas.height)
for(let x=0; x < ctx.canvas.width; x++) {
  for(let y=0; y < ctx.canvas.height; y++) {
    if(image.data[x*image.height + y] < 128) {
      image.data[x*image.height + y] = 0;
    } else {
      image.data[x*image.height + y] = 255;
    }
  }
}

si su canal de imagen es 3 o 4, necesita modificar el índice de la matriz como

x*image.height*number_channel + y*number_channel + channel
Jaewon.AC
fuente
0

Solo dos notas sobre la respuesta de StashOfCode:

  1. Solo funciona para un lienzo opaco en escala de grises (relleneRect con blanco y luego dibuje con negro, o viceversa)
  2. Puede fallar cuando las líneas son delgadas (~ 1px de ancho de línea)

Es mejor hacer esto en su lugar:

Trace y rellene con #FFFFFF, luego haga esto:

imageData.data[i] = (imageData.data[i] >> 7) * 0xFF

Eso lo resuelve para líneas con un ancho de 1 px.

Aparte de eso, la solución de StashOfCode es perfecta porque no requiere escribir sus propias funciones de rasterización (piense no solo en líneas sino también en béziers, arcos circulares, polígonos rellenos con agujeros, etc.)

Matías Moreno
fuente
0

Aquí hay una implementación básica del algoritmo de Bresenham en JavaScript. Se basa en la versión aritmética de enteros descrita en este artículo de wikipedia: https://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm

    function range(f=0, l) {
        var list = [];
        const lower = Math.min(f, l);
        const higher = Math.max(f, l);
        for (var i = lower; i <= higher; i++) {
            list.push(i);
        }
        return list;
    }

    //Don't ask me.
    //https://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm
    function bresenhamLinePoints(start, end) {

        let points = [];

        if(start.x === end.x) {
            return range(f=start.y, l=end.y)
                        .map(yIdx => {
                            return {x: start.x, y: yIdx};
                        });
        } else if (start.y === end.y) {
            return range(f=start.x, l=end.x)
                        .map(xIdx => {
                            return {x: xIdx, y: start.y};
                        });
        }

        let dx = Math.abs(end.x - start.x);
        let sx = start.x < end.x ? 1 : -1;
        let dy = -1*Math.abs(end.y - start.y);
        let sy = start.y < end.y ? 1 : - 1;
        let err = dx + dy;

        let currX = start.x;
        let currY = start.y;

        while(true) {
            points.push({x: currX, y: currY});
            if(currX === end.x && currY === end.y) break;
            let e2 = 2*err;
            if (e2 >= dy) {
                err += dy;
                currX += sx;
            }
            if(e2 <= dx) {
                err += dx;
                currY += sy;
            }
        }

        return points;

    }
Elliottdehn
fuente