Click here to Skip to main content
Click here to Skip to main content

Flood Fill Algorithms in C# and GDI+

By , 4 Oct 2003
 

Sample Image - floodfill.jpg

Prerequisites

This article assumes that you have a basic understanding of:

  • Direct pixel access in GDI+
  • Pointers
  • I highly recommend reading all of the articles in Christian Graus's image processing series, but if you don't read them all, at least you should read the first one - it gives you the basic knowledge necessary for pixel manipulation in GDI+.

Introduction/Overview

GDI+ does not have built-in flood fill capabilities. This example shows you how to create three different flood-fill algorithms for GDI+. It expands on the typical flood fill implementation by allowing adjustable color tolerance, and optional 8-way (diagonal) branching.

The flood fill algorithms will not be as fast as they would be if they were written in assembler, but you most likely won't notice this in ordinary use, because they are still quite fast.

Background

There are two main types of flood fills. The most common is 4-direction flood fill. This type of flood fill starts from a single point and branches up, down, and to the right and left. The 8-direction flood fill is similar to the 4-direction, except that it also branches diagonally.

Sample screenshot

Figure 1: The 4-direction flood fill branches in 4 directions from each pixel, whereas the 8-direction flood fill branches in 8 directions from each pixel.

The algorithms

We will look at 3 different flood fill algorithms - linear, recursive, and queue.

Recursive

This is the most common algortihm, and simplest to implement. The recursive algorithm branches in all directions at once. This can (read: often will) lead to stack overflows in a managed environment.

Queue

The queue algorithm is similar to the recursive algorithm, except that it adds the points to be checked to a queue rather than calling them directly. The loop returns immediately to the main fill method, rather than calling itself recursively. It requires extra heap space for a queue, but it uses hardly any stack space.

The queue algorithm is by far the slowest algorithm, taking about twice as long as the others. This may be partly because of lack of optimizations in the .NET Queue class, but the queue method would be slower regardless of whether the Queue class was optimized.

Linear

The linear algorithm first finds the horizontal extent of the color on a given level, then it moves horizontally from left to right, initializing the fill loop upwards and downwards for each point along the way.

By handling vertical and horizontal checking separately, this algorithm consumes only half the stack space that the recursive algorithm uses, while avoiding the extra heap space needed for a queue. It is also just as fast as the recursive algorithm.

Needless to say, the linear algorithm is the best algorithm of the three covered here. I decided to cover the others because they would inevitably be brought up anyway if I didn't do it in the article. Anyway, it's nice to see some other kinds of techniques that could be used.

The demonstration program

The demo program allows you to open and save bitmap files, and flood-fill them. It allows you to change the fill color and color tolerance for the fill operation, and has a brief explanation of each algorithm. You can watch the fill operation in slow motion (helpful for understanding how the different algorithms work). You can also see how long the flood fill operation took.

The code

So as not to take up too much space, I am only showing a small part of the code - the method that starts the fill, the 4-way version of the linear algorithm, and the function that checks the pixels.

