HTML5 Canvas:我如何处理旋转后的倒置 translate()?

HTML5 Canvas: how i can deal with inverted translate() after rotation?

我需要在绘制形状之前应用几个矩阵变换,但是(如果在某处)我使用 rotate() 坐标被反转 and/or 反转并且在不知道矩阵之前是否已经存在的情况下无法继续旋转。
如何解决这个问题?

示例:

<canvas width="300" height="300"></canvas>
<script>
let canvas = document.querySelector("canvas");
let ctx = canvas.getContext("2d");

ctx.fillStyle = "silver";
ctx.fillRect(0, 0, canvas.width, canvas.height);

ctx.strokeStyle = "black";
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(0, canvas.height/2);
ctx.lineTo(canvas.width, canvas.height/2);
ctx.stroke(); 

ctx.beginPath();
ctx.moveTo(canvas.width/2, 0);
ctx.lineTo(canvas.width/2, canvas.height);
ctx.stroke();

ctx.translate(150, 150);
ctx.rotate(-90 * 0.017453292519943295);
ctx.translate(-150, -150);

// move the red rectangle 100px to the left (top-left)
// but instead is moved on y axis (right-bottom)
ctx.translate(-100, 0);

// more matrix transformations
// ....
// ....

// now finally draw the shape
ctx.fillStyle = "red";
ctx.fillRect(150, 150, 100, 50);
</script>

这个Translation after rotation可以解决吗?

您似乎没有在每次进行新转换时都重置 canvas 矩阵。

Canvas API 有 save()restore() 方法。 Canvas 状态存储在堆栈中。每次调用 save() 方法时,都会将当前绘图状态压入堆栈。绘图状态由已应用的变换以及 fillStyle 等事物的属性组成。当您调用 restore() 时,将恢复之前的设置。

    // ...

    ctx.save(); // save the current canvas state

    ctx.translate(150, 150);
    ctx.rotate(-90 * 0.017453292519943295);
    ctx.translate(-150, -150);

    ctx.restore(); // restore the last saved state

    // now the rectangle should move the correct direction
    ctx.translate(-100, 0);


查看此 link 了解有关 saverestore 方法的更多信息。

OK 最后,我通过在应用之前旋转平移点解决了这个问题。这个函数可以解决问题:

function helperRotatePoint(point, angle) {
  let s = Math.sin(angle);
  let c = Math.cos(angle);
  return { x: point.x * c - point.y * s, y: point.x * s + point.y * c};
}

使用倒角旋转平移点我得到了修正后的平移 helperRotatePoint(translation_point, -rotation_angle);

工作代码:

let canvas = document.querySelector("canvas");

// proper size on HiDPI displays
canvas.style.width = canvas.width;
canvas.style.height = canvas.height;
canvas.width = Math.floor(canvas.width * window.devicePixelRatio);
canvas.height = Math.floor(canvas.height * window.devicePixelRatio);

let ctx = canvas.getContext("2d");
ctx.scale(window.devicePixelRatio, window.devicePixelRatio);
ctx.fillStyle = "whitesmoke";
ctx.fillRect(0, 0, canvas.width, canvas.height);

class UIElement {

  constructor(x, y, width, height, color) {
    // PoC
    this.draw_pos = {x, y};
    this.draw_size = {width, height};

    this.color = color;

    this.rotate = 0;
    this.scale = {x: 1, y: 1};
    this.translate = {x: 0, y: 0};
    this.skew = {x: 0, y: 0};

    this.childs = [];
  }

  addChild(uielement) {
    this.childs.push(uielement);
  }

  helperRotatePoint(point, angle) {
      let s = Math.sin(angle);
      let c = Math.cos(angle);
      return {
        x: point.x * c - point.y * s,
        y: point.x * s + point.y * c
      };
    }

  draw(cnvs_ctx, parent_x, parent_y) {
    // backup current state
    cnvs_ctx.save();

    let elements_drawn = 1;// "this" UIElement

    // step 1: calc absolute coordinates
    let absolute_x = parent_x + this.draw_pos.x;
    let absolute_y = parent_y + this.draw_pos.y;

    // step 2: apply all transforms
    if (this.rotate != 0) {
      cnvs_ctx.translate(absolute_x, absolute_y)
      cnvs_ctx.rotate(this.rotate);
      cnvs_ctx.translate(-absolute_x, -absolute_y);
      
      // rotate translate point before continue
      let tmp = this.helperRotatePoint(this.translate, -this.rotate);
      
      // apply rotated translate
      cnvs_ctx.translate(tmp.x, tmp.y);
    } else {
        cnvs_ctx.translate(this.translate.x, this.translate.y);
    }
    cnvs_ctx.scale(this.scale.x, this.scale.y);
    cnvs_ctx.transform(1, this.skew.y, this.skew.x, 1, 0, 0);

    // step 3: self draw (aka parent element)
    cnvs_ctx.fillStyle = this.color;
    cnvs_ctx.fillRect(absolute_x, absolute_y, this.draw_size.width, this.draw_size.height);

    // step 4: draw childs elements
    for (let i = 0; i < this.childs.length ; i++) {
        elements_drawn += this.childs[i].draw(
            cnvs_ctx, absolute_x, absolute_y
      );
    }

    // done, restore state
    cnvs_ctx.restore();
    return elements_drawn;
  }

}



// spawn some ui elements
var ui_panel = new UIElement(120, 50, 240, 140, "#9b9a9e");
var ui_textlabel = new UIElement(10, 10, 130, 18, "#FFF");
var ui_image = new UIElement(165, 25, 90, 60, "#ea9e22");
var ui_textdesc = new UIElement(17, 46, 117, 56, "#ff2100");
var ui_icon = new UIElement(5, 5, 10, 10, "#800000");

ui_panel.addChild(ui_textlabel);
ui_panel.addChild(ui_image);
ui_panel.addChild(ui_textdesc);
ui_textdesc.addChild(ui_icon);

// add some matrix transformations
ui_textdesc.skew.x = -0.13;
ui_textdesc.translate.x = 13;
ui_image.rotate = -90 * 0.017453292519943295;
ui_image.translate.y = ui_image.draw_size.width;
ui_panel.rotate = 15 * 0.017453292519943295;
ui_panel.translate.x = -84;
ui_panel.translate.y = -50;


// all ui element elements
ui_panel.draw(ctx, 0, 0);
<canvas width="480" height="360"></canvas>