Resize and move rectangles drawn on canvas - javascript

I am allowing the user to draw rectangles on an image. At the same time , the user should be able to resize or move any of the rectangles at any point of time.
With some help, i have been able to draw the rectangles but i am unable to come up with resizing and moving part of it.
The rectangles that are being drawn do not overlap one another and the same has to be validated while resizing and moving too.
I am using javascript and jquery.
This is a demo of what i have done so far :
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;
ctx.strokeStyle = "lightgray";
ctx.lineWidth = 3;
function handleMouseDown(e) {
mouseX = parseInt(e.clientX - offsetX);
mouseY = parseInt(e.clientY - offsetY);
// Put your mousedown stuff here
startX = mouseX;
startY = mouseY;
isDown = true;
}
function handleMouseUp(e) {
mouseX = parseInt(e.clientX - offsetX);
mouseY = parseInt(e.clientY - offsetY);
$("#uplog").html("Up: " + mouseX + " / " + mouseY);
// Put your mouseup stuff here
isDown = false;
}
function handleMouseMove(e) {
mouseX = parseInt(e.clientX - offsetX);
mouseY = parseInt(e.clientY - offsetY);
// Put your mousemove stuff here
if (!isDown) {
return;
}
ctx.clearRect(0, 0, canvas.width, canvas.height);
drawRectangle(mouseX, mouseY);
}
function drawRectangle(mouseX, mouseY) {
var width = mouseX - startX;
var height = mouseY - startY;
ctx.beginPath();
ctx.rect(startX, startY, width, height);
ctx.stroke();
}
$("#canvas").mousedown(function (e) {
handleMouseDown(e);
});
$("#canvas").mousemove(function (e) {
handleMouseMove(e);
});
$("#canvas").mouseup(function (e) {
handleMouseUp(e);
});
as i am running short of time and i am not able to figure out how this can be done.

AFAK, HTML canvas element is simply an array of pixels.
Then drawing/moving/resizing rectangles is, simply again, to keep redrawing canvas.
So first, drawn objects need to be stored (maybe in array).
Second, corresponding mouse events are necessary.
Last, canvas redrawing is required.
Like:
var boxes = [];
var tmpBox = null;
document.getElementById("canvas").onmousedown = function(e) {...};
document.getElementById("canvas").onmouseup = function(e) {...};
document.getElementById("canvas").onmouseout = function(e) {...};
document.getElementById("canvas").onmousemove = function(e) {...};
Here is JSFiddle for demo: https://jsfiddle.net/wiany11/p7hxjmsj/14/

These 2 tutorials explain what you want:
http://simonsarris.com/blog/510-making-html5-canvas-useful
http://simonsarris.com/blog/225-canvas-selecting-resizing-shape
In short you should store the borders of the rectangles yourself and detect when the user clicks in the rectangle or on the border.
First you create an array to store your rectangles in
var rectangles = [];
Then you make a method to call every time you want to draw all your rectangles
function drawRectangles() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
for(var i = 0; i < rectangles.length; i++) {
var rect = rectangles[i];
ctx.beginPath();
ctx.rect(rect.startX, rect.startY, rect.endX, rect.endY);
ctx.stroke();
ctx.closePath();
}
}
In your mouseUp you then push the rectangles you have created to the array
function handleMouseUp() {
...
// store the rectangle as an object in your array
var rectangle = {startX: startX, endX: mouseX, startY: startY, endY: mouseY};
rectangles.push(rectangle);
drawRectangles();
}
In your other handlers you can then detect if you click in a rectangle of when your mouse will move in one

You can't just draw objects onto the canvas if you want to move them. You need to create instances of your shape objects and manage those (hit-testing and rendering as required). It is not very complex, but requires a lot more code than you have so far.
Try this tutorial: http://simonsarris.com/blog/510-making-html5-canvas-useful

Related

Let users draw rectangles with mouse in canvas with Javascript

