简体   繁体   中英

Draw rotated rounded corner rect in canvas without using canvas.rotate()

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.

Transforming coordinates

To rotate the box you need to apply a rotation matrix on each of the points.

The matrix

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

The transformation

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)

Local space

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)

Example

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 point
  • setOrigin sets the transform origin (where on canvas the rotation point is)
  • setRotation sets the directions of the x and y axis
  • setScale not used in example. Sets the scale of the transform. NOTE must call setScale after setRotation in this example
  • setTransform not used in example. Does the above 3 in one call
  • roundRect 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 corners

There 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>

Update

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();
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM