I have the following simple blur algorithm:
for (y = 0; y < height; ++y) {
for (x = 0; x < width; ++x) {
total = 0
for (ky = -radius; ky <= radius; ++ky)
for (kx = -radius; kx <= radius; ++kx)
total += source(x + kx, y + ky)
dest(x, y) = total / (radius * 2 + 1) ^ 2
}
}
And I need to make this work with an array, generated by canvas with getImageData(). The problem is that the array from canvas is one dimentional, and the algorithm needs a two dimentional one, because "kx" and "ky" are distances from the current pixel, and in a two dimentional array you just change one of the indexes in order to move left or right on the grid, but in a one dimentional array (which is trying to represent a two-dim. one) you can't do that.
What do you think, how should I approach this problem? In my opinion, the entire problem is here:
total += source(x + kx, y + ky)
This line needs to get the correct pixels from the one-dimentional array and it should work fine.
P.S. I forgot to say that I'm trying to blur the image one channel at a time, like this:
for (var i=0; i<imgData.data.length; i+=4) {
red[index] = imgData.data[i];
green[index] = imgData.data[i+1];
blue[index] = imgData.data[i+2];
index++;
}
and I'm passing each array (red, green and blue) individually to the algorithm.
In order access a single dimensional array with two dimensional coordinates, you have the following formula:
var pixel = pixels[ ( ( y * width ) + x ) * 4 ];
The * 4 was added to account for the r,g,b,a values.
xResolution is the width if the image.
So source() would be:
function source( imageArray, x, y, imageWidth )
{
var pos = ( ( y * imageWidth ) + x ) * 4;
var val =
{
r : imageArray[ pos ],
g : imageArray[ pos + 1 ],
b : imageArray[ pos + 2 ],
a : imageArray[ pos + 3 ]
};
return val;
}
Related
I'm stumped on what is probably some pretty simple math. I need to get the X and Y coordinates from each tiles referenced ID. The grid below shows the order the ids are generated in. Each tile has a width and height of 32. Number ones x & y would be equal to (0,0). This is for a game I'm starting to make with canvas using a tileset.
1|2|3
4|5|6
7|8|9
So far for X, I've come up with...
(n % 3) * 32 - 32 // 3 is the width of the source image divded by 32
And for Y...
(n / 3) * 32
This is obviously wrong, but It's the closest I've come, and I don't think I'm too far off from the actual formula.
Here is my actual code so far:
function startGame() {
const canvas = document.getElementById("rpg");
const ctx = canvas.getContext("2d");
const tileSet = new Image();
tileSet.src = "dungeon_tiles.png";
let map = {
cols: 10,
rows: 10,
tsize: 32,
getTileX: function(counter, tiles) {
return ((tiles[counter] - 1) % 64) * 32;
},
getTileY: function(counter, tiles) {
return ((tiles[counter] - 1) / 64) * 32;
}
};
let counter = 0;
tileSet.onload = function() {
for (let c = 0; c < map.cols; c++) {
for (let r = 0; r < map.rows; r++) {
let x = map.getTileX(counter, mapObj.layers[0].data); // mapObj.layers[0].data is the array of values
let y = map.getTileY(counter, mapObj.layers[0].data);
counter += 1;
ctx.drawImage(
tileSet, // image
x, // source x
y, // source y
map.tsize, // source width
map.tsize, // source height
r * map.tsize, // target x
c * map.tsize, // target y
map.tsize, // target width
map.tsize // target height
);
}
}
};
}
If 1 is (0,0) and each tile is 32*32, then finding your horizontal position is a simple 32*(t-1) where t is your tile number. t-1 because your tiles start from 1 instead of 0. Now, you have 3 tiles per row so you want to reset every 3, so the final formula for your x is 32*((t-1)%3).
For the vertical position it's almost the same, but you want to increase your position by 32 only once every 3 tiles, so this is your y: 32*floor((t-1)/3).
floor((t-1)/3) is simply integer division since the numbers are always positive.
If I understand this correctly, you want to get the 1|2|3 values based on x, y correct? You can do something like this:
((y * total # of rows) + x) + 1
This would convert the 2D x, y index to a single index which is, as you stated, 1|2|3. This formula is based on your example where count starts at 1 and not 0. If you want to convert it to 0 base, just remove the + 1.
If you have the width and height, or probably location of input/character, you can have a GetX(int posX) and GetY(int posY) to get the x and y based on the position. Once you have converted the position to x, y values, use the formula above.
int GetX(int posX)
{
return (posX / 32);
}
int GetY(int posY)
{
return (posY / 32);
}
int GetIndex(int posX, int posY)
{
return ((GetY(posY) / totalRows) + GetX(posX)) + 1;
}
I have an object that sits at point 0,0. This object cannot share space with any other object of its type that may appear on top of it, next to it, above it, etc.. There may be more than a few of these objects present overlapping each other and i have no knowledge of where the other ones are placed until i try the collision detection method.
My thinking is that i'll use a collision detection along side a grid search. Along the lines of the photo below.
The object will first try its default best case location. If that doesn't work then it tries to the left, left-above, left-below, etc, until it has searched all the #1 positions. Then it moves onto the #2 positions and so on until it finds a place to drop the element where it won't be overlapping another.
this is the code i'm playing around with right now but it is choosing some very, very random locations for things. I'm pretty sure it isn't following the algorithm i described above.
for (let i = 0; i < 5 && this._hasCollisions(this._tagWrapper); i++) {
/**
* This algorithm explores positions inside nested boxes.
* The move algorithm behaves the following way. It goes,
* down, up, left, down, up, right * 2, repeat.
*
* For example this is how it works given the height of 5 and a width of 7
* numbers are expressed in the offset generated
* 1: 5,0 4: 5,-7 7: 5,7 10: 10,-14
* 2: -5,0 5: -5,-7 8: -5,7 11: -10,-14
* 3: 0,-7 6: 0,7 9: 0,-14
*/
// Calculate which box the collision detector is working in
// This happens every 9 iterations
let multiplier = (i / 9) + 1;
/**
* Get the x offset
*/
if (i % 3 === 0) {
// Clear the height offset on multiples of 3
xOffset = 0;
} else {
// Set the height to the multiplier
xOffset = this._tagWrapper.offsetWidth * multiplier;
}
if (i % 3 === 2) {
// Get the sequence 2, 5, 8, 11, 14, etc..
xOffset *= -1;
}
/**
* Get the y offset
*/
if (i > 2) {
// Set the width to a multiple of the multiplier and assign the existing negativeness
yOffset = this._tagWrapper.offsetHeight * multiplier * (yOffset > 0 ? 1 : -1);
}
if (i % 3 === 0) {
// Flip the sign every 3 numbers
yOffset *= -1;
}
console.log('iteration', i);
this._tagWrapper.style.top = (basePosition.y + yOffset) + 'px';
this._tagWrapper.style.left = (basePosition.x + xOffset) + 'px';
}
What is the best way to go about performing this search? I already hav
Something like this work? (most of the code is just for the visualization)
// just draw a table to visualize
var SIZE = 15;
for (var i = 0; i < SIZE; i++) {
$("#a").append("<tr>");
for (var j = 0; j < SIZE; j++) {
$("#a > tr").last().append("<td>.</td>");
}
}
// where to start searching from
var startX = 8;
var startY = 8;
function loop() {
// tell the world which grid we are on
$("#a > tr:nth-child(" + y + ") > td:nth-child(" + x + ")").css("backgroundColor", "red");
// check if done here!!! - x and y are our positions in the grid
// also do bounds checking here
if (isX) {
x = x + xDirection;
i--;
if (!i) {
// switch dimension
isX = !isX;
i = moveFor;
// switch direction
xDirection *= -1;
}
} else {
y = y + yDirection;
i--;
if (!i) {
// switch dimension
isX = !isX;
// increase the width / height we are spanning
moveFor += 1;
i = moveFor;
// switch direction
yDirection *= -1;
}
}
// jsut so that we have a nice animation
if (x > 0 && y > 0 && x <= SIZE && y <= SIZE) {
setTimeout(loop, 10)
}
}
var x = startX;
var y = startY;
var moveFor = 1;
// our step (down) counter
var i = moveFor;
var xDirection = -1;
var yDirection = -1;
// are we moving along x or y
var isX = true;
loop();
body {
font-family: monospace;
}
td {
height: 20px;
width: 20px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<table>
<tbody id="a"></tbody>
</table>
I would suggest using distance solution
point1 has x1 and y1
point2 has x2 and y2
var d = Math.sqrt( (x2-=x1)*x2 + (y2-=y1)*y2 );
link here: Get distance between two points in canvas
Here is an implementation of scanning points in a ring around a center point. You define the center point and the distance you want to sample and it returns the list of points in clock-wise order. It is in Python not JavaScript but it is simple enough that you can translate if needed.
jsfiddle here: http://jsfiddle.net/yw0w18m3/2/
I'm using paper.js to make a background image that looks somthing like this:
Basically, I'm creating a couple thousand triangles over a loop and rotating them on every other iteration.
function Tri(x, y, rotate) {
var tri = new Path([
new Point((x - 42), (y - 48)),
new Point((x - 42), y),
new Point(x, (y - 24)),
new Point((x - 42), (y - 48))
]);
tri.fillColor = {
hue: Math.random() * 360,
saturation: 0,
brightness: ( (( Math.random() ) * .95) + .3 )
};
if(rotate) { tri.rotate(180); }
}
for (var i = 0; i < 2000; i++) {
rotate = false;
if( i % 2 ) {
rotate = true;
}
new Tri(x, y, rotate);
x = x + 42;
if( x > (winWidth + 42) ) {
x = 0 ;
y = y + 24;
}
}
There seems to be a brief 1-2 second pause/freeze though while the shapes are being drawn. Is there a more efficient way to draw all the shapes first (or push to an array) then add that to the canvas all at once?
I based my code off of the example here: http://paperjs.org/examples/candy-crash/ (click "source" in the upper right corner).
Any help is much appreciated.
Thanks!
I would end up creating two triangles, one rotated, so they don't have to be built from new points each time. Then choose the correct triangle based on the rotation variable and clone it, as opposed to create points and a triangle from scratch each time. Finally, just change the position of the cloned triangle.
Last, I would correct the maxTri so it doesn't do more than it needs to. The paren should follow the 48, not the 24. You're doing an order of magnitude more triangles than needed.
Here's a link to the sketch.paperjs.org solution I created based on your code. I find sketch easier to use than jsfiddle for paper examples.
proto1 = new Path([
new Point(0, -24),
new Point(0, 24),
new Point(42, 0)
]);
proto1.closed = true;
proto2 = proto1.clone();
proto2.rotate(180);
function putTriangle(pos, rotate) {
var tri = (rotate ? proto2 : proto1).clone();
tri.position = pos;
tri.position = tri.position.subtract([21, 0])
tri.fillColor = {
hue: Math.random() * 360,
saturation: 0,
brightness: Math.random() * 0.5 + 0.5
}
}
var tris = [],
x = 42,
y = 24,
rotate,
winWidth = paper.view.size.width,
winHeight = paper.view.size.height,
rows = (winHeight + 48) / 24,
cols = (winWidth + 42) / 42,
numTri = rows * cols,
numTriOrig = (winWidth + 42) / 42 * (winHeight + 48 / 24);
//console.log(numTri, numTriOrig);
x = 0;
y = 0;
for (var row = 0; row < rows; row++) {
rowrotate = row % 2;
for (var col = 0; col <= cols; col++) {
rotate = rowrotate ^ col % 2;
putTriangle([x,y], rotate);
x += 42;
}
x = 0;
y = y + 24;
}
Two thoughts:
I see you use rotate to transform you triangles into place. This is an expensive operation. You could replace the rotate with a less geometric & more arithmetic calculation of the triangles orientation.
Also, I see is that the fill color is being changed with each triangle and state changes (like fill) are modestly expensive. You could group all the similarly colored triangles and draw them in a single batch.
I am creating a Tangram puzzle game using Javascript. And I need to detect when a user has drawn a circle (or circle like shape) with their finger. I have been able to gather hundreds (if not thousands) of x and y points with:
var touchX = event.targetTouches[0].pageX - canvas.offsetLeft;
var touchY = event.targetTouches[0].pageY - canvas.offsetTop;
I then push each x and y coordinate into an array:
touchMoveX.push(touchX);
touchMoveY.push(touchY);
I then loop through each array and create two points:
for(var i = 0; i < touchMoveX.length; i++)
{
for(var l=0; l < touchMoveY.length; l++)
{
var xPosition = touchMoveX[i];
var yPosition = touchMoveY[l];
var v1x = touchMoveX[i];
var v2x = touchMoveX[i + 1];
var v1y = touchMoveY[l];
var v2y = touchMoveY[l + 1];
Then using those two points, I use the following formula to figure out the angle between these two points in degrees:
var v1 = {x: v1x, y: v1y}, v2 = {x: v2x, y: v2y},
angleRad = Math.acos( (v1.x * v2.x + v1.y * v2.y) /
(Math.sqrt(v1.x*v1.x + v1.y*v1.y) * Math.sqrt(v2.x*v2.x + v2.y*v2.y) ) ),
angleDeg = angleRad * 180 / Math.PI;
I then sum up all of the angles and see if they are around 360 degrees.
But the above code I have described isn't working very well. Does someone out there have a better way to do this? Thank you very much.
yeah compute the average of all points (giving you a cheaply approximated center) then check if more than a certain percent of points are within a certain threshold. You can tune those values to adjust the precision until it feels right.
edit: Didn't consider that the circle could have multiple sizes, but you could just add another step computing the average of all distances. Adjusted the example for that.
var totalAmount = touchMoveX.length;
// sum up all coordinates and divide them by total length
// the average is a cheap approximation of the center.
var averageX = touchMoveX.reduce( function ( previous, current) {
return previous + current;
} ) / totalAmount ;
var averageY = touchMoveY.reduce( function ( previous, current) {
return previous + current;
} ) / totalAmount ;
// compute distance to approximated center from each point
var distances = touchMoveX.map ( function ( x, index ) {
var y = touchMoveY[index];
return Math.sqrt( Math.pow(x - averageX, 2) + Math.pow(y - averageY, 2) );
} );
// average of those distance is
var averageDistance = distances.reduce ( function ( previous, current ) {
return previous + current;
} ) / distances.length;
var min = averageDistance * 0.8;
var max = averageDistance * 1.2;
// filter out the ones not inside the min and max boundaries
var inRange = distances.filter ( function ( d ) {
return d > min && d < max;
} ).length;
var minPercentInRange = 80;
var percentInRange = inRange.length / totalAmount * 100;
// by the % of points within those boundaries we can guess if it's circle
if( percentInRange > minPercentInRange ) {
//it's probably a circle
}
So, i'm trying to implement hough transform, this version is 1-dimensional (its for all dims reduced to 1 dim optimization) version based on the minor properties.
Enclosed is my code, with a sample image... input and output.
Obvious question is what am i doing wrong. I've tripled check my logic and code and it looks good also my parameters. But obviously i'm missing on something.
Notice that the red pixels are supposed to be ellipses centers , while the blue pixels are edges to be removed (belong to the ellipse that conform to the mathematical equations).
also, i'm not interested in openCV / matlab / ocatve / etc.. usage (nothing against them).
Thank you very much!
var fs = require("fs"),
Canvas = require("canvas"),
Image = Canvas.Image;
var LEAST_REQUIRED_DISTANCE = 40, // LEAST required distance between 2 points , lets say smallest ellipse minor
LEAST_REQUIRED_ELLIPSES = 6, // number of found ellipse
arr_accum = [],
arr_edges = [],
edges_canvas,
xy,
x1y1,
x2y2,
x0,
y0,
a,
alpha,
d,
b,
max_votes,
cos_tau,
sin_tau_sqr,
f,
new_x0,
new_y0,
any_minor_dist,
max_minor,
i,
found_minor_in_accum,
arr_edges_len,
hough_file = 'sample_me2.jpg',
edges_canvas = drawImgToCanvasSync(hough_file); // make sure everything is black and white!
arr_edges = getEdgesArr(edges_canvas);
arr_edges_len = arr_edges.length;
var hough_canvas_img_data = edges_canvas.getContext('2d').getImageData(0, 0, edges_canvas.width,edges_canvas.height);
for(x1y1 = 0; x1y1 < arr_edges_len ; x1y1++){
if (arr_edges[x1y1].x === -1) { continue; }
for(x2y2 = 0 ; x2y2 < arr_edges_len; x2y2++){
if ((arr_edges[x2y2].x === -1) ||
(arr_edges[x2y2].x === arr_edges[x1y1].x && arr_edges[x2y2].y === arr_edges[x1y1].y)) { continue; }
if (distance(arr_edges[x1y1],arr_edges[x2y2]) > LEAST_REQUIRED_DISTANCE){
x0 = (arr_edges[x1y1].x + arr_edges[x2y2].x) / 2;
y0 = (arr_edges[x1y1].y + arr_edges[x2y2].y) / 2;
a = Math.sqrt((arr_edges[x1y1].x - arr_edges[x2y2].x) * (arr_edges[x1y1].x - arr_edges[x2y2].x) + (arr_edges[x1y1].y - arr_edges[x2y2].y) * (arr_edges[x1y1].y - arr_edges[x2y2].y)) / 2;
alpha = Math.atan((arr_edges[x2y2].y - arr_edges[x1y1].y) / (arr_edges[x2y2].x - arr_edges[x1y1].x));
for(xy = 0 ; xy < arr_edges_len; xy++){
if ((arr_edges[xy].x === -1) ||
(arr_edges[xy].x === arr_edges[x2y2].x && arr_edges[xy].y === arr_edges[x2y2].y) ||
(arr_edges[xy].x === arr_edges[x1y1].x && arr_edges[xy].y === arr_edges[x1y1].y)) { continue; }
d = distance({x: x0, y: y0},arr_edges[xy]);
if (d > LEAST_REQUIRED_DISTANCE){
f = distance(arr_edges[xy],arr_edges[x2y2]); // focus
cos_tau = (a * a + d * d - f * f) / (2 * a * d);
sin_tau_sqr = (1 - cos_tau * cos_tau);//Math.sqrt(1 - cos_tau * cos_tau); // getting sin out of cos
b = (a * a * d * d * sin_tau_sqr ) / (a * a - d * d * cos_tau * cos_tau);
b = Math.sqrt(b);
b = parseInt(b.toFixed(0));
d = parseInt(d.toFixed(0));
if (b > 0){
found_minor_in_accum = arr_accum.hasOwnProperty(b);
if (!found_minor_in_accum){
arr_accum[b] = {f: f, cos_tau: cos_tau, sin_tau_sqr: sin_tau_sqr, b: b, d: d, xy: xy, xy_point: JSON.stringify(arr_edges[xy]), x0: x0, y0: y0, accum: 0};
}
else{
arr_accum[b].accum++;
}
}// b
}// if2 - LEAST_REQUIRED_DISTANCE
}// for xy
max_votes = getMaxMinor(arr_accum);
// ONE ellipse has been detected
if (max_votes != null &&
(max_votes.max_votes > LEAST_REQUIRED_ELLIPSES)){
// output ellipse details
new_x0 = parseInt(arr_accum[max_votes.index].x0.toFixed(0)),
new_y0 = parseInt(arr_accum[max_votes.index].y0.toFixed(0));
setPixel(hough_canvas_img_data,new_x0,new_y0,255,0,0,255); // Red centers
// remove the pixels on the detected ellipse from edge pixel array
for (i=0; i < arr_edges.length; i++){
any_minor_dist = distance({x:new_x0, y: new_y0}, arr_edges[i]);
any_minor_dist = parseInt(any_minor_dist.toFixed(0));
max_minor = b;//Math.max(b,arr_accum[max_votes.index].d); // between the max and the min
// coloring in blue the edges we don't need
if (any_minor_dist <= max_minor){
setPixel(hough_canvas_img_data,arr_edges[i].x,arr_edges[i].y,0,0,255,255);
arr_edges[i] = {x: -1, y: -1};
}// if
}// for
}// if - LEAST_REQUIRED_ELLIPSES
// clear accumulated array
arr_accum = [];
}// if1 - LEAST_REQUIRED_DISTANCE
}// for x2y2
}// for xy
edges_canvas.getContext('2d').putImageData(hough_canvas_img_data, 0, 0);
writeCanvasToFile(edges_canvas, __dirname + '/hough.jpg', function() {
});
function getMaxMinor(accum_in){
var max_votes = -1,
max_votes_idx,
i,
accum_len = accum_in.length;
for(i in accum_in){
if (accum_in[i].accum > max_votes){
max_votes = accum_in[i].accum;
max_votes_idx = i;
} // if
}
if (max_votes > 0){
return {max_votes: max_votes, index: max_votes_idx};
}
return null;
}
function distance(point_a,point_b){
return Math.sqrt((point_a.x - point_b.x) * (point_a.x - point_b.x) + (point_a.y - point_b.y) * (point_a.y - point_b.y));
}
function getEdgesArr(canvas_in){
var x,
y,
width = canvas_in.width,
height = canvas_in.height,
pixel,
edges = [],
ctx = canvas_in.getContext('2d'),
img_data = ctx.getImageData(0, 0, width, height);
for(x = 0; x < width; x++){
for(y = 0; y < height; y++){
pixel = getPixel(img_data, x,y);
if (pixel.r !== 0 &&
pixel.g !== 0 &&
pixel.b !== 0 ){
edges.push({x: x, y: y});
}
} // for
}// for
return edges
} // getEdgesArr
function drawImgToCanvasSync(file) {
var data = fs.readFileSync(file)
var canvas = dataToCanvas(data);
return canvas;
}
function dataToCanvas(imagedata) {
img = new Canvas.Image();
img.src = new Buffer(imagedata, 'binary');
var canvas = new Canvas(img.width, img.height);
var ctx = canvas.getContext('2d');
ctx.patternQuality = "best";
ctx.drawImage(img, 0, 0, img.width, img.height,
0, 0, img.width, img.height);
return canvas;
}
function writeCanvasToFile(canvas, file, callback) {
var out = fs.createWriteStream(file)
var stream = canvas.createPNGStream();
stream.on('data', function(chunk) {
out.write(chunk);
});
stream.on('end', function() {
callback();
});
}
function setPixel(imageData, x, y, r, g, b, a) {
index = (x + y * imageData.width) * 4;
imageData.data[index+0] = r;
imageData.data[index+1] = g;
imageData.data[index+2] = b;
imageData.data[index+3] = a;
}
function getPixel(imageData, x, y) {
index = (x + y * imageData.width) * 4;
return {
r: imageData.data[index+0],
g: imageData.data[index+1],
b: imageData.data[index+2],
a: imageData.data[index+3]
}
}
It seems you try to implement the algorithm of Yonghong Xie; Qiang Ji (2002). A new efficient ellipse detection method 2. p. 957.
Ellipse removal suffers from several bugs
In your code, you perform the removal of found ellipse (step 12 of the original paper's algorithm) by resetting coordinates to {-1, -1}.
You need to add:
`if (arr_edges[x1y1].x === -1) break;`
at the end of the x2y2 block. Otherwise, the loop will consider -1, -1 as a white point.
More importantly, your algorithm consists in erasing every point which distance to the center is smaller than b. b supposedly is the minor axis half-length (per the original algorithm). But in your code, variable b actually is the latest (and not most frequent) half-length, and you erase points with a distance lower than b (instead of greater, since it's the minor axis). In other words, you clear all points inside a circle with a distance lower than latest computed axis.
Your sample image can actually be processed with a clearing of all points inside a circle with a distance lower than selected major axis with:
max_minor = arr_accum[max_votes.index].d;
Indeed, you don't have overlapping ellipses and they are spread enough. Please consider a better algorithm for overlapping or closer ellipses.
The algorithm mixes major and minor axes
Step 6 of the paper reads:
For each third pixel (x, y), if the distance between (x, y) and (x0,
y0) is greater than the required least distance for a pair of pixels
to be considered then carry out the following steps from (7) to (9).
This clearly is an approximation. If you do so, you will end up considering points further than the minor axis half length, and eventually on the major axis (with axes swapped). You should make sure the distance between the considered point and the tested ellipse center is smaller than currently considered major axis half-length (condition should be d <= a). This will help with the ellipse erasing part of the algorithm.
Also, if you also compare with the least distance for a pair of pixels, as per the original paper, 40 is too large for the smaller ellipse in your picture. The comment in your code is wrong, it should be at maximum half the smallest ellipse minor axis half-length.
LEAST_REQUIRED_ELLIPSES is too small
This parameter is also misnamed. It is the minimum number of votes an ellipse should get to be considered valid. Each vote corresponds to a pixel. So a value of 6 means that only 6+2 pixels make an ellipse. Since pixels coordinates are integers and you have more than 1 ellipse in your picture, the algorithm might detect ellipses that are not, and eventually clear edges (especially when combined with the buggy ellipse erasing algorithm). Based on tests, a value of 100 will find four of the five ellipses of your picture, while 80 will find them all. Smaller values will not find the proper centers of the ellipses.
Sample image is not black & white
Despite the comment, sample image is not exactly black and white. You should convert it or apply some threshold (e.g. RGB values greater than 10 instead of simply different form 0).
Diff of minimum changes to make it work is available here:
https://gist.github.com/pguyot/26149fec29ffa47f0cfb/revisions
Finally, please note that parseInt(x.toFixed(0)) could be rewritten Math.floor(x), and you probably want to not truncate all floats like this, but rather round them, and proceed where needed: the algorithm to erase the ellipse from the picture would benefit from non truncated values for the center coordinates. This code definitely could be improved further, for example it currently computes the distance between points x1y1 and x2y2 twice.