Validate if mouse position is within rotated rectangle in HTML5 Canvas - javascript

I am trying to use the HTML5 canvas features. I set up a rectangle on the canvas and it is rotated. It is not returning back a true when I click inside the rotated rectangle. Perhaps I am just tripping up on understanding the solution. What am I doing incorrectly? I have the code below.
I am using the solution referenced in:
Mouse position within rotated rectangle in HTML5 Canvas
<?php
$canvas_width=800;
$canvas_height=1336;
echo <<<_END
<canvas id="myCanvas" width=$canvas_width height=$canvas_height style="border:1px solid #d3d3d3;">
Your browser does not support the HTML5 canvas tag.
</canvas>
<script>
//Get the canvas element by using the getElementById method.
canvas = document.getElementById('myCanvas');
HEIGHT = canvas.height;
WIDTH = canvas.width;
CANVAS_LEFT = 9;
CANVAS_RIGHT = canvas.width+CANVAS_LEFT;
CANVAS_TOP = 9;
CANVAS_BOTTOM = canvas.height+CANVAS_TOP;
lastMouseX = 0; // The last seen mouse X coordinate
lastMouseY = 0; // the last seen mouse Y coordinate
rectangleDrawAndRotated=false;
mouseX =0;
mouseY=0;
offsetX = 0;
offsetY = 0;
originX=0;
originY=0;
width=0;
height=0;
//Get a 2D drawing context for the canvas.
ctx = canvas.getContext('2d');
myimage=new Image()
myimage.src='/Farm_planner/images/The_Farm_at_Dover_Vineyards_Google_Maps.png'
myimage.onload=function()
{
ctx.drawImage(myimage,1,1,800,1336)
}
// When true, moving the mouse draws on the canvas
isDrawing = false;
x = 0;
y = 0;
// event.offsetX, event.offsetY gives the (x,y) offset from the edge of the canvas.
// Add the event listeners for mousedown, mousemove, and mouseup
canvas.addEventListener('mousedown', e => {
x = e.offsetX;
y = e.offsetY;
isDrawing = true;
console.log("mouse down",x,y,width,height);
if (rectangleDrawAndRotated ===true){
originX = x + width/2;
originY = y + height/2;
getMouse(e);
// get the locations of the mouse and assign to mouseX and mouseY
clickState=false;
console.log(clickState,mouseX, mouseY,x,y);
if (clickedOnRectangle(x,y)) clickState=true;
console.log(clickState,mouseX, mouseY,x,y);
}
});
canvas.addEventListener('mousemove', e => {
if (isDrawing === true) {
// drawLine(ctx, x, y, e.offsetX, e.offsetY);
// x = e.offsetX;
// y = e.offsetY;
}
});
window.addEventListener('mouseup', e => {
if (isDrawing === true) {
drawLine(ctx, x, y, e.offsetX, e.offsetY);
isDrawing = false;
mouseX =0;
mouseY=0;
rectangleDrawAndRotated=true;
console.log("mouse up",x,y,width,height);
}
});
function drawLine(ctx, x1, y1, x2, y2) {
// ctx.beginPath();
// ctx.strokeStyle = 'black';
ctx.lineWidth = 3;
// ctx.moveTo(x1, y1);
// ctx.lineTo(x2, y2);
// ctx.stroke();
// ctx.closePath();
width=x2-x1;
height=y2-y1;
xCoord=x1;
yCoord=y1;
drawRectangle(x1,y1,width,height) ;
rotateDrawing(x1,y1,width,height);
lastMouseX = 0; // The last seen mouse X coordinate
lastMouseY = 0; // the last seen mouse Y coordinate
}
function drawRectangle(xCoord,yCoord,width,height) {
// ctx.strokeRect(xCoord,yCoord,width,height);
ctx.rect(xCoord,yCoord,width,height);
originX = xCoord + width/2;
originY = yCoord + height/2;
r=45*(Math.PI / 180);
}
function clickedOnRectangle(x,y) {
// dtermine if the user clicked on the area of the rectangle that has been rotated
// Our origin of rotation is the center of the rectangle
// Our rectangle has its upper-left corner defined by x,y, its width
// defined in w, height in h, and rotation(in radians) in r.
// translate mouse point values to origin
dx = mouseX - originX;
dy = mouseY - originY;
// distance between the point and the center of the rectangle
var h1 = Math.sqrt(dx*dx + dy*dy);
var currA = Math.atan2(dy,dx);
// Angle of point rotated around origin of rectangle in opposition
var newA = currA - r;
// New position of mouse point when rotated
var x2 = Math.cos(newA) * h1;
var y2 = Math.sin(newA) * h1;
console.log(x,y,width,height,mouseX,mouseY,originX,originY,dx,dy,h1,currA,r,newA,x2,y2)
// Check relative to center of rectangle
if (x2 > -0.5 * width && x2 < 0.5 * width && y2 > -0.5 * height && y2 < 0.5 * height){
console.log('true');
return true;
}
}
// Sets mouseX and mouseY variables taking into account padding and borders
function getMouse(e) {
var element = canvas;
var offsetX = 0;
var offsetY = 0;
// Calculate offsets
if (element.offsetParent) {
do {
offsetX += element.offsetLeft;
offsetY += element.offsetTop;
} while ((element = element.offsetParent));
}
// Calculate the mouse location
mouseX = e.pageX - offsetX;
mouseY = e.pageY - offsetY;
// Calculate the change in mouse position for the last
// time getMouse was called
changeInX = mouseX - lastMouseX;
changeInY = mouseY - lastMouseY;
// Store the current mouseX and mouseY positions
lastMouseX = mouseX;
lastMouseY = mouseY;
}
function rotateDrawing(xCoord,yCoord,width,height)
{
ctx.setTransform(1,0,0,1,0,0);
ctx.translate(xCoord+.5*width,yCoord+.5*height );
r=45*(Math.PI / 180); // 45 degrees as radian
ctx.rotate(r); //
ctx.fillStyle = "red";
ctx.fillRect(-.5*width, -.5*height, width, height);
console.log(xCoord,yCoord,width,height)
}
</script>
_END;

Yes, you are tripping on the solution, treat your rectangle as a polygon and you don't have to worry about doing rotation, also you can have more complex shapes than rectangles.
I'm using the ray-casting algorithm:
https://github.com/substack/point-in-polygon/blob/master/index.js
With that, all we need to do is check if the mouse is inside the polygon, that is all.
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext("2d");
const rect = canvas.getBoundingClientRect();
const poly = [[89, 9], [13, 19], [19, 56], [98, 36], [89, 9]]
function draw(p) {
p.map(x => ctx.lineTo(x[0], x[1]));
ctx.stroke();
}
function inside(p, vs) {
var inside = false;
for (var i = 0, j = vs.length - 1; i < vs.length; j = i++) {
var xi = vs[i][0], yi = vs[i][1];
var xj = vs[j][0], yj = vs[j][1];
var intersect = ((yi > p[1]) != (yj > p[1])) && (p[0] < (xj - xi) * (p[1] - yi) / (yj - yi) + xi);
if (intersect) inside = !inside;
}
return inside;
};
draw(poly)
canvas.addEventListener('mousemove', function(evt) {
ctx.clearRect(100, 0, canvas.width, canvas.height);
let x = inside([ evt.clientX - rect.left, evt.clientY - rect.top ], poly)
ctx.fillText(x, 110, 20);
}, false);
<canvas id="canvas"></canvas>
I did it on mousemove so you can see the change right away...
but same can be applied to any event you like

