如何计算偏移量以绘制到画布中应用了比例变换的最近像素?
Posted
技术标签:
【中文标题】如何计算偏移量以绘制到画布中应用了比例变换的最近像素?【英文标题】:How to calculate the offset to draw to the nearest pixel in a canvas with scale transformation applied? 【发布时间】:2021-03-19 15:52:40 【问题描述】:我正在尝试在 html 画布上绘制一个具有像素完美定位的矩形,而不管应用于画布变换的平移和缩放(假设在这种情况下不使用旋转)。
在这种情况下,我总是希望矩形的边框在屏幕上为 1px 宽,而不管矩形在应用比例的情况下如何放大(我在下面的演示中工作过),但是我也想要它以精确的像素坐标绘制,因此没有应用抗锯齿。
在下面的代码中,我很确定我必须操纵 ctx.rect 的参数来为每个位置添加偏移量,以便将其四舍五入以准确显示在屏幕上最近的像素上。但我不确定要使用什么数学方法到达那里?
正如您在此演示中所见,应用 1.5 缩放后,矩形不再绘制在像素完美坐标上。
const canvases = [
ctx: document.getElementById('canvasOriginal').getContext('2d'),
scale: 1,
translateX: 0,
translateY: 0
,
ctx: document.getElementById('canvasZoomed').getContext('2d'),
scale: 1.5,
translateX: -0.5,
translateY: -0.5
];
for (const ctx, scale, translateX, translateY of canvases)
ctx.translate(translateX, translateY);
ctx.scale(scale, scale);
ctx.beginPath();
ctx.rect(1.5, 1.5, 4, 4);
ctx.lineWidth = 1 / scale;
ctx.strokeStyle = 'red';
ctx.stroke();
canvas
border: 1px solid #ccc;
image-rendering: pixelated;
image-rendering: crisp-edges;
width: 100px;
height: 100px;
<canvas id="canvasOriginal" ></canvas>
<canvas id="canvasZoomed" ></canvas>
这是我在上面sn-p中缩放后想要的结果:
编辑:请不要忽略翻译。
【问题讨论】:
这能回答你的问题吗? html5 canvas prevent linewidth scaling 也许吧!我会玩的。 不,这是一个有趣的技巧,但它呈现的结果与我提供的演示完全相同。 【参考方案1】:给定的答案有很多底层开销,获取和创建一个 DOM 矩阵,创建一个 DOM 点,转换点然后反转矩阵并转换回来是文字 100+ 乘法和加法(忽略内存管理开销)。
由于您只是缩放,没有平移或旋转,因此可以一次除法、八次乘法和八次加法/减法完成,并且速度至少快一个数量级。
示例
const canvases = [
ctx: document.getElementById('canvasOriginal').getContext('2d'), scale: 1 ,
ctx: document.getElementById('canvasZoomed').getContext('2d'), scale: 1.5 ,
];
function pathRectPixelAligned(ctx, scale, x, y, w, h)
const invScale = 1 / scale;
x = (Math.round(x * scale) + 0.5) * invScale ;
y = (Math.round(y * scale) + 0.5) * invScale ;
w = (Math.round((x + w) * scale) - 0.5) * invScale - x;
h = (Math.round((y + h) * scale) - 0.5) * invScale - y;
ctx.rect(x, y, w, h);
for (const ctx, scale of canvases)
ctx.scale(scale, scale);
ctx.beginPath();
pathRectPixelAligned(ctx, scale, 1, 1, 4, 4)
ctx.lineWidth = 1;
ctx.strokeStyle = 'red';
ctx.setTransform(1,0,0,1,0,0);
ctx.stroke();
canvas
border: 1px solid #ccc;
image-rendering: pixelated;
image-rendering: crisp-edges;
width: 100px;
height: 100px;
<canvas id="canvasOriginal" ></canvas>
<canvas id="canvasZoomed" ></canvas>
【讨论】:
感谢您的性能提示,但是当我将非整数转换应用于变换矩阵时,此示例会中断。我意识到我在这个问题中暗示了这一点,因为我只是说忽略旋转。 jsfiddle.net/oe07xykq 我实际上是在应用平移、旋转和缩放。当旋转为 90 度的倍数时,我只希望它看起来像像素完美。这就是我说忽略旋转的原因,因为在这种情况下像素完美并不重要。但是,是的,我从来没有说过忽略翻译。【参考方案2】:好吧,我呃……我自己想通了。您可以使用 DOMPoint 类通过画布的变换矩阵来变换坐标。所以我写了一个函数,通过矩阵转换点,将其四舍五入到最接近的半像素(因为在中心渲染了一个 1 像素宽的笔划,例如像素的半点),然后通过逆矩阵。
这会导致将缩放的 1px 笔画渲染到屏幕上最近的像素。
希望这个问题对其他浏览互联网的人有用,因为在发布这个问题之前我花了很长时间才弄清楚这个问题......
const canvases = [
ctx: document.getElementById('canvasOriginal').getContext('2d'),
scale: 1,
translateX: 0,
translateY: 0
,
ctx: document.getElementById('canvasZoomed').getContext('2d'),
scale: 1.5,
translateX: -0.5,
translateY: -0.5
];
const roundPointToHalfIdentityCoordinates = (ctx, x, y) =>
let point = new DOMPoint(x, y);
point = point.matrixTransform(ctx.getTransform());
point.x = Math.round(point.x - 0.5) + 0.5;
point.y = Math.round(point.y - 0.5) + 0.5;
point = point.matrixTransform(ctx.getTransform().inverse());
return point;
;
for (const ctx, scale, translateX, translateY of canvases)
ctx.translate(translateX, translateY);
ctx.scale(scale, scale);
ctx.beginPath();
const topLeft = roundPointToHalfIdentityCoordinates(ctx, 1.5, 1.5);
const bottomRight = roundPointToHalfIdentityCoordinates(ctx, 5.5, 5.5);
ctx.rect(
topLeft.x,
topLeft.y,
bottomRight.x - topLeft.x,
bottomRight.y - topLeft.y
);
ctx.lineWidth = 1 / scale;
ctx.strokeStyle = 'red';
ctx.stroke();
canvas
border: 1px solid #ccc;
image-rendering: pixelated;
image-rendering: crisp-edges;
width: 100px;
height: 100px;
<canvas id="canvasOriginal" ></canvas>
<canvas id="canvasZoomed" ></canvas>
【讨论】:
以上是关于如何计算偏移量以绘制到画布中应用了比例变换的最近像素?的主要内容,如果未能解决你的问题,请参考以下文章