///<SUMMARY>initializes the FloodFill operation</SUMMARY>
public override void FloodFill(Bitmap bmp, Point pt)
{
    //timeGetTime() is used instead of the 
    //performance ctr, for Win98/ME compatibility
    int ctr=timeGetTime(); 
    
    //get the color's int value, and convert it from 
    //RGBA to BGRA format (as GDI+ uses BGRA)
    m_fillcolor=ColorTranslator.ToWin32(m_fillcolorcolor);
    m_fillcolor=BGRA(GetB(m_fillcolor),
        GetG(m_fillcolor),GetR(m_fillcolor),GetA(m_fillcolor));
    
    //get the bits
    BitmapData bmpData=bmp.LockBits(
            new Rectangle(0,0,bmp.Width,bmp.Height),
            ImageLockMode.ReadWrite,
            PixelFormat.Format32bppArgb);
    System.IntPtr Scan0 = bmpData.Scan0;
    
    unsafe
    {
        //resolve pointer
        byte * scan0=(byte *)(void *)Scan0;
        //get the starting color
        //[loc += Y offset + X offset]
        int loc=CoordsToIndex(pt.X,pt.Y,bmpData.Stride);
        int color= *((int*)(scan0+loc));
        
        //create the array of bools that indicates whether each pixel
        //has been checked.  
        //(Should be bitfield, but C# doesn't support bitfields.)
        PixelsChecked=new bool[bmpData.Width+1,bmpData.Height+1];
        
        //do the first call to the loop
        switch(m_FillStyle)
        {
            case FloodFillStyle.Linear :
                if(m_FillDiagonal)
                {
                        LinearFloodFill8(scan0,pt.X,pt.Y,
                            new Size(bmpData.Width,bmpData.Height),
                            bmpData.Stride,
                            (byte*)&color);
                }else{
                        LinearFloodFill4(scan0,pt.X,pt.Y,
                            new Size(bmpData.Width,bmpData.Height),
                            bmpData.Stride,
                            (byte*)&color);
                }
                break;
            case FloodFillStyle.Queue :
                QueueFloodFill(scan0,pt.X,pt.Y,
                    new Size(bmpData.Width,bmpData.Height),
                    bmpData.Stride,
                    (byte*)&color);
                break;
            case FloodFillStyle.Recursive :
                if(m_FillDiagonal)
                {
                        RecursiveFloodFill8(scan0,pt.X,pt.Y,
                            new Size(bmpData.Width,bmpData.Height),
                            bmpData.Stride,
                            (byte*)&color);
                }else{
                        RecursiveFloodFill4(scan0,pt.X,pt.Y,
                            new Size(bmpData.Width,bmpData.Height),
                            bmpData.Stride,
                            (byte*)&color);
                }
                break;
        }
    }
    
    bmp.UnlockBits(bmpData);
        
    m_TimeBenchmark=timeGetTime()-ctr;
        
}



unsafe void LinearFloodFill4( byte* scan0, int x, int y,Size bmpsize,
                                int stride, byte* startcolor)
{
        
    //offset the pointer to the point passed in
    int* p=(int*) (scan0+(CoordsToIndex(x,y, stride)));
    
    
    //FIND LEFT EDGE OF COLOR AREA
    int LFillLoc=x; //the location to check/fill on the left
    int* ptr=p; //the pointer to the current location
    while(true)
    {
        ptr[0]=m_fillcolor;      //fill with the color
        PixelsChecked[LFillLoc,y]=true;
        LFillLoc--;               //de-increment counter
        ptr-=1;                      //de-increment pointer
        if(LFillLoc<=0 || 
            !CheckPixel((byte*)ptr,startcolor) ||  
            (PixelsChecked[LFillLoc,y]))
                //exit loop if we're at edge of bitmap or color area
                break;
        
    }
    LFillLoc++;
    
    //FIND RIGHT EDGE OF COLOR AREA
    int RFillLoc=x; //the location to check/fill on the left
    ptr=p;
    while(true)
    {
        ptr[0]=m_fillcolor; //fill with the color
        PixelsChecked[RFillLoc,y]=true;
        RFillLoc++;          //increment counter
        ptr+=1;                 //increment pointer
        if(RFillLoc>=bmpsize.Width || 
            !CheckPixel((byte*)ptr,startcolor) ||  
            (PixelsChecked[RFillLoc,y]))
                //exit loop if we're at edge of bitmap or color area
                break;
        
    }
    RFillLoc--;
    
    
    //START THE LOOP UPWARDS AND DOWNWARDS            
    ptr=(int*)(scan0+CoordsToIndex(LFillLoc,y,stride));
    for(int i=LFillLoc;i<=RFillLoc;i++)
    {
      //START LOOP UPWARDS
      //if we're not above the top of the bitmap 
      //and the pixel above this one is within the color tolerance
      if(y>0 && 
       CheckPixel((byte*)(scan0+CoordsToIndex(i,y-1,stride)),startcolor) && 
       (!(PixelsChecked[i,y-1])))
           LinearFloodFill4(scan0, i,y-1,bmpsize,stride,startcolor);

      //START LOOP DOWNWARDS
      if(y<(bmpsize.Height-1) && 
      CheckPixel((byte*)(scan0+CoordsToIndex(i,y+1,stride)),startcolor) && 
      (!(PixelsChecked[i,y+1])))
           LinearFloodFill4(scan0, i,y+1,bmpsize,stride,startcolor);
      ptr+=1;
    }
        
}

