奇怪的HTML 5画布抗锯齿

5

我一直在使用canvas元素,发现当我试图在一些宽度/高度配置下绘制相邻的NxN均匀纯色单元格时,它们之间会出现模糊的白色线。

例如,这个画布应该是黑色的,但包含某种我推测是由于浏览器中错误的反锯齿处理而形成的网格

The canvas demonstrating the issue.

可以说,这个bug只在某些配置中出现,但我希望彻底解决它。有没有办法规避这个问题?您是否曾经遇到过canvas中的反锯齿问题?

我制作了这个fiddle来演示这个问题,并允许您玩弄画布的尺寸和单元格数量。它还包含我用来绘制单元格的代码,以便您检查并告诉我是否做错了什么。

var ctx = canvas.getContext('2d');  
ctx.clearRect(0, 0, canvasWidth, canvasHeight);
for (var i = 0; i < numberOfCells; ++i) {
    for (var j = 0; j < numberOfCells; ++j) {
    ctx.fillStyle = '#000';
    ctx.fillRect(j * cellWidth, i * cellHeight, cellWidth, cellHeight);
  }
}

提前感谢您,

Petr.


刚刚注意到了。抱歉,给你! :) - Petr Mánek
一个常用的技巧是在开始时将整个画布向右移动一个像素来避免此问题,我相信。 - Evan Knowles
1
这是有道理的,300 / 70 = 4.285... 这将使任何边缘反锯齿化,因此会出现一条线。由于四舍五入的原因,它似乎做到了预期的效果... - somethinghere
@EvanKnowles 我也听说过这个问题,但是有人说是半像素的问题。是否可能转换偏移量与绘制对象的尺寸有关? - Petr Mánek
@somethinghere 你说得没错,但这些行是由于舍入导致的精度损失所产生的后果,这是不好的。 - Petr Mánek
显示剩余2条评论
3个回答

4

jsFiddle: https://jsfiddle.net/ngxjnywz/2/

JavaScript代码片段

var cellWidth = Math.ceil(canvasWidth / numberOfCells);
var cellHeight = Math.ceil(canvasHeight / numberOfCells);

根据宽度、高度和单元格数的不同,有时会出现 4.2 这种数字,但实际上是 4,但这样显示是错误的,会导致出现一个像素的空白行。所以你只需要使用 Math.ceil 函数,就能使单元格的宽度和高度始终为更高的数字,就不会再出现空白行了。


不幸的是,只有当线条平行于X或Y轴时,舍入才起作用,也不考虑可能应用的任何变换。 - Blindman67
这真是一个非常好的和简单的解决方案。由于单元格开始重叠,可能会变得有点混乱,并且这可能会导致绘图顺序突然变得相关,但由于相同的上取整的“cellWidth”将用于偏移绘图过程,因此这将被防止。 - Petr Mánek
我刚刚尝试了"ctx.fillRect(j * cellWidth + 151, i * cellHeight + 77, cellWidth, cellHeight);",它没有任何问题。但是我可以确认,在网格上进行任何“变换”都会产生线条,但是如果在网格之后进行任何变换,则不会出现问题。请查看我的链接https://jsfiddle.net/ngxjnywz/4/。 - Canvas

2
最佳解决方案是在所有填充周围添加0.5像素宽的描边,使用与填充相同的样式,并偏移所有绘图,以便您在像素中心而不是左上角渲染。
如果您添加了缩放或平移,则必须调整坐标,以便仍然为您的绘图坐标提供中心点。
最终,您只能减少伪影,但对于许多情况,您将无法完全消除它们。
此答案向您展示了如何删除未转换画布的伪影。 如何填补空隙

这个过程看起来相当复杂。在我的特定设置中,我将不得不完全重新考虑我的网格绘制的数学。 - Petr Mánek
@PetrMánek。半像素技巧的效果很好,但需要重新设计。另一个解决方法是在第二个画布上创建“构建块”,并从这些构建块中组装您的设计。 (https://dev59.com/1Inca4cB1Zd3GeqP8Dop) - markE
尝试将绘图偏移0.5像素,但问题并未消失。虽然我的画布中的线条和文本现在看起来更清晰,但在某些情况下,分隔单个正方形的细网格线仍然可见。当我添加0.5像素宽的描边时,一切开始变得笨重和混乱。 - Petr Mánek
您可以尝试使用小于0.5像素的各种笔画宽度。另一种选择是将其渲染到一个比原始画布大2或4倍的画布上,然后使用ctx.drawImage(largeCanvas, 0, 0, canvas.width, canvas.height)ctx.imageSmoothingEnabled = true;将该画布绘制到原始画布上,这将进一步减少伪影。或者使用ctx.getImageData()并逐像素地渲染框。如果只是框,那么涉及的内容不会太多。 - Blindman67
我不喜欢猜测笔画宽度的想法,特别是因为我的画布会根据窗口的尺寸进行调整。另一方面,使用第二个画布的想法可能会奏效。查看完整答案:https://dev59.com/eJLea4cB1Zd3GeqPyyo6#34365436 - Petr Mánek
显示剩余2条评论

1
经过阅读和尝试了几种方法后,我决定自己想出一个方法。我创建了另一个(虚拟的)画布,其整数尺寸对应于网格中单元格的数量。
在那里绘制所有单元格后,我在主画布上调用context.drawImage(),并传递虚拟画布作为参数,以及偏移和缩放参数,使其适合我的其余绘图。假设浏览器将整个虚拟画布的图像作为整体进行缩放(而不是作为单个单元格),我希望能够摆脱不需要的分隔线。
尽管我已经尝试了很多努力,但这些线仍然存在。有什么建议吗?
这是演示我的技术的代码片段: https://jsfiddle.net/ngxjnywz/5/

我找到了罪魁祸首。原来只要我不将次画布移动0.5像素(如其他答案所建议的那样),这种方法就有效。在删除context.translate(0.5, 0.5)之后,其他所有东西都落到了正确的位置。 - Petr Mánek

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接