Find all black rectangles in canvas - javascript

Let's say I have an scanned paper with some black filled rectangles and I want to positionate all of them, getting their coordinates (X and Y) and their dimensions (Width and Height).
Is there any accurate algorithm which does what I need? I'm new to pixel processing with Javascript and Canvas and I need some help. Thanks in advance!

Identifying the x,y,width,height of all black rectangles involves these steps:
Use context.getImageData to get an array of all the r,g,b,a pixel information on the canvas.
Scan the pixel colors to find any one black pixel.
Find the bounding box of the black rectangle containing that one black pixel.
That bounding box will give you the x,y,width,height of one black rectangle.
Clear that black rectangle so that it is not found when searching for the next black rectangle.
Repeat step#1 until all the rectangles are identified.
Here's example code and a Demo: http://jsfiddle.net/m1erickson/3m0dL368/
<!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;}
#clips{border:1px solid blue; padding:5px;}
img{margin:3px;}
</style>
<script>
$(function(){
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,50,"2");
drawTestRect(170,30,50,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);
});
}); // end $(function(){});
</script>
</head>
<body>
<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>
</body>
</html>

Related

HTML5 Canvas: Replace Cursor with Crossing Lines

I'm trying to replace my cursor within the canvas with two perpendicular lines: one horizontal and one vertical. Example here: http://imgur.com/tUBkQn8
I need to do three things
1) Hide the cursor
2) Draw the crossing lines
3) Erase old lines on mouse move and draw new lines based on new mouse location
The following code is a non-performant way of achieving #2 and #3 (it's incredibly slow re-rendering the entire canvas every time the mouse moves):
function getMousePos(canvas, evt) {
var rect = canvas.getBoundingClientRect();
return {
x: evt.clientX - rect.left,
y: evt.clientY - rect.top
};
}
canvas.addEventListener('mousemove', function(evt) {
// clear the canvas
ctx.clearRect(0, 0, xsize, ysize);
// re-draw the base
drawCanvas();
var mousePos = getMousePos(canvas, evt);
// draw vertical line
ctx.beginPath();
ctx.moveTo(mousePos.x,0);
ctx.lineTo(mousePos.x,ysize);
ctx.stroke();
ctx.closePath();
// draw horizontal line
ctx.beginPath();
ctx.moveTo(0,mousePos.y);
ctx.lineTo(xsize,mousePos.y);
ctx.stroke();
ctx.closePath();
}, false);
So my questions:
1) How do I hide the cursor but still display the lines?
2) Is there a way I can just re-render the crossing lines ONLY, rather than the whole canvas every time the mouse moves? Thanks!
Here's one way to create a custom cursor and maintain performance:
Create a second canvas on top of your main drawing canvas.
Hide the system mouse cursor on both the canvases.
Draw/Move your X-cursor and nothing else on the top canvas
Example code and a Demo: http://jsfiddle.net/m1erickson/abokzhn6/
<!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; }
#wrapper{position:relative;}
#canvas,#cursor{position:absolute; cursor:none;}
#canvas{border:1px solid red;}
</style>
<script>
$(function(){
var canvas=document.getElementById("cursor");
var ctx=canvas.getContext("2d");
var $canvas=$("#cursor");
var canvasOffset=$canvas.offset();
var offsetX=canvasOffset.left;
var offsetY=canvasOffset.top;
$("#cursor").mousemove(function(e){handleMouseMove(e);});
function handleMouseMove(e){
e.preventDefault();
e.stopPropagation();
mouseX=parseInt(e.clientX-offsetX);
mouseY=parseInt(e.clientY-offsetY);
ctx.clearRect(0,0,canvas.width,canvas.height);
ctx.beginPath();
ctx.moveTo(mouseX,mouseY-15);
ctx.lineTo(mouseX,mouseY+15);
ctx.moveTo(mouseX-15,mouseY);
ctx.lineTo(mouseX+15,mouseY);
ctx.stroke();
}
}); // end $(function(){});
</script>
</head>
<body>
<div id='wrapper'>
<canvas id="canvas" width=300 height=300></canvas>
<canvas id="cursor" width=300 height=300></canvas>
</div>
</body>
</html>
Ad 1
I didn't test it but I think that you can hide cursor by changing cursor image in CSS to white 1x1 image.
Ad 2
Use http://kineticjs.com/

Scaling a polygon with the screen

