简体   繁体   中英

What's wrong with my flood fill implementation?

I wrote a flood fill implementation in Java (code see below). I want to use it to fill the inside of the following polygon (the yellow area):

图片

A dot ( . ) represents a white pixel, X a black pixel and + position of the variable n during current iteration.

Something is wrong with this implementation because the algorithm fills the entire grid with Xes, and not only the space inside the polygon:

图片2

You can see the state of the grid at various iteration in this PDF file . First page shows the grid before the invokation of the floodFill method, all subsequent ones - the state at the call of visualizer.visualize(grid, n); .

What is wrong with my algorithm (method floodFill ) and how can I fix it?

Code

protected void floodFill(final FloodFillGridPoint[][] grid,
    final FloodFillGridPoint centroid, IFloodFillGridVisualizer visualizer) {
    final Queue<FloodFillGridPoint> queue = new LinkedList<>();
    queue.add(centroid);
    while (!queue.isEmpty()) {
        final FloodFillGridPoint n = queue.poll();
        if ((n.color() == COLOR_WHITE) && !n.isProcessed()) {
            n.markProcessed();
            final FloodFillGridPoint west = getWestBoundary(grid, n);
            final FloodFillGridPoint east = getEastBoundary(grid, n);
            for (int x=west.x(); x <= east.x(); x++) {
                final FloodFillGridPoint n2 = getPoint(grid, x, n.y());
                n2.setColor(COLOR_BLACK);

                final FloodFillGridPoint n2North = getPoint(grid, n2.x(),
                    n2.y()-1);
                if ((n2North != null) && (n2North.color() == COLOR_WHITE)) {
                    queue.add(n2North);
                }

                final FloodFillGridPoint n2South = getPoint(grid, n2.x(),
                    n2.y()+1);
                if ((n2South != null) && (n2South.color() == COLOR_WHITE)) {
                    queue.add(n2South);
                }
            }
            visualizer.visualize(grid, n);
        }
    }
}

private FloodFillGridPoint getEastBoundary(final FloodFillGridPoint[][] grid, final FloodFillGridPoint n) {
    FloodFillGridPoint east = n;
    while (east.color() == COLOR_WHITE) {
        final FloodFillGridPoint newNode =
            getPoint(grid, east.x()+1, east.y());
        if (newNode == null) {
            break;
        }
        east = newNode;
    }
    return east;
}

private FloodFillGridPoint getWestBoundary(final FloodFillGridPoint[][] grid, final FloodFillGridPoint n) {
    FloodFillGridPoint west = n;
    while (west.color() == COLOR_WHITE) {
        final FloodFillGridPoint newNode =
            getPoint(grid, west.x()-1, west.y());
        if (newNode == null) {
            break;
        }
        west = newNode;
    }
    return west;
}

private FloodFillGridPoint getPoint(final FloodFillGridPoint[][] grid,
    final int x, final int y) {
    if (x < 0) {
        return null;
    }
    if (y < 0) {
        return null;
    }
    if (x >= grid.length) {
        return null;
    }
    if (y >= grid[0].length) {
        return null;
    }

    return grid[x][y];
}


public class FloodFillGridPoint {
    private final int x;
    private final int y;
    private int color;
    private boolean processed = false;

    public FloodFillGridPoint(final int x, final int y, final int color) {
        this.x = x;
        this.y = y;
        this.color = color;
    }
    public void setColor(final int ncolor) {
        this.color = ncolor;
    }
    public int color() {
        return this.color;
    }
    public void markProcessed() {
        this.processed = true;
    }
    public int x() {
        return this.x;
    }

    public int y() {
        return this.y;
    }
    public boolean isProcessed() {
        return this.processed;
    }

}

The algorithm treats this as a hole on the boundary:

 .......... ....X..... ..XX.XXX.. ..X....X.. ..X....X.. ..X....X.. ..X....X.. ..XXXXXX.. .......... .......... 

You see that crack at the top? The flooding spills out through there, and ends up covering the entire grid.

Given this grid, it works fine, and able to correctly fill either from inside or outside the inner square, it does the right thing:

 .......... .......... ..XXXXXX.. ..X....X.. ..X....X.. ..X....X.. ..X....X.. ..XXXXXX.. .......... .......... 

So if you want to consider the square in the first example as closed , and prevent the flooding to spill out diagonally, then review your implementation and make the necessary changes.

The pixels South and North of boundary pixels should not be put into the queue as this might cause "diagonal steps", as seen on page 4 of the PDF. Ie you might want to try

for (int x=west.x()+1; x <= east.x()-1; x++) {

to exclude the boundary pixels from recursion.

The others are correct about part of the issue being your diagonal corners- your east and west boundaries are the actual points where a black occurs, and going to the north or south of one of these points may lead you to a white which is actually outside the container. Doing a quick run of it myself seemed to indicate other issues as well- I noted that if you adjusted the definition of east and west boundaries to be the point just to the left/right of the east/west boundaries, it wouldn't fill some containers. Here's an adjustment to your algorithm which both makes it simpler (by avoiding explicitly finding the east/west boundaries) and is easier to see is logically correct. It is safe against the diagonal corner issue as well:

protected void floodFill(final FloodFillGridPoint[][] grid,
        final FloodFillGridPoint centroid, IFloodFillGridVisualizer visualizer) {
        final Queue<FloodFillGridPoint> queue = new LinkedList<>();
        queue.add(centroid);
        while (!queue.isEmpty()) {
            final FloodFillGridPoint n = queue.poll();
            if ((n.color() == COLOR_WHITE) && !n.isProcessed()) {
                n.markProcessed();
                n.setColor(COLOR_BLACK);

                final FloodFillGridPoint west = getPoint(grid,n.x()-1,n.y());
                final FloodFillGridPoint east = getPoint(grid,n.x()+1,n.y());
                final FloodFillGridPoint north = getPoint(grid,n.x(),n.y()-1);
                final FloodFillGridPoint south = getPoint(grid,n.x(),n.y()+1);
                for(FloodFillGridPoint neighbor : Arrays.asList(west,east,north,south)){
                    if(neighbor!=null && neighbor.color()!=COLOR_BLACK){
                        queue.add(neighbor);
                    }
                }
                visualizer.visualize(grid, n);
            }
        }
    }

Results of an example run:

.........    
.........    
....x....    
...x.x...    
..x.+.x..    
...x.x...    
....x....    
.........    
.........

------------------------------------------------



.........    
.........    
....x....    
...xxx...    
..x+x.x..    
...x.x...    
....x....    
.........    
.........

------------------------------------------------



.........    
.........    
....x....    
...xxx...    
..x.x+x..    
...xxx...    
....x....    
.........    
.........

------------------------------------------------



.........    
.........    
....x....    
...x+x...    
..xxx.x..    
...xxx...    
....x....    
.........    
.........

------------------------------------------------



.........    
.........    
....x....    
...xxx...    
..xxxxx..    
...x+x...    
....x....    
.........    
.........

------------------------------------------------

*Also, a word to the wise: note that you have defined two places where the position of a point is considered. One is in the actual location in the grid data structure (defined by indexes into the grid), and the other is in the point itself (in its x and y fields). There's a principal in software "Don't Repeat Yourself" (DRY), meaning that each piece of info should have one representation. It may be worth it in a simple case like this, but I figured it was worth mentioning since I bumped into an error where the point's internal (x,y) were transposed from the grid's- it caused all sorts of weirdness. If you're still having trouble, check that yourself. And refactor the duplication out if your other constraints allow it.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM