我可以在HTML <canvas>元素上关闭抗锯齿吗?

Blo*_*ard 77 html javascript canvas antialiasing

我正在玩<canvas>元素,绘制线条等.

我注意到我的对角线是抗锯齿的.我更喜欢看到我正在做的事情 - 有什么方法可以关闭这个功能吗?

all*_*lan 63

1-pixel在坐标上绘制线条ctx.lineTo(10.5, 10.5).在点上绘制一条像素线(10, 10)意味着1该位置处的该像素到达时9.510.5导致在画布上绘制两条线.

0.5如果你有很多单像素线,并不总是需要将你想要绘制的实际坐标添加到一个很好的技巧,一ctx.translate(0.5, 0.5)开始是你的整个画布.

  • 这并没有消除抗锯齿,但确实使抗锯齿线看起来好多了 - 比如当你真正想要一个像素时,摆脱两个像素厚的令人尴尬的水平或垂直线. (6认同)

Kor*_*nel 50

对于现在的图像.context.imageSmoothingEnabled= false

但是,没有什么能明确控制线条画.您可能需要使用和绘制自己的线条(艰难的方式).getImageDataputImageData

  • 这真的有效吗?我正在使用“putImageData”画一条线,但它**仍然**会造成附近像素的混叠。 (2认同)

小智 27

添加这个:

image-rendering: pixelated; image-rendering: crisp-edges;
Run Code Online (Sandbox Code Playgroud)

canvas 元素的 style 属性有助于在画布上绘制清晰的像素。通过这篇很棒的文章发现:

https://developer.mozilla.org/en-US/docs/Games/Techniques/Crisp_pixel_art_look

  • 这应该更高! (3认同)

小智 24

它可以在Mozilla Firefox中完成.将其添加到您的代码中:

contextXYZ.mozImageSmoothingEnabled = false;
Run Code Online (Sandbox Code Playgroud)

在Opera中,它目前是一个功能请求,但希望它很快就会添加.

  • OP希望取消反别名行,但这仅适用于图像.根据[规范](http://www.w3.org/TR/2dcontext2/#image-smoothing),它确定`"是否填充图案并且drawImage()方法将尝试平滑图像,如果它们的像素不当缩放图像"`时,与显示器完全对齐 (6认同)

Cod*_*ith 12

我发现了一种使用上下文filter属性禁用路径/形状渲染上的抗锯齿的更好方法:

魔法/TL;DR:

ctx = canvas.getContext('2d');

// make canvas context render without antialiasing
ctx.filter = "url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxmaWx0ZXIgaWQ9ImZpbHRlciIgeD0iMCIgeT0iMCIgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgY29sb3ItaW50ZXJwb2xhdGlvbi1maWx0ZXJzPSJzUkdCIj48ZmVDb21wb25lbnRUcmFuc2Zlcj48ZmVGdW5jUiB0eXBlPSJpZGVudGl0eSIvPjxmZUZ1bmNHIHR5cGU9ImlkZW50aXR5Ii8+PGZlRnVuY0IgdHlwZT0iaWRlbnRpdHkiLz48ZmVGdW5jQSB0eXBlPSJkaXNjcmV0ZSIgdGFibGVWYWx1ZXM9IjAgMSIvPjwvZmVDb21wb25lbnRUcmFuc2Zlcj48L2ZpbHRlcj48L3N2Zz4=#filter)";
Run Code Online (Sandbox Code Playgroud)

揭秘:

数据 url 是对包含单个过滤器的 SVG 的引用:

<svg xmlns="http://www.w3.org/2000/svg">
    <filter id="filter" x="0" y="0" width="100%" height="100%" color-interpolation-filters="sRGB">
        <feComponentTransfer>
            <feFuncR type="identity"/>
            <feFuncG type="identity"/>
            <feFuncB type="identity"/>
            <feFuncA type="discrete" tableValues="0 1"/>
        </feComponentTransfer>
    </filter>
</svg>
Run Code Online (Sandbox Code Playgroud)

然后在 url 的最后是一个 id 引用#filter

"url(data:image/svg+...Zz4=#filter)";
Run Code Online (Sandbox Code Playgroud)

SVG 滤镜在 Alpha 通道上使用离散变换,在渲染时仅选择 50% 边界上的完全透明或完全不透明。如果需要,可以对此进行调整以添加一些抗锯齿功能,例如:

...
<feFuncA type="discrete" tableValues="0 0 0.25 0.75 1"/>
...
Run Code Online (Sandbox Code Playgroud)

缺点/注意事项/陷阱

请注意,我没有用图像测试此方法,但我可以假设它会影响图像的半透明部分。我还可以猜测,它可能不会阻止不同颜色边界的图像上的抗锯齿。它不是“最接近的颜色”解决方案,而是二元透明度解决方案。它似乎最适合路径/形状渲染,因为 alpha 是唯一使用路径抗锯齿的通道。

此外,至少使用lineWidth1 个是安全的。较细的线条变得稀疏或经常可能完全消失。


编辑:

我发现,在 Firefox 中,设置filterdataurl 不能立即/同步工作:dataurl 必须首先“加载”。

例如,以下内容在 Firefox 中不起作用:

ctx.filter = "url(data:image/svg+xml;base64,...#filter)";

ctx.beginPath();
ctx.moveTo(10,10);
ctx.lineTo(20,20);
ctx.strokeStyle = 'black';
ctx.lineWidth = 2;
ctx.stroke();

ctx.filter = "none";
Run Code Online (Sandbox Code Playgroud)

但是等到下一个 JS 框架就可以正常工作了:

ctx.filter = "url(data:image/svg+xml;base64,...#filter)";
setTimeout(() => {
    ctx.beginPath();
    ctx.moveTo(10,10);
    ctx.lineTo(20,20);
    ctx.strokeStyle = 'black';
    ctx.lineWidth = 2;
    ctx.stroke();

    ctx.filter = "none";
}, 0);
Run Code Online (Sandbox Code Playgroud)


Izh*_*aki 10

它必须antialias矢量图形

正确绘制涉及非整数坐标(0.4,0.4)的矢量图形需要抗锯齿,所有这些坐标都很少.

当给定非整数坐标时,画布有两个选项:

  • Antialias - 根据整数坐标与非整数坐标的距离(舍入误差)绘制坐标周围的像素.
  • -应用一些舍入函数应用于所述非整数坐标(1.4,从而将成为1,例如).

后一种策略适用于静态图形,但对于小图形(半径为2的圆),曲线将显示清晰的步骤而不是平滑的曲线.

真正的问题是当图形被平移(移动)时 - 一个像素与另一个像素之间的跳跃(1.6 => 2,1.4 => 1)意味着形状的原点可能相对于父容器跳跃(不断变换) 1像素上/下和左/右).

一些技巧

提示#1:您可以通过缩放画布(例如x)来软化(或硬化)抗锯齿,然后自己将倒数比例(1/x)应用于几何(不使用画布).

比较(无缩放):

几个矩形

with(画布比例:0.75;手动比例:1.33):

相同的矩形边缘更柔和

和(帆布比例:1.33;手动比例:0.75):

相同的矩形边缘较暗

提示#2:如果看起来像是一个锯齿状的外观,请尝试几次绘制每个形状(不擦除).每次绘制时,抗锯齿像素都会变暗.

相比.画完一次后:

一些路径

画完三次后:

相同的路径但更暗,没有可见的抗锯齿.


raR*_*aRa 8

我会使用自定义线算法绘制所有内容,例如Br​​esenham的线算法.看看这个javascript实现:http: //members.chello.at/easyfilter/canvas.html

我认为这肯定会解决你的问题.

  • 正是我需要的,我唯一要补充的是你需要实现`setPixel(x,y)`; 我在这里使用了接受的答案:http://stackoverflow.com/questions/4899799/whats-the-best-way-to-set-a-single-pixel-in-an-html5-canvas (2认同)

Kai*_*ido 7

虽然我们在 2D 上下文上仍然没有适当的shapeSmoothingEnabledshapeSmoothingQuality选项(我会提倡这一点,并希望它在不久的将来能够实现),但由于SVGFilters ,我们现在有了近似“无抗锯齿”行为的方法,可以通过其属性应用于上下文.filter

因此,需要明确的是,它本身不会停用抗锯齿功能,但在实现和性能方面提供了一种廉价的方法(?,应该是硬件加速的,这应该比 CPU 上的自制 Bresenham 更好)以便在绘制时删除所有半透明像素,但它也可能会创建一些像素斑点,并且可能不会保留原始输入颜色。

为此,我们可以使用<feComponentTransfer>节点来仅抓取完全不透明的像素。

const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
ctx.fillStyle = "#ABEDBE";
ctx.fillRect(0,0,canvas.width,canvas.height);
ctx.fillStyle = "black";
ctx.font = "14px sans-serif";
ctx.textAlign = "center";

// first without filter
ctx.fillText("no filter", 60, 20);
drawArc();
drawTriangle();
// then with filter
ctx.setTransform(1, 0, 0, 1, 120, 0);
ctx.filter = "url(#remove-alpha)";
// and do the same ops
ctx.fillText("no alpha", 60, 20);
drawArc();
drawTriangle();

// to remove the filter
ctx.filter = "none";


function drawArc() {
  ctx.beginPath();
  ctx.arc(60, 80, 50, 0, Math.PI * 2);
  ctx.stroke();
}

function drawTriangle() {
  ctx.beginPath();
  ctx.moveTo(60, 150);
  ctx.lineTo(110, 230);
  ctx.lineTo(10, 230);
  ctx.closePath();
  ctx.stroke();
}
// unrelated
// simply to show a zoomed-in version
const zoomed = document.getElementById("zoomed");
const zCtx = zoomed.getContext("2d");
zCtx.imageSmoothingEnabled = false;
canvas.onmousemove = function drawToZoommed(e) {
  const
    x = e.pageX - this.offsetLeft,
    y = e.pageY - this.offsetTop,
    w = this.width,
    h = this.height;
    
  zCtx.clearRect(0,0,w,h);
  zCtx.drawImage(this, x-w/6,y-h/6,w, h, 0,0,w*3, h*3);
}
Run Code Online (Sandbox Code Playgroud)
<svg width="0" height="0" style="position:absolute;z-index:-1;">
  <defs>
    <filter id="remove-alpha" x="0" y="0" width="100%" height="100%">
      <feComponentTransfer>
        <feFuncA type="discrete" tableValues="0 1"></feFuncA>
      </feComponentTransfer>
      </filter>
  </defs>