I need to scale polygons with the size of my screen. Originally the polygons are created on a canvas with a size of 640x480. I would like to scale them down or up to a new resolution of whatever I desire and maintain their relative positions and sizes. Here is the code I am currently using:
this.scale = function (x1, y1) {
for (var i = 0; i < this.points.length; i++) {
this.points[i].x *= x1;
this.points[i].y *= y1;
}
}
Where x1 and y1 equal the new size of the screen divided by the original size. The issue I am facing is that the polygons appear to be offset from where they should be relative to an image. I also tried translating the vectors first before scaling and then translating them back to the original centroid using(pseudocode):
centroid = points / number of points
points = points - centroid
points = points * scale
points = points + centroid
This further offsets the polygons from where they should be relative to the image drawn on the canvas. Is there something I am missing here?
Edit: Added an example image of the issue: http://i.stack.imgur.com/EGeSw.jpg
Thanks!
Just multiply all points by the scaling factor of the canvas.
Example Code and a Demo: http://jsfiddle.net/m1erickson/xEqU5/
<!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 scale=1.00;
var img=new Image();
img.onload=start;
img.src="https://dl.dropboxusercontent.com/u/139992952/multple/scale8.jpg";
function start(){
drawAtScale();
}
function drawAtScale(){
canvas.width=img.width*scale;
canvas.height=img.height*scale;
drawImage();
drawPoly();
}
function drawImage(){
ctx.drawImage(img,0,0,img.width*scale,img.height*scale);
}
function drawPoly(){
ctx.save();
ctx.lineWidth=3;
ctx.beginPath();
ctx.moveTo(185*scale,46*scale);
ctx.lineTo(269*scale,53*scale);
ctx.lineTo(621*scale,170*scale);
ctx.lineTo(630*scale,329*scale);
ctx.lineTo(382*scale,304*scale);
ctx.lineTo(163*scale,234*scale);
ctx.closePath();
ctx.strokeStyle="gold";
ctx.stroke();
ctx.restore();
}
$("#smaller").click(function(){
scale/=1.10;
drawAtScale();
});
$("#larger").click(function(){
scale*=1.10;
drawAtScale();
});
}); // end $(function(){});
</script>
</head>
<body>
<button id="smaller">Scale Canvas Smaller</button>
<button id="larger">Scale Canvas Larger</button><br>
<canvas id="canvas" width=300 height=300></canvas>
</body>
</html>
I was able to fix my issue. I was scaling correctly however when the shapes were being created they were being created on a differently sized image which had black boxes at the top causing an offset.
Using a simple translation fixed the issue. The code looked something like this:
//My offset.
this.translate(-10, -50);
this.translate = function (x1, y1) {
for (var i = 0; i < this.points.length; i++) {
this.points[i].x += x1;
this.points[i].y += y1;
}
}

Canvas and events

