13

I am using the recursive Flood fill algorithm in Java to fill some areas of a image. With very small images it works fine, but when de image becomes larger the JVM gives me a Stack Over Flow Error.

That's the reason why I have to reimplement the method using a Flood Fill with my own stack. (I read that's the best way to do it in these kind of cases)

Can anyone explain me how to code it? (if you don't have the code at hand, with the pseudo-code of the algorithm will be fine)

I've read a lot in the Internet but I haven't understood it very well.

EDIT: I added my recursive code

public void floodFill(int x, int y, Color targetColor,Color replacementColor) {

    if (img.getRGB(x, y) != targetColor.getRGB()) return;

    img.setRGB(x, y, replacementColor.getRGB());
    floodFill(x - 1, y, targetColor, replacementColor);
    floodFill(x + 1, y, targetColor, replacementColor);
    floodFill(x, y - 1, targetColor, replacementColor);
    floodFill(x, y + 1, targetColor, replacementColor);

    return;

}

Thanks!

skaffman
  • 398,947
  • 96
  • 818
  • 769
dafero
  • 1,017
  • 4
  • 13
  • 27
  • If you were to show your existing code (or a high-level summary thereof), someone might be able to tell you how to write it in a way that is not recursive, if that's what you are after. – Bill K May 06 '10 at 17:46
  • 1
    [Wikipedia: Flood fill algorithm](http://en.wikipedia.org/wiki/Flood_fill) – bitc May 06 '10 at 17:45
  • I suggest choosing one of the queue-based algorithms presented there, they are easier to implement than a stack-based solution in my opinion. – IVlad May 06 '10 at 17:53
  • Thanks for the answer, I tried to implement with the explanation from Wikipedia but I didn't know how. That's the reason why I make a question here. If anyone could explain it to me with other words I'll be very grateful. – dafero May 06 '10 at 18:05

4 Answers4

17

You can use Queue to remove recursion from floodfill algorithm. Here are some basic ideas:

  1. Have a way to mark visited points
  2. At the beginning, queue the start point.
  3. While the queue is not empty, continue dequeuing its element. And with each element
    1. Fill its color and mark just-dequeued point as visited
    2. Enqueue unvisited adjacent points that has the same color

The below is my Java code to solve similar but different problem of blob detection. I hope you can get some ideas from this and can adapt it the the problem. The code is not well-factored though.

package blobdetector;

import java.awt.Point;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.LinkedList;
import java.util.Queue;
import javax.imageio.ImageIO;
import javax.management.Query;

public class Main {

    public Main() {
    }

    public static boolean isBlack(BufferedImage image, int posX, int posY) {

        int color = image.getRGB(posX, posY);

        int brightness = (color & 0xFF) + ((color >> 2) & 0xFF)
        + ((color >> 4) & 0xFF);
        brightness /= 3;

        return brightness < 128;
    }

    public static void main(String[] args) {
        if (args.length != 1) {
            System.err.println("ERROR: Pass filename as argument.");
            return;
        }

        String filename = args[0];
        // String filename =
        // "C:\\Users\\Natthawut\\Desktop\\blob.jpg";
        try {
            BufferedImage bimg = ImageIO.read(new File(filename));

            boolean[][] painted = new boolean[bimg.getHeight()][bimg.getWidth()];

            for (int i = 0; i < bimg.getHeight(); i++) {
                for (int j = 0; j < bimg.getWidth(); j++) {

                    if (isBlack(bimg, j, i) && !painted[i][j]) {

                        Queue<Point> queue = new LinkedList<Point>();
                        queue.add(new Point(j, i));

                        int pixelCount = 0;
                        while (!queue.isEmpty()) {
                            Point p = queue.remove();

                            if ((p.x >= 0)
                                    && (p.x < bimg.getWidth() && (p.y >= 0) && (p.y < bimg
                                            .getHeight()))) {
                                if (!painted[p.y][p.x]
                                                  && isBlack(bimg, p.x, p.y)) {
                                    painted[p.y][p.x] = true;
                                    pixelCount++;

                                    queue.add(new Point(p.x + 1, p.y));
                                    queue.add(new Point(p.x - 1, p.y));
                                    queue.add(new Point(p.x, p.y + 1));
                                    queue.add(new Point(p.x, p.y - 1));
                                }
                            }
                        }
                        System.out.println("Blob detected : " + pixelCount
                                + " pixels");
                    }

                }
            }

        } catch (IOException ex) {
            ex.printStackTrace();
        }

    }

}

Test input:

alt text

Community
  • 1
  • 1
Gant
  • 29,661
  • 6
  • 46
  • 65
  • Mmmm I see...but how can I adapt your code? I need paint green some areas, for example: all the black areas of the image. Thanks for all! – dafero May 06 '10 at 18:23
  • 1
    In the code sample, what the program does is incrementing the value of `pixelCount` variable. Maybe you want to `setRGB` at that point instead? Also, it scans the entire area of the image for every blob. You may want to fill only a single blob, start filling at certain point. – Gant May 06 '10 at 18:31
  • Yeah! I get it! Without your help would not be possible!! Just I have to change the line "pixelCount++" by "img.setRGB(p.x, p.y, Color.GREEN.getRGB());" and that's it! I am very grateful!! Thanks a lot! – dafero May 06 '10 at 18:37
  • Beautiful, don't know how I hadn't thought about this solution. Worked flawlessly in my code! – FelipeKunzler Nov 15 '15 at 19:26
9

here is my implementation base on infos from this page and others gathered on the web (tested and working)

have fun ;-)

public static void floodFillImage(BufferedImage image,int x, int y, Color color) 
{
    int srcColor = image.getRGB(x, y);
    boolean[][] hits = new boolean[image.getHeight()][image.getWidth()];

    Queue<Point> queue = new LinkedList<Point>();
    queue.add(new Point(x, y));

    while (!queue.isEmpty()) 
    {
        Point p = queue.remove();

        if(floodFillImageDo(image,hits,p.x,p.y, srcColor, color.getRGB()))
        {     
            queue.add(new Point(p.x,p.y - 1)); 
            queue.add(new Point(p.x,p.y + 1)); 
            queue.add(new Point(p.x - 1,p.y)); 
            queue.add(new Point(p.x + 1,p.y)); 
        }
    }
}

private static boolean floodFillImageDo(BufferedImage image, boolean[][] hits,int x, int y, int srcColor, int tgtColor) 
{
    if (y < 0) return false;
    if (x < 0) return false;
    if (y > image.getHeight()-1) return false;
    if (x > image.getWidth()-1) return false;

    if (hits[y][x]) return false;

    if (image.getRGB(x, y)!=srcColor)
        return false;

    // valid, paint it

    image.setRGB(x, y, tgtColor);
    hits[y][x] = true;
    return true;
}
Phil
  • 708
  • 1
  • 11
  • 22
1

An important point in flood fill is if you are processing points depth first or breadth first. Depth first is the original solution you were looking at using a stack, breadth first are algorithms shown below using a queue to store point. The difference is substantial when filling in big convex spaces. A breadth first method stores on a perfectly convex area roughly the circle edge (or fill edge). If you use a depth first method you may in worst case store every pixel in the conxex area, that means that in worst case a 1000x1000 image flood filled may require 1000000 of stack frames.

RolfS
  • 11
  • 1
  • To clarify (cmiiw) a stack-based **data structure** (filo queue) would be breadth-first, filling from a convex edge _inward_, wheras stack-based **recursion** would be depth-first. A **fifo** queue, by comparison, would work from the origin outward as each point is processed in the order it's submitted. Fifo queues tend to have more overhead, if you can't grow a ring buffer w/o copying it –  Jan 05 '17 at 13:32
1

You should return the last floodFill statement, turning it into a tail call. This will save you stack space.

Puppy
  • 144,682
  • 38
  • 256
  • 465
  • 1
    Tail recursion can always be translated to an iterative format. That would save even more stack space. – Jon W May 06 '10 at 18:17