HTML5 Canvas 和抗锯齿

发布于 2024-10-04 01:22:18 字数 297 浏览 1 评论 0原文

如何在画布上启用抗锯齿

下面的代码没有画出一条平滑的线:

var context = mainCanv.getContext("2d");
if (context) {
   context.moveTo(0,0);
   context.lineTo(100,75);

   context.strokeStyle = "#df4b26";
   context.lineWidth = 3;
   context.stroke();
}

How to turn on the anti-aliasing on an canvas.

The following code doesn't draw a smooth line:

var context = mainCanv.getContext("2d");
if (context) {
   context.moveTo(0,0);
   context.lineTo(100,75);

   context.strokeStyle = "#df4b26";
   context.lineWidth = 3;
   context.stroke();
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(9

以可爱出名 2024-10-11 01:22:18

您可以将画布平移半像素距离。

ctx.translate(0.5, 0.5);

最初是物理像素之间的画布定位点。

You may translate canvas by half-pixel distance.

ctx.translate(0.5, 0.5);

Initially the canvas positioning point between the physical pixels.

野鹿林 2024-10-11 01:22:18

抗锯齿功能无法打开或关闭,由浏览器控制。

我可以关闭 HTML上的抗锯齿功能吗?元素?

Anti-aliasing cannot be turned on or off, and is controlled by the browser.

Can I turn off antialiasing on an HTML <canvas> element?

心清如水 2024-10-11 01:22:18

现在已经是 2018 年了,我们终于有了廉价的方法来解决这个问题......

事实上,由于 2d context API 现在有一个 filter 属性,并且此过滤器属性可以接受 SVGFilters,我们可以构建一个 SVGFilter,它只保留绘图中完全不透明的像素,从而消除默认的抗锯齿功能。

因此,它本身不会停用抗锯齿功能,但在实现和性能方面提供了一种廉价的方法来在绘图时删除所有半透明像素。

我并不是真正的 SVGFilters 专家,因此可能有更好的方法,但例如,我将使用 节点仅抓取完全不透明的像素。

var 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
var zCtx = zoomed.getContext('2d');
zCtx.imageSmoothingEnabled = false;
canvas.onmousemove = function drawToZoommed(e) {
  var 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);
}
<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>

对于那些不喜欢在 DOM 中附加 元素的人,您还可以将其保存为外部 svg 文件并设置 filter 属性到path/to/svg_file.svg#remove-alpha

It's now 2018, and we finally have cheap ways to do something around it...

Indeed, since the 2d context API now has a filter property, and that this filter property can accept SVGFilters, we can build an SVGFilter that will keep only fully opaque pixels from our drawings, and thus eliminate the default anti-aliasing.

So it won't deactivate antialiasing per se, but provides a cheap way both in term of implementation and of performances to remove all semi-transparent pixels while drawing.

I am not really a specialist of SVGFilters, so there might be a better way of doing it, but for the example, I'll use a <feComponentTransfer> node to grab only fully opaque pixels.

var 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
var zCtx = zoomed.getContext('2d');
zCtx.imageSmoothingEnabled = false;
canvas.onmousemove = function drawToZoommed(e) {
  var 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);
}
<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>

And for the ones that don't like to append an <svg> element in their DOM, you can also save it as an external svg file and set the filter property to path/to/svg_file.svg#remove-alpha.

誰認得朕 2024-10-11 01:22:18

我不需要打开抗锯齿,因为默认情况下它是打开的,但我需要将其关闭。如果它可以关闭,那么它也可以打开。

ctx.imageSmoothingEnabled = true;

当我处理画布角色扮演游戏时,我通常会关闭它,这样当我放大图像时就不会显得模糊。

I haven't needed to turn on anti-alias because it's on by default but I have needed to turn it off. And if it can be turned off it can also be turned on.

ctx.imageSmoothingEnabled = true;

I usually shut it off when I'm working on my canvas rpg so when I zoom in the images don't look blurry.

芸娘子的小脾气 2024-10-11 01:22:18

这是一个解决方法,要求您逐像素绘制线条,但会防止抗锯齿。

// some helper functions
// finds the distance between points
function DBP(x1,y1,x2,y2) {
    return Math.sqrt((x2-x1)*(x2-x1)+(y2-y1)*(y2-y1));
}
// finds the angle of (x,y) on a plane from the origin
function getAngle(x,y) { return Math.atan(y/(x==0?0.01:x))+(x<0?Math.PI:0); }
// the function
function drawLineNoAliasing(ctx, sx, sy, tx, ty) {
    var dist = DBP(sx,sy,tx,ty); // length of line
    var ang = getAngle(tx-sx,ty-sy); // angle of line
    for(var i=0;i<dist;i++) {
        // for each point along the line
        ctx.fillRect(Math.round(sx + Math.cos(ang)*i), // round for perfect pixels
                     Math.round(sy + Math.sin(ang)*i), // thus no aliasing
                     1,1); // fill in one pixel, 1x1
    }
}