Please help me to understand the events of the canvas.
Take for example two of the square. Each has its own area where you need to process such events:
Hover the square fills it with colour.
Click invokes filling the square third color and displays in a separate block, for example, the ID of the square.
Accordingly, it is possible to work with only one square. Click on the second square will reset the first square and output data from the second.
While moving the mouse in the area of one of the squares near the mouse, a pop-up window that displays the ID of the square.
And how can I make a link to a separate square? That is, to the user clicks a link that invokes the event, similar to a click on a separate square.
HTML code
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<script type="text/javascript" src="scripts/canvas.js"></script>
<script>
window.onload = function()
{
drawCanvas('mainCanvas');
};
</script>
</head>
<body style="margin: 0px;">
<canvas id="mainCanvas" width="300" height="200"></canvas>
<aside>ID of the square</aside>
</body>
</html>
JS code
function makeRect(x, y, w, h)
{
return { x: x, y: y, w: w, h: h };
}
function drawCanvas(canvasId)
{
//// General Declarations
var canvas = document.getElementById(canvasId);
var context = canvas.getContext('2d');
//// Color Declarations
var blackColor = 'rgba(0, 0, 0, 1)';
var whiteColor = 'rgba(255, 255, 255, 1)';
//// Frames
var frameOne = makeRect(64, 70, 50, 50);
var frameTwo = makeRect(194, 70, 50, 50);
//// RectangleOne Drawing
context.beginPath();
context.rect(frameOne.x, frameOne.y, 50, 50);
context.fillStyle = whiteColor;
context.fill();
context.strokeStyle = blackColor;
context.lineWidth = 1;
context.stroke();
//// RectangleTwo Drawing
context.beginPath();
context.rect(frameTwo.x, frameTwo.y, 50, 50);
context.fillStyle = whiteColor;
context.fill();
context.strokeStyle = blackColor;
context.lineWidth = 1;
context.stroke();
}
You ask a really broad question!
This will get you started:
About canvas rectangles
When you draw a rect on the canvas it becomes just “painted pixels” (like a painting of a rectangle on an artists canvas).
Nothing about the rect is “remembered” by canvas.
This means you can’t hit-test the rect to see if your mouse is hovering over that rect. The canvas doesn’t know anything about your rect.
Keeping track of rectangles
You must keep track of each rect’s properties yourself (x-coordinate, y-coordinate, width, height, color).
A convienient way to do this is creating a javascript object with the rect’s properties:
var rect1 = { x:30, y:30, width:50, height:25, color:"blue" };
Then use this rect1 object to draw the rect on your canvas
context.fillStyle=rect1.color;
context.fillRect( rect1.x, rect1.y, rect1.width, rect1.height );
Now you can always refer to rect1 to get the properties of your rectangle.
Mouse events
The canvas mouse events always relate to the canvas element itself, never to a rect drawn on the canvas.
Here’s how to listen to the mouse events on canvas:
// use jQuery to ask the browser to call `handleMouseMove` whenever the mouse is moved
$("#canvas").mousemove(function(e){handleMouseMove(e);});
// this is called every time your mouse moves
function handleMouseMove(e){
// get the mouses current X,Y position
// Note: offsetX/offsetY -- you must adjust for the offset of the canvas relative to the web page
mouseX=parseInt(e.clientX-offsetX);
mouseY=parseInt(e.clientY-offsetY);
}
Testing if the mouse is inside the rect
Remember that canvas knows nothing about your rect1, so use the rect1 object to “hit-test” whether the mouse is inside rect1:
if(
mouseX>=rect1.x &&
mouseX<=rect1.x+rect1.width &&
mouseY>=rect1.y &&
mouseY<=rect1.y+rect1.height
){
// the mouse is inside rect1
ctx.fillStyle="red";
ctx.fillRect(rect1.x,rect1.y,rect1.width,rect1.height);
}else{
// the mouse is not inside rect1
ctx.fillStyle=rect1.color;
ctx.fillRect(rect1.x,rect1.y,rect1.width,rect1.height);
}
This introduction should get you started coding…experiment for yourself!
Here’s a working demo: http://jsfiddle.net/m1erickson/tPjWX/
<!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 rect1 = { x:30, y:30, width:50, height:25, color:"blue" };
ctx.fillStyle=rect1.color;
ctx.fillRect(rect1.x,rect1.y,rect1.width,rect1.height);
function handleMouseMove(e){
mouseX=parseInt(e.clientX-offsetX);
mouseY=parseInt(e.clientY-offsetY);
if(
mouseX>=rect1.x &&
mouseX<=rect1.x+rect1.width &&
mouseY>=rect1.y &&
mouseY<=rect1.y+rect1.height
){
ctx.fillStyle="red";
ctx.fillRect(rect1.x,rect1.y,rect1.width,rect1.height);
}else{
ctx.fillStyle=rect1.color;
ctx.fillRect(rect1.x,rect1.y,rect1.width,rect1.height);
}
}
$("#canvas").mousemove(function(e){handleMouseMove(e);});
}); // end $(function(){});
</script>
</head>
<body>
<canvas id="canvas" width=300 height=300></canvas>
</body>
</html>
Canvas is only an element.
You can catch event for all canvas not for squares, circle, line...
But you can hold the position of square , line, circle and check "if ( mouse's position in square position) and redraw canvas
Personally, you can try to use SVG and you can catch the events for individual element.

Show converted mouse coordinates of an element with javascript