I am going to create a canvas to let users can draw some rectangles in canvas. It can show the rectangle when the user is dragging the mouse. Also, it allows the user to draw one or more rectangles in canvas. I've found a solution like this:
// get references to the canvas and context
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
// style the context
ctx.strokeStyle = "blue";
ctx.lineWidth = 3;
// calculate where the canvas is on the window
// (used to help calculate mouseX/mouseY)
var $canvas = $("#canvas");
var canvasOffset = $canvas.offset();
var offsetX = canvasOffset.left;
var offsetY = canvasOffset.top;
var scrollX = $canvas.scrollLeft();
var scrollY = $canvas.scrollTop();
// this flage is true when the user is dragging the mouse
var isDown = false;
// these vars will hold the starting mouse position
var startX;
var startY;
function handleMouseDown(e) {
e.preventDefault();
e.stopPropagation();
// save the starting x/y of the rectangle
startX = parseInt(e.clientX - offsetX);
startY = parseInt(e.clientY - offsetY);
// set a flag indicating the drag has begun
isDown = true;
}
function handleMouseUp(e) {
e.preventDefault();
e.stopPropagation();
// the drag is over, clear the dragging flag
isDown = false;
}
function handleMouseOut(e) {
e.preventDefault();
e.stopPropagation();
// the drag is over, clear the dragging flag
isDown = false;
}
function handleMouseMove(e) {
e.preventDefault();
e.stopPropagation();
// if we're not dragging, just return
if (!isDown) {
return;
}
// get the current mouse position
mouseX = parseInt(e.clientX - offsetX);
mouseY = parseInt(e.clientY - offsetY);
// Put your mousemove stuff here
// clear the canvas
ctx.clearRect(0, 0, canvas.width, canvas.height);
// calculate the rectangle width/height based
// on starting vs current mouse position
var width = mouseX - startX;
var height = mouseY - startY;
// draw a new rect from the start position
// to the current mouse position
ctx.strokeRect(startX, startY, width, height);
}
// listen for mouse events
$("#canvas").mousedown(function (e) {
handleMouseDown(e);
});
$("#canvas").mousemove(function (e) {
handleMouseMove(e);
});
$("#canvas").mouseup(function (e) {
handleMouseUp(e);
});
$("#canvas").mouseout(function (e) {
handleMouseOut(e);
});
http://jsfiddle.net/m1erickson/6E2yd/
However, the solution can only allow users to draw one rectangle. If users draw the second rectangle, the previous rectangle will be wiped out because it will call ctx.clearRect() every time during dragging the mouse.
Edit: Sorry for my mistake. I missed the position:relative property for the container. Now it should work.
The code in your jsfiddle redraw repeatedly to give indications of the rectangle. I think it would be better to separate this indication canvas to a new layer, and use an overlapped layer to show the drawn rectangles.
JS:
// get references to the canvas and context
var canvas = document.getElementById("canvas");
var overlay = document.getElementById("overlay");
var ctx = canvas.getContext("2d");
var ctxo = overlay.getContext("2d");
// style the context
ctx.strokeStyle = "blue";
ctx.lineWidth = 3;
ctxo.strokeStyle = "blue";
ctxo.lineWidth = 3;
// calculate where the canvas is on the window
// (used to help calculate mouseX/mouseY)
var $canvas = $("#canvas");
var canvasOffset = $canvas.offset();
var offsetX = canvasOffset.left;
var offsetY = canvasOffset.top;
var scrollX = $canvas.scrollLeft();
var scrollY = $canvas.scrollTop();
// this flage is true when the user is dragging the mouse
var isDown = false;
// these vars will hold the starting mouse position
var startX;
var startY;
var prevStartX = 0;
var prevStartY = 0;
var prevWidth = 0;
var prevHeight = 0;
function handleMouseDown(e) {
e.preventDefault();
e.stopPropagation();
// save the starting x/y of the rectangle
startX = parseInt(e.clientX - offsetX);
startY = parseInt(e.clientY - offsetY);
// set a flag indicating the drag has begun
isDown = true;
}
function handleMouseUp(e) {
e.preventDefault();
e.stopPropagation();
// the drag is over, clear the dragging flag
isDown = false;
ctxo.strokeRect(prevStartX, prevStartY, prevWidth, prevHeight);
}
function handleMouseOut(e) {
e.preventDefault();
e.stopPropagation();
// the drag is over, clear the dragging flag
isDown = false;
}
function handleMouseMove(e) {
e.preventDefault();
e.stopPropagation();
// if we're not dragging, just return
if (!isDown) {
return;
}
// get the current mouse position
mouseX = parseInt(e.clientX - offsetX);
mouseY = parseInt(e.clientY - offsetY);
// Put your mousemove stuff here
// calculate the rectangle width/height based
// on starting vs current mouse position
var width = mouseX - startX;
var height = mouseY - startY;
// clear the canvas
ctx.clearRect(0, 0, canvas.width, canvas.height);
// draw a new rect from the start position
// to the current mouse position
ctx.strokeRect(startX, startY, width, height);
prevStartX = startX;
prevStartY = startY;
prevWidth = width;
prevHeight = height;
}
// listen for mouse events
$("#canvas").mousedown(function (e) {
handleMouseDown(e);
});
$("#canvas").mousemove(function (e) {
handleMouseMove(e);
});
$("#canvas").mouseup(function (e) {
handleMouseUp(e);
});
$("#canvas").mouseout(function (e) {
handleMouseOut(e);
});
HTML:
<h4>Drag the mouse to create a rectangle</h4>
<div id = "canvasWrapper">
<canvas id="overlay" width=300 height=300></canvas>
<canvas id="canvas" width=300 height=300></canvas>
</div>
CSS:
body{ background-color: ivory; }
canvas{
border: 1px solid red;
position: absolute;
}
#canvasWrapper{
position:relative;
}
http://jsfiddle.net/xkmqz9ho/
I've never tried anything on canvas, but you could try adding ctx.save() to the mouseout function and replacing ctx.clear(...) with ctx.restore().
more information about this on: https://developer.mozilla.org/es/docs/Web/API/CanvasRenderingContext2D/save