</svg>

<canvas id="canvas" width="250" height="250" ></canvas>
<canvas id="zoomed" width="250" height="250" ></canvas>
Run Code Online (Sandbox Code Playgroud)

对于那些不喜欢<svg>在 DOM 中附加元素的人,以及生活在不久的将来(或带有实验标志的人),我们正在开发的 CanvasFilter 接口应该允许在没有 DOM 的情况下执行此操作(因此从工人也是):

if (!("CanvasFilter" in globalThis)) {
  throw new Error("Not Supported", "Please enable experimental web platform features, or wait a bit");
}

const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
ctx.fillStyle = "#ABEDBE";
ctx.fillRect(0,0,canvas.width,canvas.height);
ctx.fillStyle = "black";
ctx.font = "14px sans-serif";
ctx.textAlign = "center";

// first without filter
ctx.fillText("no filter", 60, 20);
drawArc();
drawTriangle();
// then with filter
ctx.setTransform(1, 0, 0, 1, 120, 0);
ctx.filter = new CanvasFilter([
  {
    filter: "componentTransfer",
    funcA: {
      type: "discrete",
      tableValues: [ 0, 1 ]
    }
  }
]);
// and do the same ops
ctx.fillText("no alpha", 60, 20);
drawArc();
drawTriangle();

// to remove the filter
ctx.filter = "none";


function drawArc() {
  ctx.beginPath();
  ctx.arc(60, 80, 50, 0, Math.PI * 2);
  ctx.stroke();
}

function drawTriangle() {
  ctx.beginPath();
  ctx.moveTo(60, 150);
  ctx.lineTo(110, 230);
  ctx.lineTo(10, 230);
  ctx.closePath();
  ctx.stroke();
}
// unrelated
// simply to show a zoomed-in version
const zoomed = document.getElementById("zoomed");
const zCtx = zoomed.getContext("2d");
zCtx.imageSmoothingEnabled = false;
canvas.onmousemove = function drawToZoommed(e) {
  const
    x = e.pageX - this.offsetLeft,
    y = e.pageY - this.offsetTop,
    w = this.width,
    h = this.height;
    
  zCtx.clearRect(0,0,w,h);
  zCtx.drawImage(this, x-w/6,y-h/6,w, h, 0,0,w*3, h*3);
};
Run Code Online (Sandbox Code Playgroud)
<canvas id="canvas" width="250" height="250" ></canvas>
<canvas id="zoomed" width="250" height="250" ></canvas>
Run Code Online (Sandbox Code Playgroud)

或者,您也可以将 SVG 保存为外部文件并将filter属性设置为path/to/svg_file.svg#remove-alpha.


eri*_*i0o 6

我想补充一点,我在缩小图像和在画布上绘图时遇到了麻烦,它仍然使用平滑,即使它在升级时没有使用.

我用这个解决了:

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

你可以像这样使用这个函数:

var canvas = document.getElementById('mycanvas')
setpixelated(canvas.getContext('2d'))
Run Code Online (Sandbox Code Playgroud)

也许这对某人有用.

  • 确实如此,这完全是一回事,在 JavaScript 中,obj['name'] 或 obj.name 一直是,并且将永远是相同的,对象是命名值(元组)的集合,使用类似于哈希表,两种符号都将被以相同的方式处理,完全没有理由你的代码以前不会工作,最坏的情况是它分配了一个无效的值(因为它是为另一个浏览器设计的。一个简单的例子:写obj = {a:123}; console.log(obj['a'] === obj.a ?“是的,它是真的”:“不,不是的”) (2认同)

小智 5

ctx.translate(0.5, 0.5);
ctx.lineWidth = .5;
Run Code Online (Sandbox Code Playgroud)

有了这个组合,我可以画出漂亮的1px细线.

  • 您不需要将lineWidth设置为.5 ...将(或应该)仅将其设置为半透明度. (6认同)

小智 5

注意一个非常有限的技巧。如果您想创建 2 色图像,您可以在颜色 #000000 的背景上使用颜色 #010101 绘制任何您想要的形状。完成此操作后,您可以测试 imageData.data[] 中的每个像素并将任何不是 0x00 的值设置为 0xFF :

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);
Run Code Online (Sandbox Code Playgroud)

结果将是非抗锯齿的黑白图片。这不会是完美的,因为会发生一些抗锯齿,但是这种抗锯齿将非常有限,形状的颜色非常类似于背景的颜色。


小智 5

尝试类似的东西canvas { image-rendering: pixelated; }

如果您只想让一行不被抗锯齿,这可能不起作用。

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

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

不过,我还没有在许多浏览器上测试过。

  • 这应该标记为答案。 (2认同)