Implementing the flood fill algorithm

From CodeCodex

The flood fill algorithm is a method of determining connected regions in an array (e.g. for filling an area of pixels with a colour).

Implementations[edit]

Pseudocode[edit]

One implicitly stack-based recursion flood-fill implementation (for a two-dimensional array) goes as follows:

Flood-fill (node, target-color, replacement-color):
 1. If the color of node is not equal to target-color, return.
 2. Set the color of node to replacement-color.
 3. Perform Flood-fill (one step to the west of node, target-color, replacement-color).
    Perform Flood-fill (one step to the east of node, target-color, replacement-color).
    Perform Flood-fill (one step to the north of node, target-color, replacement-color).
    Perform Flood-fill (one step to the south of node, target-color, replacement-color).
 4. Return.

An explicitly queue-based implementation might resemble the following:

Flood-fill (node, target-color, replacement-color):
 1. Set Q to the empty queue.
 2. If the color of node is not equal to target-color, return.
 3. Add node to the end of Q.
 4. For each element n of Q:
 5.  Set the color of n to replacement-color.
 6.  If the color of the node to the west of n is target-color, add that node to the end of Q.
     If the color of the node to the east of n is target-color, add that node to the end of Q.
     If the color of the node to the north of n is target-color, add that node to the end of Q.
     If the color of the node to the south of n is target-color, add that node to the end of Q.
 7. Continue looping until Q is exhausted.
 8. Return.

Most practical implementations use a loop for the west and east directions as an optimization to avoid the overhead of queue management:

Flood-fill (node, target-color, replacement-color):
 1. Set Q to the empty queue.
 2. If the color of node is not equal to target-color, return.
 3. Add node to the end of Q.
 4. For each element n of Q:
 5.  Set w and e equal to n.
 6.  Move w to the west until the color of the node to the west of w no longer matches target-color.
 7.  Move e to the east until the color of the node to the east of e no longer matches target-color.
 8.  Set the color of nodes between w and e to replacement-color.
 9.  For each node n between w and e:
10.   If the color of the node to the north of n is target-color, add that node to the end of Q.
      If the color of the node to the south of n is target-color, add that node to the end of Q.
11. Continue looping until Q is exhausted.
12. Return.

Adapting the algorithm to use an additional array to store the shape of the region allows generalization to cover "fuzzy" flood filling, where an element can differ by up to a specified threshold from the source symbol. Using this additional array as an alpha channel allows the edges of the filled region to blend somewhat smoothly with the not-filled region.

ActionScript (Flash)[edit]

      fill_map = new Array();
      fill_map[0] = [1, 1, 1, 1, 1, 1, 1, 1, 1];
      fill_map[1] = [1, 0, 0, 0, 1, 0, 0, 0, 1];
      fill_map[2] = [1, 0, 0, 0, 1, 0, 0, 0, 1];
      fill_map[3] = [1, 0, 0, 1, 0, 0, 0, 0, 1];
      fill_map[4] = [1, 1, 1, 0, 0, 0, 1, 1, 1];
      fill_map[5] = [1, 0, 0, 0, 0, 1, 0, 0, 1];
      fill_map[6] = [1, 0, 0, 0, 1, 0, 0, 0, 1];
      fill_map[7] = [1, 0, 0, 0, 1, 0, 0, 0, 1];
      fill_map[8] = [1, 1, 1, 1, 1, 1, 1, 1, 1];
      for (y=0; y<9; y++) {
          for (x=0; x<9; x++) {
              tile = _root.attachMovie("tile", "tile_"+(y+x*9), _root.getNextHighestDepth(), {_x:y*50, _y:x*50});
              tile.gotoAndStop(1+fill_map[x][y]);
          }
      }
      _root.attachMovie("cursor", "cursor", _root.getNextHighestDepth());
      cursor.onEnterFrame = function() {
          this._x = 50*Math.floor(_root._xmouse/50);
          this._y = 50*Math.floor(_root._ymouse/50);
      };
      _root.onMouseDown = function() {
          pos_x = Math.floor(_root._xmouse/50);
          pos_y = Math.floor(_root._ymouse/50);
          flood_fill(pos_x, pos_y);
      };
      function flood_fill(x, y) {
          pos = x+y*9;
          if (fill_map[y][x] == 0) {
              fill_map[y][x] = 2;
              _root["tile_"+(x+y*9)].gotoAndStop(3);
              flood_fill(x+1,y);
              flood_fill(x-1,y);
              flood_fill(x,y+1);
              flood_fill(x,y-1);
          }
      }

C[edit]

 #define BOARD_WIDTH  10
 #define BOARD_HEIGHT 20
 
 typedef struct MAP
 {
   unsigned char b[BOARD_HEIGHT][BOARD_WIDTH];
 } MAP;
 static void flood_loop(MAP *map, int x, int y,
                        unsigned int dst_c, unsigned  int src_c)
 {
   int fillL, fillR, i;
   int in_line = 1;
   //unsigned char c = src_c, fillC = dst_c;
 
   /* find left side, filling along the way */
   fillL = fillR = x;
   while( in_line )
   {
     map->b[y][fillL] = dst_c;
     fillL--;
     in_line = (fillL < 0) ? 0 : (map->b[y][fillL] == src_c);
   }
   fillL++;
   /* find right side, filling along the way */
   in_line = 1;
   while( in_line )
   {
     map->b[y][fillR] = dst_c;
     fillR++;
     in_line = (fillR > BOARD_WIDTH-1) ? 0 : (map->b[y][fillR] == src_c);
   }
   fillR--;
   /* search top and bottom */
   for(i = fillL; i <= fillR; i++)
   {
     if( y > 0 && map->b[y - 1][i] == src_c )
         flood_loop(map, i, y - 1, dst_c, src_c);
     if( y < BOARD_HEIGHT-1 && map->b[y + 1][i] == src_c )
         flood_loop(map, i, y + 1, dst_c, src_c);
   }
 }
 void flood_fill(MAP *map, int x, int y, unsigned int c)
 {
   flood_loop(map, x, y, c, map->b[y][x]);
   map->b[y][x] = c;  /* some buggy optimizers needed this line */
 }

Micromouse[edit]

Implemented in C

    #define NORTH      1
    #define EAST       2
    #define SOUTH      4
    #define WEST       8           
    #define VISITED      16
    #define ONROUTE      32

    #define NOTNORTH  (255-NORTH)
    #define NOTEAST   (255-EAST)
    #define NOTSOUTH  (255-SOUTH)
    #define NOTWEST   (255-WEST)
    #define ALLWALLS  (NORTH|EAST|SOUTH|WEST)     

    // maze storage
    #define NUMCELLS 256
    extern CELL maze[NUMCELLS] ;
    extern CELL map[NUMCELLS];

    /*
      floodMaze solves the maze using wall data found in the array maze
      and places the flooding data - effectively the routemap - into
      the array map. The destination cell is passed in to the function
      to allow solving for any location.
      The state of map on entry is not important and it is not important
      whether all the walls have been found. The algorithm simply performs
      the flood based on what it knows at the time it is called.
      At worst, up to 255 passes are made through the maze date in generating
      the map. If that many passes are made, there has been an error and
      the maze should be considered to be seriously in error.
      Passes of the algorithm are made until either the limit has been reached
      or the change flag indicates that none of the map data has changed in
      that pass. At that point the maze can be considered to have been solved.
      Clearly, unless all the walls have been observed, the map will contain
      invalid paths. The mouse will discover new walls and call the solver again
      as needed.
      The function returns the number of passes required to solve the maze.
      A returned value of 255 indicates an error.
      Note that the array is allowed to wrap around in a most dangerous
      manner by relying on the 8 bit pointer.
      This should not be a real problem in as much as the existence of the outer
      walls means we never have to make use of the value
    */

    UCHAR floodMaze(UCHAR goal)
    {
      UCHAR i,j;
      UCHAR now,next;
      UCHAR passes;
      UCHAR cellwalls;    // the wall data for a given cell
      UCHAR changed;

      i = 0;
      do{
        map[i--] = 255;
      } while (i);

      map[goal]=0;
      passes = 0;
      now = 0;
      next = now+1;
      do
      {
        changed = 0;
        i = 0;
        do
        {
          if (map[i]==now)
          {
            cellwalls=maze[i];
            if ((cellwalls & NORTH) == 0)
            {
              j = i+1;
              if (map[j] == 255){ map[j] = next; changed=1;}
            }
            if ((cellwalls & EAST) == 0)
            {
              j = i + 16;
              if (map[j] == 255){ map[j] = next; changed=1;}
            }
            if ((cellwalls & SOUTH) == 0)
            {
              j = i + 255;
              if (map[j] == 255){ map[j] = next; changed=1;}
            }
            if ((cellwalls & WEST) == 0)
            {
              j = i + 240;
              if (map[j] == 255){ map[j] = next; changed=1;}
            }
          }
          i--;
        } while(i);
        now  = now+1;
        next = now+1;
        passes++;
      } while(changed);
      return passes;
    }

Java[edit]

