How to draw in javascript canvas and compare it to a shape? - javascript

so to keep it short I'm wondering how would I be able to track a user drawing from the moment they click to when they let go and compare it to check its accuracy with say, a perfect circle?
Would this be possible, and if so what are the best ways to check its accuracy compared to a perfect circle and how?
Also thinking of this like if the user has drawn a circle but the start and end points don't exactly meet, maybe draw a line to connect them, etc?

I did this "stay inside the text" game a while back. All you try to do is trace the number. The more you stay inside the lines while tracing, the higher your score is.
You're welcome to start with this and modify it as needed. :-)
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 scrollX=$canvas.scrollLeft();
var scrollY=$canvas.scrollTop();
ctx.font="216px arial";
ctx.fillStyle="white";
ctx.fillText("2",100,200);
ctx.strokeText("2",100,200);
var cw=canvas.width;
var ch=canvas.height;
var imgData=ctx.getImageData(0,0,cw,ch);
var data=imgData.data;
var isDown=false;
var wasInside=true;
var score=0;
function draw(mouseX,mouseY){
var alpha = data[((cw*mouseY)+mouseX)*4+3];
score+=(alpha>19?1:-1);
if(alpha<20 && wasInside){
wasInside=false;
ctx.fillStyle="white";
ctx.fillRect(0,0,cw,ch);
ctx.fillStyle="white";
ctx.fillText("2",100,200);
ctx.strokeText("2",100,200);
}else if(alpha>19 && !wasInside){
wasInside=true;
ctx.fillStyle="white";
ctx.fillRect(0,0,cw,ch);
ctx.fillStyle="green";
ctx.fillText("2",100,200);
ctx.strokeText("2",100,200);
}
}
function handleMouseDown(e){
e.preventDefault();
mouseX=parseInt(e.clientX-offsetX);
mouseY=parseInt(e.clientY-offsetY);
var alpha = data[((cw*mouseY)+mouseX)*4+3];
wasInside=(alpha<20);
score=0;
isDown=true;
draw(mouseX,mouseY);
}
function handleMouseUp(e){
e.preventDefault();
isDown=false;
$("#score").text("Score: "+score);
}
function handleMouseOut(e){
e.preventDefault();
isDown=false;
}
function handleMouseMove(e){
if(!isDown){return;}
e.preventDefault();
mouseX=parseInt(e.clientX-offsetX);
mouseY=parseInt(e.clientY-offsetY);
draw(mouseX,mouseY);
}
$("#canvas").mousedown(function(e){handleMouseDown(e);});
$("#canvas").mousemove(function(e){handleMouseMove(e);});
$("#canvas").mouseup(function(e){handleMouseUp(e);});
$("#canvas").mouseout(function(e){handleMouseOut(e);});
$(window).scroll(function(){
var BB=canvas.getBoundingClientRect();
offsetX=BB.left;
offsetY=BB.top;
});
body{ background-color: white; }
#canvas{border:1px solid red;}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<h4>Drag inside the number<br>Number stays green while you're inside.</h4>
<h3 id="score">Score:</h3>
<canvas id="canvas" width=300 height=300></canvas>

Here's how i would do to compare a figure : I would let globalComposite operation 'xor' mode do the 'substraction' of the right figure, then, with getImageData, count the non-null pixels to estimate the error.
Steps :
• Let the user draw and store the points coordinates.
• When figure is complete, compute the figure bounding box (min/max on x/y basically).
• Draw again the figure (as a filled polygon) on a temporary canvas having the size of the bbox.
• Set globalComposite to 'xor', and draw what would be the perfect figure on this canvas.
• Grab the imageData of the canvas and count non-null pixels.
• Compute the 'score' out of non-null pixels count vs canvas size. I suppose that less than 5% error is perfect, less than 25% is very good... Only trying can tell.

Related

HTML Canvas Zoom to region on graph