///<SUMMARY>Sees if a pixel is within the color tolerance range.</SUMMARY>
//px - a pointer to the pixel to check
//startcolor - a pointer to the color of the pixel we started at
unsafe bool CheckPixel(byte* px, byte* startcolor)
{
    bool ret=true;
    for(byte i=0;i<3;i++)
            ret&= (px[i]>= (startcolor[i]-m_Tolerance[i])) &&
                    px[i] <= (startcolor[i]+m_Tolerance[i]);            
    return ret;
}

Undoubtedly there will be optimizations that I did not think of, and you are more than welcome to mention any that you find.

History

  • 10/05/03 - Posted the article

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

About the Author

J. Dunlap
Web Developer
United States United States
My main goal as a developer is to improve the way software is designed, and how it interacts with the user. I like designing software best, but I also like coding and documentation. I especially like to work with user interfaces and graphics.
 
I have extensive knowledge of the .NET Framework, and like to delve into its internals. I specialize in working with VG.net and MyXaml. I also like to work with ASP.NET, AJAX, and DHTML.

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
GeneralFor reportmemberMember 953082428-Jan-13 2:44 
Sir,I m engineering student and i m doing flood fill project so can you please send me the report and documentation of Flood Fill project.i really need this pleaseee mail me @ saudansari94@gmail.com
GeneralMy vote of 5membermanoj kumar choubey18-Feb-12 3:25 
Nice
GeneralMy vote of 5memberasaab4-Jan-11 12:34 
very well structured and explained, i couldn't imaging finding such sample.
 
thanks much, you saved a lot of time!
GeneralThanks for this article!memberpawnipt10-Nov-09 16:54 
Thanks to this article i've successfully implemented the linearfloodfill4 into my realbasic application using GDI+ Smile | :)
one thing i found that optimized its speed was to put PixelsChecked before each CheckColor in each if statement (since boolean comparisons are much faster then multiple byte comparisons). In realbasic this resulted in speeds of up to twice as fast.
QuestionWebmembermdv11315-May-09 2:54 
Awesome floodfill. Smile | :)
Ive been looking for a way to do a floodfill on areas of a bitmap loaded in a web page. Any ideas on how to implement your floodfill in silverlight or ASP.NET?
 
(I now use an javaapplet that I created. But it is slow and there all sorts of compatibility problems with java and security mostly blocks the applet)Unsure | :~
 
M.A

GeneralA little suggestionmembersprinter25226-Dec-08 3:33 
Hi,
 
a great work! Congratulations. I've just made a little tuning inside the FillFinished event handler of the sample form:
 

public void FillFinished(object sender, FillFinishedEventArgs ev)
{
if (!this.InvokeRequired)
{
//exception handling
if (ev.exception != null && ev.exception.GetType() != typeof(System.Threading.ThreadAbortException))
MessageBox.Show(String.Format("An error of type '{0}' occurred when using the {1} algorithm.", ev.exception.ToString(), Enum.GetName(typeof(FloodFillStyle), floodfiller.FillStyle)));
 
//join and close the floodfill thread
if (thread != null) thread.Join();
thread = null;
 
//update image
panel.ResumeLayout();
panel.BackgroundImage = (Image)chosenfloodfiller.Bmp;
panel.Refresh();
 
//update the statusbar & button
statusBar.Panels[0].Text = "Time Taken - " + " Fill: " + floodfiller.TimeBenchmark + ", Total: " + (timeGetTime() - time);
buttonStop.Enabled = false;
}
else
{
FillFinishedEventHandler d = new FillFinishedEventHandler(FillFinished);
this.Invoke(d, sender, ev);
}
}

 
This removes the winforms-related errors because of threading.
 