基本上,您找到线的长度,然后逐步遍历该线,舍入每个位置并填充像素。

调用它

var context = cv.getContext("2d");
drawLineNoAliasing(context, 20,30,20,50); // line from (20,30) to (20,50)

Here's a workaround that requires you to draw lines pixel by pixel, but will prevent anti aliasing.

// some helper functions
// finds the distance between points
function DBP(x1,y1,x2,y2) {
    return Math.sqrt((x2-x1)*(x2-x1)+(y2-y1)*(y2-y1));
}
// finds the angle of (x,y) on a plane from the origin
function getAngle(x,y) { return Math.atan(y/(x==0?0.01:x))+(x<0?Math.PI:0); }
// the function
function drawLineNoAliasing(ctx, sx, sy, tx, ty) {
    var dist = DBP(sx,sy,tx,ty); // length of line
    var ang = getAngle(tx-sx,ty-sy); // angle of line
    for(var i=0;i<dist;i++) {
        // for each point along the line
        ctx.fillRect(Math.round(sx + Math.cos(ang)*i), // round for perfect pixels
                     Math.round(sy + Math.sin(ang)*i), // thus no aliasing
                     1,1); // fill in one pixel, 1x1
    }
}

Basically, you find the length of the line, and step by step traverse that line, rounding each position, and filling in a pixel.

Call it with

var context = cv.getContext("2d");
drawLineNoAliasing(context, 20,30,20,50); // line from (20,30) to (20,50)
余生共白头 2024-10-11 01:22:18

如果您需要对画布进行像素级控制,可以使用 createImageDataputImageData

HTML:

<canvas id="qrCode" width="200", height="200">
  QR Code
</canvas>

和 JavaScript:

function setPixel(imageData, pixelData) {
  var index = (pixelData.x + pixelData.y * imageData.width) * 4;
    imageData.data[index+0] = pixelData.r;
    imageData.data[index+1] = pixelData.g;
    imageData.data[index+2] = pixelData.b;
    imageData.data[index+3] = pixelData.a;
}

element = document.getElementById("qrCode");
c = element.getContext("2d");

pixcelSize = 4;
width = element.width;
height = element.height;


imageData = c.createImageData(width, height);

for (i = 0; i < 1000; i++) {
  x = Math.random() * width / pixcelSize | 0; // |0 to Int32
  y = Math.random() * height / pixcelSize| 0;

  for(j=0;j < pixcelSize; j++){
    for(k=0;k < pixcelSize; k++){
     setPixel( imageData, {
         x: x * pixcelSize + j,  
         y: y * pixcelSize + k,
         r: 0 | 0,
         g: 0 | 0,
         b: 0 * 256 | 0,
         a: 255 // 255 opaque
       });
      }
  }
}

c.putImageData(imageData, 0, 0);

此处的工作示例

If you need pixel level control over canvas you can do using createImageData and putImageData.

HTML:

<canvas id="qrCode" width="200", height="200">
  QR Code
</canvas>

And JavaScript:

function setPixel(imageData, pixelData) {
  var index = (pixelData.x + pixelData.y * imageData.width) * 4;
    imageData.data[index+0] = pixelData.r;
    imageData.data[index+1] = pixelData.g;
    imageData.data[index+2] = pixelData.b;
    imageData.data[index+3] = pixelData.a;
}

element = document.getElementById("qrCode");
c = element.getContext("2d");

pixcelSize = 4;
width = element.width;
height = element.height;


imageData = c.createImageData(width, height);

for (i = 0; i < 1000; i++) {
  x = Math.random() * width / pixcelSize | 0; // |0 to Int32
  y = Math.random() * height / pixcelSize| 0;

  for(j=0;j < pixcelSize; j++){
    for(k=0;k < pixcelSize; k++){
     setPixel( imageData, {
         x: x * pixcelSize + j,  
         y: y * pixcelSize + k,
         r: 0 | 0,
         g: 0 | 0,
         b: 0 * 256 | 0,
         a: 255 // 255 opaque
       });
      }
  }
}

c.putImageData(imageData, 0, 0);

Working sample here

春庭雪 2024-10-11 01:22:18

