Posso desligar o anti-serrilhamento em um elemento HTML <canvas>?

88

Estou brincando com o <canvas>elemento, desenhando linhas e tal.

Percebi que minhas linhas diagonais são suavizadas. Eu prefiro a aparência irregular para o que estou fazendo - há alguma maneira de desativar esse recurso?

Blorgbeard está fora
fonte
Eu acho que isso é bastante relacionado ao navegador. Talvez algumas informações adicionais sobre o software que você usa sejam úteis.
Tomalak
Eu preferiria um método para vários navegadores, mas um método que funcione em qualquer navegador único ainda seria interessante para mim
Blorgbeard foi lançado em
Eu só queria ver se já houve alguma mudança neste tópico.
vternal3 de
existe alguma atualização sobre isso?
Roland

Respostas:

64

Para imagens, agora .context.imageSmoothingEnabled= false

No entanto, não há nada que controle explicitamente o desenho da linha. Você pode precisar desenhar suas próprias linhas ( da maneira mais difícil ) usando getImageDatae putImageData.

Kornel
fonte
1
Eu me pergunto sobre o desempenho de um algoritmo de linha javascript. Pode dar uma chance ao Bresenham em algum momento.
Blorgbeard será lançado em
Os fornecedores de navegadores estão promovendo novos mecanismos JS super-rápidos recentemente, então, finalmente, haveria um bom uso para eles.
Kornel
1
Isso realmente funciona? Estou desenhando uma linha usando, putImageData mas ainda faz aliasing de pixels próximos, caramba.
Pacerier
se eu desenhar para uma tela menor (cache) e, em seguida, desenharImage para outra tela com essa definição desativada, funcionará como esperado?
SparK
69

Desenhe suas 1-pixellinhas em coordenadas como ctx.lineTo(10.5, 10.5). Desenhar uma linha de um pixel sobre o ponto (10, 10)significa que esse 1pixel nessa posição alcança de 9.5até o 10.5que resulta em duas linhas que são desenhadas na tela.

Um bom truque para nem sempre precisar adicionar o 0.5à coordenada real sobre a qual deseja desenhar, se você tiver muitas linhas de um pixel, é em ctx.translate(0.5, 0.5)toda a sua tela no início.

Allan
fonte
hmm, estou tendo problemas para me livrar do anti-aliasing usando esta técnica. Talvez, estou com saudades de entender algo? Você se importaria de postar um exemplo em algum lugar?
Xavi
7
Isso não elimina o anti-serrilhamento, mas faz as linhas de anti-serrilhamento parecerem muito melhores - como se livrar daquelas linhas verticais ou horizontais embaraçosas que têm dois pixels de espessura quando você realmente queria um pixel.
David Dado
1
@porneL: Não, as linhas são desenhadas entre os cantos dos pixels. Quando sua linha tem 1 pixel de largura, isso se estende a meio pixel em qualquer direção
Eric
Adicionar +0,5 funciona para mim, mas ctx.translate(0.5,0.5)não funcionou. em FF39.0
Paulo Bueno
Muito obrigado! Não acredito que tenho linhas reais de 1px para variar!
Chunky Chunk
24

Isso pode ser feito no Mozilla Firefox. Adicione isto ao seu código:

contextXYZ.mozImageSmoothingEnabled = false;

No Opera, é atualmente um pedido de recurso, mas espero que seja adicionado em breve.

Francholi
fonte
legal. 1 por sua contribuição. Eu me pergunto se a desativação do AA acelera o
desenho de linhas
7
O OP deseja remover o anti-alias de linhas, mas isso só funciona em imagens. De acordo com as especificações , ele 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
13

Deve gráficos vetoriais antialias

O suavização é necessário para a plotagem correta de gráficos vetoriais que envolvem coordenadas não inteiras (0,4, 0,4), o que quase todos os clientes fazem.

Quando são fornecidas coordenadas não inteiras, a tela tem duas opções:

  • Antialias - pinte os pixels ao redor da coordenada com base em quão longe a coordenada inteira está de um não inteiro (isto é, o erro de arredondamento).
  • Arredondar - aplique alguma função de arredondamento à coordenada não inteira (então 1,4 se tornará 1, por exemplo).

A última estratégia funcionará para gráficos estáticos, embora para gráficos pequenos (um círculo com raio de 2) as curvas mostrem etapas claras em vez de uma curva suave.

O verdadeiro problema é quando os gráficos são traduzidos (movidos) - os saltos entre um pixel e outro (1,6 => 2, 1,4 => 1), significam que a origem da forma pode saltar em relação ao contêiner pai (mudando constantemente 1 pixel para cima / baixo e esquerda / direita).

Algumas dicas

Dica nº 1 : você pode suavizar (ou endurecer) o anti-serrilhamento dimensionando a tela (digamos por x) e, em seguida, aplicar a escala recíproca (1 / x) às geometrias (sem usar a tela).

Compare (sem escala):

Alguns retângulos

com (escala da tela: 0,75; escala manual: 1,33):

Mesmos retângulos com bordas mais suaves

e (escala da tela: 1,33; escala manual: 0,75):

Mesmos retângulos com bordas mais escuras

Dica # 2 : se você realmente quer uma aparência recortada, tente desenhar cada forma algumas vezes (sem apagar). A cada desenho, os pixels de suavização ficam mais escuros.

Comparar. Depois de desenhar uma vez:

