I have a canvas
where I can draw with the mouse.
At some point I can rotate and scale the canvas container.
What I would like is to be able to still draw, but the problem that I have right now is that the mouse coordinates are rotated and scaled.
What would be the right way to unrotate and unscale the mouse coordinates so that the new drawings are rendered normally from the user point of view?
I've tried switching X
coordinates with Y
, but can't get the maths right.
CODE & FIDDLE
HTML
<div id="canvasDiv">
<canvas id="canvas"></canvas>
</div>
<button>Rotate & Scale</button>
CSS
div
{
outline: 1px solid red;
text-align: center;
width: 250px;
height: 250px;
position: relative;
}
canvas
{
outline: 1px solid blue;
width: 250px;
height: 250px;
bottom: 0;
left: 0;
position: absolute;
right: 0;
top: 0;
z-index: 5;
}
.transformed
{
transform: rotate(90deg) scale(1.4);
margin-top: 75px;
margin-left: 75px;
}
JS
isMouseDown = false;
canvas_offset = {left: 0, top: 0};
mouse = {x1: 0, y1: 0, x2: 0, y2: 0};
ppts = [];
canvas = $('#canvas').get(0);
ctx = canvas.getContext('2d');
canvas_container = $('#canvasDiv').get(0);
canvas_container_style = getComputedStyle(canvas_container);
canvas.width = parseInt(canvas_container_style.getPropertyValue('width'));
canvas.height = parseInt(canvas_container_style.getPropertyValue('height'));
var offset = $('#canvas').offset();
canvas_offset.left = offset.left;
canvas_offset.top = offset.top;
// Creating a tmp canvas
tmp_canvas = document.createElement('canvas');
tmp_ctx = tmp_canvas.getContext('2d');
tmp_canvas.id = 'tmp_canvas';
tmp_canvas.width = canvas.width;
tmp_canvas.height = canvas.height;
tmp_canvas.area = tmp_canvas.getBoundingClientRect();
canvas_container.appendChild(tmp_canvas);
$(document).on("mousedown", tmp_canvas, function(e)
{
mouse.x1 = parseInt(e.clientX - tmp_canvas.area.left);
mouse.y1 = parseInt((e.clientY - tmp_canvas.area.top - canvas_offset.top) + $(document).scrollTop());
ppts.push({
x: mouse.x1,
y: mouse.y1,
size: 1,
color: "#000000"
});
isMouseDown = true;
});
$(document).on("mouseup", tmp_canvas, function(e)
{
isMouseDown = false;
ctx.drawImage(tmp_canvas, 0, 0);
tmp_ctx.clearRect(0, 0, tmp_canvas.width, tmp_canvas.height);
ppts = [];
});
$(document).on("mousemove", tmp_canvas, function(e)
{
mouse.x2 = parseInt(e.clientX - tmp_canvas.area.left);
mouse.y2 = parseInt((e.clientY - tmp_canvas.area.top - canvas_offset.top) + $(document).scrollTop());
if(isMouseDown)
{
onPaint();
}
});
var onPaint = function()
{
// Saving all the points in an array
ppts.push({
x: mouse.x2,
y: mouse.y2,
size: 1,
color: "#000000"
});
if(ppts.length < 3)
{
var b = ppts[0];
tmp_ctx.beginPath();
//ctx.moveTo(b.x, b.y);
//ctx.lineTo(b.x+50, b.y+50);
tmp_ctx.arc(b.x, b.y, tmp_ctx.lineWidth / 2, 0, Math.PI * 2, !0);
tmp_ctx.fill();
tmp_ctx.closePath();
return;
}
// Tmp canvas is always cleared up before drawing.
tmp_ctx.clearRect(0, 0, tmp_canvas.width, tmp_canvas.height);
tmp_ctx.beginPath();
tmp_ctx.moveTo(ppts[0].x, ppts[0].y);
for(var i = 1; i < ppts.length - 2; i++)
{
var c = (ppts[i].x + ppts[i + 1].x) / 2;
var d = (ppts[i].y + ppts[i + 1].y) / 2;
tmp_ctx.lineWidth = ppts[i].size;
tmp_ctx.strokeStyle = ppts[i].color;
tmp_ctx.quadraticCurveTo(ppts[i].x, ppts[i].y, c, d);
}
// For the last 2 points
tmp_ctx.quadraticCurveTo(
ppts[i].x,
ppts[i].y,
ppts[i + 1].x,
ppts[i + 1].y
);
tmp_ctx.stroke();
};
$("button").click(function()
{
$("div").addClass("transformed");
});
FIDDLE
https://jsfiddle.net/h0cuycp4/
Finally I've got this working!!
All credits goes to Blindman67, I've modified his code to adapt it to my needs.
HTML
<div id="main"></div>
<br>
<span id="screen" style="border: 2px solid red;"></span>
<span id="world" style="border: 2px solid blue;"></span>
<button id="btnRotate">ROTATE!</button>
JS
var canvas = null;
var ctx = null;
var gridStart = null;
var gridEnd = null;
var gridStepMajor = null;
var gridStepMinor = null;
var minorCol = null;
var majorCol = null;
var minorWidth = null;
var majorWidth = null;
var scale = 1;
var rotation = 45;
var painting = false,
lastX = 0,
lastY = 0,
lineThickness = 1;
var matrix = [1, 0, 0, 1, 0, 0]; // normal matrix
var invMatrix = [1, 0, 0, 1]; // inverse matrix
function createMatrix(x, y, scale, rotation)
{
rotation = rotation * (Math.PI / 180);
var m = matrix; // just to make it easier to type and read
var im = invMatrix; // just to make it easier to type and read
// create the scale and rotation part of the matrix
m[3] = m[0] = Math.cos(rotation) * scale;
m[2] = -(m[1] = Math.sin(rotation) * scale);
// translation
m[4] = x;
m[5] = y;
// calculate the inverse transformation
// first get the cross product of x axis and y axis
cross = m[0] * m[3] - m[1] * m[2];
// now get the inverted axies
im[0] = m[3] / cross;
im[1] = -m[1] / cross;
im[2] = -m[2] / cross;
im[3] = m[0] / cross;
}
// function to transform to world space
function toWorld(x,y)
{
var xx, yy, m;
m = invMatrix;
xx = x - matrix[4];
yy = y - matrix[5];
return{
x: parseInt(xx * m[0] + yy * m[2], 10) ,
y: parseInt(xx * m[1] + yy * m[3], 10)
}
}
//----------------------------------------------------------------------------
var mouseWorldPos = toWorld(0, 0);
function draw()
{
gridStart = 0;
gridEnd = canvas.width;
gridStepMajor = canvas.width / 10;
gridStepMinor = canvas.width / 20;
minorCol = "#999";
majorCol = "#000";
minorWidth = 1;
majorWidth = 3;
ctx.lineWidth = 2;
ctx.beginPath();
ctx.strokeStyle = majorCol ;
ctx.lineWidth = majorWidth;
for(i = gridStart; i <= gridEnd; i+= gridStepMajor)
{
ctx.moveTo(gridStart, i);
ctx.lineTo(gridEnd, i);
ctx.moveTo(i, gridStart);
ctx.lineTo(i, gridEnd);
}
ctx.stroke();
ctx.strokeStyle = minorCol;
ctx.lineWidth = minorWidth;
for(i = gridStart+gridStepMinor; i < gridEnd; i+= gridStepMinor)
{
ctx.moveTo(gridStart, i);
ctx.lineTo(gridEnd, i);
ctx.moveTo(i, gridStart);
ctx.lineTo(i, gridEnd);
}
ctx.stroke();
ctx.fillStyle = "red";
ctx.strokeStyle = "red";
ctx.lineWidth = 4;
ctx.beginPath();
ctx.arc(50, 50, 6, 0, Math.PI*2);
ctx.fill();
}
function demo()
{
canvas = document.createElement("canvas");
canvas.width = 500;
canvas.height = 300;
canvas.ctx = canvas.getContext("2d");
ctx = canvas.ctx;
$("#main").append(canvas);
draw();
}
var timer = 0;
var timerStep = 0.5;
var seconds = 15;
function rotate()
{
timer += timerStep;
var cw = canvas.width / 2;
var ch = canvas.height / 2;
ctx.setTransform(1, 0, 0, 1, 0, 0); // reset the transform so we can clear
ctx.clearRect(0, 0, canvas.width, canvas.height); // clear the canvas
createMatrix(cw, ch -50, scale, timer);
var m = matrix;
ctx.setTransform(m[0], m[1], m[2], m[3], m[4], m[5]);
draw();
if(timer <= rotation )
{
requestAnimationFrame(rotate);
}
}
$(document).ready(function()
{
demo();
$(canvas).mousedown(function(e)
{
painting = true;
ctx.fillStyle = "#0000FF";
mouseWorldPos = toWorld(e.pageX, e.pageY);
lastX = mouseWorldPos.x;
lastY = mouseWorldPos.y;
});
$(canvas).mousemove(function(e)
{
$("#screen").text("X: " + e.pageX + " - Y:" + e.pageY);
mouseWorldPos = toWorld(e.pageX, e.pageY);
$("#world").text("X: " + mouseWorldPos.x + " - Y:" + mouseWorldPos.y);
if (painting)
{
mouseX = mouseWorldPos.x;
mouseY = mouseWorldPos.y;
// find all points between
var x1 = mouseX,
x2 = lastX,
y1 = mouseY,
y2 = lastY;
var steep = (Math.abs(y2 - y1) > Math.abs(x2 - x1));
if (steep){
var x = x1;
x1 = y1;
y1 = x;
var y = y2;
y2 = x2;
x2 = y;
}
if (x1 > x2) {
var x = x1;
x1 = x2;
x2 = x;
var y = y1;
y1 = y2;
y2 = y;
}
var dx = x2 - x1,
dy = Math.abs(y2 - y1),
error = 0,
de = dy / dx,
yStep = -1,
y = y1;
if (y1 < y2) {
yStep = 1;
}
lineThickness = 5 - Math.sqrt((x2 - x1) *(x2-x1) + (y2 - y1) * (y2-y1))/10;
if(lineThickness < 1){
lineThickness = 1;
}
for (var x = x1; x < x2; x++) {
if (steep) {
ctx.fillRect(y, x, lineThickness , lineThickness );
} else {
ctx.fillRect(x, y, lineThickness , lineThickness );
}
error += de;
if (error >= 0.5) {
y += yStep;
error -= 1.0;
}
}
lastX = mouseX;
lastY = mouseY;
}
});
$(canvas).mouseup(function(e)
{
painting = false;
});
$("#btnRotate").click(function()
{
rotate();
});
});
CSS
#main{outline: 1px solid orange; display: inline-block; position: relative;}
span{position: relative;}
DEMO
https://jsfiddle.net/mgf8uz7s/