How to draw rectangles onto a JavaScript video?

I am trying to allow users to upload their own video which will then be displayed into a canvas. From there on, they can draw rectangles onto the canvas (to annotate for object detection), and clear the rectangles when they want to. However, what I want is that when the users draw the rectangles and play the video, the rectangles will follow an object on the video. Also, when the user wants to re-draw the rectangles to reposition them, they will clear the rectangles on the canvas - but the previously-drawn rectangles will already be saved.
This is the current code I have:
function update(){
context.drawImage(video,0,0,1580,700);
requestAnimationFrame(update); // wait for the browser to be ready to present another animation fram.
}
function readyToPlayVideo(event){ // this is a referance to the video
// the video may not match the canvas size so find a scale to fit
videoContainer.scale = Math.min(
canvas.width / this.videoWidth,
canvas.height / this.videoHeight);
videoContainer.ready = true;
// the video can be played so hand it off to the display function
requestAnimationFrame(updateCanvas);
// add instruction
/*document.getElementById("playPause").textContent = "Click video to play/pause.";*/
document.querySelector(".mute").textContent = "Mute";
}
function updateCanvas(){
context.clearRect(0,0,canvas.width,canvas.height);
// only draw if loaded and ready
if(videoContainer !== undefined && videoContainer.ready){
// find the top left of the video on the canvas
video.muted = muted;
var scale = videoContainer.scale;
var vidH = videoContainer.video.videoHeight;
var vidW = videoContainer.video.videoWidth;
var top = canvas.height / 2 - (vidH /2 ) * scale;
var left = canvas.width / 2 - (vidW /2 ) * scale;
// now just draw the video the correct size
context.drawImage(videoContainer.video, left, top, vidW * scale, vidH * scale);
/*if(videoContainer.video.paused){ // if not playing show the paused screen
drawPayIcon();
}*/
}
// all done for display
// request the next frame in 1/60th of a second
requestAnimationFrame(updateCanvas);
}
var canvasOffset = $("#canvas2").offset();
var offsetX = canvasOffset.left;
var offsetY = canvasOffset.top;
var isDrawing = false;
var startX;
var startY;
var mouseIsDown = true;
var mouseIsUp = true;
var startX;
var startY;
function handleMouseDown(e) {
mouseX = parseInt(e.clientX - offsetX);
mouseY = parseInt(e.clientY - offsetY);
console.log(mouseX,mouseY);
$("#downlog").html("Down: " + mouseX + " / " + mouseY);
// Put your mousedown stuff here
if (mouseIsDown) {
console.log('1');
canvas2.style.cursor="crosshair";
mouseIsDown=false;
mouseIsUp=false;
console.log(mouseIsDown);
} else {
handleMouseUp();
}
mouseIsDown=false;
mouseIsUp=true;
}
function handleMouseUp(e) {
mouseIsDown=false;
startX = parseInt(e.clientX - offsetX);
startY = parseInt(e.clientY - offsetY);
if (mouseIsUp) {
console.log('2');
context2.beginPath();
context2.rect(startX,startY,mouseX-startX,mouseY-startY);
context2.strokeStyle="limegreen";
context2.lineWidth=2;
context2.stroke();
canvas2.style.cursor="default";
mouseIsUp=true;
}
}
$("#canvas2").mousedown(function (e) {
handleMouseDown(e);
});
$("#canvas2").mouseup(function (e) {
handleMouseUp(e);
});
function clearcanvas()
{
var canvas2 = document.getElementById('canvas2'),
context2 = canvas2.getContext("2d");
context2.clearRect(0, 0, canvas2.width, canvas2.height);
}
Any help is appreciated, thank you!

