Reputation: 385
I used quadraticCurveTo to draw the rounded corner rect in canvas
function roundRect(x0, y0, x1, y1, r, color) {
var w = x1 - x0;
var h = y1 - y0;
if (r > w/2) r = w/2;
if (r > h/2) r = h/2;
context.beginPath();
context.moveTo(x1 - r, y0);
context.quadraticCurveTo(x1, y0, x1, y0 + r);
context.lineTo(x1, y1-r);
context.quadraticCurveTo(x1, y1, x1 - r, y1);
context.lineTo(x0 + r, y1);
context.quadraticCurveTo(x0, y1, x0, y1 - r);
context.lineTo(x0, y0 + r);
context.quadraticCurveTo(x0, y0, x0 + r, y0);
context.closePath();
context.fillStyle = color;
context.fill();
}
Now I got the (x1,y1), (x2,y2), (x3,y3), (x4,y4) four points of the rect, and want to draw the rotated rounded corner rect in canvas without using canvas.rotate()
function roundRect(x1, y1, x2, y2, x3, y3, x4, y4, r, color) {
var w = x4 - x1;
var h = y4 - y1;
if (r > w/2) r = w/2;
if (r > h/2) r = h/2;
context.beginPath();
context.moveTo(x2 - r, y2);
context.quadraticCurveTo(x2, y2, x2, y2 + r);
context.lineTo(x4, y4-r);
context.quadraticCurveTo(x4, y4, x4 - r, y4);
context.lineTo(x3 + r, y3);
context.quadraticCurveTo(x3, y3, x3, y3 - r);
context.lineTo(x1, y1 + r);
context.quadraticCurveTo(x1, y1, x1 + r, y1);
context.closePath();
context.fillStyle = color;
context.fill();
}
The corner was wrong position of this code, any solution to draw the rotated rounded rect using x1-x4, y1-y4 without using canvas.rotate()? I sure that my x1-x4, y1-y4 is works.
Upvotes: 0
Views: 813
Reputation: 54049
To rotate the box you need to apply a rotation matrix on each of the points.
The matrix defines the x axis, (top) and y axis, (right side of a pixel, including scale, or how big a pixel is), and where the origin is (coordinate {x:0, y:0}
)
const xAx = Math.cos(angle) * scale; // scale is the size of a pixel
const xAy = Math.sin(angle) * scale;
const yAx = Math.cos(angle + Math.PI / 2) * scale; // Y axis 90 deg CW from x axis
const yAy = Math.sin(angle + Math.PI / 2) * scale;
matrix[0] = xAx; // x part of x axis
matrix[1] = xAy; // y part of x axis
matrix[2] = yAx; // x part of y axis
matrix[3] = yAy; // y part of y axis
matrix[4] = 0; // origin x
matrix[5] = 0; // origin y
When you transform a coordinate x
, y
to tx
, ty
...
const x = ?
const y = ?
var tx, ty;
...you first move it alone the x axis...
tx = x * matrix[0]
ty = x * matrix[1]
... which scales it along the x axis at the same time. Then move and scale along the y axis.
tx += y * matrix[2]
ty += y * matrix[3]
Then move to the origin
tx += matrix[4]
ty += matrix[5]
This transformation moves a coordinate from local space to world space (or in 2D world space is often called the view)
When you rotate a shape you need to pick a point around which you want to rotate it, for example the center, or at one corner.
To do that you define the shape relative to the rotation point (in the shape's local space). If for example you want to rotate the box around the center you define top left and bottom right points to be equal distance from zero eg [-100, -50]
, [100, 50]
To rotate at a corner you position the box relative to that corner. eg top left the box is [0, 0]
, [200, 100]
You position the shape in world space, by setting the origin of the matrix (where on the canvas the rotation center will be)
The above matrix calculations can be simplified if we know that the scale is uniform (x and y axis scale the same amount), and that the x, and y axis are always 90 degree from each other.
The example uses an array to hold the matrix, the functions
transformPoint
applies the matrix to a pointsetOrigin
sets the transform origin (where on canvas the rotation point is)setRotation
sets the directions of the x and y axissetScale
not used in example. Sets the scale of the transform. NOTE must call setScale
after setRotation
in this examplesetTransform
not used in example. Does the above 3 in one callroundRect
draws the shape, it is given the top left and bottom right coordinates of the box in local space. It constrains the corner radius to the min size that will fit and still maintain (near as beziers are never round) round cornersThere are two boxes to demonstrate changing the center of rotation. One rotates about its center the other around the top left corner.
A third box (red) demonstrates that there is no reason to manually transform the box, that using the 2D API transformations is identical but far simpler and a lot quicker
requestAnimationFrame(update);
const ctx = canvas.getContext("2d");
const matrix = [1,0,0,1,0,0];
function transfromPoint(x, y) {
const m = matrix;
return [x * m[0] + y * m[2] + m[4], x * m[1] + y * m[3] + m[5]];
}
function setOrigin(x, y) {
matrix[4] = x;
matrix[5] = y;
}
function setRotation(angle) {
const ax = Math.cos(angle);
const ay = Math.sin(angle);
matrix[0] = ax;
matrix[1] = ay;
matrix[2] = -ay;
matrix[3] = ax;
}
function setScale(scale) {
matrix[0] *= scale;
matrix[1] *= scale;
matrix[2] *= scale;
matrix[3] *= scale;
}
function setTransform(ox, oy, rot, scale) {
const ax = Math.cos(rot) * scale;
const ay = Math.sin(rot) * scale;
matrix[0] = ax;
matrix[1] = ay;
matrix[2] = -ay;
matrix[3] = ax;
matrix[4] = ox;
matrix[5] = oy;
}
function roundRect(x1, y1, x2, y2, r, color = "#000", lineWidth = 2) {
ctx.strokeStyle = color;
ctx.lineWidth = lineWidth;
const min = Math.min(Math.abs(x1 - x2), Math.abs(y1 - y2));
r = r > min ? min / 2 : r;
ctx.beginPath();
ctx.moveTo(...transfromPoint(x2 - r, y1));
ctx.quadraticCurveTo(...transfromPoint(x2, y1), ...transfromPoint(x2, y1 + r));
ctx.lineTo(...transfromPoint(x2, y2 - r));
ctx.quadraticCurveTo(...transfromPoint(x2, y2), ...transfromPoint(x2 - r, y2));
ctx.lineTo(...transfromPoint(x1 + r, y2));
ctx.quadraticCurveTo(...transfromPoint(x1, y2), ...transfromPoint(x1 , y2 - r));
ctx.lineTo(...transfromPoint(x1, y1 + r));
ctx.quadraticCurveTo(...transfromPoint(x1, y1), ...transfromPoint(x1 + r, y1));
ctx.closePath();
ctx.stroke();
}
function roundRectAPITransform(x1, y1, x2, y2, r, color = "#F00", lineWidth = 2) {
ctx.strokeStyle = color;
ctx.lineWidth = lineWidth;
const min = Math.min(Math.abs(x1 - x2), Math.abs(y1 - y2));
r = r > min ? min / 2 : r;
ctx.beginPath();
ctx.moveTo(x2 - r, y1);
ctx.quadraticCurveTo(x2, y1, x2, y1 + r);
ctx.lineTo(x2, y2 - r);
ctx.quadraticCurveTo(x2, y2, x2 - r, y2);
ctx.lineTo(x1 + r, y2);
ctx.quadraticCurveTo(x1, y2, x1 , y2 - r);
ctx.lineTo(x1, y1 + r);
ctx.quadraticCurveTo(x1, y1, x1 + r, y1);
ctx.closePath();
ctx.stroke();
}
function update(time) {
ctx.clearRect(0,0,ctx.canvas.width,ctx.canvas.height);
// around center
setOrigin(100, 100);
setRotation(time * Math.PI / 2000); // one rotation every 4 seconds
roundRect(-60, -35, 60, 35, 15);
// around top right corner
setOrigin(300, 100);
setRotation(-time * Math.PI / 2000); // one rotation every 4 seconds
roundRect(-60, 0, 0, 35, 5);
// red box using API
ctx.setTransform(1,0,0,1,200,100);
ctx.rotate(-time * Math.PI / 4000) // once every 8 seconds;
roundRectAPITransform(-30, -15, 30, 15, 12);
ctx.setTransform(1,0,0,1,0,0); // restore default
requestAnimationFrame(update);
}
<canvas id="canvas" width="400" height="200"></canvas>
Re comments
You don't want to rotate a rectangle, you already have it rotated.
The following function adds rounded corners to a rotate rectangle
// MUST BE RECTANGULAR!!
function roundRectangle(x1, y1, x2, y2, x3, y3, x4, y4, r, color) {
// get top and left edge vectors and lengths
var tx = x2 - x1;
var ty = y2 - y1;
const td = (tx * tx + ty * ty) ** 0.5;
var lx = x3 - x1;
var ly = y3 - y1;
const ld = (lx * lx + ly * ly) ** 0.5;
// Constrain corner radius
const min = Math.min(td, ld) / 2;
r = r > min ? min : r;
// Normalize vectors to length of corner radius
tx *= r / td;
ty *= r / td;
lx *= r / ld;
ly *= r / ld;
// draw rotated retangle
ctx.fillStyle = color;
ctx.beginPath();
ctx.lineTo(x2 - tx, y2 - ty);
ctx.quadraticCurveTo(x2, y2, x2 + lx, y2 + ly);
ctx.lineTo(x4 - lx, y4 - ly);
ctx.quadraticCurveTo(x4, y4, x4 - tx, y4 - ty);
ctx.lineTo(x3 + tx, y3 + ty);
ctx.quadraticCurveTo(x3, y3, x3 - lx, y3 - ly);
ctx.lineTo(x1 + lx, y1 + ly);
ctx.quadraticCurveTo(x1, y1, x1 + tx, y1 + ty);
ctx.fill();
}
Upvotes: 2