Am simulating a shape click and trying to do drag/drop inside canvas using Sahi framework.I have found some references that by looping through an array containing that shapes position/sizes , we get whether shape is clicked.While testing other website using sahi framework/native jaascript , how can it be achieved ? I wanted to select that shape inside canvas and do drag and drop inside canvas.This is my goal.

- 31
- 2
- 7
-
Please post an example image that you are attempting to analyze. – markE Dec 21 '15 at 05:07
-
Am trying to achieve the same in the link - simonsarris.com/blog/140-canvas-moving-selectable-shapes..In this link , there's a canvas with 2 rectangles placed.am trying to do a click in this site and drag the rectangle.Though it already have a source code ,am trying to apply the logic which you have quoted--to scan all the pixels from imagedata.But Data array from imagedata is coming with zero values.I am trying to use the logic you have mentioned because i wont be knowing shape's position or size when testing customer applications. – Aish Dec 21 '15 at 15:05
-
To discover the position of unknown rectangles, you will have to use the `getImageData` script in my answer. If the rects are not black (as in my example) you will have to adjust the test to find your desired rectangles. You could test for non-zero alpha pixels if you don't know the color of the rects. – markE Dec 21 '15 at 17:22
-
Actual customer application is having a satellite map in canvas and on top of it it has a rectangle shape.So when user do the drag/drop of that shape to another position in canvas,we should be able to record that and playback.Hope if user clicks somewhere other than this shape (means in the satellite map) , then also i will get the non zero alpha values based on opacity.So i cannot determine whether user clicked on a rectangle or the satellite map.I can get the rgba values at the point when user clicks.So based on that data how can i determine that its a rectangle in which they clicked ? – Aish Dec 22 '15 at 12:33
-
Your explanations of the images are difficult to understand. **Please take a screenshot and post it for us to look at.** – markE Dec 22 '15 at 17:00
-
Sorry am new to posting here.could you please tell me how to post a screenshot? – Aish Dec 22 '15 at 17:18
-
Sure. Click the "edit" link under your question. Then add an image by clicking the [image button](https://dl.dropboxusercontent.com/u/139992952/multple/temp-1.png). Then drag your screenshot onto the add-image dialog. – markE Dec 22 '15 at 17:47
-
Thanks Mark.Have added the screenshot..User can drag and drop the rectangle to anywhere inside canvas.So we have to record that useraction.For this i need to find out the element means the rectangle here and simulate the drag drop over canvas.Am not ableto identify that rectangle. – Aish Dec 23 '15 at 05:33
-
You will need to develop a plan to identify the rectangle. Will it always be a red stroke containing a white cross with the same number of blue rectangles? How will you identify the rectangle -- not in code, but rather what characteristics make the rectangle unique? – markE Dec 23 '15 at 06:11
-
As of now, i have seen with same red stroke with same number of blue rectangles with white cross..But am not sure it might get change..Its with one of the customer application.I dont have access to this data actually.So i have to devise the generic strategy to identify it so that it could work for n number of applications and not specific to this one . – Aish Dec 23 '15 at 07:08
1 Answers
In html5 canvas, A "Shape" is defined and drawn using a Path.
You define a shape by:
- Declaring that you a beginning a new path:
context.beginPath
Using one or more of the path definition commands:
moveTo, lineTo, arc
, etc.// define a triangle path context.beginPath(); context.moveTo(x+50,y+50); context.lineTo(x+100,y+100); context.lineTo(x+0,y+100); // see not below about .closePath() context.closePath();
Note: context.closePath
is NOT used to close a context.beginPath
command -- it is NOT like a closing parenthesis! Instead, it is used to draw a line from the last coordinate to the starting coordinate -- to "enclose" the path. In our example, it draws an "enclosing" line from [x+0,y+100] to [x+50,y+50].
Just defining a path will not cause it to be drawn onto the canvas. To actually draw the shape onto the canvas you can:
- Stroke the outline of the path, and / or
Fill the inside of the path.
context.stroke(); context.fill();
For example, here's how to define and draw a triangle. You can also use offset variables ([x,y] in the example) to reposition the triangle anywhere on the canvas.
// set the offset of the triangle
var x=30;
var y=40;
// define the path
context.beginPath();
context.moveTo(x+50,y+50);
context.lineTo(x+100,y+100);
context.lineTo(x+0,y+100);
context.closePath();
// stroke the path
context.stroke();
// if desired, you can also fill the inside of the path
context.fill();
To drag a shape, you must test whether the mouse is over that shape. You can "hit-test" the most recently defined shape using context.isPointInPath
.
Be sure you read that carefully!
You can hit-test the most recently "defined" path. If you define and draw multiple paths then isPointInPath
will only hit-test the last defined path.
if(context.isPointInPath(mouseX,mouseY)){
console.log('Yes, the mouse is in the triangle.');
}
Also note that you don't have to re-stroke the path being tested so your drawings won't be altered by the hit-testing process. So you hit-test multiple paths by:
- Define one of the paths
- Hit test with
isPointInPath(mouseX,mouseY)
- Repeat step#1 for the next path (path==shape)
Nothing drawn to the canvas can be moved -- everything is as permanent as dried paint. So to "move" a shape on canvas you clear the entire canvas and redraw the shape in its moved position:
// clear the canvas
context.clearRect(canvas.width,canvas.height);
// move the canvas by changing it's offsets
x+=20;
y+=30;
// redefine and restroke the shape
context.beginPath();
context.moveTo(x+50,y+50);
context.lineTo(x+100,y+100);
context.lineTo(x+0,y+100);
context.closePath();
context.stroke();
To make redefining and re-stroking the shape more reusable you can put the code in a function:
function myTriangle(alsoStroke){
context.beginPath();
context.moveTo(x+50,y+50);
context.lineTo(x+100,y+100);
context.lineTo(x+0,y+100);
context.closePath();
if(alsoStroke){
context.stroke();
}
}
You can read more about dragging a shape in this previous post. Since you can't move a shape you can't "drag" a shape either. You must again clear the canvas and redraw it in its newly dragged position.
To drag a shape you need to listen to 4 mouse events.
In mousedown: Check if the mouse is over the shape, and, if yes, set a flag indicating a drag has begun. To check if the mouse is over the shape, you can use canvas context's isPointInPath method which tests if an [x,y] point is inside the most recently drawn path.
In mousemove: If the dragging flag is set (indicating a drag is in process), change the position of the selected text by the distance the user has dragged and redraw the shape in its new position
In mouseup or in mouseout: The drag is over so clear the dragging flag.
Here's 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 startX,startY;
var poly={
x:0,
y:0,
points:[{x:50,y:50},{x:75,y:25},{x:100,y:50},{x:75,y:125},{x:50,y:50}],
}
ctx.fillStyle='skyblue';
ctx.strokeStyle='gray';
ctx.lineWidth=3;
draw();
// listen to 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);});
function draw(){
ctx.clearRect(0,0,cw,ch);
define();
ctx.fill();
ctx.stroke()
}
function define(){
ctx.beginPath();
ctx.moveTo(poly.points[0].x+poly.x,poly.points[0].y+poly.y);
for(var i=0;i<poly.points.length;i++){
ctx.lineTo(poly.points[i].x+poly.x,poly.points[i].y+poly.y);
}
ctx.closePath();
}
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
define();
if(ctx.isPointInPath(startX,startY)){
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);
// Put your mousemove stuff here
var dx=mouseX-startX;
var dy=mouseY-startY;
startX=mouseX;
startY=mouseY;
poly.x+=dx;
poly.y+=dy;
draw();
}
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>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<h4>Drag the polygon</h4>
<canvas id="canvas" width=300 height=300></canvas>
[ Addition: Discovering Rectangles using context.getImageData ]
If you don't have the shape's position/size and you instead have an image of a shape then you must get the position/size by searching the pixels. Here's an example showing how to isolate rectangles by searching pixels:
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var cw, ch;
// background definition
// OPTION: look at the top-left pixel and assume == background
// then set these vars automatically
var isTransparent = false;
var bkColor = {
r: 255,
g: 255,
b: 255
};
var bkFillColor = "rgb(" + bkColor.r + "," + bkColor.g + "," + bkColor.b + ")";
cw = canvas.width;
ch = canvas.height;
ctx.fillStyle = "white";
ctx.fillRect(0, 0, canvas.width, canvas.height);
drawTestRect(30, 30, 50, 50, "1");
drawTestRect(100, 30, 50, 30, "2");
drawTestRect(170, 30, 30, 50, "3");
function drawTestRect(x, y, w, h, label) {
ctx.fillStyle = "black";
ctx.fillRect(x, y, w, h);
ctx.fillStyle = "white";
ctx.font = "24px verdana";
ctx.fillText(label, x + 10, y + 25);
}
function clipBox(data) {
var pos = findEdge(data);
if (!pos.valid) {
return;
}
var bb = findBoundary(pos, data);
alert("Found target at "+bb.x+"/"+bb.y+", size: "+bb.width+"/"+bb.height);
clipToImage(bb.x, bb.y, bb.width, bb.height);
if (isTransparent) {
// clear the clipped area
// plus a few pixels to clear any anti-aliasing
ctx.clearRect(bb.x - 2, bb.y - 2, bb.width + 4, bb.height + 4);
} else {
// fill the clipped area with the bkColor
// plus a few pixels to clear any anti-aliasing
ctx.fillStyle = bkFillColor;
ctx.fillRect(bb.x - 2, bb.y - 2, bb.width + 4, bb.height + 4);
}
}
function xyIsInImage(data, x, y) {
// find the starting index of the r,g,b,a of pixel x,y
var start = (y * cw + x) * 4;
if (isTransparent) {
return (data[start + 3] > 25);
} else {
var r = data[start + 0];
var g = data[start + 1];
var b = data[start + 2];
var a = data[start + 3]; // pixel alpha (opacity)
var deltaR = Math.abs(bkColor.r - r);
var deltaG = Math.abs(bkColor.g - g);
var deltaB = Math.abs(bkColor.b - b);
return (!(deltaR < 5 && deltaG < 5 && deltaB < 5 && a > 25));
}
}
function findEdge(data) {
for (var y = 0; y < ch; y++) {
for (var x = 0; x < cw; x++) {
if (xyIsInImage(data, x, y)) {
return ({
x: x,
y: y,
valid: true
});
}
}
}
return ({
x: -100,
y: -100,
valid: false
});
}
function findBoundary(pos, data) {
var x0 = x1 = pos.x;
var y0 = y1 = pos.y;
while (y1 <= ch && xyIsInImage(data, x1, y1)) {
y1++;
}
var x2 = x1;
var y2 = y1 - 1;
while (x2 <= cw && xyIsInImage(data, x2, y2)) {
x2++;
}
return ({
x: x0,
y: y0,
width: x2 - x0,
height: y2 - y0 + 1
});
}
function drawLine(x1, y1, x2, y2) {
ctx.beginPath();
ctx.moveTo(x1, y1);
ctx.lineTo(x2, y2);
ctx.strokeStyle = "red";
ctx.lineWidth = 0.50;
ctx.stroke();
}
function clipToImage(x, y, w, h) {
// don't save anti-alias slivers
if (w < 3 || h < 3) {
return;
}
// save clipped area to an img element
var tempCanvas = document.createElement("canvas");
var tempCtx = tempCanvas.getContext("2d");
tempCanvas.width = w;
tempCanvas.height = h;
tempCtx.drawImage(canvas, x, y, w, h, 0, 0, w, h);
var image = new Image();
image.width = w;
image.height = h;
image.src = tempCanvas.toDataURL();
$("#clips").append(image);
}
$("#unbox").click(function () {
var imgData = ctx.getImageData(0, 0, cw, ch);
var data = imgData.data;
clipBox(data);
});
body {
background-color: ivory;
}
canvas {
border:1px solid red;
}
#clips {
border:1px solid blue;
padding:5px;
}
img {
margin:3px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<button id="unbox">Clip next sub-image</button><br>
<canvas id="canvas" width=300 height=150></canvas><br>
<h4>Below are images clipped from the canvas above.</h4><br>
<div id="clips"></div>
[Addition: discovering the bounds of a reddishness stroked rectangle]
You can test for "reddishness" by checking if the red component value of a pixel is much larger than the green & blue component values.
function xyIsInImage(data, x, y) {
// find the starting index of the r,g,b,a of pixel x,y
var n = (y * cw + x) * 4;
return(data[n+3]>240 && // this pixel is mostly opaque
data[n]-data[n+1]>180 && // this pixel is more reddish than green
data[n]-data[n+2]>180 // this pixel is more reddish then blue
);
Then use this reddishness test to find the bounds of the reddish stroked rectangle:
- Find the theoretical top-left pixel of the red rect. The discovered pixel is probably on the top of the rect. It might not be on the left of the rect because your image shows the rect's corner pixels are much less reddish. So declare the
y
value as the top of the rect. - Move towards the center of the rect by a few pixels.
- Test each vertical pixel downward until you find the bottom reddish border of the stroked rect.
- Test each horizontal pixel rightward until you find the right reddish border of the stroked rect.
- Test each horizontal pixel leftward until you find the left reddish border of the stroked rect.
Now you have found the top-left and bottom-right bounds of the rect.
Here's example code and a demo using your image:
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;
var img=new Image();
img.crossOrigin='anonymous';
img.onload=start;
img.src="https://dl.dropboxusercontent.com/u/139992952/multple/raw.png";
function start(){
cw=canvas.width=img.width;
ch=canvas.height=img.height;
ctx.drawImage(img,0,0);
var data=ctx.getImageData(0,0,cw,ch).data;
var edge=findEdge(data);
var top=edge.y;
var x,y,left,bottom,right;
var off=25;
for(var y=edge.y+off;y<ch;y++){if(xyIsInImage(data,edge.x+off,y)){bottom=y; break;}}
for(var x=edge.x+off;x<cw;x++){if(xyIsInImage(data,x,edge.y+off)){right=x;break;}}
for(var x=edge.x+off;x>=0;x--){if(xyIsInImage(data,x,edge.y+off)){left=x;break;}}
dot({x:left,y:top});
dot({x:right,y:bottom});
}
//
function dot(pt){
ctx.beginPath();
ctx.arc(pt.x,pt.y,4,0,Math.PI*2);
ctx.closePath();
ctx.fillStyle='red';
ctx.fill();
ctx.strokeStyle='gold';
ctx.lineWidth=2;
ctx.stroke();
}
function xyIsInImage(data, x, y) {
// find the starting index of the r,g,b,a of pixel x,y
var n = (y * cw + x) * 4;
return(data[n+3]>240 &&
data[n]-data[n+1]>180 &&
data[n]-data[n+2]>180
);
}
function findEdge(data) {
for (var y = 0; y < ch; y++) {
for (var x = 0; x < cw; x++) {
if(xyIsInImage(data, x, y)){ return ({x:x,y:y,valid:true}); }
}
}
return ({x:-100,y:-100,valid:false});
}
body{ background-color: ivory; }
#canvas{border:1px solid red; margin:0 auto; }
<h4>Red-gold dots are on top-left & bottom-right of target rect.</h4>
<canvas id="canvas" width=300 height=300></canvas>
-
you can also add a mouseLeave/mouseEnter event on the canvas, so if you drag the object and your mousecursor moves out of the canvas nothing will break. – Flocke Dec 19 '15 at 15:38
-
Thanks for the detailed explanation with code.In this code snippet , you know the sizes like height,width and shapes's position x,y to process futher while repainting canvas.But while testing other website,i will have access to the canvas as a whole and event(mouse position where the user has clicked).So in this case,i don know the sizes/position of already drawn rectangle inside canvas.So how to find that its a rectangle which is clicked and how to redraw it without knowing its size ? – Aish Dec 20 '15 at 07:21
-
If you don't have the shape's position/size and you instead have an image of a shape then you must get the position/size by searching the pixels. Here's an example showing how to isolate rectangles by searching pixels: http://jsfiddle.net/m1erickson/3m0dL368/ – markE Dec 20 '15 at 15:34
-
Thanks Mark.I am trying to use the same as reference.I am getting data from imagedata and passing the same to find whether imageisinXY function.but am keep on getting the values as zero in imagedata.Am trying to simulate the click/drag using the same with the demo in this link --http://simonsarris.com/blog/140-canvas-moving-selectable-shapes. – Aish Dec 21 '15 at 05:03
-
I've added to my answer showing how to find the bounds of a reddish stroked rectangle – markE Dec 24 '15 at 21:41