开发者

HTML5 Canvas and Anti-aliasing [duplicate]

This question already has answers here: 开发者_开发知识库 Can I turn off antialiasing on an HTML <canvas> element? (15 answers) Closed 1 year ago.

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();
}


You may translate canvas by half-pixel distance.

ctx.translate(0.5, 0.5);

Initially the canvas positioning point between the physical pixels.


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

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


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.


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.


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)


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


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!


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'


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.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