首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >HTML画布坐标系统与渲染过程

HTML画布坐标系统与渲染过程
EN

Stack Overflow用户
提问于 2022-11-29 15:41:02
回答 2查看 48关注 0票数 2

我在html画布上玩画画,我对不同的坐标系是如何工作的并不感到困惑。到目前为止,我学到的是有更多的坐标系统:

  • 画布坐标系
  • css坐标系
  • 物理(显示)坐标系

所以当我用CanvasRenderingContext2D画一条线时

代码语言:javascript
复制
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(3, 1);
ctx.lineTo(3, 5);
ctx.stroke();

在将像素绘制到显示器之前,路径必须为

  1. 根据ctx转换矩阵进行缩放(如果有的话)
  2. 根据css画布元素尺寸(canvas.style.widthcanvas.style.height)和画布绘图尺寸(canvas.widthcanvas.height)之间的比例进行缩放。
  3. 根据window.devicePixelRatio进行缩放(高分辨率显示)

现在,当我想划一条清晰的界线时,我发现有两件事值得我去战斗。第一种是画布使用反走样。所以当我在整数坐标上画一条1的边界时,它就会变得模糊。

要解决这个问题,需要将其移动0.5像素。

代码语言:javascript
复制
ctx.moveTo(3.5, 1);
ctx.lineTo(3.5, 5);

要考虑的第二件事是window.devicePixelRatio。它用于将逻辑css像素映射到物理像素。如何使画布适应高分辨率设备的方法是按比例进行调整。

代码语言:javascript
复制
const ratio = window.devicePixelRatio || 1;
const clientBoundingRectangle = canvas.getBoundingClientRect();
  
canvas.width = clientBoundingRectangle.width * ratio;
canvas.height = clientBoundingRectangle.height * ratio;

const ctx = canvas.getContext('2d');
ctx.scale(ratio, ratio);

我的问题是,如何解决反走样问题与缩放有关的高分辨率显示器?

假设我的显示器是高精度的,window.devicePixelRatio2.0。当我应用上下文缩放来调整画布以适应高分辨率显示,并想用1的厚度绘制线时,我可以忽略上下文缩放并绘制吗?

代码语言:javascript
复制
ctx.moveTo(3.5, 1);
ctx.lineTo(3.5, 5);

在这种情况下

代码语言:javascript
复制
ctx.moveTo(7, 2);
ctx.lineTo(7, 10);

还是我必须考虑比例比例并使用类似的方法

代码语言:javascript
复制
ctx.moveTo(3.75, 1);
ctx.lineTo(3.75, 5);

为了弄到清脆的台词?

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2022-11-29 23:51:06

可以在画布位图缓冲区上的呈现中、绘制到画布位图缓冲区的时候以及CSS在监视器上显示的时候进行反混叠。

直线的0.5px偏移量仅适用于奇数整数的线宽。正如你所暗示的那样,这样的笔画,只能对齐路径的中心,从而在实际路径的内外传播一半的线宽,落在全像素坐标上。有关全面的解释,请参见我先前的回答

缩放画布缓冲器到监视器的像素比是有效的,因为在高分辨率设备上,将使用多个物理点覆盖单个px区域。这允许有更多的细节,例如文本,或其他矢量图形。然而,对于位图而言,这意味着浏览器必须“假装”它一开始就更大。例如,在2x监视器上呈现的100x100图像必须像200x200图像一样呈现,才能具有与1x监视器相同的大小。在缩放过程中,浏览器可能会再次使用反混叠,或者使用另一种缩放算法来“创建”丢失的像素。

通过直接按像素比例放大画布,并通过CSS缩小画布,我们最终得到一个原始位图,即它将呈现的大小,并且CSS不再需要缩放任何东西。

但是现在,您的画布上下文也是按照这个像素比进行缩放的,如果我们回到直线上,假设是2x监视器,那么0.5px偏移量现在实际上变成了1 2x偏移量,这是无用的。lineWidth of 1实际上将生成一个2px笔画,它不需要任何偏移量。

