I've seen this done with a queue. Essentially the pseudo-code looks something like:
points = new list()
queue = new queue()
for x,y in image_coordinates:
if is_cloud(x,y):
point = new point(x=x, y=y, distance=0, cloud_x=x, cloud_y=y)
queue.push(point)
else:
point = new point(x=x, y=y, distance=null, could_x=null, cloud_y=null)
points.push(point)
while(!queue.is_empty()):
point = queue.pop()
for neighbor in point.neighbors():
if angle_is_correct(point.cloud_x, point.cloud_y, neighbor.point.x, neighbor.point.y):
if neighbor.is_direct(): //N,S,E,W
new_distance = point.distance + 1
else: //NE, SE, SW, NW
new_distance = point.distance + SQRT_2
if neighbor.point.distance == null or neighbor.point.distance > new_distance:
neighbor.point.distance = new_distance
neighbor.point.cloud_x = point.cloud_x
neighbor.point.cloud_y = point.cloud_y
queue.push(neighbor.point)
When the run is finished points will be a list of x,y coordinates, their distance to the nearest cloud, and the x,y coordinate of the nearest cloud (which you probably don't need). You should use the angle_is_correct
function to make sure to only consider clouds which are in the correct direction. You may also be able to further optimize it to stop adding points to the queue if their distance exceeds the max distance.
I'm not entirely sure the algorithmic complexity but I suspect one could devise a proof to show this is O(n) or O(log(n)). All I know is that it worked quickly for me when I needed it.