Reputation: 165
I need to apply several matrix transformations before drawing a shape, however (if on somewhere) I use rotate()
the coordinates are inverted and/or reversed and cannot continue without knowing if the matrix was previously rotated.
How can solve this problem?
Example:
<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>
Can be this Translation after rotation the solution?
Upvotes: 0
Views: 631
Reputation: 165
OK finally, i solved the problem by rotating the translation point before applying it. This function does the trick:
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};
}
rotating the translation point using the inverted angle I obtain the corrected translation
helperRotatePoint(translation_point, -rotation_angle);
working code:
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>
Upvotes: 0
Reputation: 127
It looks like you aren't resetting the canvas matrix each time you make a new transformation.
The Canvas API has the save()
and restore()
methods. Canvas states are stored on a stack. Every time the save()
method is called, the current drawing state is pushed onto the stack. A drawing state consists of transformations that have been applied along with the attributes of things like the fillStyle
. When you call restore()
, the previous settings are restored.
// ...
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);
Check out this link for more information on the save
and restore
methods.
Upvotes: 1