Related

How to get coordinates of html canvas tag

I want to get coordinates of the painted area of an image placed in an HTML canvas tag and send it to the database. And populate it on the same area of that image in another page. And how can I reset or clear the painted area by clicking the reset button?
JSfiddle example
var canvas = document.getElementById("canvas");
var img = document.getElementById("imagearea"),
ctx = canvas.getContext("2d"),
painting = false,
lastX = 0,
lastY = 0,
lineThickness = 1;
canvas.width = canvas.height = 600;
ctx.fillRect(0, 0, 600, 600);
ctx.drawImage(img, 10, 10);
canvas.onmousedown = function(e) {
painting = true;
ctx.fillStyle = "#ff0000";
lastX = e.pageX - this.offsetLeft;
lastY = e.pageY - this.offsetTop;
};
canvas.onmouseup = function(e){
painting = false;
}
canvas.onmousemove = function(e) {
if (painting) {
mouseX = e.pageX - this.offsetLeft;
mouseY = e.pageY - this.offsetTop;
// 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 id="canvas">
</canvas>
<img id="imagearea" src="https://media.istockphoto.com/photos/green-apple-with-leaf-and-cut-isolated-on-white-picture-id1141004606?k=6&m=1141004606&s=170667a&w=0&h=zwbN4lLc7MFb6f_aZ4npNL3i4Tgde-yINlYTztlI1QQ=" style="display: none;" />
<button> Reset </button>
To achieve what you're looking for, you need to store the min(x, y) coords for the (top, left) position and the max(x, y) coords for the (bottom, right) position, and you can't get the image on the same canvas if you are looking to remove the drawing on the area. use an HTML element with absolute position relative to the canvas for the area frame, attach an event to crop, and display the target area another one to remove it.
Here is a working example with a lot of comments, it should be clear. Click on the area to display the preview on the "x" to remove it with the drawing, it can handle multiple areas.
const source = "https://media.istockphoto.com/photos/green-apple-with-leaf-and-cut-isolated-on-white-picture-id1141004606?k=6&m=1141004606&s=170667a&w=0&h=zwbN4lLc7MFb6f_aZ4npNL3i4Tgde-yINlYTztlI1QQ=";
const container = document.querySelector("#container");
const canvas = container.querySelector("canvas");
const ctx = canvas.getContext("2d");
const resetButton = document.querySelector("button");
let lastDrawnArea = [[Infinity, Infinity], [0, 0]];
let image;
let painting = false;
let lastX = 0;
let lastY = 0;
let lineThickness = 1;
init();
async function init() {
await loadDrawImage();
// Start Event Listening
canvas.onmousedown = function(e) {
painting = true;
ctx.fillStyle = "#ff0000";
lastX = e.pageX - this.offsetLeft;
lastY = e.pageY - this.offsetTop;
};
canvas.onmouseup = function(e){
painting = false;
// Set the drawing area
setDrawingArea();
}
canvas.onmousemove = function(e) {
if (painting) {
mouseX = e.pageX - this.offsetLeft;
mouseY = e.pageY - this.offsetTop;
// 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;
// Set The min, max coordinate of the current drawing
// to define the current drawing area
lastDrawnArea = [
[// Top left min([x, y]) coords
Math.min(lastDrawnArea[0][0], mouseX),
Math.min(lastDrawnArea[0][1], mouseY)
],
[// Bottom right max([x, y]) coords
Math.max(lastDrawnArea[1][0], mouseX),
Math.max(lastDrawnArea[1][1], mouseY)
]
]
}
}
}
async function loadDrawImage() {
image = new Image();
// Load the image
await new Promise(resolve => {
image.onload = resolve;
image.src = source;
});
const [width, height] = [image.naturalWidth, image.naturalHeight];
// Set the container and canvas size
container.style.width = `${width}px`;
container.style.height = `${height}px`;
canvas.width = width;
canvas.height = height;
// Set the container in the background
container.style.background = `url(${image.src})`;
}
function setDrawingArea(){
const [TOP_LEFT, BOTTOM_RIGHT, X, Y] = [0, 1, 0, 1];
const container = document.querySelector("#container");
const template = document.querySelector("#areaTemplate");
const area = template.content.firstElementChild.cloneNode(true);
// You should replace this with the lineThickness
const offset = 10;
// Get the area size
const width = lastDrawnArea[BOTTOM_RIGHT][X] - lastDrawnArea[TOP_LEFT][X];
const height = lastDrawnArea[BOTTOM_RIGHT][Y] - lastDrawnArea[TOP_LEFT][Y];
area.style.left = `${lastDrawnArea[TOP_LEFT][X] - offset}px`;
area.style.top = `${lastDrawnArea[TOP_LEFT][Y] - offset}px`;
area.style.width = `${width + (offset * 2)}px`;
area.style.height = `${height + (offset * 2)}px`;
// Draw the template
container.append(area);
// Add the events
area.onclick = previewArea; // Preveiw event
area.querySelector("b").onclick = removeArea; // Remove event
// Reset "lastDrawnArea" value
lastDrawnArea = [[Infinity, Infinity], [0, 0]];
}
function previewArea(e) {
const preview = document.querySelector("#preview");
const previewCanvas = preview.querySelector("canvas");
const previewCtx = previewCanvas.getContext("2d");
// Get the drawing area coords
const area = e.target;
const [x, y, width, height] = [
parseFloat(area.style.left),
parseFloat(area.style.top),
parseFloat(area.style.width),
parseFloat(area.style.height)
];
// Draw the preview area
previewCanvas.width = width;
previewCanvas.height = height;
previewCtx.drawImage(image,
x, y, width, height,
0, 0, width, height);
}
function removeArea(e) {
const area = e.target.parentElement;
const [x, y, width, height] = [
parseFloat(area.style.left),
parseFloat(area.style.top),
parseFloat(area.style.width),
parseFloat(area.style.height)
];
ctx.clearRect(x, y, width, height);
area.remove();
}
resetButton.onclick = () => {
ctx.clearRect(0, 0, canvas.width, canvas.height);
document.querySelectorAll('.area').forEach(el => el.remove());
}
body {
display: flex;
}
#container {
position: relative;
}
.area {
position: absolute;
border: 2px solid #333;
color: #333;
cursor: pointer;
}
.area b {
position: absolute;
right: 0;
top: 0;
transform: translate(100%, -100%);
color: red;
cursor: pointer;
}
<div id="container">
<canvas></canvas>
</div>
<div id="preview">
<canvas></canvas>
</div>
<template id="areaTemplate">
<div class="area">
<b>x</b>
</div>
</template>
<button> Reset All </button>
There is one problem with this example if two areas overlap the drawing will be removed at the intersection of both areas, to overcome this issue you need to keep each drawing on its own canvas (... a lot of work).
Last but not least, you can achieve the same result with ease and have better control over the canvas and its elements if you use a library like fabric js, take a look at this example of freedrawing, you will get the drawing coordinates for free, and you can still have everything on the same canvas (the image or whatever you need to add to canvas and all the drawing with no overlap ...), the initial learning curve may take some time but by the end of the day, you'll get a better understanding of HTML canvas overall.
Side note: the image you're using has a cross-origin limitation, you should use images from the same domain or from domains that allow cross-origin.
EDIT : Update with a working demo based on your fiddle
You may have to adapt this function to also include the thikness of the drawed lines (they may appear outside of the registered area).
But like this, you have te position and the size of your drawed area.
You can now do a ROI on it if you want.
You can track the area drawed with a function like this one:
var drawedArea = [0,0,0,0];
function drawedAreaTrack(x, y) {
// top left x
if (drawedArea[0] === 0) {
drawedArea[0] = x;
} else {
drawedArea[0] = Math.min(drawedArea[0], x);
}
// top left y
if (drawedArea[1] === 0) {
drawedArea[1] = y;
} else {
drawedArea[1] = Math.min(drawedArea[1], y);
}
// bottom right x
drawedArea[2] = Math.max(drawedArea[2], x);
// bottom right y
drawedArea[3] = Math.max(drawedArea[3], y);
console.log(drawedArea);
}
You could use those two point to get the total area drawed.
Here is a working example-> Fiddle :
http://jsfiddle.net/b90h6gaq/8/
The 'onmousemove' is doing all the drawing, I would save all you need to an array then on some event send to the database or draw to another canvas...
Here is a very simple example. I'm keeping the code minimal to convey my point that you already have all you need, all that is needed is to store it in a variable, You can bring back the lineThickness, the rest of the calculations and logic later, that should not be a problem.
var button = document.getElementById("btn");
var canvas = document.getElementById("canvas1");
var canvas2 = document.getElementById("canvas2");
var ctx = canvas.getContext("2d");
var ctx2 = canvas2.getContext("2d");
var painting = false;
var coordinates = [];
canvas.onmousedown = function(e) {painting = true;}
canvas.onmouseup = function(e) {painting = false;}
canvas.onmousemove = function(e) {
if (painting) {
x = e.pageX - this.offsetLeft;
y = e.pageY - this.offsetTop;
coordinates.push({x, y})
ctx.fillRect(x, y, 5, 5);
}
}
button.onmousedown = function(e) {
ctx.clearRect(0, 0, 300, 150);
coordinates.forEach(coord => {
ctx2.fillRect(coord.x, coord.y, 5, 5);
});
};
<canvas id="canvas1" width=300 height=150></canvas>
<button id="btn"> Reset </button>
<canvas id="canvas2" width=300 height=150></canvas>
Here we have a new event on the button click, I'm clearing the initial canvas and drawing all the coordinates to a second canvas, we could also be sending that data to server to be stored that should not be a problem.
I don’t know if this is quite what you were thinking, but if you simply want the same image to appear on another webpage, you could use ctx.getImageData() to copy the drawing from the canvas as an object, convert it to json, then send it to the database. Then on the other end, turn it back into an object and use ctx.putImageData() to place it back on the new canvas.

How to bound image pan when zooming (HTML Canvas)

I'm trying to limit boundaries and I'm running into issues. I'm upscaling an image from another canvas and then implementing zoom and pan. My issue (code below) is with limiting/capping the offsetx/y so that you never see the whitespace; only parts of the image.
Pardon the mess! Any help is appreciated! :P
var zoomIntensity = 0.2;
var canvas = document.getElementById("canvas");
var canvas2 = document.getElementById("canvas2");
var context = canvas.getContext("2d");
var context2 = canvas2.getContext("2d");
var width = 200;
var height = 200;
var scale = 1;
var originx = 0;
var originy = 0;
var offset = {x:0, y:0};
//fill smaller canvas with random pixels
for(var x = 0; x < 100; x++)
for(var y = 0; y < 100; y++)
{
var rando = function(){return Math.floor(Math.random() * 9)};
var val = rando();
context2.fillStyle = "#" + val + val + val;
context2.fillRect(x,y,1,1);
}
//draw the larger canvas
function draw()
{
context.imageSmoothingEnabled = false;
// Clear screen to white.
context.fillStyle = "white";
context.fillRect(originx - offset.x, originy - offset.y, width/scale, height/scale);
context.drawImage(canvas2, 0,0, width, height);
}
// Draw loop at 60FPS.
setInterval(draw, 1000/60);
canvas.onmousewheel = function (event){
event.preventDefault();
// Get mouse offset.
var mousex = event.clientX - canvas.offsetLeft;
var mousey = event.clientY - canvas.offsetTop;
// Normalize wheel to +1 or -1.
var wheel = event.wheelDelta/120;
// Compute zoom factor.
var zoom = Math.exp(wheel*zoomIntensity);
// Translate so the visible origin is at the context's origin.
context.translate(originx - offset.x, originy - offset.y); //offset is panning
//make sure we don't zoom out further than normal scale
var resultingScale = scale * zoom;
if(resultingScale < 1)
zoom = 1/scale;
// Compute the new visible origin. Originally the mouse is at a
// distance mouse/scale from the corner, we want the point under
// the mouse to remain in the same place after the zoom, but this
// is at mouse/new_scale away from the corner. Therefore we need to
// shift the origin (coordinates of the corner) to account for this.
originx -= mousex/(scale*zoom) - mousex/scale;
originy -= mousey/(scale*zoom) - mousey/scale;
// Scale it (centered around the origin due to the trasnslate above).
context.scale(zoom, zoom);
// Offset the visible origin to it's proper position.
context.translate(-originx + offset.x, -originy + offset.y); //offset is panning
// Update scale and others.
scale *= zoom;
}
document.onkeydown = function (evt)
{
var offsetx = 0;
var offsety = 0;
switch(evt.which)
{
case 37: //left
offsetx = 1;
break;
case 38: //up
offsety = 1;
break;
case 39: //right
offsetx = -1
break;
case 40: //down
offsety = -1;
break;
}
offsetx /= scale;
offsety /= scale;
offset.x += offsetx;
offset.y += offsety;
context.translate(offsetx,offsety);
}
<canvas id="canvas" width="200" height="200"></canvas>
<canvas id="canvas2" width="100" height="100"></canvas>
Using transformation matrix to constrain a view
To constrain the position you need to transform the corner coordinates of the image to screen coordinates. As getting the transform is still not standard across browsers the demo below holds a copy of the transform.
The object view holds the canvas view. When you use the function view.setBounds(top,left,right,bottom); the view will be locked to that area (the image you are viewing 0,0,100,100)
The scale and position (origin) will be constrained to keep the bounds outside or one the edge of the canvas context set by view.setContext(context).
The function scaleAt(pos,amount); will scale at a specified pos (eg mouse position)
To set the transform use view.apply() this will update the view transform and set the context transform.
The are a few other functions that may prove handy see code.
Demo uses mouse click drag to pan and wheel to zoom.
Demo is a copy of the OP's example width modifications to answer question.
// use requestAnimationFrame when doing any form of animation via javascript
requestAnimationFrame(draw);
var zoomIntensity = 0.2;
var canvas = document.getElementById("canvas");
var canvas2 = document.getElementById("canvas2");
var context = canvas.getContext("2d");
var context2 = canvas2.getContext("2d");
var width = 200;
var height = 200;
context.font = "24px arial";
context.textAlign = "center";
context.lineJoin = "round"; // to prevent miter spurs on strokeText
//fill smaller canvas with random pixels
for(var x = 0; x < 100; x++){
for(var y = 0; y < 100; y++) {
var rando = function(){return Math.floor(Math.random() * 9)};
var val = rando();
if(x === 0 || y === 0 || x === 99 || y === 99){
context2.fillStyle = "#FF0000";
}else{
context2.fillStyle = "#" + val + val + val;
}
context2.fillRect(x,y,1,1);
}
}
// mouse holds mouse position button state, and if mouse over canvas with overid
var mouse = {
pos : {x : 0, y : 0},
worldPos : {x : 0, y : 0},
posLast : {x : 0, y : 0},
button : false,
overId : "", // id of element mouse is over
dragging : false,
whichWheel : -1, // first wheel event will get the wheel
wheel : 0,
}
// View handles zoom and pan (can also handle rotate but have taken that out as rotate can not be contrained without losing some of the image or seeing some of the background.
const view = (()=>{
const matrix = [1,0,0,1,0,0]; // current view transform
const invMatrix = [1,0,0,1,0,0]; // current inverse view transform
var m = matrix; // alias
var im = invMatrix; // alias
var scale = 1; // current scale
const bounds = {
topLeft : 0,
left : 0,
right : 200,
bottom : 200,
}
var useConstraint = true; // if true then limit pan and zoom to
// keep bounds within the current context
var maxScale = 1;
const workPoint1 = {x :0, y : 0};
const workPoint2 = {x :0, y : 0};
const wp1 = workPoint1; // alias
const wp2 = workPoint2; // alias
var ctx;
const pos = { // current position of origin
x : 0,
y : 0,
}
var dirty = true;
const API = {
canvasDefault () { ctx.setTransform(1,0,0,1,0,0) },
apply(){
if(dirty){ this.update() }
ctx.setTransform(m[0],m[1],m[2],m[3],m[4],m[5]);
},
getScale () { return scale },
getMaxScale () { return maxScale },
matrix, // expose the matrix
invMatrix, // expose the inverse matrix
update(){ // call to update transforms
dirty = false;
m[3] = m[0] = scale;
m[1] = m[2] = 0;
m[4] = pos.x;
m[5] = pos.y;
if(useConstraint){
this.constrain();
}
this.invScale = 1 / scale;
// calculate the inverse transformation
var cross = m[0] * m[3] - m[1] * m[2];
im[0] = m[3] / cross;
im[1] = -m[1] / cross;
im[2] = -m[2] / cross;
im[3] = m[0] / cross;
},
constrain(){
maxScale = Math.min(
ctx.canvas.width / (bounds.right - bounds.left) ,
ctx.canvas.height / (bounds.bottom - bounds.top)
);
if (scale < maxScale) { m[0] = m[3] = scale = maxScale }
wp1.x = bounds.left;
wp1.y = bounds.top;
this.toScreen(wp1,wp2);
if (wp2.x > 0) { m[4] = pos.x -= wp2.x }
if (wp2.y > 0) { m[5] = pos.y -= wp2.y }
wp1.x = bounds.right;
wp1.y = bounds.bottom;
this.toScreen(wp1,wp2);
if (wp2.x < ctx.canvas.width) { m[4] = (pos.x -= wp2.x - ctx.canvas.width) }
if (wp2.y < ctx.canvas.height) { m[5] = (pos.y -= wp2.y - ctx.canvas.height) }
},
toWorld(from,point = {}){ // convert screen to world coords
var xx, yy;
if(dirty){ this.update() }
xx = from.x - m[4];
yy = from.y - m[5];
point.x = xx * im[0] + yy * im[2];
point.y = xx * im[1] + yy * im[3];
return point;
},
toScreen(from,point = {}){ // convert world coords to screen coords
if(dirty){ this.update() }
point.x = from.x * m[0] + from.y * m[2] + m[4];
point.y = from.x * m[1] + from.y * m[3] + m[5];
return point;
},
scaleAt(at, amount){ // at in screen coords
if(dirty){ this.update() }
scale *= amount;
pos.x = at.x - (at.x - pos.x) * amount;
pos.y = at.y - (at.y - pos.y) * amount;
dirty = true;
},
move(x,y){ // move is in screen coords
pos.x += x;
pos.y += y;
dirty = true;
},
setContext(context){
ctx = context;
dirty = true;
},
setBounds(top,left,right,bottom){
bounds.top = top;
bounds.left = left;
bounds.right = right;
bounds.bottom = bottom;
useConstraint = true;
dirty = true;
}
};
return API;
})();
view.setBounds(0,0,canvas2.width,canvas2.height);
view.setContext(context);
//draw the larger canvas
function draw(){
view.canvasDefault(); // se default transform to clear screen
context.imageSmoothingEnabled = false;
context.fillStyle = "white";
context.fillRect(0, 0, width, height);
view.apply(); // set the current view
context.drawImage(canvas2, 0,0);
view.canvasDefault();
if(view.getScale() === view.getMaxScale()){
context.fillStyle = "black";
context.strokeStyle = "white";
context.lineWidth = 2.5;
context.strokeText("Max scale.",context.canvas.width / 2,24);
context.fillText("Max scale.",context.canvas.width / 2,24);
}
requestAnimationFrame(draw);
if(mouse.overId === "canvas"){
canvas.style.cursor = mouse.button ? "none" : "move";
}else{
canvas.style.cursor = "default";
}
}
// add events to document so that mouse is captured when down on canvas
// This allows the mouseup event to be heard no matter where the mouse has
// moved to.
"mousemove,mousedown,mouseup,mousewheel,wheel,DOMMouseScroll".split(",")
.forEach(eventName=>document.addEventListener(eventName,mouseEvent));
function mouseEvent (event){
mouse.overId = event.target.id;
if(event.target.id === "canvas" || mouse.dragging){ // only interested in canvas mouse events including drag event started on the canvas.
mouse.posLast.x = mouse.pos.x;
mouse.posLast.y = mouse.pos.y;
mouse.pos.x = event.clientX - canvas.offsetLeft;
mouse.pos.y = event.clientY - canvas.offsetTop;
view.toWorld(mouse.pos, mouse.worldPos); // gets the world coords (where on canvas 2 the mouse is)
if (event.type === "mousemove"){
if(mouse.button){
view.move(
mouse.pos.x - mouse.posLast.x,
mouse.pos.y - mouse.posLast.y
)
}
} else if (event.type === "mousedown") { mouse.button = true; mouse.dragging = true }
else if (event.type === "mouseup") { mouse.button = false; mouse.dragging = false }
else if(event.type === "mousewheel" && (mouse.whichWheel === 1 || mouse.whichWheel === -1)){
mouse.whichWheel = 1;
mouse.wheel = event.wheelDelta;
}else if(event.type === "wheel" && (mouse.whichWheel === 2 || mouse.whichWheel === -1)){
mouse.whichWheel = 2;
mouse.wheel = -event.deltaY;
}else if(event.type === "DOMMouseScroll" && (mouse.whichWheel === 3 || mouse.whichWheel === -1)){
mouse.whichWheel = 3;
mouse.wheel = -event.detail;
}
if(mouse.wheel !== 0){
event.preventDefault();
view.scaleAt(mouse.pos, Math.exp((mouse.wheel / 120) *zoomIntensity));
mouse.wheel = 0;
}
}
}
div { user-select: none;} /* mouse prevent drag selecting content */
canvas { border:2px solid black;}
<div>
<canvas id="canvas" width="200" height="200"></canvas>
<canvas id="canvas2" width="100" height="100"></canvas>
<p>Mouse wheel to zoom. Mouse click drag to pan.</p>
<p>Zoomed image constrained to canvas</p>
</div>

How to move a circle from a canvas with the mouse (when it's hold)?

I have this problem, and I don't know how to deal with it.
I struggled almost 2 hours to fix it. I tried various methods, but with no luck. If it's a nobbish question please forgive me and explain me in depth every step you do.
I have created a canvas, and I drew a circle in it. Now, I want to move the circle with the mouse, but while the mouse is hold. Is there any way to do it, or I'm just wasting time?
Here is the code:
<canvas draggable="true" id="canv" name="canv" width="600" height="500" style="border:1px solid #000000;" onclick="">Your browser does not support the HTML5 canvas tag.</canvas> <!-- < -- the canvas -->
<script>
var canv = document.getElementById("canv");
var context = canv.getContext("2d");
var width = canv.offsetWidth-1;
var height = canv.offsetHeight-1;
context.beginPath();
context.arc(width/2, height/2, 75, 0, 2*Math.PI);
context.stroke();
</script>
Some methods I have tried. These one are left in remarks /**/. Other ones I deleted them.
1.
<canvas draggable="true" id="canv" name="canv" width="600" height="500" style="border:1px solid #000000;" onclick="circle_move()"></canvas>
function circle_move(event){
var x = event.clientX;
var y = event.clientY;
document.getElementById("txt").setAttribute("value", "X: "+x+" and Y: "+y);
document.getElementById("canv").addEventListener("onmousemove", function(){
context.beginPath();
context.clearRect(0, 0, width, height);
context.arc(x, y, 75, 0, 2*Math.PI);
context.stroke();
});
}
2.
document.getElementById("canv").addEventListener("mousedown",
function(event){
var x = event.clientX;
var y = event.clientY;
document.getElementById("txt").setAttribute("value", "X: "+x+" and Y: "+y);
context.beginPath();
context.clearRect(0, 0, width, height);
context.arc(x, y, 75, 0, 2*Math.PI);
context.stroke();
}
);
3.
<canvas draggable="true" id="canv" name="canv" width="600" height="500" style="border:1px solid #000000;" onmousedown="launch"></canvas>
function loop(event){
var x = event.clientX;
var y = event.clientY;
document.getElementById("txt").setAttribute("value", "X: "+x+" and Y: "+y);
context.beginPath();
context.clearRect(0, 0, width, height);
context.arc(x, y, 75, 0, 2*Math.PI);
context.stroke();
}
function launch(event){
loop();
if(event.which !== 1 || event.which !== 2 || event.which !== 3){
launch(event);
}
}
Animation and UI
When you drag anything on the canvas you are effectively animating the canvas. To get the best results you need to stop thinking in terms of how you did stuff with the DOM and start to codelike you are writing a game.
One function to rule them all.
To coordinate all that is going on you need one function that handles all the animation. Sometimes called the mainLoop in gaming circles it makes working with animated content a lot easier.
The main loop
function mainLoop(time){ // this is called 60 times a second if there is no delay
// clear the canvas ready to be rendered on.
ctx.clearRect(0,0,canvas.width,canvas.height);
updateDisplay(); // call the function that is rendering the display
// get the next frame
requestAnimationFrame(mainLoop);
}
requestAnimationFrame(mainLoop);
From the main loop you can check the current app state and call the the appropriate functionality. In this example there is not much to its just calling updateDisplay()
KISS IO events.
The mouse, keyboard, touch, etc event handlers should not be doing any functional logic. Theses event can fire very rapidly, but the display will ony update once every 60th of a second. The is no point rendering from an event that can fire 500 times in a second when the user can only see just over 10% of the effort the CPU is doing.
The best way to write IO events is just as data loggers. Get what info you can as quick as possible and get out, don't write a different event listener for each type of event, keep the code simple, write just one event handler to handle as much as you can. If you need the keyboard add that to the same listener.
var mouse = (function(){
var bounds;
var m = {x:0,y:0,button:false};
function mouseEvent(event){
bounds = event.currentTarget.getBoundingClientRect();
m.x = event.pageX - bounds.left + scrollX;
m.y = event.pageY - bounds.top + scrollY;
if(event.type === "mousedown"){
m.button = true;
}else if(event.type === "mouseup"){
m.button = false;
}
}
m.start = function(element){
["mousemove","mousedown","mouseup"].forEach(eventType => element.addEventListener(eventType, mouseEvent));
}
return m;
}())
mouse.start(canvas);
Now you have access to the mouse whenever you need it via the simple mouse interface.
Circles
There is no point adding a nice interface if there is nothing to move about in it. The following is a object that helps manage circle. It creates, draws, and locates circles.
Included is the findClosest function it get the circle under the mouse. It will return the smallest circle under the mouse. If nothing under the mouse it will return undefined.
var circles = {
items [],
drawCircle(){ // function for the circle
ctx.beginPath();
ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
ctx.stroke();
},
each(callback){ // iterator
var i;
for(i = 0; i < this.items.length; i++){
callBack(this.items[i],i);
}
},
drawCircles(){
this.each(c => {
c.draw();
})
},
addCircle(x,y,radius){
var circle = {x, y, radius, draw : this.drawCircle};
this.items.push(circle);
return circle;
},
getClosest(pos){
var minDist, i, dist, x, y, foundCircle;
minDist = Infinity;
this.each(c =>{
x = pos.x - c.x;
y = pos.y - c.y;
dist = Math.sqrt(x * x + y * y);
if(dist <= c.radius && dist < minDist){
minDist = dist;
foundCircle = c;
}
})
return foundCircle;
}
}
How to Drag.
There is a lot involved in dragging objects. You need a function to find the closest object to the mouse. You need to provide feed back so that the user can see what can be dragged.
You may end up with many different drag type events. Drag to create, drag to move, drag something onto the canvas, or manipulate a custom rendered UI object. If you manage the dragging state from one object you can ensure that you dont accidentally click a UI item when dragging a circle.
When dragging starts you check what the drag will be doing. Then you flag that you are dragging, and indicate what the drag is to do. (see updateDisplay function)
While the drag is active do that action. When the mouse is up then just deactivate the drag
var dragging = {
started : false, // true if dragging
type : null, // string with the type of drag event
currentObj : null, // what we are dragging
startX : 0, // info about where the drag started
startY : 0,
start(type, obj){ // called at the start of a drag.
this.startX = mouse.x;
this.startY = mouse.y;
this.started = true;
this.type = type;
this.currentObj = obj;
}
}
Then the render function. It is called once every frame. I checks if the mouse button is down, what to do if it is, sets the cursor so people know what to do, and draws the circles.
var cursor = "default"; // to hold the cursor
var overCircle = null; // this holds whatever circle happens to be under the mouse when not dragging
function updateDisplay(){
var x,y, c;
cursor = "default"; // set default cursor
// check that the mouse if over the canvas
if(mouse.x >= 0 && mouse.x < canvas.width && mouse.y >= 0 && mouse.y < canvas.height){
cursor = "crosshair";
}
// is the mouse button down
if(mouse.button){ // the button is down
if(!dragging.started){ // if not dragging start
if(overCircle){ // start a move drag if over a circle
dragging.start("move",overCircle)
overCircle = null;
}else{ // start a create drag
dragging.start("create",circles.addCircle(mouse.x, mouse.y, 1));
}
}
c = dragging.currentObj;
// Update the drag state o fthe object being draged and the type of drag
if(dragging.type === "create"){
x = c.x - mouse.x;
y = c.y - mouse.y;
c.radius = Math.sqrt(x * x + y * y);
}else if(dragging.type === "move"){
x = dragging.startX - mouse.x;
y = dragging.startY - mouse.y;
c.x -= x;
c.y -= y;
dragging.startX = mouse.x;
dragging.startY = mouse.y;
}
cursor = "none";
} else { // button must be up
if(dragging.started){ // have we been dragging something.
dragging.started = false; // drop it
}
}
// draw the circles
ctx.strokeStyle = "black";
circles.draw();
// if not dragging
if(!dragging.started){
// find circle under the mouse
c = circles.getClosest(mouse);
if(c !== undefined){ // if there is a circle under the mouse highlight it
cursor = "move";
ctx.strokeStyle = "red";
ctx.fillStyle = "rgba(0,255,255,0.1)";
c.draw();
ctx.fill();
overCircle = c;
}else{
overCircle = null;
}
}
// set the cursor.
canvas.style.cursor = cursor;
}
As a working example.
var canvas = document.createElement("canvas");
canvas.style.border = "1px black solid";
canvas.width = 512;
canvas.height = 200;
var ctx = canvas.getContext("2d");
document.body.appendChild(canvas);
function mainLoop(time){ // this is called 60 times a second if there is no delay
ctx.clearRect(0,0,canvas.width,canvas.height);
updateDisplay(); // call the function that is rendering the display
// get the next frame
requestAnimationFrame(mainLoop);
}
// request the first frame. It will not start untill all the code below has been run
requestAnimationFrame(mainLoop);
var mouse = (function(){
var bounds;
var m = {x:0,y:0,button:false};
function mouseEvent(event){
bounds = event.currentTarget.getBoundingClientRect();
m.x = event.pageX - bounds.left + scrollX;
m.y = event.pageY - bounds.top + scrollY;
if(event.type === "mousedown"){
m.button = true;
}else if(event.type === "mouseup"){
m.button = false;
}
}
m.start = function(element){
["mousemove","mousedown","mouseup"].forEach(eventType => element.addEventListener(eventType, mouseEvent));
}
return m;
}())
mouse.start(canvas);
var circles = {
items : [],
drawCircle(){ // function for the circle
ctx.beginPath();
ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
ctx.stroke();
},
each(callback){ // iterator
var i;
for(i = 0; i < this.items.length; i++){
callback(this.items[i],i);
}
},
draw(){
this.each(c => {
c.draw();
})
},
addCircle(x,y,radius){
var circle = {x, y, radius, draw : this.drawCircle};
this.items.push(circle);
return circle;
},
getClosest(pos){
var minDist, i, dist, x, y, foundCircle;
minDist = Infinity;
this.each(c =>{
x = pos.x - c.x;
y = pos.y - c.y;
dist = Math.sqrt(x * x + y * y);
if(dist <= c.radius){
if(foundCircle === undefined || (foundCircle && c.radius < foundCircle.radius)){
minDist = dist;
foundCircle = c;
}
}
})
return foundCircle;
}
}
var dragging = {
started : false,
type : null,
currentObj : null, // what we are dragging
startX : 0,
startY : 0,
start(type, obj){
this.startX = mouse.x;
this.startY = mouse.y;
this.started = true;
this.type = type;
this.currentObj = obj;
}
}
var cursor = "default";
var overCircle = null;
function updateDisplay(){
var x,y, c;
cursor = "default"
if(mouse.x >= 0 && mouse.x < canvas.width && mouse.y >= 0 && mouse.y < canvas.height){
cursor = "crosshair";
}
if(mouse.button){ // the button is down
if(!dragging.started){
if(overCircle){
dragging.start("move",overCircle)
overCircle = null;
}else{
dragging.start("create",circles.addCircle(mouse.x, mouse.y, 1));
}
}
c = dragging.currentObj;
if(dragging.type === "create"){
x = c.x - mouse.x;
y = c.y - mouse.y;
c.radius = Math.sqrt(x * x + y * y);
}else if(dragging.type === "move"){
x = dragging.startX - mouse.x;
y = dragging.startY - mouse.y;
c.x -= x;
c.y -= y;
dragging.startX = mouse.x;
dragging.startY = mouse.y;
}
cursor = "none";
} else { // button must be up
if(dragging.started){ // have we been dragging something.
dragging.started = false; // drop it
}
}
ctx.strokeStyle = "black";
circles.draw();
if(!dragging.started){
c = circles.getClosest(mouse);
if(c !== undefined){
cursor = "move";
ctx.strokeStyle = "red";
ctx.fillStyle = "rgba(0,255,255,0.1)";
c.draw();
ctx.fill();
overCircle = c;
}else{
overCircle = null;
}
}
canvas.style.cursor = cursor;
}
<div style="font-family:12px arial;">Click drag to create circle</div>
Note this is written in ES6 and will not run on IE without some type of compiler.
You can see from the comments and code what is happening. Basically you need a global mouse key state variable that lets the function know if there mouse key is depressed. I used the mousemove event to actually kick off the drawing of the circle. Finally, you need the distance between the cursor and center of circle to be less than the radius, otherwise you can drag the circle from outside the circle (which I presume is not what you wanted).
//keep track of previous x and y coordinates
var x0 = 200;
var y0 = 300;
//keep track of mouse key state
var down = false;
var x;
var y;
//draw the initial circle someplace random
var context = document.getElementById("canv").getContext("2d");
context.clearRect(0, 0, 600, 500);
context.arc(200,300, 75, 0, 2*Math.PI);
context.stroke();
document.addEventListener("mousedown", function()
{
//if mousedown event is logged and we are within the area of the circle then state of down is true
if(getDistance(x, x0, y, y0) < 75) down = true;
});
document.addEventListener("mouseup", function()
{
//if mouseup event is logged then down is now false
down = false;
});
document.getElementById("canv").addEventListener("mousemove",
function(event){
x = event.clientX;
y = event.clientY;
//we need to be in "down" state in order for a redraw to be necessary
if(down)
{
//set the previous coordinates to the new coordinates we are drawing.
x0 = x;
y0 = y;
//draw the darn thing
context.beginPath();
context.clearRect(0, 0, 600, 500);
context.arc(x, y, 75, 0, 2*Math.PI);
context.stroke();
}
}
);
function getDistance(x0, x1, y0, y1)
{
return Math.sqrt(Math.pow(x1-x0,2) + Math.pow(y1-y0, 2));
}
<canvas id="canv" name="canv" width="600" height="500" style="border:1px solid #000000;" onclick="">Your browser does not support the HTML5 canvas tag.</canvas>
var offsetx,offsety,posx,posy,run;
window.onmousedown=function(e){
run=true;
offsetx=e.clientX;
offsety=e.clientY;
};
window.onmouseup=function(){
run=false;
};
window.onmousemove=function(e){
if(run){
movedx=e.clientX-offsetx;//mouse moved this value
movedy=e.clientY-offsety;
posx+=movedx;
posy+=movedyy;
offsetx=e.clientX;
offsety=e.clientY
//todo draw at posx,posy
}
};
//todo draw circle+set posx,posy to its position

HTML5 Canvas - Rotate by anchor using a mouse

I am developing in html5's canvas element. I have the follwing code and it is draggable and resizable image. How can I turn it to rotatable by a anchor too? How can I provide live rotating by an anchor. I saw other codes samples, but do not know how to implement it.
Sampler Working:
http://jsfiddle.net/LAS8L/588/
<canvas id="canvas" width=350 height=350></canvas>
Javascript
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var canvasOffset = $("#canvas").offset();
var offsetX = canvasOffset.left;
var offsetY = canvasOffset.top;
var startX;
var startY;
var isDown = false;
var pi2 = Math.PI * 2;
var resizerRadius = 8;
var rr = resizerRadius * resizerRadius;
var draggingResizer = {
x: 0,
y: 0
};
var imageX = 50;
var imageY = 50;
var imageWidth, imageHeight, imageRight, imageBottom;
var draggingImage = false;
var startX;
var startY;
var cx = canvas.width / 2;
var cy = canvas.height / 2;
var w;
var h;
var r = 0;
var img = new Image();
img.onload = function () {
imageWidth = img.width;
imageHeight = img.height;
imageRight = imageX + imageWidth;
imageBottom = imageY + imageHeight
draw(true, false);
}
img.src = "https://dl.dropboxusercontent.com/u/139992952/stackoverflow/facesSmall.png";
function draw(withAnchors, withBorders) {
// clear the canvas
ctx.clearRect(0, 0, canvas.width, canvas.height);
// draw the image
ctx.drawImage(img, 0, 0, img.width, img.height, imageX, imageY, imageWidth, imageHeight);
// optionally draw the draggable anchors
if (withAnchors) {
drawDragAnchor(imageX, imageY);
drawDragAnchor(imageRight, imageY);
drawDragAnchor(imageRight, imageBottom);
drawDragAnchor(imageX, imageBottom);
}
// optionally draw the connecting anchor lines
if (withBorders) {
ctx.beginPath();
ctx.moveTo(imageX, imageY);
ctx.lineTo(imageRight, imageY);
ctx.lineTo(imageRight, imageBottom);
ctx.lineTo(imageX, imageBottom);
ctx.closePath();
ctx.stroke();
}
}
function drawDragAnchor(x, y) {
ctx.beginPath();
ctx.arc(x, y, resizerRadius, 0, pi2, false);
ctx.closePath();
ctx.fill();
}
function anchorHitTest(x, y) {
var dx, dy;
// top-left
dx = x - imageX;
dy = y - imageY;
if (dx * dx + dy * dy <= rr) {
return (0);
}
// top-right
dx = x - imageRight;
dy = y - imageY;
if (dx * dx + dy * dy <= rr) {
return (1);
}
// bottom-right
dx = x - imageRight;
dy = y - imageBottom;
if (dx * dx + dy * dy <= rr) {
return (2);
}
// bottom-left
dx = x - imageX;
dy = y - imageBottom;
if (dx * dx + dy * dy <= rr) {
return (3);
}
return (-1);
}
function desenhe() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
drawRotationHandle(true);
drawRect();
}
function drawRect() {
ctx.save();
ctx.translate(cx, cy);
ctx.rotate(r);
ctx.drawImage(img, 0, 0, img.width, img.height, -imageWidth, -imageheight, w, h);
// ctx.fillStyle="yellow";
// ctx.fillRect(-w/2,-h/2,w,h);
ctx.restore();
}
function drawRotationHandle(withFill) {
ctx.save();
ctx.translate(cx, cy);
ctx.rotate(r);
ctx.beginPath();
ctx.moveTo(0, -1);
ctx.lineTo(imageWidth + 20, -1);
ctx.lineTo(imageWidth + 20, -7);
ctx.lineTo(imageWidth + 30, -7);
ctx.lineTo(imageWidth + 30, 7);
ctx.lineTo(imageWidth + 20, 7);
ctx.lineTo(imageWidth + 20, 1);
ctx.lineTo(0, 1);
ctx.closePath();
if (withFill) {
ctx.fillStyle = "blue";
ctx.fill();
}
ctx.restore();
}
function hitImage(x, y) {
return (x > imageX && x < imageX + imageWidth && y > imageY && y < imageY + imageHeight);
}
function handleMouseDown(e) {
startX = parseInt(e.clientX - offsetX);
startY = parseInt(e.clientY - offsetY);
draggingResizer = anchorHitTest(startX, startY);
draggingImage = draggingResizer < 0 && hitImage(startX, startY);
}
function handleMouseUp(e) {
draggingResizer = -1;
draggingImage = false;
draw(true, false);
}
function handleMouseOut(e) {
handleMouseUp(e);
}
function handleMouseMove(e) {
if (draggingResizer > -1) {
mouseX = parseInt(e.clientX - offsetX);
mouseY = parseInt(e.clientY - offsetY);
// resize the image
switch (draggingResizer) {
case 0:
//top-left
imageX = mouseX;
imageWidth = imageRight - mouseX;
imageY = mouseY;
imageHeight = imageBottom - mouseY;
break;
case 1:
//top-right
imageY = mouseY;
imageWidth = mouseX - imageX;
imageHeight = imageBottom - mouseY;
break;
case 2:
//bottom-right
imageWidth = mouseX - imageX;
imageHeight = mouseY - imageY;
break;
case 3:
//bottom-left
imageX = mouseX;
imageWidth = imageRight - mouseX;
imageHeight = mouseY - imageY;
break;
}
if(imageWidth<25){imageWidth=25;}
if(imageHeight<25){imageHeight=25;}
// set the image right and bottom
imageRight = imageX + imageWidth;
imageBottom = imageY + imageHeight;
// redraw the image with resizing anchors
draw(true, true);
} else if (draggingImage) {
imageClick = false;
mouseX = parseInt(e.clientX - offsetX);
mouseY = parseInt(e.clientY - offsetY);
// move the image by the amount of the latest drag
var dx = mouseX - startX;
var dy = mouseY - startY;
imageX += dx;
imageY += dy;
imageRight += dx;
imageBottom += dy;
// reset the startXY for next time
startX = mouseX;
startY = mouseY;
// redraw the image with border
draw(false, true);
}
}
$("#canvas").mousedown(function (e) {
handleMouseDown(e);
});
$("#canvas").mousemove(function (e) {
handleMouseMove(e);
});
$("#canvas").mouseup(function (e) {
handleMouseUp(e);
});
$("#canvas").mouseout(function (e) {
handleMouseOut(e);
});
I would like to work this way: http://jsfiddle.net/m1erickson/QqwKR/ but I don't know how merge the code. Can somebody help me?
Here is a function the will set the transform to scale rotate and translate an rectangle or anything.
function setTransform(ctx,x,y,scale,rotate){
var xdx = Math.cos(rotate) * scale; // create the x axis
var xdy = Math.sin(rotate) * scale;
ctx.setTransform(xdx, xdy, - xdy, xdx, x, y);
}
The canvas transform is set after that. Now just draw the object in its local space coords.
Eg draw at center of canvas double scale and 45deg rotation
setTransform(ctx,canvas.width/2,canvas.height/2,2,Math.PI /4);
ctx.strokeRect(-100,-100,200,200);
You will notice that the coordinates are negative. This is because I want the center of the box to be at the center of the canvas. If I wanted the top left of the box to be at the center
ctx.strokeRect(0,0,200,200);
or the bottom right
ctx.strokeRect(-200,-200,200,200);
Back to the centered box I can move it anywhere
setTransform(
ctx,
Math.random() * canvas.width, // random x pos
Math.random() * canvas.height, // random y pos
Math.random() * 10 + 0.1, // random scale
Math.random() * Math.PI * 2 // random rotation
);
The transform is random but I do not need to care I can still draw the box in local coordinates.
ctx.strokeRect(-100,-100,200,200);
And it will be draw where ever the transform has set it to.
Using ctx.setTransform saves the hassle of using save and restore.
If at any stage you need to return to the default transform
ctx.setTransform(1,0,0,1,0,0);
The problem you will next face is that the mouse coordinates are in canvas coordinates while the object is in its local space. You need to convert the mouse coordinates into the local object coordinates.
This is done by multiplying the mouse coordinates by the inverse of the matrix. A bit of maths here.
function getMouseLocal(mousex,mouseY,x,y,scale,rot){
var xdx = Math.cos(rotate) * scale; // create the x axis
var xdy = Math.sin(rotate) * scale;
// get the cross product of the two axies
var cross = xdx * xdx - xdy * -xdy;
// or
var cross = Math.pow(xdx,2) + Math.pow(xdy,2);
// then create the inverted axies
var ixdx = xdx / cross; // create inverted x axis
var ixdy = -xdy / cross;
var iydx = xdy / cross; // create inverted y axis
var iydy = xdx / cross;
// now remove the origin from the mouse coords
mouseX -= x;
mouseY -= y;
// multiply by the invers matrix
var localMouseX = mouseX * ixdx + mouseY * iydx;
var localMouseY = mouseX * ixdy + mouseY * iydy;
// and return the result
return {x : localMouseX, y : localMouseY};
}
Now you have the mouse coordinates in the local space. If you need to find out if the mouse is inside the box you
setTransform(ctx,100,100,2,Math.PI/4);
ctx.strokeRect(-100,-100,200,200);
var localMouse= getMouseLocal(mouseX,mouseY,100,100,2,Math.PI/4);
if(localMouse.x > -100 && localMouse.x < -100 + 200 && localMouse.y > -100 && localMouse.y < -100 + 200){
// mouse is inside the box
}
That should give you what you need.
UPDATE
I forgot you want to scale both x and y.. So below are the modified functions for scaling both X and Y axies
// sx and sy are scale x and y
function setTransform(ctx,x,y,sx,sy,rotate){
var xdx = Math.cos(rotate); // create the x axis
var xdy = Math.sin(rotate);
ctx.setTransform(xdx * sx, xdy * sx, - xdy * sy, xdx * sy, x, y);
}
And getting the mouse to local
function getMouseLocal(mousex,mouseY,x,y,sx,sy,rot){
var xdx = Math.cos(rotate); // create the x axis
var xdy = Math.sin(rotate);
// get the cross product of the two axies
var cross = xdx * sx * xdx * sy - xdy *sx * -xdy * sy;
// or
// this shortcut does not work now.
// var cross = Math.pow(xdx,2) + Math.pow(xdy,2);
// then create the inverted axies
var ixdx = (xdx * sy) / cross; // create inverted x axis
var ixdy = (-xdy * sx) / cross;
var iydx = (xdy * sy) / cross; // create inverted y axis
var iydy = (xdx * sx) / cross;
// now remove the origin from the mouse coords
mouseX -= x;
mouseY -= y;
// multiply by the invers matrix
var localMouseX = mouseX * ixdx + mouseY * iydx;
var localMouseY = mouseX * ixdy + mouseY * iydy;
// and return the result
return {x : localMouseX, y : localMouseY};
}

HTML5 canvas, make image rotate around click to select and drag circle

I have completed code that has a user click a button (to the right of the canvas), then the image is added to the canvas and is constrained to only move around the circumference of a circle. In order to move the image the user just needs to click the image and then move the mouse. To release the image the user simply needs to click where the image goes on the canvas.
Here is a fiddle showing what the current code does.
http://jsfiddle.net/smacnabb/68awv7sq/9/
Question: I am looking to be able to have the images that move around the circumference of the circle rotate while moving around the circumference of the circle.
This is what I mean:
Here is a fiddle for the code I added to try and make this happen
http://jsfiddle.net/smacnabb/68awv7sq/11/
in the handlemousemove method, it calls state.draw() every time the mouse move i'm passing mouseX, mouseY to state.draw.
state.draw() is in addstate method and this was the code I added to make the image rotate
var dx = mouseX - centerX;
var dy = mouseY - centerY;
var radianAngle = Math.atan2(dy, dx);
context.save();
context.translate(centerX, centerY);
context.rotate(radianAngle);
if (this.dragging) {
context.strokeStyle = 'black';
context.strokeRect(this.x, this.y, this.width + 2, this.height + 2)
}
context.drawImage(this.image, this.x, this.y);
context.restore();
What am I doing wrong?
You are close...Take a look at this example:
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var $canvas = $("#canvas");
var canvasOffset = $canvas.offset();
var offsetX = canvasOffset.left;
var offsetY = canvasOffset.top;
var radianAngle = 0;
var cx = 225;
var cy = 125;
var radius = 50;
// image loader
var imageURLs = [];
var imagesOK = 0;
var imgs = [];
imageURLs.push("https://dl.dropboxusercontent.com/u/139992952/multple/cars.png");
imageURLs.push("https://dl.dropboxusercontent.com/u/139992952/multple/plane.png");
imageURLs.push("https://dl.dropboxusercontent.com/u/139992952/multple/cars1.png");
imageURLs.push("https://dl.dropboxusercontent.com/u/139992952/multple/plane1.png");
loadAllImages(start);
function loadAllImages(callback) {
for (var i = 0; i < imageURLs.length; i++) {
var img = new Image();
imgs.push(img);
img.onload = function() {
imagesOK++;
if (imagesOK >= imageURLs.length) {
callback();
}
};
img.onerror = function() {
alert("image load failed");
}
img.crossOrigin = "anonymous";
img.src = imageURLs[i];
}
}
var imagesY = 20;
var targets = [];
var selectedTarget = -1;
function start() {
var n = imgs.length / 2;
for (var i = 0; i < n; i++) {
var target = imgs[i + n];
ctx.drawImage(target, 15, imagesY);
targets.push({
x: 15,
y: imagesY,
width: target.width,
height: target.height,
image: imgs[i]
});
imagesY += target.height + 10;
}
}
function handleMouseDown(e) {
e.preventDefault();
x = parseInt(e.clientX - offsetX);
y = parseInt(e.clientY - offsetY);
for (var i = 0; i < targets.length; i++) {
var t = targets[i];
if (x >= t.x && x <= t.x + t.width && y >= t.y && y <= t.y + t.height) {
selectedTarget = i;
draw(x, y);
}
}
}
function handleMouseMove(e) {
if (selectedTarget < 0) {
return;
}
e.preventDefault();
mouseX = parseInt(e.clientX - offsetX);
mouseY = parseInt(e.clientY - offsetY);
draw(mouseX, mouseY);
}
function draw(mouseX, mouseY) {
var dx = mouseX - cx;
var dy = mouseY - cy;
var radianAngle = Math.atan2(dy, dx);
// Drawing code goes here
var img = targets[selectedTarget].image;
ctx.clearRect(100, 0, canvas.width, canvas.height);
// draw the circle
ctx.beginPath();
ctx.arc(cx, cy, radius, 0, Math.PI * 2);
ctx.closePath();
ctx.stroke();
// draw the image rotated around the circumference
ctx.save();
ctx.translate(cx, cy);
ctx.rotate(radianAngle);
ctx.drawImage(img, radius - img.width / 2, -img.height / 2);
ctx.restore();
}
$("#canvas").mousedown(function(e) {
handleMouseDown(e);
});
$("#canvas").mousemove(function(e) {
handleMouseMove(e);
});
body{ background-color: ivory; }
canvas{border:1px solid red;}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<h4>Select an icon on left by clicking<br>
Then move mouse to have icon rotate around circle</h4>
<canvas id="canvas" width=350 height=350></canvas>

Categories

Resources