所以我假设这现在有点不使用了,但一种方法实际上是使用 document.body.style.zoom=2.0; 但如果你这样做,那么你所有的画布测量都会有除以变焦。另外,将变焦设置得更高以获得更多锯齿效果。这很有用,因为它是可调节的。另外,如果使用此方法,我建议您使函数执行与 ctx.fillRect() 等相同的操作,但要考虑缩放。例如

function fillRect(x, y, width, height) {
    var zoom = document.body.style.zoom;
    ctx.fillRect(x/zoom, y/zoom, width/zoom, height/zoom);
}

希望这有帮助!

另外,旁注:这也可以用来锐化圆形边缘,这样它们看起来就不会那么模糊。只需使用 0.5 等缩放即可!

so I am assuming this is kinda out of use now but one way to do it is actually using document.body.style.zoom=2.0; but if you do this then all of your canvas measurements will have to be divided by the zoom. Also, set the zoom higher for more aliasing. This is helpful because it is adjustable. Also if using this method, I suggest that you make functions to do the same as ctx.fillRect() etc. but with the zoom taken into account. E.g.

function fillRect(x, y, width, height) {
    var zoom = document.body.style.zoom;
    ctx.fillRect(x/zoom, y/zoom, width/zoom, height/zoom);
}

Hope this helps!

Also, a sidenote: this can be used to sharpen circle edges as well so that they don't look as blurred. Just use a zoom such as 0.5!

紙鸢 2024-10-11 01:22:18

我遇到了同样的问题,并且能够通过使用小阴影和线条上下文来获得更平滑的线条,例如:

ctx.shadowOffsetX = 0
ctx.shadowOffsetY = 0
ctx.shadowBlur = 2
ctx.shadowColor = 'black'

I had the same issue and was able to get smoother lines by using a small shadow along with the line context, such as:

ctx.shadowOffsetX = 0
ctx.shadowOffsetY = 0
ctx.shadowBlur = 2
ctx.shadowColor = 'black'
过气美图社 2024-10-11 01:22:18

我需要 0.5 平移和像素数据操作的组合:

    context.strokeStyle = "rgb(255, 255, 255)";
    for (let [x1, y1, x2, y2] of linePoints) {
        context.beginPath();
        context.moveTo(x1 + 0.5, y1 + 0.5);
        context.lineTo(x2 + 0.5, y2 + 0.5);
        context.stroke();
    }

    // remove anti-aliasing
    const id = context.getImageData(0, 0, width, height);
    for (let y = 0; y < id.height; ++y) {
        for (let x = 0; x < id.width; ++x) {
            const index = (y * id.width + x) * 4;
            id.data[index] = id.data[index] < 128 ? 0 : 255;
            id.data[index+1] = id.data[index+1] < 128 ? 0 : 255;
            id.data[index+2] = id.data[index+2] < 128 ? 0 : 255;
            id.data[index+3] = id.data[index+3] < 128 ? 0 : 255;
        }
    }
    context.putImageData(id, 0, 0);

我在坐标中添加了 0.5。这可能可以通过翻译来完成。但这 0.5 很重要。如果没有它,左边缘像素和上边缘像素就会被推离图像。它还修复了一些不一致的舍入。原本应该是一条直线的地方出现了一些倾斜。

然后将所有像素值四舍五入为 0 或 255。如果您需要不同的颜色,可以四舍五入到这些值而不是 0 或 255。

I needed a combination of the 0.5 translation and pixel data manipulation:

    context.strokeStyle = "rgb(255, 255, 255)";
    for (let [x1, y1, x2, y2] of linePoints) {
        context.beginPath();
        context.moveTo(x1 + 0.5, y1 + 0.5);
        context.lineTo(x2 + 0.5, y2 + 0.5);
        context.stroke();
    }

    // remove anti-aliasing
    const id = context.getImageData(0, 0, width, height);
    for (let y = 0; y < id.height; ++y) {
        for (let x = 0; x < id.width; ++x) {
            const index = (y * id.width + x) * 4;
            id.data[index] = id.data[index] < 128 ? 0 : 255;
            id.data[index+1] = id.data[index+1] < 128 ? 0 : 255;
            id.data[index+2] = id.data[index+2] < 128 ? 0 : 255;
            id.data[index+3] = id.data[index+3] < 128 ? 0 : 255;
        }
    }
    context.putImageData(id, 0, 0);

I added 0.5 to the coordinates. That could probably be done with a translate. But that 0.5 is important. Without it, the left edge pixels and the top edge pixels get pushed off of the image. It also fixed some inconsistent rounding. What should have been a straight line had some dips in it.

Then just round all the pixel values to 0 or 255. If you need different colors, you can round to those values instead of 0 or 255.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文