views:

4509

answers:

5
+4  A: 

general reference

optimized algorithm in C#

Steven A. Lowe
note that is theoretically no difference between an 'occupied' pixel and an 'occupied' tile
Steven A. Lowe
+3  A: 

Wikpedia provides some pseudocode examples of different flood fill techniques on their Flood fill article. Which technique you choose depends on the application.

Remember that flood filling can easily be threaded (similar to how quicksort can be).

strager
+1  A: 

In all fairness it should be quite simple. Since you have the basic tile structure anyway the algorithm would be fairly simple:

Select Tile To Fill:    
Fill Till    
Check neighbouring Tiles - If Empty Then Fill    
Repeat, for all filled tiles.

Disclaimer: The above is a very basic description. There are many references on the web which explain it a lot better than I can.

Jamie Lewis
+1  A: 

We had to program that for school:

1: stuff the start pixel into a queue, note its color. note it as added.
2: begin picking a pixel off the queue. If it's similar to the start pixel:
   2: put all its neighbours into the queue
      for each added pixel, note it's added. if already noted for a pixel, don't 
      add it anymore.
   3: color it with the destination color.
3: nonempty => jump back to 2
4: empty => we are finished

Depending on whether we do 8-neighbour or 4-neighbour, we check all 8 neighbour pixels, or only pixels left/right or above/below a certain pixel. Here is the code (using ImageJ. I removed some code not relevant). I hope it makes sense, it's Java. Just ask away for questions:

public class Uebung1_2 implements PlugInFilter, MouseListener {
    private ImageProcessor ip;
    boolean[] state;
    int[] pixels;
    Queue<Integer> nextPixels;
    int threshould;

    /**
     * adds one pixel to the next-pixel queue only if it's not
     * already added.
     */
    void addNextPixel(int p) {
        if(!state[p]) {
            nextPixels.add(p);
            state[p] = true;
        }
    }

    boolean pixelsSimilar(int color1, int color2) {
        int dr = Math.abs(((color1 >> 16) & 0xff) -
                          ((color2 >> 16) & 0xff));
        int dg = Math.abs(((color1 >>  8) & 0xff) -
                          ((color2 >>  8) & 0xff));
        int db = Math.abs(((color1 >>  0) & 0xff) -
                          ((color2 >>  0) & 0xff));
        return ((double)(dr + dg + db) / 3.0) <= threshould;
    }

    /**
     * actually does the hard work :)
     * @param x the x position from which to start filling
     * @param y the y position from which to start filling
     */
    private void doFill(int x, int y, boolean connect8) {
        // first, add the start pixel
        int width = ip.getWidth(),
            height = ip.getHeight();
        /* for 8bit, we just gonna take the median of rgb */
        Color colorC = ij.gui.Toolbar.getForegroundColor();
        int color = colorC.getRGB();
        int firstPixel = ip.get(x, y);

        // go on with the mainloop
        addNextPixel(y * width + x);
        while(!nextPixels.isEmpty()) {
            int nextPixel = nextPixels.remove();
            int pixel = pixels[nextPixel];
            if(pixelsSimilar(pixel, firstPixel)) {
                // yay it matches. put the neighbours.
                int xN = nextPixel % width,
                    yN = nextPixel / width;
                /* the three pixels above */
                if(yN - 1 >= 0) {
                    if(connect8) {
                        if(xN + 1 < width) { 
                            addNextPixel(nextPixel - width + 1);
                        }
                        if(xN - 1 >= 0) {
                            addNextPixel(nextPixel - width - 1);
                        }
                    }
                    addNextPixel(nextPixel - width);
                }

                /* pixels left and right from the current one */
                if(xN > 0) {
                    addNextPixel(nextPixel - 1);
                }
                if(xN + 1 < width) {
                    addNextPixel(nextPixel + 1);
                }

                /* three pixels below */
                if(yN + 1 < height) {
                    if(connect8) {
                        if(xN + 1 < width) { 
                            addNextPixel(nextPixel + width + 1);
                        }
                        if(xN - 1 >= 0) {
                            addNextPixel(nextPixel + width - 1);
                        }
                    }
                    addNextPixel(nextPixel + width);
                }

                /* color it finally */
                pixels[nextPixel] = color;
            }
        }
    }

    @Override
    public void run(ImageProcessor ip) {
        ij.WindowManager.getCurrentImage().getCanvas().addMouseListener(this);
        this.ip = ip;
        this.pixels = (int[])ip.getPixels();
        this.state = new boolean[ip.getPixelCount()];
        this.nextPixels = new LinkedList<Integer>();
    }

    @Override
    public int setup(String arg0, ImagePlus arg1) {
        return DOES_RGB;
    }

    @Override
    public void mouseClicked(MouseEvent e) {
        ij.WindowManager.getCurrentWindow().getCanvas().removeMouseListener(this);
        ij.gui.GenericDialog g = new GenericDialog("Please enter parameters");
        g.addChoice("connection", new String[]{"4-connect", "8-connect"}, "8-connect");
        g.addNumericField("Threshould (0..255)", 0.0, 3);
        g.showDialog();

        boolean connect8 = g.getNextChoice().equals("8-connect");
        threshould = (int) g.getNextNumber();
        doFill(e.getX(), e.getY(), connect8);
        ij.WindowManager.getCurrentImage().draw();
    }
}
Johannes Schaub - litb
+4  A: 

The Wikipedia article is pretty good. As long as your grids are small, just about anything will work.

Earlier this fall I did some flood filling on 10 megapixel scanned images. (The problem was to remove black edges from book pages that had been scanned on a photocopier.) In that case there are only two colors so I essentially treated the problem like a search in an undirected graph, with each pixel connected to its neighbors along the four compass directions. I maintained a separate bitmap to keep track of which pixels had been visited.

The main findings were

  • Don't try recursive depth-first search. You really want an explicit data structure.

  • An auxiliary queue uses much less space than a stack. About forty times less space. In other words, prefer breadth-first search to depth-first search.

Again, these findings apply only to grids with multiple megapixels. On a nice small grid like the one shown in your question, any simple algorithm should work.

Norman Ramsey