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?
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?
Respostas:
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 getImageData
e putImageData
.
putImageData
mas ainda faz aliasing de pixels próximos, caramba.
Desenhe suas 1-pixel
linhas em coordenadas como ctx.lineTo(10.5, 10.5)
. Desenhar uma linha de um pixel sobre o ponto (10, 10)
significa que esse 1
pixel nessa posição alcança de 9.5
até o 10.5
que 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.
ctx.translate(0.5,0.5)
não funcionou. em FF39.0
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.
"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"
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:
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).
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):
com (escala da tela: 0,75; escala manual: 1,33):
e (escala da tela: 1,33; escala manual: 0,75):
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:
Depois de desenhar três vezes:
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.
setPixel(x, y)
; Usei a resposta aceita aqui: stackoverflow.com/questions/4899799/…
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.
ctx.translate(0.5, 0.5);
ctx.lineWidth = .5;
Com esta combinação posso desenhar linhas finas de 1px.
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.
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
Apenas duas notas sobre a resposta do StashOfCode:
É 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 ...)
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;
}
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.