I'd like to show a mouse tooltip like this:
with a coordinate system relative to its image.
Whenever the mouse is hovered over one of the 75x75 cells, the position is displayed in text. I can only show the mouse's raw coordinates, but can't figure out the math to display it like it is in the picture.
I'm open to HTML5 implementations as well.
Here’s how to convert mouse coordinates to cell coordinates and display a tooltip
This math calculates which 75x75 cell your mouse is inside:
var col=parseInt(mouseX/75);
var row=parseInt(mouseY/75);
And here is the math to calculate a tip rectangle in the upper-right of that cell:
var tipX=tipCol*75+75-tipWidth;
var tipY=tipRow*75;
You can use canvas to draw the tip inside the cell at your calculated coordinates:
function tip(x,y){
var tipX=tipCol*75+75-tipWidth;
var tipY=tipRow*75;
ctx.beginPath();
ctx.rect(tipX,tipY,tipWidth,tipHeight);
ctx.fillStyle="ivory";
ctx.fill();
ctx.fillStyle="blue";
ctx.fillText(tipCol+","+tipRow,tipX+2,tipY+17);
}
Here is code and a Fiddle: http://jsfiddle.net/m1erickson/9V5QK/
<!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; padding:25px;}
#canvas{border:1px solid red;}
</style>
<script>
$(function(){
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 tipWidth=35;
var tipHeight=22;
var tipRow;
var tipCol;
ctx.font="14pt verdana";
draw();
function draw(){
// you would just draw your image here
// ctx.drawImage(0,0,image.width,image.height);
// but for illustration, this just recreates your image
ctx.beginPath();
ctx.rect(0,0,375,225);
for(var x=1;x<5;x++){ ctx.moveTo(x*75,0); ctx.lineTo(x*75,canvas.height); }
for(var y=1;y<3;y++){ ctx.moveTo(0,y*75); ctx.lineTo(canvas.width,y*75); }
ctx.fillStyle="black";
ctx.fill();
ctx.strokeStyle="gray";
ctx.lineWidth=2;
ctx.stroke();
}
function tip(x,y){
var tipX=tipCol*75+75-tipWidth;
var tipY=tipRow*75;
ctx.beginPath();
ctx.rect(tipX,tipY,tipWidth,tipHeight);
ctx.fillStyle="ivory";
ctx.fill();
ctx.fillStyle="blue";
ctx.fillText(tipCol+","+tipRow,tipX+2,tipY+17);
}
function handleMouseMove(e){
mouseX=parseInt(e.clientX-offsetX);
mouseY=parseInt(e.clientY-offsetY);
$("#movelog").html("Move: "+ mouseX + " / " + mouseY);
// Put your mousemove stuff here
var col=parseInt(mouseX/75);
var row=parseInt(mouseY/75);
if(!(row==tipRow && col==tipCol)){
tipCol=col;
tipRow=row;
draw();
tip();
}
}
$("#canvas").mousemove(function(e){handleMouseMove(e);});
}); // end $(function(){});
</script>
</head>
<body>
<p>Move mouse over grid to display current cell</p>
<p id="movelog">Move</p>
<canvas id="canvas" width=375 height=225></canvas>
</body>
</html>

Moving an object around on dynamic canvas

There are plenty of questions about how to move objects around in the first place, but this is different: suppose that I (obviously) want my object to go "in front" of the background, and that my background is being changed/generated all the time. Therefore, when I move the object from one place to another, I want what would have been generated if the object wasn't blocking it to appear where it was before. How should I handle this? Should I keep a record of "what would have been generated" where the object is and plop it on when it moves, or is there a less annoying way to get around this?
Canvas has a drawing setting to do exactly what you want !
You can use the canvas context's globalCompositeOperation.
This allows you to move your "front" image on top of your "background-changing" image.
Here is some code and a Fiddle: http://jsfiddle.net/m1erickson/TrXB4/
<!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; background-color:black; }
canvas{border:1px solid red;}
</style>
<script>
$(function(){
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var sun = new Image();
var moon = new Image();
var earth = new Image();
function init(){
sun.src = 'http://cdn-img.easyicon.cn/png/36/3642.png';
moon.src = 'http://openclipart.org/people/purzen/purzen_A_cartoon_moon_rocket.svg';
earth.src = 'http://iconbug.com/data/26/256/e5b23e861bc9979da6c3d03b75862b7e.png';
setInterval(draw,100);
}
function draw() {
var ctx = document.getElementById('canvas').getContext('2d');
ctx.globalCompositeOperation = 'destination-over';
ctx.clearRect(0,0,350,350); // clear canvas
ctx.fillStyle = 'rgba(0,0,0,0.4)';
ctx.strokeStyle = 'rgba(0,153,255,0.4)';
ctx.save();
ctx.translate(150,150);
// Earth
var time = new Date();
ctx.rotate( ((2*Math.PI)/60)*time.getSeconds() + ((2*Math.PI)/60000)*time.getMilliseconds() );
ctx.translate(105,0);
ctx.fillRect(0,-12,50,24); // Shadow
ctx.drawImage(earth,-12,-12,48,48);
// Moon
ctx.save();
ctx.rotate( ((2*Math.PI)/6)*time.getSeconds() + ((2*Math.PI)/6000)*time.getMilliseconds() );
ctx.translate(0,28.5);
ctx.drawImage(moon,-3.5,-3.5,16,32);
ctx.restore();
ctx.restore();
ctx.beginPath();
ctx.arc(150,150,105,0,Math.PI*2,false); // Earth orbit
ctx.stroke();
ctx.drawImage(sun,100,100,96,96);
}
init();
}); // end $(function(){});
</script>
</head>
<body>
<canvas id="canvas" width=350 height=350></canvas>
</body>
</html>

Categories

Resources