Canvas Drawing Application. Rotating Drawing

I am making canvas drawing application and I want to implement rotation of the drawing. But when I rotate it and want to keep drawing mouse cursor isn't pointing at the pixels which are being painted.
How can I fix that?
Here is my code:
<button id="rotate">rotate right</button>
var clickX = new Array();
var clickY = new Array();
var clickDrag = new Array();
var paint = false;
var context = null;
var canvas = null;
canvas = document.getElementById('drawing');
context = canvas.getContext('2d');
rotate.onclick = function() {
context.translate(400,0);
context.rotate(90*Math.PI/180);
draw();
}
canvas.onmousedown = function(e){
paint = true;
addClick(e.pageX - this.offsetLeft, e.pageY - this.offsetTop,false);
draw();
}
canvas.onmousemove = function(e){
if(paint){
addClick(e.pageX - this.offsetLeft, e.pageY - this.offsetTop, true);
draw();
}
canvas.onmouseup = function(){
paint = false;
}
function addClick(x, y, dragging)
{
clickX.push(x);
clickY.push(y);
clickDrag.push(dragging);
}
function redraw(){
context.clearRect(0, 0, 400, 400);
context.lineJoin = "round";
// usunieto context.lineWidth = 5;
for(var i=0; i < clickX.length; i++)
{
context.beginPath();
if(clickDrag[i]){
context.moveTo(clickX[i-1], clickY[i-1]);
}else{
context.moveTo(clickX[i], clickY[i]);
}
context.lineTo(clickX[i], clickY[i]);
context.closePath();
context.stroke();
}
}
}
Applying an transformation to the canvas remains as the canvas state. You need to save the canvas state, apply the transformation, draw, then restore the canvas state. The 2D context API provides ctx.save() and ctx.restore() to do this for you. It uses a stack to save the state so that means saves can be nested but each save must be removed with a matching restore;
ctx.save(); // push the current state onto the save stack
ctx.rotate(Math.PI/2); // rotate 90deg clockwise
// call your draw function
ctx.restore(); // pop the last saved state from the save stack
That should fix you problem.

how can I put multiple objects on the same canvas?