By the way: it is a little bit confusing to me, that MS has forgotten about this function in GDI/Drawing? What the ...? Even PHP got a method for this! Shame on them! Sniff | :^)
 
HTH,
sprinter252
GeneralPermissionmemberrax_s1-Nov-06 20:00 
Hi Justin,
 
I was thinking of a way to extract corners of polygons from some images. And came upon your program in codeproject. The polygons are manually created from scanned copies of road maps. From the corners - will have to convert the x,y coordinates to kml (keyhole markup language) format based on a few placemarks.
 
I just started modifying your program to do this - and it looks like I should be done in a couple of days. Will save a couple of friends a lot of time (on some personal work) if it all works.
 
Thought I would let you know if it ok that I modified your code for this (read the copyright - but wanted to ask anyway). What I am doing is for personal, non-commercial use only.
 
Thanks,
Rax
GeneralRe: PermissionmemberJ. Dunlap1-Nov-06 22:00 
Go ahead and use it however you want. Smile | :)
 
I've got an improved algorithm that's much faster now, and uses a non-pointer-based pixel manipulation method. I'm going to post it as an article when I get a chance, but if you email me, I'll send you the code right away.

 

GeneralRe: Permissionmemberrax_s2-Nov-06 1:16 
Sent an email.
 
A non-pointer based method would really help! (Most of my work is done and I was just now struggling with converting the pointer back to coordinates etc.)
 
Rax
GeneralRe: Permissionmemberrax_s3-Nov-06 3:53 
My work is done.
 
Dont know what I was saying there about the coordinates (they were just available as the variables LFillLoc/RFillLoc and y).
 
Rax
PS: My work is done but an article with a non-pointer based pixel implentation which is efficient would be interesting to see nevertheless...
GeneralRe: PermissionmemberJ. Dunlap15-Nov-06 17:01 
Hi Rax,
I have posted my pointerless image processing algorithm at http://www.codeproject.com/useritems/pointerlessimageproc.asp[^], and my new flood fill algorithm at http://www.codeproject.com/useritems/queuelinearfloodfill.asp[^].
regards,
Justin
 

GeneralGreat!!membereliran18-Sep-06 4:09 
Thanks a lot Smile | :)
GeneralRe: Great!!memberJ. Dunlap1-Nov-06 22:01 
You're welcome! Glad it's useful for you! Smile | :)

 

Generalm_fillcolor always has alpha of 0memberUnprofessional91123-Mar-06 4:51 
I've been trying to use your FloodFill classes on a Bitmap I'm using in one of my projects... the actual fill logic works perfectly, but for some reason, the alpha of the fill logic is always 0. I can't figure out where I'm going wrong...
 
// *** The method call from my code ***
// "_image" is a Bitmap that is 300x300 pixels. It is entirely white, except
// for four thin black lines that make an enclosed polygon to fill
FloodFill.FloodFiller floodFill = new FloodFiller();
floodFill.FillColor = Color.Red;
floodFill.Tolerance[0]=(byte)0;
floodFill.Tolerance[1]=(byte)0;
floodFill.Tolerance[2]=(byte)0;
floodFill.FillStyle = FloodFillStyle.Linear;
 
AbstractFloodFiller chosenFloodFill = floodFill;
chosenFloodFill.Bmp = _image;
chosenFloodFill.Pt = new Point(x,y);
chosenFloodFill.FloodFill();
 