因此,不,不要忽略缩放时,抵消您的上下文直线。

但是,最好的方法可能是根本不使用这种偏移技巧,而是使用rect()调用和fill(),如果您希望您的线条完全适合像素。

代码语言:javascript
复制
const canvas = document.querySelector("canvas");
// devicePixelRatio may not be accurate, see below
setCanvasSize(canvas);

function draw() {
  const dPR = devicePixelRatio;
  const ctx = canvas.getContext("2d");
  // scale() with weird zoom levels may produce antialiasing
  // So one might prefer to do the scaling of all coords manually:
  const lineWidth = Math.round(1 * dPR);
  const cellSize  = Math.round(10 * dPR);
  for (let x = cellSize; x < canvas.width; x += cellSize) {
    ctx.rect(x, 0, lineWidth, canvas.height);
  }
  for (let y = cellSize; y < canvas.height; y += cellSize) {
    ctx.rect(0, y, canvas.width, lineWidth);
  }
  ctx.fill();
}

function setCanvasSize(canvas) {
// We resize the canvas bitmap based on the size of the viewport
// while respecting the actual dPR
// Thanks to gman for the reminder of how to suppport all early impl.
// https://stackoverflow.com/a/65435847/3702797
  const observer = new ResizeObserver(([entry]) => {
    let width;
    let height;
    const dPR = devicePixelRatio;
    if (entry.devicePixelContentBoxSize) {
      width  = entry.devicePixelContentBoxSize[0].inlineSize;
      height = entry.devicePixelContentBoxSize[0].blockSize;
    } else if (entry.contentBoxSize) {
      if ( entry.contentBoxSize[0]) {
        width  = entry.contentBoxSize[0].inlineSize * dPR;
        height = entry.contentBoxSize[0].blockSize  * dPR;
      } else {
        width  = entry.contentBoxSize.inlineSize * dPR;
        height = entry.contentBoxSize.blockSize  * dPR;
      }
    } else {
      width  = entry.contentRect.width  * dPR;
      height = entry.contentRect.height * dPR;
    }
    canvas.width  = width;
    canvas.height = height;
    canvas.style.width  = (width  / dPR) + 'px';
    canvas.style.height = (height / dPR) + 'px';
    // we need to redraw
    draw();
  });
  // observe the scrollbox size changes
  try {
    observer.observe(canvas, { box: 'device-pixel-content-box' });
  }
  catch(err) {
    observer.observe(canvas, { box: 'content-box' });
  }
}
代码语言:javascript
复制
canvas { width: 300px; height: 150px; }
代码语言:javascript
复制
<canvas></canvas>

票数 0
EN

Stack Overflow用户

发布于 2022-11-29 17:44:41

防止反混叠要求画布的像素(这是光栅图像)与屏幕像素对齐,这可以由将画布大小乘以devicePixelRatio完成,同时使用CSS大小将画布保持到原来的大小:

代码语言:javascript
复制
canvas.width = pixelSize * window.devicePixelRatio;
canvas.height = pixelSize * window.devicePixelRatio;
canvas.style.width = pixelSize + 'px';
canvas.style.height = pixelSize + 'px';

然后,您可以在上下文中使用缩放,这样绘制的图像就不会被更高的devicePixelRatios缩小。我在这里四舍五入,这样行就可以清晰地显示不是整数的比率:

代码语言:javascript
复制
let roundedScale = Math.round(window.devicePixelRatio);
context.scale(roundedScale, roundedScale);

然后,该示例从一个像素的中心顶部绘制一条垂直线到另一个像素的中心顶部:

代码语言:javascript
复制
context.moveTo(100.5, 10);
context.lineTo(100.5, 190);

要记住的一件事是放大。如果你放大这个例子,当浏览器放大光栅图像时,它会变成反别名。如果再次单击run,它将再次变得清晰(在大多数浏览器上)。这是因为大多数浏览器更新devicePixelRatio以包括任何缩放。如果您在动画循环中呈现,而它们正在缩放,则舍入可能会导致一些闪烁。

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/74616600

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档