i'm creating a web page where you dynamically draw multiple rectangles. I can draw the single object, but once I tried to draw another one, the previous one is gone away. I tried to save the state using save() and restore(), and it seems that I can't put it here. Isn't it logical that save method is put in the mouseup and restore method is called in the mousedown event? Any help or advice will be appreciated.
const canvas = document.getElementById("circle"),
ctx = canvas.getContext("2d"),
circle = {},
drag = false,
circleMade = false,
mouseMoved = false;
function draw() {
ctx.beginPath();
ctx.arc(circle.X, circle.Y, circle.radius, 0, 2.0 * Math.PI);
ctx.stroke();
}
function mouseDown(e) {
ctx.restore();
circle.startX = e.pageX - this.offsetLeft;
circle.startY = e.pageY - this.offsetTop;
circle.X = circle.startX;
circle.Y = circle.startY;
if (!circleMade) {
circle.radius = 0;
}
drag = true;
}
function mouseUp() {
drag = false;
circleMade = true;
if (!mouseMoved) {
circle = {};
circleMade = false;
ctx.clearRect(0, 0, canvas.width, canvas.height);
}
mouseMoved = false;
ctx.save();
}
function mouseMove(e) {
if (drag) {
mouseMoved = true;
circle.X = e.pageX - this.offsetLeft;
circle.Y = e.pageY - this.offsetTop;
if (!circleMade) {
circle.radius = Math.sqrt(Math.pow((circle.X - circle.startX), 2) + Math.pow((circle.Y - circle.startY), 2));
}
ctx.clearRect(0, 0, canvas.width, canvas.height);
draw();
}
}
function init() {
canvas.addEventListener('mousedown', mouseDown, false);
canvas.addEventListener('mouseup', mouseUp, false);
canvas.addEventListener('mousemove', mouseMove, false);
}
init();
you need to save the information about what you are drawing in a separate object, every time you make a draw to the canvas you will wipe and redraw the new object.
so when you clearRect and then draw you are clearing and then drawing a fresh one but the old ones are being left behind. A good example:
var SavedCircles = [];
var circleInfo = function()
{
this.x = 0;
this.y = 0;
this.startX = 0;
this.startY = 0;
this.radius = 0;
}
circle = {};
function draw()
{
for(var x=0;x<SavedCircles.length;x++)
{
ctx.beginPath();
ctx.arc(SavedCircles[x].X, SavedCircles[x].Y, SavedCircles[x].radius, 0, 2.0 * Math.PI);
ctx.stroke();
}
}
function mouseDown()
{
circle = new circleInfo();
}
function mouseUp()
{
SavedCircles.push(circle);
}
function mouseMove()
{
draw();
}
so you can get rid of save and restore, also its much faster to clear a canvas simply by:
canvas.width = canvas.width;
this should help you keep all circles ever drawn. fill in the rest with your code.

Drawing a circle on the canvas using mouse events

I am trying to draw a circle using mouse on the canvas using mouse events, but it does not draw anything:
tools.circle = function () {
var tool = this;
this.started = false;
this.mousedown = function (ev) {
tool.started = true;
tool.x0 = ev._x;
tool.y0 = ev._y;
ctx.moveTo(tool.x0,tool.y0);
};
this.mousemove = function (ev) {
var centerX = Math.max(tool.x0,ev._x) - Math.abs(tool.x0 - ev._x)/2;
var centerY = Math.max(tool.y0,ev._y) - Math.abs(tool.y0 - ev._y)/2;
var distance = Math.sqrt(Math.pow(tool.x0 - ev._x,2) + Math.pow(tool.y0 - ev._y));
context.circle(tool.x0, tool.y0, distance/2,0,Math.PI*2 ,true);
context.stroke();
};
};
What am I doing wrong?
Well, this code snippet doesn't tell us much, but there are a couple of obvious errors in your code.
First, DOM Event object doesn't have _x and _y properties. but rather clientX and clientY or pageX and pageY.
To get relative mouse coordinates from the current event object, you would do something like:
element.onclick = function(e) {
var x = e.pageX - this.offsetLeft;
var y = e.pageY - this.offsetTop;
}
Next, canvas' 2d context doesn't have a method called circle, but you could write your own, maybe something like:
var ctx = canvas.context;
ctx.fillCircle = function(x, y, radius, fillColor) {
this.fillStyle = fillColor;
this.beginPath();
this.moveTo(x, y);
this.arc(x, y, radius, 0, Math.PI*2, false);
this.fill();
}
Anyhow, here's a test html page to test this out: http://jsfiddle.net/ArtBIT/kneDX/
I hope this helps.
Cheers

Categories