just wondering has anyone got any solution for zooming into a region on a graph. When I say region I mean, you mouse down at a point on the canvas, move the mouse to another location while still pressed, and when mouse up I want to zoom into that block. and only scale the x axis, no the the y.
This is exactly what I want to do - http://canvasjs.com/docs/charts/basics-of-creating-html5-chart/zooming-panning/
Now I've been able to zoom in, but my graph is a mess after the zoom. Lines and points are all stretched, twisted etc, which I've tried to adjust using the scale.
This is what I've been doing so far.
let y = heightOfCanvas / 2,
// Get the region in pixels to zoom to. If canvas width
// is 500, this is lets say 100px to 400px.
regionToZoom = toX - fromX,
// Subtract from actual width, so we get what will be scaled
// out.
difference = widthOfCanvas - regionToZoom,
// Scale = fullWidth / partWidth
scale = widthOfCanvas / difference,
// This is how I worked out the value to translate back,
// so that it lines up right
translateBack = widthOfCanvas * scale
ctx.translate(from, y);
ctx.scale(scale, 1);
from = from * translateBack;
ctx.translate(-from, -y);
// REDRAW GRAPH AFTER
Like this zooms in fine, but as I said, it becomes a mess after.
If anyone has done something similar in the past, like in the example above, I would be so grateful for the help.
Sean.
Here's a quick example showing how to select & display a subset of a graph
// canvas vars
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
ctx.font='14px arial';
var cw=canvas.width;
var ch=canvas.height;
function reOffset(){
var BB=canvas.getBoundingClientRect();
offsetX=BB.left;
offsetY=BB.top;
}
var offsetX,offsetY;
reOffset();
window.onscroll=function(e){ reOffset(); }
window.onresize=function(e){ reOffset(); }
var isDrilled=false;
var isDown=false;
var startX,startY;
// graph vars
var axisY=ch/3;
var points=[];
// DEMO: add random data points
var demoPointCount=50;
for(var i=0;i<demoPointCount;i++){
points.push({y:Math.random()*150-75});
}
// draw the full data graph
draw(points,axisY);
// listen to mouse events
window.onmousedown=(function(e){handleMouseDown(e);});
window.onmousemove=(function(e){handleMouseMove(e);});
window.onmouseup=(function(e){handleMouseUpOut(e);});
window.onmouseout=(function(e){handleMouseUpOut(e);});
function draw(pts,axisY,startX,mouseX,drillStart,drillEnd){
// redraw the given pts data
ctx.clearRect(0,0,cw,ch);
ctx.beginPath();
ctx.moveTo(0,pts[0].y+axisY);
for(var i=0;i<pts.length;i++){
var x=cw/(pts.length-1)*i;
ctx.lineTo(x,pts[i].y+axisY);
}
ctx.stroke();
// used when highlighting a drilldown section of full data
if(startX && mouseX){
ctx.globalAlpha=0.10;
ctx.fillStyle='black';
ctx.fillRect(startX,0,mouseX-startX,ch);
ctx.globalAlpha=1.00;
}else if(drillStart && drillEnd){
ctx.fillText('Viewing '+drillStart+' - '+drillEnd+'. Click to return to all data view.',10,20);
}else{
ctx.fillText('Drag to select data to drill into.',10,20);
}
}
function handleMouseDown(e){
// if displaying drilled data, return to full data
if(isDrilled){
isDrilled=false;
draw(points,axisY);
return;
}
// tell the browser we're handling this event
e.preventDefault();
e.stopPropagation();
// start mouse position
startX=parseInt(e.clientX-offsetX);
startY=parseInt(e.clientY-offsetY);
// Put your mousedown stuff here
isDown=true;
}
function handleMouseUpOut(e){
if(!isDown){return;}
// tell the browser we're handling this event
e.preventDefault();
e.stopPropagation();
// mouse position
mouseX=parseInt(e.clientX-offsetX);
mouseY=parseInt(e.clientY-offsetY);
// Put your mouseup stuff here
isDown=false;
isDrilled=true;
// normalize
if(mouseX<startX){ var t=startX; startX=mouseX; mouseX=t; }
// fetch highlighted start & end data points
drillStart=parseInt(startX/cw*points.length);
drillEnd=parseInt(mouseX/cw*points.length);
var subset=points.slice(drillStart,drillEnd+1);
// draw the data subset
draw(subset,axisY,null,null,drillStart,drillEnd);
}
function handleMouseMove(e){
if(!isDown){return;}
// tell the browser we're handling this event
e.preventDefault();
e.stopPropagation();
// mouse position
mouseX=parseInt(e.clientX-offsetX);
mouseY=parseInt(e.clientY-offsetY);
// Put your mousemove stuff here
draw(points,axisY,startX,mouseX);
}
body{ background-color:white; }
#canvas{border:1px solid red; }
<canvas id="canvas" width=512 height=512></canvas>
[Previous outdated answer]
Here's a way to zoom without all the fuss about remembering transformations
This is a quick-thought so some tinkering may be needed.
Copy the graph by drawing it on a second in-memory canvas:
var memCanvas=myCanvas.cloneNode();
var memCtx=memCanvas.getContext('2d');
memCtx.drawImage(myCanvas,0,0);
DrawImage the memCanvas onto the displayed canvas:
context.drawImage(memCanvas,0,0);
On mousedown, grab the top-left coordinates of your selection box.
On mouseup, grab the bottom-right coordinates of your selection box. Note: you might have to flip top & bottom or left & right if top>bottom or left>right.
Calculate the scaling factor required to draw just the selection box full-frame into the visible canvas:
var scale = Math.min(
(myCanvas.width/selectionboxWidth),
(myCanvas.height/selectionboxHeight)
);
Use the clipping form of drawImage to pull just the selection box from the memCanvas and draw it scaled into the visible canvas:
var x=sboxLeft;
var y=sboxTop;
var w=sboxRight-sboxLeft;
var h=sboxBottom-sboxTop;
sboxcontext.drawImage(
memCanvas, // fetch from the memCanvas
x,y,w,h, // clip the selected box
0,0,w*scale,h*scale); // scale the selected box into the visible canvas
Then when you want to unzoom you just redraw the full memCanvas to the visible canvas (no need to untransform). The resulting graph is not a mess!
context.drawImage(memCanvas,0,0); // unzoomed -- simply!