Alguns caminhos

Depois de desenhar três vezes:

Mesmos caminhos, mas mais escuros e sem suavização visível.

Izhaki
fonte
@vanowm fique à vontade para clonar e brincar com: github.com/Izhaki/gefri . Todas as imagens são capturas de tela da pasta / demo (com código ligeiramente modificado para a dica # 2). Tenho certeza de que você achará fácil introduzir o arredondamento inteiro nas figuras desenhadas (levei 4 minutos) e depois apenas arraste para ver o efeito.
Izhaki de
Pode parecer inacreditável, mas não são raras situações em que você deseja antialias desligado. Na verdade, eu apenas programei um jogo onde as pessoas devem pintar áreas em uma tela e eu preciso que haja apenas 4 cores e estou preso por causa do antialias. Repetir o padrão três vezes não resolveu o problema (subi para 200) e ainda há pixels com as cores erradas.
Arnaud
9

Eu desenharia tudo usando um algoritmo de linha personalizado, como o algoritmo de linha de Bresenham. Confira esta implementação de javascript: http://members.chello.at/easyfilter/canvas.html

Acho que isso definitivamente resolverá seus problemas.

Jón Trausti Arason
fonte
2
Exatamente o que eu precisava, a única coisa que eu acrescentaria é que você precisa implementar setPixel(x, y); Usei a resposta aceita aqui: stackoverflow.com/questions/4899799/…
Tina Vall
8

Quero acrescentar que tive problemas ao reduzir o tamanho de uma imagem e desenhar na tela, ainda estava usando suavização, embora não estivesse usando durante o aumento de escala.

Resolvi usando este:

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

Você pode usar esta função assim:

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

Talvez isso seja útil para alguém.

eri0o
fonte
por que não context.imageSmoothingEnabled = false?
Martijn Scheffer,
Isso não funcionou no momento em que escrevi minha resposta. Funciona agora?
eri0o,
1
fez, é EXATAMENTE a mesma coisa, em javascript escrevendo obj ['nome'] ou obj.name sempre foi, e sempre será o mesmo, um objeto é uma coleção de valores nomeados (tuplas), usando algo que se assemelha a um tabela de hash, ambas as notações serão tratadas da mesma maneira, não há nenhuma razão para que seu código não tenha funcionado antes, na pior das hipóteses ele atribui um valor que não tem efeito (porque é destinado a outro navegador. um exemplo simples: escrever obj = {a: 123}; console.log (obj ['a'] === obj.a? "sim, é verdade": "não, não é")
Martijn Scheffer
Achei que você quisesse dizer por que todas as outras coisas, o que quis dizer com meu comentário é que, na época, os navegadores exigiam propriedades diferentes.
eri0o
ok sim, claro :) eu estava falando sobre a sintaxe, não sobre a validade do código em si (funciona)
Martijn Scheffer
6
ctx.translate(0.5, 0.5);
ctx.lineWidth = .5;

Com esta combinação posso desenhar linhas finas de 1px.

retepaskab
fonte
6
Você não precisa definir a largura da linha para .5 ... isso irá (ou deveria) apenas torná-la metade da opacidade.
aaaidan
4

Observe um truque muito limitado. Se você deseja criar uma imagem de 2 cores, você pode desenhar qualquer forma que desejar com a cor # 010101 em um fundo com a cor # 000000. Feito isso, você pode testar cada pixel em imageData.data [] e definir como 0xFF qualquer valor que não seja 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);

O resultado será uma imagem em preto e branco sem suavização. Isso não será perfeito, uma vez que algum antialiasing ocorrerá, mas esse antialiasing será muito limitado, a cor da forma sendo muito parecida com a cor do fundo.

StashOfCode
fonte
1

Para quem ainda procura respostas. aqui está minha solução.

Presumindo que a imagem é cinza de 1 canal. Acabei de atingir o limite após 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;
    }
  }
}

se o seu canal de imagem é 3 ou 4. você precisa modificar o índice da matriz como

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

Apenas duas notas sobre a resposta do StashOfCode:

  1. Só funciona para telas opacas e em tons de cinza (preencha com branco e desenhe com preto ou vice-versa)
  2. Pode falhar quando as linhas são finas (largura da linha ~ 1px)

É melhor fazer isso:

Dê um toque e preencha #FFFFFF, então faça isto:

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

Isso resolve para linhas com largura de 1px.

Fora isso, a solução do StashOfCode é perfeita porque não requer a escrita de suas próprias funções de rasterização (pense não apenas em linhas, mas em beziers, arcos circulares, polígonos preenchidos com buracos, etc ...)

Matías Moreno
fonte
0

Aqui está uma implementação básica do algoritmo de Bresenham em JavaScript. É baseado na versão aritmética de inteiros descrita neste artigo da 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
fonte
0

Tente algo como canvas { image-rendering: pixelated; } .

Isso pode não funcionar se você estiver tentando fazer com que apenas uma linha não seja suavizada.

const canvas = document.querySelector("canvas");
const ctx = canvas.getContext("2d");

ctx.fillRect(4, 4, 2, 2);
canvas {
  image-rendering: pixelated;
  width: 100px;
  height: 100px; /* Scale 10x */
}
<html>
  <head></head>
  <body>
    <canvas width="10" height="10">Canvas unsupported</canvas>
  </body>
</html>

Eu não testei isso em muitos navegadores embora.

Soshimee
fonte