// *** In FloodFiller.LinearFloodFill4 ***
while(true)
{
ptr[0]=m_fillcolor; //fill with the color "16711680"
 

If it's any help, the alpha is normal when using DemoFloodFiller instead of FloodFiller; and the only difference I can see is that Demo uses SetPixel instead of pointers. So... any idea as to where I'm going wrong here?...
 
Any help would be greatly appreciated!
GeneralRe: m_fillcolor always has alpha of 0memberCoLithium11-Apr-06 16:01 
I assume you are using a 32bppArgb Bitmap? This code doesn't work for that format. You will have to modify it. Just know that in memory a pixel looks like (in the 32bppArgb format which is what format he uses when he calls .LockBits):
BBBBBBBB GGGGGGGG RRRRRRRR AAAAAAAA
 
The reason the code doesn't work:
ptr[0]=m_fillcolor; //fill with the color "16711680"
//16711680 = 11111111 00000000 00000000 = Color.Blue
 
ptr is an int pointer. An int is 4 bytes long meaning that it can hold BGRA as one value. Let's step through the conversion process.
 
1) We set the color (m_fillcolorcolor) to Color.Blue (00000000 00000000 11111111 11111111 as a Color in memory, 11111111 00000000 00000000 11111111 as a Bitmap color in memory)
2) After this line (m_fillcolor=ColorTranslator.ToWin32(m_fillcolorcolor);)
m_fillcolor == 16711680 (00000000 11111111 00000000 00000000)
3) After this line (m_fillcolor=BGRA(GetB(m_fillcolor),GetG(m_fillcolor),GetR(m_fillcolor),GetA(m_fillcolor));)
m_fillcolor == 255 (00000000 00000000 00000000 11111111)
 
