我的洪水填充实施有什么问题?

时间:2015-10-03 19:39:01

标签: java flood-fill

我用Java编写了一个泛洪填充实现(代码见下文)。我想用它来填充下面的多边形(黄色区域):

Image

点(.)表示当前迭代期间变量X的白色像素,+黑色像素和n位置。

这个实现有问题,因为算法用Xes填充整个网格,而不仅仅是多边形内的空间:

Image 2

您可以在this PDF file中的各种迭代中看到网格的状态。第一页显示floodFill方法调用之前的网格,所有后续方法 - 调用visualizer.visualize(grid, n);时的状态。

我的算法(方法floodFill)有什么问题?如何解决?

代码

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;
    }

}

3 个答案:

答案 0 :(得分:2)

该算法将此视为边界上的一个洞:

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

你看到顶部的裂缝?洪水从那里溢出, 并最终覆盖整个网格。

鉴于这个网格,它工作正常,能够从内部正方形内部或外部正确填充,它做正确的事情:

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

因此,如果您想将第一个示例中的方块视为已关闭, 并防止洪水沿对角线溢出, 然后检查您的实施并进行必要的更改。

答案 1 :(得分:2)

不应将边界像素的南和北像素放入队列,因为这可能会导致“对角线步骤”,如PDF第4页所示。即你可能想尝试

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

从递归中排除边界像素。

答案 2 :(得分:1)

其他人对于你的对角线的部分问题是正确的 - 你的东西边界是黑色发生的实际点,而去往其中一个点的北或南可能会导致你变成白色实际上是在容器之外。快速运行它似乎也表明了其他问题 - 我注意到如果你将东西边界的定义调整为东/西边界左/右边的点,它就不会填充一些容器。这是对算法的一种调整,它使得它更简单(通过避免明确地找到东/西边界)并且更容易看到在逻辑上是正确的。对角对角线问题也是安全的:

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);
            }
        }
    }

示例运行的结果:

.........    
.........    
....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....    
.........    
.........

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

*另外,明智的一句话:请注意,您已经定义了两个位置,其中考虑了一个点的位置。一个是在网格数据结构中的实际位置(由进入网格的索引定义),另一个是在点本身(在其x和y字段中)。软件中的主体是“不要重复自己”(DRY),这意味着每条信息都应该有一个表示。在这样一个简单的情况下它可能是值得的,但我认为值得一提,因为我碰到了一个错误,其中点的内部(x,y)从网格转换 - 它引起了各种各样的怪异。如果您仍然遇到问题,请自行检查。如果你的其他限制允许,重构出来的重复。