This is next algorithm adapted by Claudio Santana and Damian Johnson for Java:

  /**
   * Fills the selected pixel and all surrounding pixels of the same color with the fill color.
   * @param img image on which operation is applied
   * @param fillColor color to be filled in
   * @param loc location at which to start fill
   * @throws IllegalArgumentException if loc is out of bounds of the image
   */
  public static void floodFill(BufferedImage img, Color fillColor, Point loc) {
    if (loc.x < 0 || loc.x >= img.getWidth() || loc.y < 0 || loc.y >= img.getHeight()) throw new IllegalArgumentException();
    
    WritableRaster raster = img.getRaster();
    int[] fill =
        new int[] {fillColor.getRed(), fillColor.getGreen(), fillColor.getBlue(),
            fillColor.getAlpha()};
    int[] old = raster.getPixel(loc.x, loc.y, new int[4]);
    
    // Checks trivial case where loc is of the fill color
    if (isEqualRgba(fill, old)) return;
    
    floodLoop(raster, loc.x, loc.y, fill, old);
  }
  
  // Recursively fills surrounding pixels of the old color
  private static void floodLoop(WritableRaster raster, int x, int y, int[] fill, int[] old) {
    Rectangle bounds = raster.getBounds();
    int[] aux = {255, 255, 255, 255};
    
    // finds the left side, filling along the way
    int fillL = x;
    do {
      raster.setPixel(fillL, y, fill);
      fillL--;
    } while (fillL >= 0 && isEqualRgba(raster.getPixel(fillL, y, aux), old));
    fillL++;
    
    // find the right right side, filling along the way
    int fillR = x;
    do {
      raster.setPixel(fillR, y, fill);
      fillR++;
    } while (fillR < bounds.width - 1 && isEqualRgba(raster.getPixel(fillR, y, aux), old));
    fillR--;
    
    // checks if applicable up or down
    for (int i = fillL; i <= fillR; i++) {
      if (y > 0 && isEqualRgba(raster.getPixel(i, y - 1, aux), old)) floodLoop(raster, i, y - 1,
          fill, old);
      if (y < bounds.height - 1 && isEqualRgba(raster.getPixel(i, y + 1, aux), old)) floodLoop(
          raster, i, y + 1, fill, old);
    }
  }
  
  // Returns true if RGBA arrays are equivalent, false otherwise
  // Could use Arrays.equals(int[], int[]), but this is probably a little faster...
  private static boolean isEqualRgba(int[] pix1, int[] pix2) {
    return pix1[0] == pix2[0] && pix1[1] == pix2[1] && pix1[2] == pix2[2] && pix1[3] == pix2[3];
  }

Matlab[edit]

function flood_fill(xc,yc,x,y,new,old,width,height)
% flood fills area of matrix
% (xc,yc) = start point of flood fill
% (x,y) = current test location
% new = new value to be filled
% old = old value to replace
%
% example use:
% global fill
% width = 20;
% height = 20;
% fill = zeros(width,height);
% new = 1;
% old = 0;
% xc = 10;
% yc = 10;
% flood_fill(xc,yc,xc,yc,new,old,width,height);
%
% Results:
% The flood fill will alter all values of 0 to one flood filling from a
% start point (xc,yc)
% If the flood fill starts in an enclosed space it will fill up to the
% boundary
%
% Author: James Goodwin, June 2003
% email: JamesRichardGoodwin@lyocs.com


global fill;
global call;

call = call + 1;

if (x<2 || x>=width)
    x = xc;
end

if (y<2 || y>=height)
    y = yc;
end

if (fill(y,x) == old) 
    fill(y,x) = new;
    flood_fill(xc,yc,x+1,y,new,old,width,height);
    flood_fill(xc,yc,x,y+1,new,old,width,height);
    flood_fill(xc,yc,x-1,y,new,old,width,height);
    flood_fill(xc,yc,x,y-1,new,old,width,height);
end

OCaml[edit]

Naive non-tail-recursive implementation (this is more than adequate in practice because OCaml only allocates tiny stack frames, allowing deep recursion):

# let rec flood img targ repl (x, y) =
    match try Some img.(x).(y) with _ -> None with
    | Some color when color = targ ->
        img.(x).(y) <- repl;
        let f = flood img targ repl in
        f(x-1, y); f(x+1, y); f(x, y-1); f(x, y+1)
    | _ -> ();;
val flood : 'a array array -> 'a -> 'a -> int * int -> unit = <fun>

For example:

# let test =
    Array.init 10 (fun y -> Array.init 10 (fun x -> if (x+1)/(y+1)<2 then 1 else 0));;
