Reputation: 501
I have a circle at [x, y] with a radius r. I want to scale it by x1.1, while keeping it at the same [x, y] position. I tried suggested code from other questions but I just couldn't do it.
Here is my code:
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();
Any suggestions? Thanks.
Upvotes: 0
Views: 980
Reputation: 54026
When rendering complex scenes containing many elements it is common to divide the scene into several abstract coordinate systems.
This allows you to easily translate, scale, rotate elements within the scene via a transforms.
A transform defines an element's, x and y position (translation) the direction of the x and y axis (rotation and or skew), and how far apart each pixel's edge is along these axis (scale)
World: The absolute coordinates of each element. This is the top most coordinate, with elements within (child elements) having coordinates relative to themselves.
Local: The relative coordinates of an element's parts (path, text, etc). For example a rectangles local coordinate is its center {x: 0, y: 0}
its top left corner is half the with and height up and left of the origin {x: -width / w, y: -height / 2}
Local coordinates can also be joined as a tree, where the parent local becomes the world coordinates for the children.
View: (AKA screen, canvas) This is the coordinate system of the display and is used to manipulate the scale, position, orientation of all items in the world. It is most often independent of the world and local coordinates systems, however a common shortcut is to make the world coordinates match the view (as done in rest of answer).
You can use these abstraction coordinate systems in part or full to make your scene building code easier to understand and manipulate.
In the following snippet we can define an element (a circle), relative to it's local coordinates, and render it via it's world coordinates which can match the view coordinates.
Thus the circle need only store its radius, to draw it you provide the world/view coordinate and scale.
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);
In the above code if you scale the circle up by 2 it will also double the line width as line width is part of the circle's local coordinates. To change the size of the circle without changing the line width you change its radius.
A more complex example adds rotations and some extra element types and draws these shapes are randomly position scaled and rotated on the canvas.
Note how the line width is scaled with the shape's scale. The text is however is a fill and there is no stroke to scale.
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>
Upvotes: 2
Reputation: 1155
ctx.scale
will change the scale of the entire canvas. Using a scale of 1.1 will make each unit of the canvas use 1.1 pixels. You need to take that into account in your transformations if you want to use pixel measurements for positioning.
If you only intend to scale the circle by 1.1, you can just multiply the radius to increase its size:
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();
Upvotes: 1