HTML5 画布和抗锯齿 [重复]

Posted

技术标签:

【中文标题】HTML5 画布和抗锯齿 [重复]【英文标题】:HTML5 Canvas and Anti-aliasing [duplicate] 【发布时间】:2011-05-14 17:59:27 【问题描述】:

如何在画布上开启抗锯齿

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

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

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

【问题讨论】:

根据this *** question,默认情况下画布抗锯齿的。您使用的是什么操作系统/浏览器/版本? 你说不流畅是什么意思?您看到锯齿状边缘/像素,还是只是模糊? 它发生在 android 的 Firefox Mobile 上,红线周围有一个难看的灰色边框。 phoboslab.org/log/2012/09/drawing-pixels-is-hard 这能回答你的问题吗? Can I turn off antialiasing on an html <canvas> element? 【参考方案1】:

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

ctx.translate(0.5, 0.5);

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

【讨论】:

请注意,如果您使用将宽度设置为自身的方法清除画布:canvas.width = canvas.width 这将重置平移矩阵,您需要再次平移半个像素.您可以改用 clearRect() 来避免这种情况。 这是真正解决 OPs 问题的唯一答案 它显然有助于直线,但对角线仍然看起来很糟糕。 它只是在出现混叠的地方移动。 请注意,浮点像素值会大大降低性能,因此如果您经常绘图,这可能不是一个好主意【参考方案2】:

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

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

【讨论】:

这不是真的。它可以通过 ctx.imageSmoothingEnabled 开启和关闭。 imageSmoothingEnabled 适用于图案填充和drawImage,它不影响一般的抗锯齿。 whatwg.org/specs/web-apps/current-work/multipage/… 哦,在那种情况下,也许是显卡可以处理这个问题。 FWIW HTML5 画布没有 GPU 加速【参考方案3】:

我不需要打开抗锯齿,因为它默认是打开的,但我需要关闭它。如果它可以关闭,它也可以打开。

ctx.imageSmoothingEnabled = true;

当我在画布 rpg 上工作时,我通常会关闭它,这样当我放大图像时不会显得模糊。

【讨论】:

imageSmoothingEnabled 适用于图案填充和drawImage,它不影响一般的抗锯齿。 whatwg.org/specs/web-apps/current-work/multipage/…【参考方案4】:

现在是 2018 年,我们终于有便宜的方法来做一些事情了...

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

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

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

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   style="position:absolute;z-index:-1;">
  <defs>
    <filter id="remove-alpha" x="0" y="0"  >
      <feComponentTransfer>
        <feFuncA type="discrete" tableValues="0 1"></feFuncA>
      </feComponentTransfer>
      </filter>
  </defs>
</svg>

<canvas id="canvas"   ></canvas>
<canvas id="zoomed"   ></canvas>

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

【讨论】:

想做类似的事情 - 将半透明映射到不透明像素...***.com/questions/53755451/… 非常棒的答案 - 你甚至包括了一个缩放来强调差异! “现在是 2018 年”,不适用于 Internet Explorer。到发表此评论时,实际上已经是 2020 年了,而且仍然不是 Internet Explorer。不幸的是,这种恐龙仍然被许多人使用,并且一些项目仍然需要支持它...... @JeremyTille 这样吗?我们是否应该因为一些公司仍在使用 IE 而剥夺世界其他地区的现代技术?我还必须为一些客户提供支持,我们只是向他们明确表示他们不会获得“最佳体验”。此页面中还有其他解决方案,如果您的项目确实需要,请选择一个。 哦,当然。我完全赞成放弃 IE,这是我每天的战斗。你的解决方案很好,但是 IE 把我们都拖了:(【参考方案5】:

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

// 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)

【讨论】:

我不认为 getAngle 是绘制线条所必需的。您需要做的就是将两点之间的差除以“dist”,然后将其乘以“i”。我错了吗? 是的,你是对的。我只是为了清楚起见使用了角度..?我猜 天哪,你刚刚为我节省了大量时间!大约一年前,我开始开发一个基于像素的绘画网络应用程序,并且对无法禁用抗锯齿感到沮丧。我认为当时找到的唯一解决方案 id 运行速度太慢以至于毫无希望,但你的运行速度非常快,并且完全符合我的需要!谢谢!!! 逐像素画线不需要调用 sin 和 cos。使用 Bresenham 的算法,这是一段图形历史:en.wikipedia.org/wiki/Bresenham%27s_line_algorithm【参考方案6】:

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

HTML:

<canvas id="qrCode" , >
  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);

Working sample here

【讨论】:

【参考方案7】:

所以我假设这现在有点用不上了,但一种方法实际上是使用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 之类的缩放!

【讨论】:

【参考方案8】:

我需要 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。这可能通过translate 来完成。但 0.5 很重要。没有它,左边缘像素和上边缘像素就会被推离图像。它还修复了一些不一致的舍入。本来应该是一条直线的地方有一些下降。

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

【讨论】:

【参考方案9】:

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

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

【讨论】:

以上是关于HTML5 画布和抗锯齿 [重复]的主要内容,如果未能解决你的问题,请参考以下文章

emWin6.x抗锯齿

画布中的抗锯齿大文本

画布的 drawImage() 不进行亚像素渲染/抗锯齿是不是有技术原因?

如何在没有抗锯齿的情况下在画布上绘制像素字体

如何通过 Emscripten 激活抗锯齿

Android Canvas 抗锯齿的两种方式