val test : int array array =
  [|[|1; 0; 0; 0; 0; 0; 0; 0; 0; 0|];
    [|1; 1; 1; 0; 0; 0; 0; 0; 0; 0|];
    [|1; 1; 1; 1; 1; 0; 0; 0; 0; 0|];
    [|1; 1; 1; 1; 1; 1; 1; 0; 0; 0|];
    [|1; 1; 1; 1; 1; 1; 1; 1; 1; 0|];
    [|1; 1; 1; 1; 1; 1; 1; 1; 1; 1|];
    [|1; 1; 1; 1; 1; 1; 1; 1; 1; 1|];
    [|1; 1; 1; 1; 1; 1; 1; 1; 1; 1|];
    [|1; 1; 1; 1; 1; 1; 1; 1; 1; 1|];
    [|1; 1; 1; 1; 1; 1; 1; 1; 1; 1|]|]
# flood test 1 2 (0,0); test;;
- : int array array =
[|[|2; 0; 0; 0; 0; 0; 0; 0; 0; 0|];
  [|2; 2; 2; 0; 0; 0; 0; 0; 0; 0|];
  [|2; 2; 2; 2; 2; 0; 0; 0; 0; 0|];
  [|2; 2; 2; 2; 2; 2; 2; 0; 0; 0|];
  [|2; 2; 2; 2; 2; 2; 2; 2; 2; 0|];
  [|2; 2; 2; 2; 2; 2; 2; 2; 2; 2|];
  [|2; 2; 2; 2; 2; 2; 2; 2; 2; 2|];
  [|2; 2; 2; 2; 2; 2; 2; 2; 2; 2|];
  [|2; 2; 2; 2; 2; 2; 2; 2; 2; 2|];
  [|2; 2; 2; 2; 2; 2; 2; 2; 2; 2|]|]

Queue-based version:

# let rec flood img targ repl = function
    | [] -> ()
    | (x, y) :: t ->
        match try Some img.(x).(y) with _ -> None with
        | Some color when color = targ ->
            img.(x).(y) <- repl;
            flood img targ repl
              ((x-1, y) :: (x+1, y) :: (x, y-1) :: (x, y+1) :: t)
        | _ -> flood img targ repl t;;
val flood : 'a array array -> 'a -> 'a -> (int * int) list -> unit = <fun>

Continuation passing style (CPS) uses a stack of closures which is shorter but slower than the explicit queue:

# let rec flood img targ repl (x, y) k () =
    match try Some img.(x).(y) with _ -> None with
    | Some color when color = targ ->
        img.(x).(y) <- repl;
        let f = flood img targ repl in
        f(x-1, y) (f(x+1, y) (f(x, y-1) (f(x, y+1) k))) ()
    | _ -> k();;
val flood : 'a array array -> 'a -> 'a -> int * int -> unit = <fun>

Perl[edit]

Assuming floodfill_*, col and the four directions are methods on some object whose instance is passed as first argument as per standard method call syntax.

sub col;    # implementation
sub west;   # left
sub east;   # as exercise
sub north;  # for
sub south;  # the reader

sub floodfill_stack {
    my $self    = shift;
    my $target  = shift;
    my $repl    = shift;

    return unless $target eq $self->col;    # get
=pod
One might either overload the eq resp. == operator
here if a colour cannot be expressed in primitive
types, or write an encapsulating method for
comparison instead:   $target->equals($self->col)
=cut

    $self->col($repl);      # set
    $self->$_->floodfill_stack($target, $repl)
        for qw(west east north south);
    return;
};
sub floodfill_queue {
    my $self    = shift;
    my $target  = shift;
    my $repl    = shift;

    my @q;
# thanks to Perl arrays, queue management is totally painless

    return unless $target eq $self->col;    # get
    push @q, $self;
    while (my $n = shift @q) {
=pod
Be careful that the assignment to $n evaluates true in
boolean context, else the loop ends prematurely. Since
$n is supposed to be an object instance and not just a
primitive number, that accident will not happen. $n is
only ever undef when the @q is actually emptied.
=cut
        $n->col($repl);     # set
        for (qw(west east north south)) {
            push @q, $n->$_ if $target eq $n->$_->col;
        };
    };
};

Python[edit]

Assuming floodfill_*, and the four directions are methods in some class, and color is an instance variable.

class ?:
                             # implementation
    def west(self): pass     # left
    def east(self): pass     # as exercise
    def north(self): pass    # for
    def south(self): pass    # the reader

    def floodfill_stack(self, target, repl):
        if target != self.color:
            return

        self.color = repl
        for x in [self.west(), self.east(), self.north(), self.south()]:
            x.floodfill_stack(target, repl)
        return
    def floodfill_queue(self, target, repl):
        if target != self.color:
            return

        q = [self]
        while q:
            n = q.pop(0)
            n.color = repl
            for x in [n.west(), n.east(), n.north(), n.south()]:
                if x.color == target:
                    x.color = repl
                    q.append(x)