在 canvas,从中心缩放形状

On canvas, scale shape from its center

我在 [x, y] 处有一个半径为 r 的圆。我想按 x1.1 缩放它,同时将其保持在相同的 [x, y] 位置。我尝试了其他问题的建议代码,但我做不到。

这是我的代码:

ctx.save();
ctx.translate(x, y);
ctx.scale(1.1, 1.1);

ctx.beginPath();
ctx.arc(x, y, r, 0, 2 * Math.PI);
ctx.fillStyle = '#000000';
ctx.fill();

ctx.restore();

有什么建议吗?谢谢

ctx.scale 将改变整个 canvas 的比例。使用 1.1 的比例将使 canvas 的每个单元使用 1.1 像素。如果您想使用像素测量进行定位,则需要在转换中考虑到这一点。

如果您只想将圆缩放 1.1,您可以只乘以半径来增加它的大小:

ctx.save();
ctx.translate(x, y);

ctx.beginPath();
ctx.arc(x, y, r * 1.1, 0, 2 * Math.PI);
ctx.fillStyle = '#000000';
ctx.fill();

ctx.restore();

坐标系

渲染包含许多元素的复杂场景时,通常会将场景划分为多个抽象坐标系。

这使您可以通过变换轻松平移、缩放、旋转场景中的元素。

变换定义元素的 x 和 y 位置(平移)x 和 y 轴的方向(旋转和/或倾斜),以及每个像素的边缘沿这些轴的距离(比例)

3种常用坐标系

  • World:各元素的绝对坐标。这是最顶层的坐标,(子元素)中的元素具有相对于自身的坐标。

  • Local:元素各部分(路径、文本等)的相对坐标。例如,一个矩形的局部坐标是它的中心 {x: 0, y: 0} 它的左上角是原点的一半和高度 {x: -width / w, y: -height / 2}

    局部坐标也可以作为一棵树连接起来,其中父局部成为子级的世界坐标。

  • View: (AKA screen, canvas) 这是显示器的坐标系,用于操作比例、位置、世界上所有物品的方向。它通常独立于世界坐标系和局部坐标系,但是一个常见的捷径是使世界坐标与视图匹配(如其余答案中所做的那样)。

使用坐标系

您可以部分或全部使用这些抽象坐标系,使您的场景构建代码更易于理解和操作。

在下面的代码片段中,我们可以定义一个元素(一个圆),相对于它的局部坐标,并通过它可以匹配视图坐标的世界坐标来渲染它。

因此,圆只需要存储它的半径,要绘制它,您需要提供 world/view 坐标和比例。

const Circle = {
    radius: 100, x: 0, y: 0,    // Local coords at circle center
    draw(wx, wy, wScale) {      // World / View coords
        ctx.setTransform(wScale, 0, 0, wScale, wx, wy);
        ctx.beginPath();
        ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
        ctx.stroke();
    }
}
Circle.draw(100, 50, 1.2);  // draw at 100, 50, scale by 1.2

// Note to reset the default transform use ctx.setTransform(1,0,0,1,0,0);

在上面的代码中,如果将圆放大 2 倍,它也会使线宽加倍,因为线宽是圆局部坐标的一部分。要在不更改线宽的情况下更改圆的大小,请更改其半径。

例子

一个更复杂的示例添加了旋转和一些额外的元素类型并绘制了这些形状,这些形状在 canvas 上随机缩放和旋转。

注意线宽如何随形状的比例缩放。然而,文本是填充的,没有可缩放的笔触。

const ctx = can.getContext("2d");
// When code ready draw scene
requestAnimationFrame(() => drawRandomShapes(shapes, 40));

const ShapeCommon = {
    draw(x, y, scale, ang) {  // ang in radians
        ctx.setTransform(scale, 0, 0, scale, x, y);
        ctx.rotate(ang);
        ctx.beginPath();
        if (this.addPath() !== false) { ctx.stroke() }
    }
};
const Circle = (r = 100, s = 0, e = Math.PI * 2) => ({
    ...ShapeCommon,
    addPath() { ctx.arc(0, 0, r, s, e); }
});
const Rectangle = (w, h = w) => ({
    ...ShapeCommon,
    addPath() { ctx.rect(- w * 0.5, -h  * 0.5, w, h); }
});
const Text = (str) => ({
    ...ShapeCommon,
    addPath() { 
        ctx.textAlign = "center";
        ctx.fillText(str, 0, 0); 
        return false; // true to indicate content has been rendered
    }
});
// Example creates some shapes to draw
const shapes = [Circle(20, 0, Math.PI), Circle(5), Rectangle(20), Rectangle(20, 3), Text("Hi local")];
function drawRandomShapes(shapes, count) {
    const w = can.width;
    const h = can.height;
    while (count--) {
         const shapeIdx = Math.random() * shapes.length | 0;
         const x = Math.random() * w;
         const y = Math.random() * h;
         const scale = Math.random() * 4 + 0.2;
         const rot = Math.random() * Math.PI * 2;
         shapes[shapeIdx].draw(x, y, scale, rot);
    }
}         
canvas { border: 1px solid black; }
<canvas id="can" width="512" height="512"></canvas>