Clearly ColorTranslator.ToWin32 ignores Alpha. You can replace those two lines with:
m_fillcolor=(m_fillcolorcolor.B << 24) | (m_fillcolorcolor.G << 16) | (m_fillcolorcolor.R << 8) | m_fillcolorcolor.A;
to correct that problem, but other places in the code (the code that determines if it has reached the bitmap's edge or the edge of the color area) breaks.
 
I would instead use byte pointers and manipulate each color channel individually to avoid all of this (bit shifts, etc.). That way you can support 24bppRgb, 32bppRgb, AND 32bppArgb/32bppPArgb.
GeneralI'm confused.....memberChristian Graus5-Jul-05 14:08 
Thanks for referencing me in your article, but I'm confused. I looked at your code, and all your functions are marked unsafe, but I don't see any direct pixel access, just calls to getpixel and setpixel ? Am I missing something ?

 
Christian Graus - Microsoft MVP - C++
GeneralRe: I'm confused.....memberJ. Dunlap16-Jul-05 18:59 
DemoFloodFiller uses GetPixel() and SetPixel(), so that the results are immediately visible on the screen (check out the "Slow" option, which lets you see the fill in realtime), whereas FloodFiller uses unsafe code for the sake of speed.
 
Since the time that I wrote this (it's been a while!), I found out about a new pixel manipulation method, which allows you to do fast pixel manipulation directly on the bitmap bits in GDI+, without unsafe code, and without the overhead of copying the bits to a buffer and back (as LockBits does). Kudos to Frank Hileman[^] for alerting me to this new method!
 
Since changes can be done in realtime directly on the bitmap, there would also be no need for separate classes with the new method - the only difference between the two speed settings would be that the one would update a small portion of the screen on each change (rather than the whole bitmap as currently!) and the other would do no screen updates until the end of the operation.
 
In my tests, the new method turned out to be over twice as fast as the "unsafe" (pointer-based) method. I did a couple of extra optimizations on the new code, and that may account for part of the speed increase, but the new method eliminates the overhead of copying the bits to and from a secondary buffer (as LockBits() does), and I'm guessing that's where a lot of the speed increase comes from. At some point I'm going to optimize the old code as well, so that I can determine how much of the speed increase is due to the new method.
 
I don't know when I'll get a chance to post an update to this article, with code that uses this new method, but I hope to do it not too long from now. I don't really want to rewrite the Queue or Recursive algorithms over again, as they aren't really useful in a practical sense - so I'm considering just rewriting the article from scratch, and omitting the other algorithms, other than a brief mention in the article. I think I could do much better now at the article anyhow. I am also considering writing an article on an island detection algorithm I've worked out, although that one may need to wait a while due to time constraints.
GeneralRe: I'm confused.....memberChristian Graus17-Jul-05 13:07 
Sure - I still don't get why the class that doesn't use unsafe methods is marked unsafe, but anyhow...
 
I sure would be interested to hear your method for direct pixel access though...

 
Christian Graus - Microsoft MVP - C++
GeneralRe: I'm confused.....memberJ. Dunlap17-Jul-05 14:59 
Christian Graus wrote:
Sure - I still don't get why the class that doesn't use unsafe methods is marked unsafe, but anyhow...
 
You're right - I just looked at the code and several of the methods of DemoFloodFiller are unnecessarily marked as "unsafe". Blush | :O Thanks for alerting me to that!
 

Christian Graus wrote:
I sure would be interested to hear your method for direct pixel access though...
 
I will finish up the article and code as soon as I get the chance. Smile | :) I've got a working, although not fully finished build already. I built it with VS2005, though, because I wanted to get the test code working as quickly and easily as possible, and that's much easier in VS2005 with all the IDE enhancements. However, many people don't have it yet, so I may have to downport it to VS2003 before I post the article.
GeneralRe: I'm confused.....memberJ. Dunlap15-Nov-06 17:03 
Hi Christian,
I've posted my pointerless pixel access method here[^].
regards,
Justin

 

GeneralRe: I'm confused.....staffChristian Graus15-Nov-06 17:11 
I've already seen and read it, it looks pretty cool Smile | :)
 

 
Christian Graus - C++ MVP

GeneralRe: I'm confused.....memberCoLithium11-Apr-06 16:22 
If it isn't too much of a bother, do you have a link describing this method? Or can you give me the gist of it? I'm very interest in Image Processing and if it halves the time... I'm impressed.
GeneralInitialize GDI Bitmap from HBITMAPmemberRenugopal15-Jun-05 20:26 
I want to know how to initialize GDI Bitmap from HBITMAP instead from file.
and i want to access bitmap address:
Sample code here:
 
Bitmap *bmpq11;
bmpq11=(Bitmap*) Bitmap::FromHBITMAP(hbmp,NULL);
 
BitmapData* bitmapData = new BitmapData;
Rect rect(0,0,FFwidth,FFheight);
bmpq11->LockBits(&rect,ImageLockModeWrite,PixelFormat32bppARGB,bitmapData);
 
void *Scan0 = bitmapData->Scan0;
byte * scan0=(byte *)(void *)Scan0;
 
here scan0 become emppty when initialize bitmap from HBTIMAP
if i give file name...scan0 has some value.
can you tell me problem in the above code.
the above code i modified to my MFC application using GDI...
it works well when i initialize by file name...
thanksSmile | :)
-Renu
 

Generalrecognitionmemberamir mortazavi26-Jan-05 18:34 
Hi dear
Can you recognition coordinate of circle in bitmap?
i searching a algorithm for do this task but i cann't find
I want search a sample picture in another picture .
for example I give a circle picture to program then program search this picture in another picture ,if find get me coordinate of circle picture.
Thank
GeneralFixed!memberjdunlap5-Oct-03 22:21 
I'm hosting the files on my hosting area until I manage to upload them to CP. The links are updated to point to the files on my hosting area.
 

"Going to church doesn't make you a Christian any more than going to the garage makes you a car." -- Laurence J. Peter

FLUID UI Toolkit | FloodFill in C# & GDI+







General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web01 | 2.6.130617.1 | Last Updated 5 Oct 2003
Article Copyright 2003 by J. Dunlap
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid