Thursday, 17 June 2021

Iterating through every pixel in concentric circles

I am trying to iterate through every pixel coordinate, starting at (0, 0), in order to fuse two pixelated shapes at the closest offset where they don't overlap.

Until now, I was using concentric squares, which are really easy to do but can end up placing the grafted image further than it could be. I then implemented Bresenham's Circle Algorithm as follows :

def generate_offsets(maxRadius : int):
    """Generate x and y coordinates in concentric circles around the origin
    Uses Bresenham's Circle Drawing Algorithm
    """
    
    for radius in range(maxRadius):
        x = 0
        y = radius
        d = 3 - (2 * radius)
        while x < y:
        
            yield x, y
            yield y, x
            yield y, -x
            yield x, -y
            yield -x, -y
            yield -y, -x
            yield -y, x
            yield -x, y
            
            if d < 0:
                d += (4 * x) + 6
            else:
                d += (4 * (x-y)) + 10
                y -= 1
            
            x += 1

However, this has the disadvantage of leaving some pixel offsets unchecked. All the solutions I have found for filling the holes propose tracing the entire line from 0,0 to the pixel, which would be extremely wasteful here.

How can I fix the holes, without revisiting any pixels ?


Here is an example showing said holes, this represents every circle or radius 1-9. Explored pixels are #, while unexplored pixels are . :

....................
....................
........#####.......
......#########.....
.....###########....
....#..#######..#...
...##..#.###.#..##..
...####.#####.####..
..####.#.###.#.####.
..#######.#.#######.
..########.########.
..#######.#.#######.
..####.#.###.#.####.
...####.#####.####..
...##..#.###.#..##..
....#..#######..#...
.....###########....
......#########.....
........#####.......
....................

Update : Here is my current solution, which does fill the whole circle but is stores a lot more state than I would like :

import itertools
def generate_offsets(minRadius : int = 0, maxRadius : int = 3_750_000):
    """Generate x and z coordinates in concentric circles around the origin
    Uses Bresenham's Circle Drawing Algorithm
    """
    def yield_points(x, y):
        
            yield x, y
            yield x, -y
            yield -x, -y
            yield -x, y
            
            if x != y:
                yield y, x
                yield y, -x
                yield -y, -x
                yield -y, x
    
    def yield_circle(radius, previousCircle):
        x = 0
        y = radius
        d = 3 - (2 * radius)
        while x < y:
        
            for point in yield_points(x, y):
                if point not in previousCircle:
                    yield point
            
            if d < 0:
                d += (4 * x) + 6
            else:
                d += (4 * (x-y)) + 10
                for point in itertools.chain(yield_points(x + 1, y), yield_points(x, y - 1)):
                    if point not in previousCircle:
                        yield point
                y -= 1
            
            x += 1
    
    previousCircle = [(0,0)]
    for radius in range(minRadius, maxRadius):
    
        circle = set()
        for point in yield_circle(radius, previousCircle):
            if point not in circle:
                yield point
                circle.add(point)
        
        previousCircle = circle

This is the most balanced solution I have found so far in terms of memory and processing. It only remembers the previous circle, which lowers the redundancy rate (rate of pixels visited twice) from around 50% without any memory to around 1.5%



from Iterating through every pixel in concentric circles

No comments:

Post a Comment