canvas画直线或圆弧时支持浮点数吗?

Does canvas support floating point numbers when drawing line or arc?

在 canvas 元素中,浮点数实际上是否被 lineToarc 方法视为浮点数?

例如:

context.moveTo(20.4562, 80.8923);
context.lineTo(120.1123, 90.2134);
context.arc(24.5113, 36.7989, 20.123, 0, Math.PI*2); 

canvas是真的支持浮点数坐标吗,还是画直线、圆弧、矩形等时只是将浮点数转为整数?

屏幕设备是一个像素设备,所以最终任何绘图都映射到整个像素,即整数。但是,在 HTML5 canvas 中使用浮点坐标而不是整数确实会改变结果。

如果您绘制的坐标不是整数,它会自动使用抗锯齿来尝试平滑线条。您可以在 this article.

中查看示例

它也会影响 drawImage,请参阅此 thread 了解更多详情。

请注意,使用浮点坐标会显着降低性能。

位图本身不支持浮点值。它只能处理整数值。 canvas 的 2D 上下文主要处理 路径 ,每个 SE 未连接到位图。

路径是任意的,在内部仅作为向量存在。当它们被描边或填充时,它们会经过 光栅化 过程,即。转换为位图表示。

路径及其在位图中的指定点本身可以​​包含小数值,但位图不能。但是,可以使用 interpolation(子像素化)来表示分数值,这给人的印象是具有更大的可用位图分辨率,如果值被剪切,这又会消除锯齿状的外观缩小到整数。

(而且分辨率是做插值的重点,一个屏幕大概相当于72-96DPI,如果屏幕分辨率更高,300+DPI,就不需要插值了,就像在打印的情况。但我们现在在那里,如果不使用插值作为对分辨率不足的补偿,绘图会看起来参差不齐。

这是它的工作原理

当像素落在整数边界值上时,将像素设置为该值是一个简单的例子。

当您处理浮点数时,没有地方可以设置部分像素,它必须向下舍入(floor'ed)或放置在下一个单元格(ceil'ed)中,即。 3.2 变成 3,3.7 变成 4 等等

插值/子像素化

然而,几十年前有人提出了将小数部分表示为实际像素和下一个像素之间的混合的想法。

如果像素值为3.5,小数部分在这里代表50%的黑色和50%的白色。它仍然会占据整个像素单元,但由于它非常小,由于周围的像素造成了幻觉,它看起来只占据了其单元的一小部分。

所以在这种情况下,最后一个像素集看起来像这样:

如果值为 3.25,则只有 25% 的剩余像素会被混合,使最后一个像素看起来像:

这将应用于所有落在分数值上的像素。当您绘制一条对角线并且一些点“交叉”两个像素时,这些点的混合将应用于基于闭合整数的位置,使该线看起来平滑。

现在,在 canvas 的情况下,形状使用 alpha 通道进行插值。然后使用 Porter-Duff 将形状与现有内容混合和合成,这就是我们看到的最终结果。

关于性能,这也是需要牢记的一点。如果需要插值,成本会上升,因为对于每个像素,浏览器(或子系统)必须计算分数表示。出于这个原因,您可以确保使用整数值,在将它们传递给路径方法之前将它们四舍五入。这当然不适用于圆弧、椭圆、某些角度的对角线等,但在某些情况下它可以帮助加快速度。

这是使用分数坐标时适用的插值原理。然而,还有更复杂的插值和子像素方式。在现代世界中,2x2 或 4x4 采样更常见以提供更准确的结果。

演示

如果我们放大一条小线,当我们改变分数值时,我们可以看到线的末端“淡入淡出”。

这条线是 3 个像素宽,然后我们在 [0, 1] 之间添加一个分数值给它,我们可以看到最后一个 ceil'ed 像素正在重新采样:

var ctx = document.querySelector("canvas").getContext("2d"),
    x = 3.1, dx = 0.1;

ctx.imageSmoothingEnabled = ctx.mozImageSmoothingEnabled = ctx.webkitImageSmoothingEnabled = false;
ctx.font = "14px sans-serif";

(function loop() {
  ctx.fillStyle = "#000";
  ctx.clearRect(0,0,350,50);
  ctx.fillRect(0,0, x, 1); // forces pixel-alignment for demo

  ctx.drawImage(ctx.canvas, 0,0,4,1, 0, 0, 200, 50);
  
  x += dx;
  if (x <= 3 || x >= 4) dx = -dx;

  info(x);
  document.querySelector("div").innerHTML = "length: " + x.toFixed(2);
  
  setTimeout(loop, 160)
})();

function info(x) {
  ctx.fillStyle = "#f00";

  ctx.fillText("Perceived length", 210, 19);
  ctx.fillRect(0, 15, x * 50, 2);
  ctx.fillRect(x*50-1, 12, 2, 7);

  x = x === 3 ? 3 : 4;
  ctx.fillText("Actual length", 210, 40);
  ctx.fillRect(0, 35, x * 50, 2);
  ctx.fillRect(x*50-1, 32, 2, 7);
}
div {font:bold 14px sans-serif}
<canvas width=500 height=50></canvas><br>
<div></div>