How to redraw mouse movement on a canvas?

I have an array of tuples in javascript. Is there an existing library that lets me view the mouse movements the user performed?
Ideally something that lets me replay the captured data from the beginning to the end. It would look like a video player (ie play, pause, adjust replay speed), but instead of a video you would see how the mouse cursor moved. This visualization would be on HTML5 canvas (ie. a square of white pixels representing the cursor that's moving through in a black HTML canvas).
Simple enough to accomplish without a library.
Listen for mousemove events
On mousemove, add each mouse position to a points array
When requested, run an requestAnimationFrame loop that redraws each point from the points array.
Example code and a Demo:
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;
function reOffset(){
var BB=canvas.getBoundingClientRect();
offsetX=BB.left;
offsetY=BB.top;
}
var offsetX,offsetY;
reOffset();
window.onscroll=function(e){ reOffset(); }
window.onresize=function(e){ reOffset(); }
var isDown=false;
var points=[];
var nextTime=0;
var nextN=0;
var duration=1000/60;
ctx.lineCap='round';
$("#canvas").mousedown(function(e){handleMouseDown(e);});
$("#canvas").mousemove(function(e){handleMouseMove(e);});
$("#canvas").mouseup(function(e){handleMouseUpOut(e);});
$("#canvas").mouseout(function(e){handleMouseUpOut(e);});
$('#fast').on('click',function(){ duration=1000/60; beginRedrawing(); });
$('#slow').on('click',function(){ duration=1000/15; beginRedrawing(); });
function beginRedrawing(){
if(points.length<2){return;}
nextN=1;
ctx.lineWidth=3;
ctx.strokeStyle=randomColor();
requestAnimationFrame(redraw);
}
function handleMouseDown(e){
// tell the browser we're handling this event
e.preventDefault();
e.stopPropagation();
// get current mouse position
ctx.lineWidth=7;
ctx.strokeStyle='black';
points.length=0;
mouseX=parseInt(e.clientX-offsetX);
mouseY=parseInt(e.clientY-offsetY);
points.push({x:mouseX,y:mouseY});
// Set dragging flag
isDown=true;
}
function handleMouseUpOut(e){
// tell the browser we're handling this event
e.preventDefault();
e.stopPropagation();
// get current mouse position
mouseX=parseInt(e.clientX-offsetX);
mouseY=parseInt(e.clientY-offsetY);
// Clear dragging flag
isDown=false;
}
function handleMouseMove(e){
if(!isDown){return;}
// tell the browser we're handling this event
e.preventDefault();
e.stopPropagation();
// get current mouse position
mouseX=parseInt(e.clientX-offsetX);
mouseY=parseInt(e.clientY-offsetY);
points.push({x:mouseX,y:mouseY});
var n=points.length-1;
lineSegment(points[n-1],points[n]);
}
function lineSegment(p0,p1){
ctx.beginPath();
ctx.moveTo(p0.x,p0.y);
ctx.lineTo(p1.x,p1.y);
ctx.stroke();
}
function redraw(time){
if(nextN>points.length-1){return;}
if(time<nextTime){requestAnimationFrame(redraw);return;}
nextTime=time+duration;
lineSegment(points[nextN-1],points[nextN]);
nextN++;
requestAnimationFrame(redraw);
}
function randomColor(){
return('#'+Math.floor(Math.random()*16777215).toString(16));
}
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>Drag to create polyline then click a redraw button below.</h4>
<button id=fast>Fast Redraw</button>
<button id=slow>Slow Redraw</button>
<br>
<canvas id="canvas" width=512 height=512></canvas>
I don't know of any libraries that do what you described, but creating an array of the positions and then using that should do roughly what you want.
//Capture all mouse movements on the browser window.
document.onmousemove = mousePos;
//Store all previous mouse locations
var ary = [];
function mousePos (e) {
//Log the current mouse position
ary.push({X: e.pageX, Y: e.pageY});
}
Now you can loop through the array to get your mouse movement.
function replay() {
for (var i = 0; i < ary.length; i++) {
//Draw a point wherever the mouse moved.
ctx.fillRect(ary[i].X, ary[i].Y, 2, 2);
}
}
You can add in a delay of your duration to playback at whichever speed you like.

How to know whether what a user draws covers most of the canvas area?

I have a canvas, where user can write something, and it shows previously saved drawing. But we detected some of them are not using the total canvas area due to some factors. So my questions are:
Can I detect the area where the user's drawn and make it larger by
shredding the usused canvas spaces?
Or, can I detect whether the user
is using the full canvas area or say more than 60% of it? If yes,
how? So that I can put an warning to the user.
Thanks.
Can I detect the area where the user's drawn and make it larger by
shredding the usused canvas spaces?
By your use of the phrase "shred the undrawn space" I assume you want to scale the existing image larger to fill all the canvas area.
Yes, you can...
Step#1
You can use getImageData to fetch the pixel color data from your canvas. This data is an array containing the red, green, blue and alpha values of every pixel on the canvas.
var pixelData=mainContext.getImageData(0,0,mainCanvas.width,mainCanvas.height).data;
The data for canvas pixel[0,0]:
pixelData[0] is the red component of canvas pixel[0,0]
pixelData[1] is the green component of canvas pixel[0,0]
pixelData[2] is the blue component of canvas pixel[0,0]
pixelData[3] is the alpha component of canvas pixel[0,0]
The data for canvas pixel[1,0]:
pixelData[4] is the red component of canvas pixel[1,0]
pixelData[5] is the green component of canvas pixel[1,0]
pixelData[6] is the blue component of canvas pixel[1,0]
pixelData[7] is the alpha component of canvas pixel[1,0]
Use the pixel data to determine the bounding box of your user's opaque pixels. You do this by determining the topmost, leftmost, bottommost and rightmost pixel with alpha>250.
var boundLeft,boundTop,boundRight,boundBottom,boundWidth,boundHeight;
function getBounds(){
boundLeft=1000000;
boundTop=1000000;
boundRight=-1000000;
boundBottom=-1000000;
//
var d=ctx.getImageData(0,0,cw,ch).data;
//
for(var i=0;i<d.length;i+=4){
// test the alpha (d[i+3])
if(d[i+3]>250){
var px=parseInt(i/4);
var pixelY=parseInt(px/cw);
var pixelX=px-pixelY*cw;
if(pixelX<boundLeft){boundLeft=pixelX;}
if(pixelX>boundRight){boundRight=pixelX;}
if(pixelY<boundTop){boundTop=pixelY;}
if(pixelY>boundBottom){boundBottom=pixelY;}
boundWidth=boundRight-boundLeft;
boundHeight=boundBottom-boundTop;
}
}
}
Step#2
Create an in-memory canvas the size of the bounding box.
var memCanvas=document.createElement('canvas');
var memContext=memCanvas.getContext('2d');
memCanvas.width=boundWidth;
memCanvas.height=boundHeight;
Step#3
Use the clipping version of context.drawImage to draw the bounding area from the main canvas to the in-memory canvas.
memContext.drawImage(mainCanvas,
// grab the "used" pixels from the main canvas
boundLeft,boundTop,boundWidth,boundHeight,
// and draw those pixels on the in-memory canvas
0,0,boundWidth,boundHeight
);
Step#4
(Optionally resize the main canvas to the bounding box size)
mainCanvas.width=boundWidth;
mainCanvas.height=boundHeight;
Draw the in-memory canvas onto the main canvas
mainContext.clearRect(0,0,mainCanvas.width,mainCanvas.height);
mainContext.drawImage(memCanvas,0,0);
Can I detect whether the user is using the full canvas area or say
more than 60% of it? If yes, how? So that I can put an warning to the
user.
Yes, you can...
Use the same technique above to calculate the boundWidth & boundHeight
Then you can calculate the percentage of "used" canvas using the ratio of the bounding box size vs the canvas size:
var percent = (boundWidth*boundHeight) / (mainCanvas.width*mainCanvas.height);
Calculating the new bounding box as the user adds to the drawing
Rather than using getImageData to calculate the new bounding box after each new pixel is drawn by the user, you can instead expand the bounding box if the new pixel is outside the existing bounds:
if(newPixelX<leftmost){boundLeft=newPixelX;}
if(newPixelX>rightmost){boundRight=newPixelX;}
if(newPixelY<topmost){boundTop=newPixelY;}
if(newPpixelY>bottommost){boundBottom=newPixelY;}
Example code and Demo:
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;
function reOffset(){
var BB=canvas.getBoundingClientRect();
offsetX=BB.left;
offsetY=BB.top;
}
var offsetX,offsetY;
reOffset();
window.onscroll=function(e){ reOffset(); }
var isDown=false;
var startX,startY;
var leftmost,topmost,rightmost,bottommost;
// load image
var img=new Image();
img.crossOrigin='anonymous';
img.onload=start;
img.src="https://dl.dropboxusercontent.com/u/139992952/multple/car.png";
function start(){
//
cw=canvas.width=img.width;
ch=canvas.height=img.height;
//
ctx.drawImage(img,0,0);
//
getBounds();
}
function getBounds(){
leftmost=1000000;
topmost=1000000;
rightmost=-1000000;
bottommost=-1000000;
//
var d=ctx.getImageData(0,0,cw,ch).data;
//
for(var i=0;i<d.length;i+=4){
// test the alpha (d[i+3])
if(d[i+3]>250){
var px=parseInt(i/4);
var pixelY=parseInt(px/cw);
var pixelX=px-pixelY*cw;
if(pixelX<leftmost){leftmost=pixelX;}
if(pixelX>rightmost){rightmost=pixelX;}
if(pixelY<topmost){topmost=pixelY;}
if(pixelY>bottommost){bottommost=pixelY;}
}
}
}
function highlightBounds(){
var previousFill=ctx.fillStyle;
ctx.globalAlpha=0.05;
ctx.fillStyle='red';
ctx.fillRect(leftmost,topmost,(rightmost-leftmost),(bottommost-topmost));
ctx.globalAlpha=1.00;
ctx.fillStyle=previousFill;
}
function handleMouseDown(e){
// tell the browser we're handling this event
e.preventDefault();
e.stopPropagation();
startX=parseInt(e.clientX-offsetX);
startY=parseInt(e.clientY-offsetY);
// Put your mousedown stuff here
isDown=true;
}
function handleMouseUp(e){
// tell the browser we're handling this event
e.preventDefault();
e.stopPropagation();
mouseX=parseInt(e.clientX-offsetX);
mouseY=parseInt(e.clientY-offsetY);
// Put your mouseup stuff here
isDown=false;
}
function handleMouseOut(e){
// tell the browser we're handling this event
e.preventDefault();
e.stopPropagation();
mouseX=parseInt(e.clientX-offsetX);
mouseY=parseInt(e.clientY-offsetY);
// Put your mouseOut stuff here
isDown=false;
}
function handleMouseMove(e){
if(!isDown){return;}
// tell the browser we're handling this event
e.preventDefault();
e.stopPropagation();
mouseX=parseInt(e.clientX-offsetX);
mouseY=parseInt(e.clientY-offsetY);
ctx.beginPath();
ctx.arc(mouseX,mouseY,3,0,Math.PI*2);
ctx.closePath();
ctx.fill();
if(mouseX<leftmost){leftmost=mouseX;}
if(mouseX>rightmost){rightmost=mouseX;}
if(mouseY<topmost){topmost=mouseY;}
if(mouseY>bottommost){bottommost=mouseY;}
var boundsArea=(rightmost-leftmost)*(bottommost-topmost);
var canvasArea=cw*ch;
$pct.text(parseInt(boundsArea/canvasArea*100)+'% of canvas area is used');
}
$("#canvas").mousedown(function(e){handleMouseDown(e);});
$("#canvas").mousemove(function(e){handleMouseMove(e);});
$("#canvas").mouseup(function(e){handleMouseUp(e);});
$("#canvas").mouseout(function(e){handleMouseOut(e);});
var $pct=$('#pct');
$('#shred').click(function(){
var width=rightmost-leftmost;
var height=bottommost-topmost;
var memCanvas=document.createElement('canvas');
var memContext=memCanvas.getContext('2d');
memCanvas.width=width;
memCanvas.height=height;
memContext.drawImage(canvas,
// grab the "used" pixels from the main canvas
leftmost,topmost,width,height,
// and draw those pixels on the in-memory canvas
0,0,width,height
);
canvas.width=width;
canvas.height=height;
ctx.drawImage(memCanvas,0,0);
});
body{ background-color: white; }
#canvas{border:1px solid red;}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<button id=shred>Eliminate outside space</button>
<h4 id=pct>Drag mouse</h4>
<canvas id="canvas" width=300 height=300></canvas>

To drag shapes drawn on canvas same as paint

I have tool similar to paint where i allow user to draw different shapes on canvas using mouse events.I want to allow the user to drag the shapes(same as paint) once they are drawn on canvas. Did anyone done like this before? I have already tried using OOPDragging but that didn't work in my case.Also my tool include all kind of shapes like Line,elbow connector,oval,text,image and not just circles and rectangles. Can anyone please suggest some easy to achieve solution for this as i need it ASAP.
Thanks in advance.
A Demo: http://jsfiddle.net/m1erickson/JrzM2/
Assume you've created this triangle in your paint program
And the points for that triangle are in an array like this:
[{x:0,y:20},{x:30,y:0},{x:70,y:45}]
To move that triangle to [20,35], you need to first offset the triangle by x:20 & y:35
var myTriangle={
x:20,
y:35,
points:[{x:0,y:20},{x:30,y:0},{x:70,y:45}]
}
Then you can draw the triangle at [20,35] like this:
Note that the offsets (20,35) are added to each point in the triangle's position
function draw(myTriangle){
var x=myTriangle.x;
var y=myTriangle.y;
var points=myTriangle.points;
ctx.beginPath();
ctx.moveTo( x+points[0].x, y+points[0].y );
for(var i=1;i<points.length;i++){
ctx.lineTo( x+points[i].x, y+points[i].y );
}
ctx.closePath();
ctx.fill();
}
To drag the triangle, you listen for mouse events
In mousedown, check if the mouse is over the triangle. If yes, start the drag.
In mousemove, add the distance the user dragged since the last mousemove to the triangle's position.
In mouseup, stop the drag
In mousedown
Canvas has a nice built-in function to test if any specified point is inside a path like the triangle.
This function is context.isPointInPath(mouseX,mouseY) and it tests if mouseX/mouseY is inside the last drawn path.
If the mouse was pressed over the triangle, we set the isSelected flag to indicate the triangle should be dragged with every mousemove.
So the mousedown function looks like this:
function handleMouseDown(e){
// tell the browser we're using mousedown,
// so don't bother doing any browser stuff with this event
e.preventDefault();
// get the current mouseX,mouseY position
mouseX=parseInt(e.clientX-offsetX);
mouseY=parseInt(e.clientY-offsetY);
// test if mouseX,mouseY is inside the triangle
if(ctx.isPointInPath(startX,startY)){
// if yes, set the "isSelected" flag
// which indicates that the triangle should
// move with the mouse
isSelected=true;
}
}
In mousemove
The mousemove event is triggered about 20-30 times per second as the user moves the mouse.
In mousemove, if the triangle isSelected, we want to calculate how far the mouse has moved since the last mousemove event.
Then we want to change the x,y position of the triangle by the distance that the mouse has moved.
So the mousemove function looks like this:
function handleMouseMove(e){
// if the triangle wasn't selected during mousedown
// there's nothing to do, so just return
if(!isSelected){return;}
e.preventDefault();
// get the current mouse position
mouseX=parseInt(e.clientX-offsetX);
mouseY=parseInt(e.clientY-offsetY);
// calculate how far the mouse has moved since the last mousemove
var dx=mouseX-startX;
var dy=mouseY-startY;
// for next mousemove, reset the starting XY to the current XY
startX=mouseX;
startY=mouseY;
// move the triangle by the change in mouse position
myTriangle.x+=dx;
myTriangle.y+=dy;
// clear the canvas and
// redraw the triangle at its new position
context.clearRect(0,0,canvas.width,canvas.height);
draw(myTriangle);
}
In mouseup
In mouseup, the isSelected flag is cleared since the drag is over:
function handleMouseUp(e){
e.preventDefault();
isSelected=false;
}
Here's code for a more complex example with multiple shapes:
<!doctype html>
<html>
<head>
<link rel="stylesheet" type="text/css" media="all" href="css/reset.css" /> <!-- reset css -->
<script type="text/javascript" src="http://code.jquery.com/jquery.min.js"></script>
<style>
body{ background-color: ivory; }
#canvas{border:1px solid red;}
</style>
<script>
$(function(){
// vars related to canvas
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 scrollX=$canvas.scrollLeft();
var scrollY=$canvas.scrollTop();
var cw=canvas.width;
var ch=canvas.height;
// vars related to dragging
var isDown=false;
var startX;
var startY;
// save your shape-points in
var shapes=[];
var selectedShape=null;
// test shapes
addShape(50,50,[{x:0,y:20},{x:30,y:0},{x:70,y:45}],"blue","red");
addShape(100,100,
[{x:0,y:10},{x:30,y:10},{x:30,y:0},
{x:45,y:15},{x:30,y:30},{x:30,y:20},{x:0,y:20}],
"green","red");
// begin...
drawAll();
function addShape(x,y,points,fill,stroke){
shapes.push({x:x,y:y,points:points,fill:fill,stroke:stroke});
}
function define(shape){
var x=shape.x;
var y=shape.y;
var points=shape.points;
ctx.beginPath();
ctx.moveTo(x+points[0].x,y+points[0].y);
for(var i=1;i<points.length;i++){
ctx.lineTo(x+points[i].x,y+points[i].y);
}
ctx.closePath();
}
function draw(shape){
define(shape);
ctx.fillStyle=shape.fill;
ctx.fill();
ctx.strokeStyle=shape.stroke;
ctx.stroke();
}
function drawAll(){
ctx.clearRect(0,0,cw,ch);
for(var i=0;i<shapes.length;i++){
draw(shapes[i]);
}
}
function handleMouseDown(e){
e.preventDefault();
startX=parseInt(e.clientX-offsetX);
startY=parseInt(e.clientY-offsetY);
for(var i=0;i<shapes.length;i++){
define(shapes[i]);
if(ctx.isPointInPath(startX,startY)){
selectedShape=shapes[i];
isDown=true;
}
}
}
function handleMouseUp(e){
e.preventDefault();
isDown=false;
selectedShape=null;
}
function handleMouseOut(e){
e.preventDefault();
isDown=false;
selectedShape=null;
}
function handleMouseMove(e){
if(!isDown){return;}
e.preventDefault();
mouseX=parseInt(e.clientX-offsetX);
mouseY=parseInt(e.clientY-offsetY);
var dx=mouseX-startX;
var dy=mouseY-startY;
startX=mouseX;
startY=mouseY;
selectedShape.x+=dx;
selectedShape.y+=dy;
drawAll();
}
$("#canvas").mousedown(function(e){handleMouseDown(e);});
$("#canvas").mousemove(function(e){handleMouseMove(e);});
$("#canvas").mouseup(function(e){handleMouseUp(e);});
$("#canvas").mouseout(function(e){handleMouseOut(e);});
}); // end $(function(){});
</script>
</head>
<body>
<h4>Drag the shapes around the canvas</h4>
<canvas id="canvas" width=300 height=300></canvas>
</body>
</html>
I had the same problem some time ago, but I only needed lines, but I think you can use my solution for all the different shapes.
Basically you'll need to overlapping canvases, one that listens to the mouse events and draws the temporary shape, once the user releases the mouse button, you get the values and draw the shape on the original canvas. Here's a link to what I did: http://www.zhereicome.com/experiments/statics/drawtogether/
and the main js file:
http://www.zhereicome.com/experiments/statics/drawtogether/js/main.js
The important part is:
temp_canvas.onmousemove = function(e) {
//console.log(e);
if ( self.mouseDown ) {
self.drawTempLine(e.clientX, e.clientY);
}
}
temp_canvas.onmousedown = function(e) {
start = {
x: e.clientX,
y: e.clientY
}
self.mouseDown = true;
}
temp_canvas.onmouseup = function(e) {
end = {
x: e.clientX,
y: e.clientY
}
self.mouseDown = false;
self.sendToServer(); //When the user has finished drawing the line, send it to the server
}
I have a function sendToServer() because my project was a realtime multiplayer drawing app. In your case you'll have to replace it with a drawOnFinalCanvas();

Clipping images into different shapes like triangle, pentagon in html5 canvas?

I am developing a game in html 5 canvas where I have to clip images into many shapes. I also want to join these clipped images using mouse events. Can I only clip a square shape? Also, is it necessary to save x and y co-ordinates of each clipped image to know its position or is there any alternate way?
Here is an example to illustrate how to:
clip an image into 4 triangle pieces,
hit-test the pieces,
move the pieces using your mouse
Here's code and a Fiddle: http://jsfiddle.net/m1erickson/r59ch/
<!doctype html>
<html>
<head>
<link rel="stylesheet" type="text/css" media="all" href="css/reset.css" /> <!-- reset css -->
<script type="text/javascript" src="http://code.jquery.com/jquery.min.js"></script>
<style>
body{ background-color: ivory; }
#canvas{border:1px solid red;}
</style>
<script>
$(function(){
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 scrollX=$canvas.scrollLeft();
var scrollY=$canvas.scrollTop();
var isDown=false;
var startX;
var startY;
var parts=[];
var selectedPart=-1;
var img=new Image();
img.onload=function(){
var cx=img.width/2;
var cy=img.height/2;
var w=img.width;
var h=img.height;
parts.push({x:25,y:25,points:[{x:0,y:0},{x:cx,y:cy},{x:0,y:h}]});
parts.push({x:25,y:25,points:[{x:0,y:0},{x:cx,y:cy},{x:w,y:0}]});
parts.push({x:125,y:25,points:[{x:w,y:0},{x:cx,y:cy},{x:w,y:h}]});
parts.push({x:25,y:25,points:[{x:0,y:h},{x:cx,y:cy},{x:w,y:h}]});
drawAll();
}
img.src="https://dl.dropboxusercontent.com/u/139992952/stackoverflow/house100x100.png";
function drawAll(){
ctx.clearRect(0,0,canvas.width,canvas.height);
for(var i=0;i<parts.length;i++){
draw(parts[i]);
}
}
function draw(part){
ctx.save();
define(part);
ctx.clip();
ctx.drawImage(img,part.x,part.y);
ctx.stroke();
ctx.restore();
}
function hit(part,x,y){
define(part);
return(ctx.isPointInPath(x,y))
}
function move(part,x,y){
part.x+=x;
part.y+=y;
draw(part);
}
function define(part){
ctx.save();
ctx.translate(part.x,part.y);
ctx.beginPath();
var point=part.points[0];
ctx.moveTo(point.x,point.y);
for(var i=0;i<part.points.length;i++){
var point=part.points[i];
ctx.lineTo(point.x,point.y);
}
ctx.closePath();
ctx.restore();
}
function handleMouseDown(e){
e.preventDefault();
startX=parseInt(e.clientX-offsetX);
startY=parseInt(e.clientY-offsetY);
// Put your mousedown stuff here
for(var i=0;i<parts.length;i++){
if(hit(parts[i],startX,startY)){
isDown=true;
selectedPart=i;
return;
}
}
selectedPart=-1;
}
function handleMouseUp(e){
e.preventDefault();
mouseX=parseInt(e.clientX-offsetX);
mouseY=parseInt(e.clientY-offsetY);
// Put your mouseup stuff here
isDown=false;
}
function handleMouseOut(e){
e.preventDefault();
mouseX=parseInt(e.clientX-offsetX);
mouseY=parseInt(e.clientY-offsetY);
// Put your mouseOut stuff here
isDown=false;
}
function handleMouseMove(e){
if(!isDown){return;}
e.preventDefault();
mouseX=parseInt(e.clientX-offsetX);
mouseY=parseInt(e.clientY-offsetY);
// Put your mousemove stuff here
var dx=mouseX-startX;
var dy=mouseY-startY;
startX=mouseX;
startY=mouseY;
//
var part=parts[selectedPart];
move(part,dx,dy);
drawAll();
}
$("#canvas").mousedown(function(e){handleMouseDown(e);});
$("#canvas").mousemove(function(e){handleMouseMove(e);});
$("#canvas").mouseup(function(e){handleMouseUp(e);});
$("#canvas").mouseout(function(e){handleMouseOut(e);});
}); // end $(function(){});
</script>
</head>
<body>
<h4>Drag the right triangle-image into place</h4>
<canvas id="canvas" width=300 height=300></canvas>
</body>
</html>
You can define a path and use that for clipping:
ctx.save(); /// store current state of canvas incl. default clip mask
ctx.beginPath();
ctx.moveTo(0, 10);
ctx.lineTo(200, 10);
ctx.lineTo(100, 110);
ctx.clip(); /// will close the path implicit
/// draw graphics here
ctx.restore(); /// restore default infinite clipping mask
This will create a triangle. If you now draw on top the graphics will be clipped to this shape. Coordinates given here are of course just for example.
A small note: the different browsers may or may not anti-alias the clipping mask. If not the result may turn out "hard-edged".
(I recommend to use save()/restore() in connection with clip(). There is suppose to be a resetClip() method but this is rarely implemented in the browsers and is currently considered for removal from the specs.)
An alternative to using clip() is to use image pattern. Define a pattern with the image you want to clip, then create the path as above and use fill() with the pattern set as fillStyle. For this to work properly you need to translate() the canvas before filling to place the image pattern in the desired position.

Categories