Related
I tried to find such an algorithm but I did not succeed so far, I hope you can help me. Thanks in advance!
So my problem is: I have a canvas. By clicking a button I can create a rectangle. When I have one rectangle, it is drawn to occupy most of the canvas. When I push it again, a new rectangle appears and then the two rectangles are redrawn to occupy each almost half of the available canvas. Then a third rectangle is created, and the three rectangles are distributed in two rows, in order to maintain certain proportions between length and width of each rectangle, occupying as much canvas as possible. And so on for n rectangles see image:
Is there any well-known algortighm(s) for doing this?
Thank you!
Your rectangles are all of the same size, so your task is to find the optimal number of rows and columns for your layout. The layout consists of a bounding rectangle, the number of rectangles k and a preferred width-to-height ratio α* plus a gap d between the rectangles:
const box = {
count: 7,
alpha: 1.2,
gap: 8,
nrow: 0, // to be determined ...
ncol: 0, // ...
width: 0, // ...
height: 0, // ...
};
A very simple algorithm would find a first guess for the number of columns m based on the input and then try out several m to find the maxium area while maintaining a reasonable aspect ratio.
Your overall area is A = W · H. The area of one box is a = w · h or a = h / α where α = w / h. A theoretical maximum area (if the gap were 0) is a* = A / k, where k is the number of rectangles. You can use this to estimate a first guess for w and there fore for the number of columns, m.
The "score" for our optimization is the area, which must be maximized. In order not to allow unpleasant aspect ratios, we incorporate the current aspect ratio α by dividing the score by the square of the error (α − α*)².
Here's a way this could be implemented:
function layout(canvas) {
let A = canvas.width * canvas.height;
let a = A / box.count;
let x = Math.sqrt(a * box.alpha);
let m = (canvas.width / x) | 0 - 2;
if (m < 1) m = 1;
let best = 0.0;
for (;;) {
let n = (((box.count - 1) / m) | 0) + 1;
let w = (canvas.width - (m - 1) * box.gap) / m;
let h = (canvas.height - (n - 1) * box.gap) / n;
if (h < 0) h = 0;
if (w < 0) w = 0;
let alpha = w / h;
let area = box.count * (w + box.gap) * (h + box.gap);
let dalpha = alpha - box.alpha;
let score = area / (dalpha * dalpha);
if (score < best) break;
box.width = w;
box.height = h;
box.ncol = m;
box.nrow = n;
best = score;
m++;
}
}
There's certainly room for improvement, for example when guessing the first shot. You can play with a rough implementation on jsbin.
Let I be a w x h frame from a 360° video stream.
Let R be a red rectangle on that frame. R is smaller than the width of the image.
To compute the centroid of this rectangle we need to distinguish two cases:
case 1 where R is on the edges
case 2 where R is fully inside the frame
As you can see there will be a problem to compute the centroid with classical methods in case 1. Please note that I only care about horizontal overlapping.
For the moment I am doing like this. First we detect the first point we find and use it as a reference, then we normalize dx which is the difference between a point and the reference and then we accumulate:
width = frame.width
rectangle_pixel = (255,0,0)
first_found_coord = (-1,-1)
centroid = (0,0)
centroid_count = 0
for pixel, coordinates in image:
if(pixel != rectangle_pixel):
continue
if(first_found_coord == (-1,-1)):
first_found_coord = coordinates
centroid = coordinates
continue
dx = coordinates.x - first_found_coord.x
if(dx > width/2):
dx -= width
else if(dx < - width/2):
dx -= width
centroid += (dx, coordinates.y)
centroid_count++
final_centroid = centroid / centroid_count
But it doesn't work as expected. Where is the problem, is there a faster solution ?
Here is a solution based on transition points, i.e when you move from red to non red, or in the other way. To capture the horizontal center, I needed the following information :
gridSize.x : width of the space where rectangles can live.
w : width of your rectangle.
PseudoCode:
redPixel = (255,0,0);
transitionPoints = [];
betweenTransitionsColor = -1;
// take i and i+1 pixel+position, increment i by one at each step.
for (pixel1, P1), (pixel1, P2) in gridX : // horizontal points for a fixed `y`
if pixel1 != pixel2: // one is red, the other white
nonRedPosition = (pixel1 != redPixel ? P1 : P2)
transitionPoints.append(nonRedPosition)
continue
if(transitionPoints.length == 1 && betweenTransitionsColor == -1):
betweenTransitionsColor = pixel2
if transitionPoints.length == 2:
break
//Case where your rectangle is on the edge (left or right)
if(transitionPoints.length == 1):
if(abs(transitionPoints[0].x - w) < 2):
xCenter = w/2
else:
xCenter = gridSize.x - w/2
else:
[tP1, tP2] = transitionPoints
// case 1 : The rectangle is splitted
if betweenTransitionsColor != redPixel:
xCenter = (tP2.x - gridSize.x + tP1.x)/2
else:
xCenter = (tP1.x + tP1.x)/2
Note :
you must start at a y position where you could get red pixels. This shouldn't be very hard to achieve. If your rectangle's height is bigger than gridSize.y/2, you can begin at gridSize.y/2. Otherwise, you can search for a first red pixel, and set y to the corresponding position.
Since I'm computing the bounding boxes in the same scope, I do it in two steps.
I first accumulate the coordinates of the pixels of interest. Then when I'm checking for overlapping bounding boxes, I subtract the with for each overlapping colors on the right half of the image. So I end up with a completed but slided rectangle.
At the end I divide by the number of point found per color. If the result is negative I shift it by the size of width of the image.
Alternatively:
def get_centroid(image, interest_color):
acc_x = 0
acc_y = 0
count = 0
first_pixel = (0,0)
for (x,y, color) in image:
if(color not in interest_color):
continue
if(count == 0):
first_pixel = (x,y)
dx = x - first_pixel.x
if(dx > L/2)
dx -= L
else if (dx < -L/2)
dx += L
acc_x += x
acc_y += y
count++
non_scaled_result = acc_x / count, acc_y / count
result = non_scaled_result + first_pixel
return result
I have a set of circles with given locations and radii on a two dimensional plane. I want to determine for every circle if it is intersecting with any other circle and the distance that is needed to separate the two. Under my current implementation, I just go through all the possible combinations of circles and then do the calculations. Unfortunately, this algorithm is O(n^2), which is slow.
The circles will generally be clustered in groups, and they will have similar (but different) radii. The approximate maximum for the number of circles is around 200. The algorithm does not have to be exact, but it should be close.
Here is a (slow) implementation I currently have in JavaScript:
// Makes a new circle
var circle = function(x,y,radius) {
return {
x:x,
y:y,
radius:radius
};
};
// These points are not representative of the true data set. I just made them up.
var points = [
circle(3,3,2),
circle(7,5,4),
circle(16,6,4),
circle(17,12,3),
circle(26,20,1)
];
var k = 0,
len = points.length;
for (var i = 0; i < len; i++) {
for (var j = k; j < len; j++) {
if (i !== j) {
var c1 = points[i],
c2 = points[j],
radiiSum = c1.radius+c2.radius,
deltaX = Math.abs(c1.x-c2.x);
if (deltaX < radiiSum) {
var deltaY = Math.abs(c1.y-c2.y);
if (deltaY < radiiSum) {
var distance = Math.sqrt(deltaX*deltaX+deltaY*deltaY);
if (distance < radiiSum) {
var separation = radiiSum - distance;
console.log(c1,c2,separation);
}
}
}
}
}
k++;
}
Also, I would appreciate it if you explained a good algorithm (KD Tree?) in plain English :-/
For starters, your algorithm above will be greatly sped-up if you just skipped the SQRT call. That's the most well known simple optimization for comparing distances. You can also precompute the "squared radius" distance so you don't redundantly recompute it.
Also, there looks to be lots of other little bugs in some of your algorithms. Here's my take on how to fix it below.
Also, if you want to get rid of the O(N-Squared) algorithm, you can look at using a kd-tree. There's an upfront cost of building the KD-Tree but with the benefit of searching for nearest neighbors as much faster.
function Distance_Squared(c1, c2) {
var deltaX = (c1.x - c2.x);
var deltaY = (c1.y - c2.y);
return (deltaX * deltaX + deltaY * deltaY);
}
// returns false if it's possible that the circles intersect. Returns true if the bounding box test proves there is no chance for intersection
function TrivialRejectIntersection(c1, c2) {
return ((c1.left >= c2.right) || (c2.right <= c1.left) || (c1.top >= c2.bottom) || (c2.bottom <= c1.top));
}
var circle = function(x,y,radius) {
return {
x:x,
y:y,
radius:radius,
// some helper properties
radius_squared : (radius*radius), // precompute the "squared distance"
left : (x-radius),
right: (x+radius),
top : (y - radius),
bottom : (y+radius)
};
};
// These points are not representative of the true data set. I just made them up.
var points = [
circle(3,3,2),
circle(7,5,4),
circle(16,6,4),
circle(17,12,3),
circle(26,20,1)
];
var k = 0;
var len = points.length;
var c1, c2;
var distance_squared;
var deltaX, deltaY;
var min_distance;
var seperation;
for (var i = 0; i < len; i++) {
for (var j = (i+1); j < len; j++) {
c1 = points[i];
c2 = points[j];
// try for trivial rejections first. Jury is still out if this will help
if (TrivialRejectIntesection(c1, c2)) {
continue;
}
//distance_squared is the actual distance between c1 and c2 'squared'
distance_squared = Distance_Squared(c1, c2);
// min_distance_squared is how much "squared distance" is required for these two circles to not intersect
min_distance_squared = (c1.radius_squared + c2.radius_squared + (c1.radius*c2.radius*2)); // D**2 == deltaX*deltaX + deltaY*deltaY + 2*deltaX*deltaY
// and so it follows
if (distance_squared < min_distance_squared) {
// intersection detected
// now subtract actual distance from "min distance"
seperation = c1.radius + c2.radius - Math.sqrt(distance_squared);
Console.log(c1, c2, seperation);
}
}
}
This article has been dormant for a long time, but I've run into and solved this problem reasonably well, so will post so that others don't have to do the same head scratching.
You can treat the nearest circle neighbor problem as a 3d point nearest neighbor search in a kd-tree or octree. Define the distance between two circles A and B as
D(A,B) = sqrt( (xA - xB)^2 + (yA - yB)^2 ) - rA - rB
This is a negative quantity iff the circles overlap. For this discussion I'll assume an octree, but a kd-tree with k=3 is similar.
Store a triple (x,y,r) in the octree for each circle.
To find the nearest neighbor to a target circle T, use the standard algorithm:
def search(node, T, nst)
if node is a leaf
update nst with node's (x,y,r) nearest to T
else
for each cuboid C subdividing node (there are 8 of them)
if C contains any point nearer to T than nst
search(C, T, nst)
end
end
Here nst is a reference to the nearest circle to T found so far. Initially it's null.
The slightly tricky part is determining if C contains any point nearer to T than nst. For this it is sufficent to consider the unique point (x,y,r) within C that is Euclidean nearest to T in x and y and has the maximum value of the r range contained in the cuboid. In other words, the cuboid represents a set of circles with centers ranging over a rectangular area in x and y and with a range of radii. The point you want to check is the one representing the circle with center closest to T and with largest radius.
Note the radius of T plays no part in the algorithm at all. You're only concered with how far inside any other circle the center of T lies. (I wish this had been as obvious at the start as it seems now...)
I have been looking all over the Web for a way to plot an ellipse from rectangle coordinates, that is, top-left corner (x, y) and size (width and height). The only ones I can find everywhere are based on the Midpoint/Bresenham algorithm and I can't use that because when working with integer pixels, I lose precisions because these algorithms use a center point and radials.
The ellipse MUST be limited to the rectangle's coordinates, so if I feed it a rectangle where the width and height are 4 (or any even number), I should get an ellipse that completely fits in a 4x4 rectangle, and not one that will be 5x5 (like what those algorithms are giving me).
Does anyone know of any way to accomplish this?
Thanks!
Can you not get the width and height (divided by 2) and center of the rectangle then plug that into any ellipse drawing routine as its major, minor axis and center? I guess I'm not seeing the problem all the way here.
I had the same need. Here is my solution with code. The error is at most half a pixel.
I based my solution on the McIlroy ellipse algorithm, an integer-only algorithm which McIlroy mathematically proved to be accurate to a half-pixel, without missing or extra points, and correctly drawing degenerate cases such as lines and circles. L. Patrick further analyzed McIlroy's algorithm, including ways to optimize it and how a filled ellipse can be broken up into rectangles.
McIlroy's algorithm traces a path through one quadrant of the ellipse; the remaining quadrants are rendered through symmetry. Each step in the path requires three comparisons. Many of the other ellipse algorithms use octants instead, which require only two comparisons per step. However, octant-based methods have are notoriously inaccurate at the octant boundaries. The slight savings of one comparison is not worth the inaccuracy of the octant methods.
Like virtually every other integer ellipse algorithm, McIlroy's wants the center at integer coordinates, and the lengths of the axes a and b to be integers as well. However, we want to be able to draw an ellipse with a bounding box using any integer coordinates. A bounding box with an even width or even height will have a center on an integer-and-a-half coordinate, and a or b will be an integer-and-a-half.
My solution was to perform calculations using integers that are double of what is needed. Any variable starting with q is calculated from double pixel values. An even q variable is on an integer coordinate, and an odd q variable is at an integer-and-a-half coordinate. I then re-worked McIroy's math to get the correct mathematical expressions with these new doubled values. This includes modifying starting values when the bounding box has even width or height.
Behold, the subroutine/method drawEllipse given below. You provide it with the integer coordinates (x0,y0) and (x1,y1) of the bounding box. It doesn't care if x0 < x1 versus x0 > x1; it will swap them as needed. If you provide x0 == x1, your will get a vertical line. Similarly for the y0 and y1 coordinates. You also provide the boolean fill parameter, which draws only the ellipse outline if false, and draws a filled ellipse if true. You also have to provide the subroutines drawPoint(x, y) which draws a single point and drawRow(xleft, xright, y) which draws a horizontal line from xleft to xright inclusively.
McIlroy and Patrick optimize their code to fold constants, reuse common subexpressions, etc. For clarity, I didn't do that. Most compilers do this automatically today anyway.
void drawEllipse(int x0, int y0, int x1, int y1, boolean fill)
{
int xb, yb, xc, yc;
// Calculate height
yb = yc = (y0 + y1) / 2;
int qb = (y0 < y1) ? (y1 - y0) : (y0 - y1);
int qy = qb;
int dy = qb / 2;
if (qb % 2 != 0)
// Bounding box has even pixel height
yc++;
// Calculate width
xb = xc = (x0 + x1) / 2;
int qa = (x0 < x1) ? (x1 - x0) : (x0 - x1);
int qx = qa % 2;
int dx = 0;
long qt = (long)qa*qa + (long)qb*qb -2L*qa*qa*qb;
if (qx != 0) {
// Bounding box has even pixel width
xc++;
qt += 3L*qb*qb;
}
// Start at (dx, dy) = (0, b) and iterate until (a, 0) is reached
while (qy >= 0 && qx <= qa) {
// Draw the new points
if (!fill) {
drawPoint(xb-dx, yb-dy);
if (dx != 0 || xb != xc) {
drawPoint(xc+dx, yb-dy);
if (dy != 0 || yb != yc)
drawPoint(xc+dx, yc+dy);
}
if (dy != 0 || yb != yc)
drawPoint(xb-dx, yc+dy);
}
// If a (+1, 0) step stays inside the ellipse, do it
if (qt + 2L*qb*qb*qx + 3L*qb*qb <= 0L ||
qt + 2L*qa*qa*qy - (long)qa*qa <= 0L) {
qt += 8L*qb*qb + 4L*qb*qb*qx;
dx++;
qx += 2;
// If a (0, -1) step stays outside the ellipse, do it
} else if (qt - 2L*qa*qa*qy + 3L*qa*qa > 0L) {
if (fill) {
drawRow(xb-dx, xc+dx, yc+dy);
if (dy != 0 || yb != yc)
drawRow(xb-dx, xc+dx, yb-dy);
}
qt += 8L*qa*qa - 4L*qa*qa*qy;
dy--;
qy -= 2;
// Else step (+1, -1)
} else {
if (fill) {
drawRow(xb-dx, xc+dx, yc+dy);
if (dy != 0 || yb != yc)
drawRow(xb-dx, xc+dx, yb-dy);
}
qt += 8L*qb*qb + 4L*qb*qb*qx + 8L*qa*qa - 4L*qa*qa*qy;
dx++;
qx += 2;
dy--;
qy -= 2;
}
} // End of while loop
return;
}
The image above shows the output for all bounding boxes up to size 10x10. I also ran the algorithm for all ellipses up to size 100x100. This produced 384614 points in the first quadrant. The error between where each of these points were plotted and where the actual ellipse occurs was calculated. The maximum error was 0.500000 (half a pixel) and the average error among all of the points was 0.216597.
The solution I found to this problem was to draw the closest smaller ellipse with odd dimensions, but pulled apart by a pixel along the even length dimension, repeating the middle pixels.
This can be done easily by using different middle points for the quadrants when plotting each pixel:
DrawPixel(midX_high + x, midY_high + y);
DrawPixel(midX_low - x, midY_high + y);
DrawPixel(midX_high + x, midY_low - y);
DrawPixel(midX_low - x, midY_low - y);
The high values are the ceil'ed midpoint, and the low values are the floored midpoint.
An image to illustrate, ellipses with width 15 and 16:
I have a 2D image randomly and sparsely scattered with pixels.
given a point on the image, I need to find the distance to the closest pixel that is not in the background color (black).
What is the fastest way to do this?
The only method I could come up with is building a kd-tree for the pixels. but I would really want to avoid such expensive preprocessing. also, it seems that a kd-tree gives me more than I need. I only need the distance to something and I don't care about what this something is.
Personally, I'd ignore MusiGenesis' suggestion of a lookup table.
Calculating the distance between pixels is not expensive, particularly as for this initial test you don't need the actual distance so there's no need to take the square root. You can work with distance^2, i.e:
r^2 = dx^2 + dy^2
Also, if you're going outwards one pixel at a time remember that:
(n + 1)^2 = n^2 + 2n + 1
or if nx is the current value and ox is the previous value:
nx^2 = ox^2 + 2ox + 1
= ox^2 + 2(nx - 1) + 1
= ox^2 + 2nx - 1
=> nx^2 += 2nx - 1
It's easy to see how this works:
1^2 = 0 + 2*1 - 1 = 1
2^2 = 1 + 2*2 - 1 = 4
3^2 = 4 + 2*3 - 1 = 9
4^2 = 9 + 2*4 - 1 = 16
5^2 = 16 + 2*5 - 1 = 25
etc...
So, in each iteration you therefore need only retain some intermediate variables thus:
int dx2 = 0, dy2, r2;
for (dx = 1; dx < w; ++dx) { // ignoring bounds checks
dx2 += (dx << 1) - 1;
dy2 = 0;
for (dy = 1; dy < h; ++dy) {
dy2 += (dy << 1) - 1;
r2 = dx2 + dy2;
// do tests here
}
}
Tada! r^2 calculation with only bit shifts, adds and subtracts :)
Of course, on any decent modern CPU calculating r^2 = dx*dx + dy*dy might be just as fast as this...
As Pyro says, search the perimeter of a square that you keep moving out one pixel at a time from your original point (i.e. increasing the width and height by two pixels at a time). When you hit a non-black pixel, you calculate the distance (this is your first expensive calculation) and then continue searching outwards until the width of your box is twice the distance to the first found point (any points beyond this cannot possibly be closer than your original found pixel). Save any non-black points you find during this part, and then calculate each of their distances to see if any of them are closer than your original point.
In an ideal find, you only have to make one expensive distance calculation.
Update: Because you're calculating pixel-to-pixel distances here (instead of arbitrary precision floating point locations), you can speed up this algorithm substantially by using a pre-calculated lookup table (just a height-by-width array) to give you distance as a function of x and y. A 100x100 array costs you essentially 40K of memory and covers a 200x200 square around the original point, and spares you the cost of doing an expensive distance calculation (whether Pythagorean or matrix algebra) for every colored pixel you find. This array could even be pre-calculated and embedded in your app as a resource, to spare you the initial calculation time (this is probably serious overkill).
Update 2: Also, there are ways to optimize searching the square perimeter. Your search should start at the four points that intersect the axes and move one pixel at a time towards the corners (you have 8 moving search points, which could easily make this more trouble than it's worth, depending on your application's requirements). As soon as you locate a colored pixel, there is no need to continue towards the corners, as the remaining points are all further from the origin.
After the first found pixel, you can further restrict the additional search area required to the minimum by using the lookup table to ensure that each searched point is closer than the found point (again starting at the axes, and stopping when the distance limit is reached). This second optimization would probably be much too expensive to employ if you had to calculate each distance on the fly.
If the nearest pixel is within the 200x200 box (or whatever size works for your data), you will only search within a circle bounded by the pixel, doing only lookups and <>comparisons.
You didn't specify how you want to measure distance. I'll assume L1 (rectilinear) because it's easier; possibly these ideas could be modified for L2 (Euclidean).
If you're only doing this for relatively few pixels, then just search outward from the source pixel in a spiral until you hit a nonblack one.
If you're doing this for many/all of them, how about this: Build a 2-D array the size of the image, where each cell stores the distance to the nearest nonblack pixel (and if necessary, the coordinates of that pixel). Do four line sweeps: left to right, right to left, bottom to top, and top to bottom. Consider the left to right sweep; as you sweep, keep a 1-D column containing the last nonblack pixel seen in each row, and mark each cell in the 2-D array with the distance to and/or coordinates of that pixel. O(n^2).
Alternatively, a k-d tree is overkill; you could use a quadtree. Only a little more difficult to code than my line sweep, a little more memory (but less than twice as much), and possibly faster.
Search "Nearest neighbor search", first two links in Google should help you.
If you are only doing this for 1 pixel per image, I think your best bet is just a linear search, 1 pixel width box at time outwards. You can't take the first point you find, if your search box is square. You have to be careful
Yes, the Nearest neighbor search is good, but does not guarantee you'll find the 'nearest'. Moving one pixel out each time will produce a square search - the diagonals will be farther away than the horizontal / vertical. If this is important, you'll want to verify - continue expanding until the absolute horizontal has a distance greater than the 'found' pixel, and then calculate distances on all non-black pixels that were located.
Ok, this sounds interesting.
I made a c++ version of a soulution, I don't know if this helps you. I think it works fast enough as it's almost instant on a 800*600 matrix. If you have any questions just ask.
Sorry for any mistakes I've made, it's a 10min code...
This is a iterative version (I was planing on making a recursive one too, but I've changed my mind).
The algorithm could be improved by not adding any point to the points array that is to a larger distance from the starting point then the min_dist, but this involves calculating for each pixel (despite it's color) the distance from the starting point.
Hope that helps
//(c++ version)
#include<iostream>
#include<cmath>
#include<ctime>
using namespace std;
//ITERATIVE VERSION
//picture witdh&height
#define width 800
#define height 600
//indexex
int i,j;
//initial point coordinates
int x,y;
//variables to work with the array
int p,u;
//minimum dist
double min_dist=2000000000;
//array for memorising the points added
struct point{
int x;
int y;
} points[width*height];
double dist;
bool viz[width][height];
// direction vectors, used for adding adjacent points in the "points" array.
int dx[8]={1,1,0,-1,-1,-1,0,1};
int dy[8]={0,1,1,1,0,-1,-1,-1};
int k,nX,nY;
//we will generate an image with white&black pixels (0&1)
bool image[width-1][height-1];
int main(){
srand(time(0));
//generate the random pic
for(i=1;i<=width-1;i++)
for(j=1;j<=height-1;j++)
if(rand()%10001<=9999) //9999/10000 chances of generating a black pixel
image[i][j]=0;
else image[i][j]=1;
//random coordinates for starting x&y
x=rand()%width;
y=rand()%height;
p=1;u=1;
points[1].x=x;
points[1].y=y;
while(p<=u){
for(k=0;k<=7;k++){
nX=points[p].x+dx[k];
nY=points[p].y+dy[k];
//nX&nY are the coordinates for the next point
//if we haven't added the point yet
//also check if the point is valid
if(nX>0&&nY>0&&nX<width&&nY<height)
if(viz[nX][nY] == 0 ){
//mark it as added
viz[nX][nY]=1;
//add it in the array
u++;
points[u].x=nX;
points[u].y=nY;
//if it's not black
if(image[nX][nY]!=0){
//calculate the distance
dist=(x-nX)*(x-nX) + (y-nY)*(y-nY);
dist=sqrt(dist);
//if the dist is shorter than the minimum, we save it
if(dist<min_dist)
min_dist=dist;
//you could save the coordinates of the point that has
//the minimum distance too, like sX=nX;, sY=nY;
}
}
}
p++;
}
cout<<"Minimum dist:"<<min_dist<<"\n";
return 0;
}
I'm sure this could be done better but here's some code that searches the perimeter of a square around the centre pixel, examining the centre first and moving toward the corners. If a pixel isn't found the perimeter (radius) is expanded until either the radius limit is reached or a pixel is found. The first implementation was a loop doing a simple spiral around the centre point but as noted that doesn't find the absolute closest pixel. SomeBigObjCStruct's creation inside the loop was very slow - removing it from the loop made it good enough and the spiral approach is what got used. But here's this implementation anyway - beware, little to no testing done.
It is all done with integer addition and subtraction.
- (SomeBigObjCStruct *)nearestWalkablePoint:(SomeBigObjCStruct)point {
typedef struct _testPoint { // using the IYMapPoint object here is very slow
int x;
int y;
} testPoint;
// see if the point supplied is walkable
testPoint centre;
centre.x = point.x;
centre.y = point.y;
NSMutableData *map = [self getWalkingMapDataForLevelId:point.levelId];
// check point for walkable (case radius = 0)
if(testThePoint(centre.x, centre.y, map) != 0) // bullseye
return point;
// radius is the distance from the location of point. A square is checked on each iteration, radius units from point.
// The point with y=0 or x=0 distance is checked first, i.e. the centre of the side of the square. A cursor variable
// is used to move along the side of the square looking for a walkable point. This proceeds until a walkable point
// is found or the side is exhausted. Sides are checked until radius is exhausted at which point the search fails.
int radius = 1;
BOOL leftWithinMap = YES, rightWithinMap = YES, upWithinMap = YES, downWithinMap = YES;
testPoint leftCentre, upCentre, rightCentre, downCentre;
testPoint leftUp, leftDown, rightUp, rightDown;
testPoint upLeft, upRight, downLeft, downRight;
leftCentre = rightCentre = upCentre = downCentre = centre;
int foundX = -1;
int foundY = -1;
while(radius < 1000) {
// radius increases. move centres outward
if(leftWithinMap == YES) {
leftCentre.x -= 1; // move left
if(leftCentre.x < 0) {
leftWithinMap = NO;
}
}
if(rightWithinMap == YES) {
rightCentre.x += 1; // move right
if(!(rightCentre.x < kIYMapWidth)) {
rightWithinMap = NO;
}
}
if(upWithinMap == YES) {
upCentre.y -= 1; // move up
if(upCentre.y < 0) {
upWithinMap = NO;
}
}
if(downWithinMap == YES) {
downCentre.y += 1; // move down
if(!(downCentre.y < kIYMapHeight)) {
downWithinMap = NO;
}
}
// set up cursor values for checking along the sides of the square
leftUp = leftDown = leftCentre;
leftUp.y -= 1;
leftDown.y += 1;
rightUp = rightDown = rightCentre;
rightUp.y -= 1;
rightDown.y += 1;
upRight = upLeft = upCentre;
upRight.x += 1;
upLeft.x -= 1;
downRight = downLeft = downCentre;
downRight.x += 1;
downLeft.x -= 1;
// check centres
if(testThePoint(leftCentre.x, leftCentre.y, map) != 0) {
foundX = leftCentre.x;
foundY = leftCentre.y;
break;
}
if(testThePoint(rightCentre.x, rightCentre.y, map) != 0) {
foundX = rightCentre.x;
foundY = rightCentre.y;
break;
}
if(testThePoint(upCentre.x, upCentre.y, map) != 0) {
foundX = upCentre.x;
foundY = upCentre.y;
break;
}
if(testThePoint(downCentre.x, downCentre.y, map) != 0) {
foundX = downCentre.x;
foundY = downCentre.y;
break;
}
int i;
for(i = 0; i < radius; i++) {
if(leftWithinMap == YES) {
// LEFT Side - stop short of top/bottom rows because up/down horizontal cursors check that line
// if cursor position is within map
if(i < radius - 1) {
if(leftUp.y > 0) {
// check it
if(testThePoint(leftUp.x, leftUp.y, map) != 0) {
foundX = leftUp.x;
foundY = leftUp.y;
break;
}
leftUp.y -= 1; // moving up
}
if(leftDown.y < kIYMapHeight) {
// check it
if(testThePoint(leftDown.x, leftDown.y, map) != 0) {
foundX = leftDown.x;
foundY = leftDown.y;
break;
}
leftDown.y += 1; // moving down
}
}
}
if(rightWithinMap == YES) {
// RIGHT Side
if(i < radius - 1) {
if(rightUp.y > 0) {
if(testThePoint(rightUp.x, rightUp.y, map) != 0) {
foundX = rightUp.x;
foundY = rightUp.y;
break;
}
rightUp.y -= 1; // moving up
}
if(rightDown.y < kIYMapHeight) {
if(testThePoint(rightDown.x, rightDown.y, map) != 0) {
foundX = rightDown.x;
foundY = rightDown.y;
break;
}
rightDown.y += 1; // moving down
}
}
}
if(upWithinMap == YES) {
// UP Side
if(upRight.x < kIYMapWidth) {
if(testThePoint(upRight.x, upRight.y, map) != 0) {
foundX = upRight.x;
foundY = upRight.y;
break;
}
upRight.x += 1; // moving right
}
if(upLeft.x > 0) {
if(testThePoint(upLeft.x, upLeft.y, map) != 0) {
foundX = upLeft.x;
foundY = upLeft.y;
break;
}
upLeft.y -= 1; // moving left
}
}
if(downWithinMap == YES) {
// DOWN Side
if(downRight.x < kIYMapWidth) {
if(testThePoint(downRight.x, downRight.y, map) != 0) {
foundX = downRight.x;
foundY = downRight.y;
break;
}
downRight.x += 1; // moving right
}
if(downLeft.x > 0) {
if(testThePoint(upLeft.x, upLeft.y, map) != 0) {
foundX = downLeft.x;
foundY = downLeft.y;
break;
}
downLeft.y -= 1; // moving left
}
}
}
if(foundX != -1 && foundY != -1) {
break;
}
radius++;
}
// build the return object
if(foundX != -1 && foundY != -1) {
SomeBigObjCStruct *foundPoint = [SomeBigObjCStruct mapPointWithX:foundX Y:foundY levelId:point.levelId];
foundPoint.z = [self zWithLevelId:point.levelId];
return foundPoint;
}
return nil;
}
You can combine many ways to speed it up.
A way to accelerate the pixel lookup is to use what I call a spatial lookup map. It is basically a downsampled map (say of 8x8 pixels, but its a tradeoff) of the pixels in that block. Values can be "no pixels set" "partial pixels set" "all pixels set". This way one read can tell if a block/cell is either full, partially full or empty.
scanning a box/rectangle around the center may not be ideal because there are many pixels/cells which are far far away. I use a circle drawing algorithm (Bresenham) to reduce the overhead.
reading the raw pixel values can happen in horizontal batches, for example a byte (for a cell size of 8x8 or multiples of it), dword or long. This should give you a serious speedup again.
you can also use multiple levels of "spatial lookup maps", its again a tradeoff.
For the distance calculatation the mentioned lookup table can be used, but its a (cache)bandwidth vs calculation speed tradeoff (I dunno how it performs on a GPU for example).
Another approach I have investigated and likely will stick to: Utilizing the Bresenham circle algorithm.
It is surprisingly fast as it saves you any sort of distance comparisons!
You effectively just draw bigger and bigger circles around your target point so that when the first time you encounter a non-black pixel you automatically know it is the closest, saving any further checks.
What I have not verified yet is whether the bresenham circle will catch every single pixel but that wasn't a concern for my case as my pixels will occur in blobs of some sort.
I would do a simple lookup table - for every pixel, precalculate distance to the closest non-black pixel and store the value in the same offset as the corresponding pixel. Of course, this way you will need more memory.