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

QuickFill: An efficient flood fill algorithm.

By , 12 Mar 2004
 

Sample Image

Original image:

Sample Image

Fill pattern:

Sample Image

Introduction

The purpose of this article is to describe how to design an efficient flood fill algorithm.

In order to show the strengths of a good design, I must first: describe the basic types of flood filling and there individual strengths and weaknesses.

Note: By efficient, I mean creating an algorithm that reads as few pixels as possible and uses the least amount of memory allocations. Although we are now talking about flood filling a bitmap, historically speaking, flood fills read and wrote directly to the video, which was very slow. Therefore, it was a must to reduce the number of reads and writes; also the amount of memory was limited, so a reduction in memory usage was also a big plus.

What is the QuickFill algorithm?

The QuickFill algorithm is a non-recursive (seed fill) method of filling a 2D graphics image using a scan line search method and doubly linked to lists of nodes to reduce the amount of memory required. The scan line method used in combination with the linked lists, greatly increases the speed at which an image can be filled and allows for multiple fill types to be implemented: background, border, and pattern fills.

Note: The original QuickFill algorithm was written, as part of my personal DOS graphics library, to fill images displayed on a video monitor.

Basic 4 way recursive method

This is the most basic of all flood filling methods, as well as the simplest.

Its strength: simple to implement by even a beginner programmer.

Its weaknesses: repeated sampling of pixels and recursion (may overflow stack).

Sample Image

The diagram above shows how the flooding of a 3x3 image progresses. As you can see: each pixel was visited 2 or more times, and the depth of recursion was 10.

// Fill background with given color
void SeedFill_1(int x, int y, COLORREF fill_color)
{
    if( fill_color != GetPixel(x,y) ) { // sample pixel color
        SeedFill_1(x,y,fill_color);
        SeedFill_1(x-1,y,fill_color);
        SeedFill_1(x+1,y,fill_color);
        SeedFill_1(x,y-1,fill_color);
        SeedFill_1(x,y+1,fill_color);
    }
}
// Fill (over write) all pixels that are not the border color
void SeedFill_2(int x, int y, COLORREF fill_color, COLORREF border_color)
{
    if( border_color != GetPixel(x,y) ) { // sample pixel color
        SeedFill_2(x,y,fill_color,border_color);
        SeedFill_2(x-1,y,fill_color,border_color);
        SeedFill_2(x+1,y,fill_color,border_color);
        SeedFill_2(x,y-1,fill_color,border_color);
        SeedFill_2(x,y+1,fill_color,border_color);
    }
}

Basic 8 way recursive method

This is similar to the 4 way method, except that it is even more inefficient.

Its strength: simple to implement by even a beginner programmer.

Its weaknesses: repeated sampling of pixels, recursion (may overflow stack), and it is designed to leak on the diagonals.

Note: This is what I call an abnormal flood fill method. Since it is designed to leak on the diagonals, it has very limited use.

Sample Image

The diagram above shows how the flooding of a 3x3 image progresses. As you can see: each pixel was visited 3 or more times, and the depth of recursion was 10.

// Fill background with given color
void SeedFill_1(int x, int y, COLORREF fill_color)
{
    if( fill_color != GetPixel(x,y) ) { // sample pixel color
        SeedFill_1(x,y,fill_color);
        SeedFill_1(x-1,y,fill_color);
        SeedFill_1(x+1,y,fill_color);
        SeedFill_1(x,y-1,fill_color);
        SeedFill_1(x,y+1,fill_color);
        SeedFill_1(x-1,y+1,fill_color);
        SeedFill_1(x+1,y-1,fill_color);
        SeedFill_1(x+1,y+1,fill_color);
        SeedFill_1(x-1,y-1,fill_color);
    }
}
// Fill (over write) all pixels that are not the border color
void SeedFill_2(int x, int y, COLORREF fill_color, COLORREF border_color)
{
    if( border_color != GetPixel(x,y) ) { // sample pixel color
        SeedFill_2(x,y,fill_color,border_color);
        SeedFill_2(x-1,y,fill_color,border_color);
        SeedFill_2(x+1,y,fill_color,border_color);
        SeedFill_2(x,y-1,fill_color,border_color);
        SeedFill_2(x,y+1,fill_color,border_color);
        SeedFill_2(x-1,y+1,fill_color,border_color);
        SeedFill_2(x+1,y-1,fill_color,border_color);
        SeedFill_2(x+1,y+1,fill_color,border_color);
        SeedFill_2(x-1,y-1,fill_color,border_color);
    }
}

Recursive scan line method

The recursive scan line method is a type of 4 way method, but is more efficient than the 4 or 8 way single pixel recursive methods.

Its strength: complete lines are scanned.

Its weaknesses: repeated sampling of some lines and recursion (may overflow stack).

Sample Image

The diagram above shows how the flooding of a 3x3 image progresses. As you can see: each line was visited 1 to 3 times, and the depth of recursion was 2.

The line fill method below may be inefficient, but it reduces the number of times that a pixel is revisited and reduces the number of recursive calls made. It also has the advantage of being optimizable, that is it can be improved through optimization techniques.

Some techniques that can be applied are:

  1. write scan and search line functions that read and write the image data directly,
  2. rewrite the function to remove recursion.
// Fill background with given color
extern int nMinX, nMaxX, nMinY, nMaxY;
void LineFill_3(int x1, int x2, int y, 
        COLORREF fill_color, COLORREF seed_color)
{
    int xL,xR;
    if( y < nMinY || nMaxY < y )
        return;
    for( xL = x1; xL >= nMinX; --xL ) { // scan left
        if( GetPixel(xL,y) != seed_color )
            break;
        SetPixel(xL,y,fill_color);
    }
    if( xL < x1 ) {
        LineFill_3(xL, x1, y-1, fill_color, seed_color); // fill child
        LineFill_3(xL, x1, y+1, fill_color, seed_color); // fill child
        ++x1;
    }
    for( xR = x2;  xR <= nMaxX; ++xR ) { // scan right
        if( GetPixel(xR,y) !=  seed_color )
            break;
        SetPixel(xR,y,fill_color);
    }
    if(  xR > x2 ) {
        LineFill_3(x2, xR, y-1, fill_color, seed_color); // fill child
        LineFill_3(x2, xR, y+1, fill_color, seed_color); // fill child
        --x2;
    }
    for( xR = x1; xR <= x2 && xR <= nMaxX; ++xR ) {  // scan betweens
        if( GetPixel(xR,y) == seed_color )
            SetPixel(xR,y,fill_color);
        else {
            if( x1 < xR ) {
                // fill child
                LineFill_3(x1, xR-1, y-1, fill_color, seed_color);
                // fill child
                LineFill_3(x1, xR-1, y+1, fill_color, seed_color);
                x1 = xR;
            }
            // Note: This function still works if this step is removed.
            for( ; xR <= x2 && xR <= nMaxX; ++xR) { // skip over border
                if( GetPixel(xR,y) == seed_color ) {
                    x1 = xR--;
                    break;
                }
            }
        }
    }
}

void SeedFill_3(int x, int y, COLORREF fill_color)
{
    COLORREF seed_color = GetPixel(x,y);
    if( fill_color != seed_color )
        LineFill_3(x,x,y,fill_color,seed_color);
}

A simple non-recursive scan line method

The non-recursive scan line method is a type of 4 way method, but is more efficient than the recursive method.

Its strengths: complete lines are scanned, and no recursion.

Its weaknesses: repeated sampling of some lines, and limited stack size.

The line fill method below is more inefficient, than the recursive method. It also has the advantage of being optimizable, that is it can be improved through optimization techniques.

Some techniques that can be applied are:

  1. write scan and search line functions that, read the image data directly,
  2. rewrite the code so that it uses a linked list for stack.

The second, so called, optimization technique, appears as if it would slow down the filling process. If it was used in the same way as the stack array in the following code, then you would be correct. The fact is, a linked list allows us to apply other optimization techniques that more than compensate for the slight loss in efficiency. For example: we could pop items off the stack based on there line number, which reduces the number of items on the stack.

Note: Since most programmers now work with bitmaps, instead of reading and writing directly to video, the first optimization above may be all that is required to make this a truly fast solid flood fill algorithm.

// The following is a rewrite of the seed fill algorithm by Paul Heckbert.
// The original was published in "Graphics Gems", Academic Press, 1990.
//
// I have rewitten it here, so that it matches the other examples
// of seed fill algorithms presented.

extern int nMinX, nMaxX, nMinY, nMaxY;
typedef struct { int x1, x2, y, dy; } LINESEGMENT;

#define MAXDEPTH 10000

#define PUSH(XL, XR, Y, DY) \
    if( sp < stack+MAXDEPTH && Y+(DY) >= nMinX && Y+(DY) <= nMaxY ) \
    { sp->xl = XL; sp->xr = XR; sp->y = Y; sp->dy = DY; ++sp; }

#define POP(XL, XR, Y, DY) \
    { --sp; XL = sp->xl; XR = sp->xr; Y = sp->y+(DY = sp->dy); }

// Fill background with given color
void SeedFill_4(int x, int y, COLORREF new_color)
{
    int left, x1, x2, dy;
    COLORREF old_color;
    LINESEGMENT stack[MAXDEPTH], *sp = stack;

    old_color = GetPixel(x, y);
    if( old_color == new_color )
        return;

    if( x < nMinX || x > nMaxX || y < nMinX || y > nMaxY )
        return;

    PUSH(x, x, y, 1);        /* needed in some cases */
    PUSH(x, x, y+1, -1);    /* seed segment (popped 1st) */

    while( sp > stack ) {
        POP(x1, x2, y, dy);

        for( x = x1; x >= nMinX && GetPixel(x, y) == old_color; --x )
            SetPixel(x, y, new_color);

        if( x >= x1 )
            goto SKIP;

        left = x+1;
        if( left < x1 )
            PUSH(y, left, x1-1, -dy);    /* leak on left? */

        x = x1+1;

        do {
            for( ; x<=nMaxX && GetPixel(x, y) == old_color; ++x )
                SetPixel(x, y, new_color);

            PUSH(left, x-1, y, dy);

            if( x > x2+1 )
                PUSH(x2+1, x-1, y, -dy);    /* leak on right? */

SKIP:        for( ++x; x <= x2 && GetPixel(x, y) != old_color; ++x ) {;}

            left = x;
        } while( x<=x2 );
    }
}

The QuickFill algorithm

Finally, we get to the QuickFill method of flood filling, which is a type of 4 way method, but is more efficient than the simple non-recursive method (believe it or not).

Note: For the purposes of this article, I will be describing (mostly) the original QuickFill algorithm, since the included code does not use optimized scan, search, and line drawing functions (remember, the original code directly accessed the video).

Its strengths are:

  1. supports 3 types of fills: background, border and pattern,
  2. optimized scan, search and drawing functions,
  3. no recursion,
  4. use of doubly linked list, to allow for efficiently removing items from list based on line number,
  5. efficient use of list for reverse line splitting, when pattern filling is required.

Its weakness: repeated sampling of some lines, during solid fills.

As you can see from the code below, this is the most complicate method of flood filling. It was derived, indirectly, from the ideas presented in the simple non-recursive method.

The steps taken to create the original code where as follows:

  1. Wrote QuickFill function based on what I could remember about scan line filling.
  2. Replaced stack array with two singly linked lists.
  3. Modified PopLine function, so that lines would be removed based on line number.
  4. Added SearchLeft, SearchRight, ScanLeft, ScanRight functions; in order to allow for optimization of searching and scanning.
  5. Added the PushOpposite function, which reverse splits lines, in order to reduce the number of lines revisited. This required that the list be changed to a doubly linked list and that the list be x-sorted. The reason behind this function was to eliminate all line revisits, but instead it just reduced the number of visits (at a cost).
  6. Optimization of all of the above.

Note: While testing the port of the code to Windows, I discovered a gapping hole in the single visit code, patterns and masks could not be empty or the code would get stuck looping for ever (hence the following).

In order to make this function more useful to Windows programmers, I added code to support flooding of image with a bitmap pattern. Since PushOpposite only reduces the number of lines revisited and does not prevent revisits, I had to add another linked list to keep track of which lines had already been visited. The PushOpposite function is still required as it still reduces the number of line revisits and, as a bonus, it reduces the number of items placed in the visited list. To optimize the visited list, I decided to use visited blocks (rectangles) instead of visited lines, this serves to reduce the number of items needed in the list, which means less memory allocations.

Note: The following is the QuickFill function taken from version 1.0 of the code.

/* Arguments:
 *        Pointer to bitmap to fill, (x,y) coordinates of seed point,
 *        color used for solid or masked fills, border color used if area to
 *        be filled is outlined (or CLR_INVALID).
 *
 * Returns:
 *         0 = Success.
 *        -1 = Invalid seed point.
 *        -2 = Memory allocation error.
 *        -3 = Invalid bitmap or unknown error.
 */
int CQuickFill::QuickFill(
    CBitmap* pBitmap,int x,int y,
    COLORREF fill_color,COLORREF border_color/*=CLR_INVALID*/)
{
    COLORREF ThisColor;
    int MaxY,MaxX,dy;
    int ChildLeft,ChildRight;
    int ParentLeft,ParentRight;

#ifdef QUICKFILL_SLOW
    HWND hWnd;
    if( m_bSlowMode )
        hWnd = ::GetActiveWindow();
#endif

    // Create dib data object
    if( !m_DibData.CreateDIB(pBitmap) )
        return -3;

    /* Initialize global variables */
#ifdef QUICKFILL_TEST
    SHORT nKeyState;
    m_CurStackSize = m_MaxStackSize = m_VisitSize = 0U;
    m_CurrentLine = 0;
#endif
    m_bXSortOn = m_bMemError = FALSE;
    m_LastY = -1;

    /* Initialize internal info based on fill type */
    if( CLR_INVALID != border_color ) {
        /* Check color at x,y position */
        ThisColor = GetPixel(x,y);
        if( ThisColor == border_color )
            return -1;

        ThisColor = border_color;
        m_bXSortOn = TRUE;
    }
    else {
        /* Check color at x,y position */
        ThisColor = GetPixel(x,y);
        if( ThisColor == fill_color && !m_DibPattern.GetDibPtr() )
            return -1;

        if( m_DibPattern.GetDibPtr() || memcmp(m_FillMask,_SolidMask,8) )
            m_bXSortOn = TRUE;
    }

    /* Using macros because we can not uses pointer to member functions.
     * This makes the code less efficient, but solves the problem.
     */
#define FindLeft(x,y,xmin,color) \
    ((CLR_INVALID != border_color) \
    ? SearchLeft(x,y,xmin,color) : ScanLeft(x,y,xmin,color))
#define FindRight(x,y,xmax,color) \
    ((CLR_INVALID != border_color) \
    ? SearchRight(x,y,xmax,color) : ScanRight(x,y,xmax,color))
#define SkipRight(x,y,xmax,color) \
    ((CLR_INVALID != border_color) \
    ? ScanRight(x,y,xmax,color) : SearchRight(x,y,xmax,color))

    /* Initialize Line list */
    if( MakeList() )
        return -2;

    /* Initialize maximum coords */
    MaxX = m_DibData.GetWidth()-1;
    MaxY = m_DibData.GetHeight()-1;

    /* Push starting point on stack */
    PushLine(x,x,y,+1);        /* Needed in one special case */
    PushLine(x,x,y+1,-1);

    /* Now start flooding */
    while( m_pLineList ) {
        PopLine(&ParentLeft,&ParentRight,&y,&dy);
        y += dy;
        if( y < 0 || MaxY < y )
            continue;
        if( m_bMemError )
            continue;
        if( m_bXSortOn && IsRevisit(ParentLeft,ParentRight,y) )
            continue;

#ifdef QUICKFILL_SLOW
        if( m_bSlowMode ) {
            nKeyState = ::GetAsyncKeyState(VK_ESCAPE);
            if( nKeyState < 0 )
                break;
        }
        m_CurrentLine = y;
#endif

        /* Find ChildLeft end ( ChildLeft>ParentLeft on failure ) */
        ChildLeft = FindLeft(ParentLeft,y,0,ThisColor)+1;
        if( ChildLeft<=ParentLeft ) {
            /* Find ChildRight end ( this should not fail here ) */
            ChildRight = FindRight(ParentLeft,y,MaxX,ThisColor)-1;
            /* Fill line */
            if( ChildLeft == ChildRight )
                SetPixel(ChildRight,y,fill_color);
            else
                DrawHorizontalLine(ChildLeft,ChildRight,y,fill_color);

#ifdef QUICKFILL_SLOW
            if( m_bSlowMode && hWnd ) {
                m_DibData.SetDIBits(pBitmap);
                ::InvalidateRect(hWnd,NULL,FALSE);
                ::UpdateWindow(hWnd);
            }
#endif

            /* Push unvisited lines */
            if( ParentLeft-1<=ChildLeft && ChildRight<=ParentRight+1 ) {
                PushLine(ChildLeft,ChildRight,y,dy);
            }
            else {
                if( m_bXSortOn )
                    PushOpposite(ParentLeft,ParentRight,
                                 ChildLeft,ChildRight,y,dy);
                else
                    PushLine(ChildLeft,ChildRight,y,-dy);
                PushLine(ChildLeft,ChildRight,y,dy);
            }
            /* Addvance ChildRight end on to border */
            ++ChildRight;
        }
        else ChildRight = ParentLeft;

        /* Fill betweens */
        while( ChildRight < ParentRight ) {
            /* Skip to new ChildLeft end 
               ( ChildRight>ParentRight on failure ) */
            ChildRight = SkipRight(ChildRight,y,ParentRight,ThisColor);
            /* If new ChildLeft end found */
            if( ChildRight<=ParentRight ) {
                ChildLeft = ChildRight;
                /* Find ChildRight end ( this should not fail here ) */
                ChildRight = FindRight(ChildLeft,y,MaxX,ThisColor)-1;
                /* Fill line */
                if( ChildLeft == ChildRight )
                    SetPixel(ChildRight,y,fill_color);
                else
                    DrawHorizontalLine(ChildLeft,ChildRight,y,fill_color);

#ifdef QUICKFILL_SLOW
                if( m_bSlowMode && hWnd ) {
                    m_DibData.SetDIBits(pBitmap);
                    ::InvalidateRect(hWnd,NULL,FALSE);
                    ::UpdateWindow(hWnd);
                }
#endif

                /* Push unvisited lines */
                if( ChildRight <= ParentRight+1 )
                    PushLine(ChildLeft,ChildRight,y,dy);
                else {
                    if( m_bXSortOn )
                        PushOpposite(ParentLeft,ParentRight,
                                      ChildLeft,ChildRight,y,dy);
                    else
                        PushLine(ChildLeft,ChildRight,y,-dy);
                    PushLine(ChildLeft,ChildRight,y,dy);
                }
                /* Addvance ChildRight end onto border */
                ++ChildRight;
            }
        }

        /* Push visited line onto visited stack */
        if( m_bXSortOn )
            PushVisitedLine(ParentLeft,ParentRight,y);
    }
    FreeList();
    m_DibData.SetDIBits(pBitmap);
    m_DibData.DeleteObject();
    return( m_bMemError?-2:0 );
}

Parent/Child line relationship:

Sample Image

Basic line pushing:

Note: In the following diagram "Scanned" impiles next line to be scanned, when line being pushed is popped off the stack.

Sample Image

Reverse line splitting:

Note: In the following diagram the scanning/pushing starts at the top.

Sample Image

Note: In the following diagram the blue (arrowed) lines represent the lines being pushed.

Sample Image

Background

At the time that the QuickFill algorithm was created, I had seen only two types of flood fills used and wanted an algorithm that could handle both types. The first type used a border-color approach, which meant that the area that needed to be filled first had to be outlined with a border of a given color; this approach is the least desirable of the flood fills. The second type used the color at the seed-point, which meant that only pixels of that color where filled; this approach is much more versatile, since it allows for multiple image objects of varying color to be contained in the area being filled. Both of these flood fill types used a horizontal scan-line approach to solve the problem of flood filling an image.

When trying to find information on how to implement a fast flood fill algorithm, I discovered that there was almost no information on the subject. The only algorithms that I found were:

  1. the basic recursive 4 way flood fill that has been mentioned in many publications (and still is), this is the worst (and slowest) method for filling an image,
  2. a non-recursive scan-line version (ref. 1), that was faster than the 4 way version (still too slow for my purposes), and
  3. a non-recursive scan-line version (ref. 2) that used two linked lists to implement a flood fill algorithm, faster (but still too slow).

The first two methods had the advantage of being very compact, and very slow. The third method on the other hand, had the potential of being very fast and complicated.

After placing the idea on the back burners for a while, since it was for my own personal graphics library and was not a priority, I had an epiphany. I was having a cup of coffee at the local coffee shop and thinking about how I could solve the problem, when it all came together in my head. I started with the ideas used in the simple scan-line algorithm (ref. 1) and proceeded to expand on the idea using a singly linked list (ref. 2) of nodes, representing a LIFO stack. Over the next nine days, I made incremental changes to the algorithm, each designed to increase the over all speed. When I was finished, I had an algorithm that was not only fast, but faster than every implementation that I could find, except for the one implemented in the Fastgraph library by Ted Gruber (written in assembly). Five months after the completion of the code, I finally went back and added the code necessary for single line visits so that the QuickFill function could also handle pattern fills.

References:

  1. A Seed Fill Algorithm by Paul Heckbert from "Graphics Gems", Academic Press, 1990
  2. An Efficient Flood Visit Algorithm by Anton Treuenfels, C/C++ Users Journal Volume 12, Number 8, August, 1994

Using the code

Note: When running the demo program in slow mode, you can press the ESC key to stop it.

CDibData specific:

If you have not installed the Windows SDK from Microsoft, you will get some compilation errors when compiling in debug mode. The reason for this is that BITMAPV5HEADER was not defined in the SDK that came with Visual C++ 6.0. Since BITMAPV5HEADER is only used for displaying debugging information, it should be easy to comment out the code.

// Normal solid filling
#include "QuickFill.h"

// Declare a quickfill variable
QuickFill qf;

// Call quickfill to flood fill image background starting at given point
qf.QuickFill(&bitmap, x, y, fill_color);

// Or call quickfill to flood fill outlined image starting at given point
qf.QuickFill(&bitmap, x, y, fill_color, border_color);
// Pattern filling
#include "QuickFill.h"

// Declare a quickfill variable
QuickFill qf;

// Set bitmap used as pattern
qf.SetPatternBitmap(&patternBitmap);

// If you want to make a color transparent (only applies to patterns)
qf.SetPatternClearColor(transparentColor);
 
// Call quickfill to flood fill image background starting at given point
qf.QuickFill(&bitmap, x, y, fill_color);

// If pattern bitmap no longer needed
// Frees pattern bitmap and invalidates pattern clear color.
qf.SetPatternBitmap();
// Masked filling
#include "QuickFill.h"

// Declare a quickfill variable
QuickFill qf;

// Set mask to use
BYTE diagMask[8] = {
    0x80, 0x80>>1, 0x80>>2, 0x80>>3,
    0x80>>4, 0x80>>5, 0x80>>6, 0x80>>6,
};
qf.SetFillMask(diagMask);

// Call quickfill to flood fill image background starting at given point
qf.QuickFill(&bitmap, x, y, fill_color);

// If mask to longer needed
qf.SetFillMask();

Points of Interest

The code included with this article relies on the CDibData class to provide direct access to bitmap pixels.

I see no barrier to rewriting the code for use with GDI+, the only reason I did not do that, is that, I do not know enough about GDI+ usage at this time. Besides, I have a personal paint program written using GDI and would like to add this code to it.

For those who want to compare the various flood fill algorithms, I suggest the following:

  1. add some tracking variables and code (like the ones in the QuickFill code),
  2. make the test function or functions private members of the QuickFill class,
  3. after the QuickFill initialization, but before the first push, add a call to the test function. Then clean up after the test function call and return without entering the main QuickFill loop.

Note: The above is how I tested the recursive scan line code. Since it was written off the top of my head, I needed to be sure it would work.

If any one knows how to completely eliminate the need for the visited list, I would be very interested. When I discovered that the original method used to reduce revisits did not eliminate them, I felt insulted by my own code. At the time I wrote the QuickFill algorithm, I may have known of this problem and just forgot about it (doubt that).

Question: How would you optimize the QuickFill algorithm for bitmaps?

Answer: Modify the CDibData class so that it has optimized functions for ScanLeft, ScanRight, SearchLeft, SearchRight and horizontal line drawing. Of course, horizontal line drawing would be the hardest, since it would have to work the same as the DrawHorizontalLine member function.

History

Version 1.0

  • Jan. ??, 2004 : Converted from C (direct video access) to C++ for use with MS Windows bitmaps and added support for: pattern bitmaps to be used as fill and visit-block-list to eliminate revisiting durring pattern & mask fills.

Note: Since the origanal code had direct video access it could take advantage (with the correct write mode) of byte block copping and color-bit-mask reading (read mode 1). Both of which where hardware supported (VGA/EGA monitors) and much more efficeint than reading/writing single pixels directly from/to a bitmap.

Version 1.1

  • Feb. 6, 2004 : Added left optimization, eliminates some revisits.
  • Feb. 8, 2004 : Added reverse clip optimization, eliminates some revisits.
  • Feb. 15, 2004 : Found PushOpposite special case and corrected it.
  • Feb. 19, 2004 : Changed internal scan, search and line drawing routines to use pixel color values, in order to increase overall speed while working with palettized bitmaps (modified CDibData).
  • Mar. 5, 2004 : (1) Moved PushVisitedLine from QuickFill to PushOpposite, this increases the number of revisits and reduces the size of the visit list (8:1). (2) Changed visit list to use HLINE_NODE, since block checking is no longer required and the number of allocations are reduce because the free list can now be used by all. (Of course HLINE_NODE is larger than we need, since it is not a visit list specific node type)
  • Mar. 9, 2004 : (1) Added argument to QuickFill so that user can specify the rectangular area to be filled. (2) Added the GetInvalidRect function and associated code so that user can retrieve the rectangular coordinates of the bitmap area that has been modified, since lack of this information forces user to redraw whole bitmap.

Changes to demo program:

  • "Fill Mask" selection option.
  • "Show Revisits" option. This is used to show which lines were placed in the visit list, when pattern bitmaps or masks are used. Line types: Cyan->line in visit list, Yellow->revisited line direction = -1, Blue->revisited line direction = +1.
  • Fill area selection via mouse (left-click and drag to select area). To fill: left-click in selected area and release to fill.
  • Added elapsed fill time and revisit count to information displayed to right of bitmap.

Credits

  • Andrew J. McCauley for modifying CDibData debug code so that header type checking occurs based on WINVER rather than on header type only. This stopped compilation errors which occurred if the user did not have the new Windows SDK installed.

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

John R. Shaw
Software Developer
United States United States
Member
I am a senior software engineer who has been designing and developing software for many years, mostly in C/C++. You might say that I think in code; which is why I am passionate about my first rule of coding: “First do no harm”. So if I get carried away in my explanations, please realize that it is just part of my personality. I enjoy learning new things and, when I have the time, passing that knowledge onto others.

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   
QuestionLicensememberjkronk28 Mar '12 - 16:48 
John,
 
From the "QuickFill: An efficient flood fill algorithm" is the following text:
 
"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."
 
Could you please reply to this post privately so I can ask you a more detailed question about acquiring a license to use the code posted in this article?
 
Thanks,
-Joe
GeneralSingle visit of each pixelmemberMember 155606918 Mar '11 - 5:51 
I just posted a paper on my website, http://www.crbond.com , which describes in detail a flood fill algorithm which only visits each processed pixel once. It supports filling from other bitmaps, tiles, gradient fills, re-bordering, and flooding under text. I was able to avoid the revisiting problem by processing filled line segments, one at a time, and pushing candidate scan areas on an "up" stack or a "down" stack, depending on which side of the current scan line the adjacent test region was on.
C. R. Bond

GeneralRe: Single visit of each pixelmemberMember 155606922 Mar '11 - 19:19 
The current version of the description of the flood fill algorithm presented on my website includes a proof that only a single visit of each pixel is possible. To my knowledge, no proof of performance for any other method has been presented in the literature. I welcome comments.
GeneralRe: Single visit of each pixelmemberJohn R. Shaw14 Apr '11 - 12:55 
Hi Bond,
 
I liked your paper. As far as I can tell, your logic is correct and no revisits would occur. You have even given me an idea, that I cannot remember testing, on how I might fix the revisit problem in my code – if I ever have the time.
 
And now for the “But” that you knew was coming. Wink | ;)
 
History:
 
At the time I wrote the original version of the QuickFill algorithm, memory was at a premium and the reduction in stack size was as important as speed. I suspect that the floodfill() in Microsoft's old DOS graphics library may have used a method similar to yours. Microsoft's version choked on one of my solid fill tests and I have always assumed that it just ran out of fixed sized stack space. I cannot remember if it locked up or just returned without finishing the job; I suspect the latter.
 
Efficiency:
 
The efficiency of your algorithm is tied directly to the size of your stacks. If they where allocated dynamically, as needed, there would be a drop in efficiency while the stacks where growing. But under normal circumstances, a reasonable initial size, that can grow by a fixed amount - if needed, would help reduce possible memory allocation [and copy] hits.
 
The reason I bring up memory allocation, as opposed to a fixed stack size, is that the size of the stacks required by your algorithm could be quite large. In the test I mentioned above, the number of segments that could be pushed on the stack was in the hundreds, if not more. That is why that method was rejected during the design of the QuickFill algorithm – it did not meet efficient memory usage requirements.
 
The Test:
 
If I remember correctly, I created a very large image filled with U shaped boxes, at least 3x2 pixels, spaced 1 or 2 pixels apart. Then set the fill point (seed point) at bottom to see what happened.
 
I believe this, or something similar, would be a good test for your algorithm, as every one of the openings to each of those boxes will be pushed onto the fill down stack before fill_up() is finished.
 

Any way, thanks for the post, I found it educational.
 
- John R. Shaw
INTP
"Program testing can be used to show the presence of bugs, but never to show their absence." - Edsger Dijkstra
"I have never been lost, but I will admit to being confused for several weeks. " - Daniel Boone

GeneralRe: Single visit of each pixelmemberMember 155606914 Apr '11 - 16:02 
Hi John,
 
Thanks for the response. Regarding the memory efficiency vs. other considerations, I guess it's too much to expect one algorithm to achieve everything. Anyway, I've written a test program which monitors the pixel visits, stack sizes, etc., and hope to expand the paper to disclose everything I've found. Ideally, a potential requirement for flood fill methods could order the priorities in a way that makes the final selection of a method fairly straightforward.
 
When I revise the paper to include test data, I'll forward a link to you.
 
Thanks again,
 
C. Bond, cbond@ix.netcom.com
GeneralRe: Single visit of each pixel [modified]memberThink-A-Tron5 Jun '11 - 21:17 
Perhaps I misunderstand something, but consider the following fill beginning at 1, where the numbers are fillable and the Xs aren't:

X X X X X
X 2 3 4 X
X 1 X 5 X
X 8 7 6 X
X X X X X

 
As I understand it:
1 would be filled and 8 would be pushed on the down stack.
234 would be filled and 5 would be pushed on the down stack.
Since the top boundary is reached, 5 would be popped and a down scan begun.
5 would be filled.
678 would be filled and 1 would be pushed on the up stack.
Since the bottom boundary is reached, 1 would be popped and an up scan begun.
1 would be revisited.
Since 1 is already filled, 8 would be popped and a down scan begun.
8 would be revisited.
The fill would be complete since 8 is already filled and the stacks are empty.

modified on Monday, June 6, 2011 3:23 AM

GeneralRe: Single visit of each pixelmemberMember 15560696 Jun '11 - 4:04 
You are correct for the situation you posted and for the explanation I gave in my paper. However, there was a section left out of the paper which explains the stack-checking algorithm which removes visited pixels from the stack.
 
On reviewing the paper, I found that the text was so poorly done, that I needed to provide graphic examples for situations such as the one you cited. This revision is in preparation and when I repost it, I'll leave a message on this forum.
 
Sorry for doing most, but not all of the job I set out to do. And thanks for taking the time to dig out an omission.
 
In short, there is an additional test required following each segment fill which removes overlapping pixels from the stack. The overhead is light, because the stack does not grow very rapidly. I have a version which reports the stack depth and have verified this over dozens of example screens -- including screens with text.
GeneralRe: Single visit of each pixelmemberJohn R. Shaw6 Jun '11 - 11:48 
Hi Bond,
 
For some reason I cannot load your paper at the moment to see what has changed. Last time we communicated, I tried to find my old revisit test result bitmaps and could not, so I did not tell you about them. Besides, I stripped all the code out that generated them without keeping a copy of it.
 
I am a visual person, so for revisit testing I created something like a 2D array of integers, that had one value for each pixel in the bitmap. Every time a pixel was read (visited), it would increment the associated value in the 2D array. After the fill was complete, it would generate a bitmap (Think Minesweeper) showing the number of visits - single visits green, revisits red, and unvisited white.
 
I used this method in a number of ways for testing. I even used it to generate a series of bitmaps, for small fills, that showed each step along the way.
 
Any way – you get the idea.
 
Happy coding,
 
INTP
"Program testing can be used to show the presence of bugs, but never to show their absence." - Edsger Dijkstra
"I have never been lost, but I will admit to being confused for several weeks. " - Daniel Boone
GeneralRe: Single visit of each pixelmemberMember 15560696 Jun '11 - 13:38 
Hi John,
 
It may be a few days before I post the complete algorithm for a single-visit flood fill, because I have to squeeze the work between other projects and some graphics are needed to illustrate the stack-test algorithm.
 
Basically, after each segment fill, the stack is scanned for overlapping pixels. This is facilitated by the fact that the line number, one of the entries on the stack, is known. Overlapping pixels are deleted or suitably trimmed.
 
For testing, I did exactly as you did: I wrote a special purpose fill routine which increments fillable pixels so revisits could be identified easily. I have confirmed the method with 2D fields consisting of backgrounds with randomly placed barriers or with ASCII text.
 
I'll try and finish the paper in a timely manner, because I don't like having incomplete or misleading documents or tutorials on my website. However, interested coders can certainly implement a stack scanner themselves if they wish. The paper will offer complete pseudo-code and possible actual C code if I can.
 
Thanks for all comments, including negative ones which point to the need for improvements. I'll post a notice here when I get things done.
GeneralRe: Single visit of each pixelmemberThink-A-Tron6 Jun '11 - 19:33 
Yes, I think that revisits can be eliminated by checking the stacks, though the challenge is to do so efficiently enough that the extra checking doesn't outweigh the advantage of avoiding revisits.
 
John R Shaw references a paper, An Efficient Flood Visit Algorithm, by Anton Treuenfels in Dr. Dobbs that describes one approach. Unfortunately, the paper isn't as clearly written as it it could be, and as I recall, the code contains a few errors. Nevertheless, I think the overall approach is quite good. I haven't yet convinced myself beyond a doubt that Treuenfels's method eliminates revisiting filled pixels, but think it's likely it does.
GeneralRe: Single visit of each pixelmemberJohn R. Shaw6 Jun '11 - 9:59 
Whoops! Laugh | :laugh:
I wrote this after seeing the question in my email and posted before seeing that the question was not for me. Oh well - enjoy the information any way. Big Grin | :-D
 
Unfortunately you did misunderstand, but to be sure I walked through it to double check.
 
Keep in mind that only filled parent segments are on the stack, along with an indicator (dy) where it may have a child segment. Also that the stack is a stack in name only; it stopped being an actual stack as soon as sorting and line segment culling was added.
 
First take a look at PushOpposite; it is doing roughly the following;
if ( No parent segment is found on stack ) {
      if ( Clipped version of current segment would be a valid child segment )
            Push clipped segment
}
else {
   if ( We can create any clipped segments that do not overlap any of its parent segments )
         Push those clipped segments
   if ( Any parent segments from the stack would completely overlap the current segment )
         Remove those parent segments from the stack
   if ( Any parent segment only partially overlaps the current segment )
         Clip that parent segment; so that it is no longer overlapping
}
Now here is what will happen:
// Initialize stack
Push S1 = { 1, dy = +1 } // parent 1, child 8
Push S2 = { 8, dy = -1 } // parent 8, child 1
Stack = { S1, S2 }
 
Pop S2 // scans/fills 1

Push S3 = { 1, dy = -1 } // parent 1, child 2
Stack = { S1, S3 }
 
Pop S3 // scans/fills 234

Push S4 =  { 234, dy = -1 } // parent 234, child is upper boundary
Push Opposite S5 = { 4, dy = +1 } // (Clipped) parent 4, child 5
Stack = { S1, S4, S5 }
 
Pop S5 // scans/fills 5

Push S6 = { 5, dy = +1 }
Stack = { S1, S3, S6 }
 
Pop S6 // scans/fills 678

Push S7 = { 678, dy = +1 } // parent 678, child is lower boundary
Push Opposite: Nothing pushed - S1 is popped/removed from stack because we just filled its child
 
Stack = { S4, S7 }
 
Pop S7 // nothing to fill
Pop S4 // nothing to fill

Stack = {empty}
 
Finished: Clean up and return result
 
Let's check your statements:
 
1) 1 would be filled and 8 would be pushed on the down stack.
 
No: 1 would be filled and 2 would be pushed on the stack. The parent of 8 is already on the stack.
 
2) 234 would be filled and 5 would be pushed on the down stack.
 
Close: 234 would be filled; the top boundary segment and 5 would be pushed on the stack.
 
3) Since the top boundary is reached, 5 would be popped and a down scan begun.
 
No: 5 would be popped because it is the next item on the stack.
 
4) 5 would be filled.
 
Correct.
 
5) 678 would be filled and 1 would be pushed on the up stack.
 
No: 678 would be filled and the bottom boundary segment would be pushed on the stack. Since 1 is a [overlapping] parent of 8 and is already on the stack, it is removed from the stack.
 
6) Since the bottom boundary is reached, 8 would be popped and a down scan begun.
 
Close: The bottom boundary is popped and scanned. The parent of 8 is no longer on the stack (See (5) above).
 
7) 8 would be revisited.
 
No: We are currently visiting the bottom boundary (See (6) above).
 
8) Since the bottom boundary is reached, 1 would be popped from the up stack.
 
No: The top boundary is popped and scanned, neither 1 nor any parent of 1 is on the stack.
 
9) 1 would be revisited.
 
No: We are currently visiting the top boundary (See (8) above).
 
10) The fill would be complete since 1 is already filled and the stacks are empty.
 
Almost correct: The fill is complete because the stack is empty.
INTP
"Program testing can be used to show the presence of bugs, but never to show their absence." - Edsger Dijkstra
"I have never been lost, but I will admit to being confused for several weeks. " - Daniel Boone

GeneralRe: Single visit of each pixelmemberThink-A-Tron6 Jun '11 - 20:26 
Actually, I have a question for you. You reference the Dr. Dobbs article An Efficient Flood Visit Algorithm by Anton Treuenfels that claims no revisits, but you mention that a change you made to your code reduced, but didn't eliminate, revisits. Did you use a different method than Treuenfels, or does his method fail to eliminate revisits? If you didn't use Treuenfels's algorithm, did you have a particular reason for not using it?
 
I'm interested in having a general fill routine that doesn't revisit filled pixels, and have been looking at the Dr. Dobbs article in background mode as a basis for writing a fill routine. I can see why a method that removes duplicate spans from the stack (or whatever memory stucture) could eliminate revisits, but I'm not yet completely convinced Treuenfels's method always succeeds in doing so. At least for me, analyzing when revisits can occur isn't easy.
 

Thanks,
Think-A-Tron
(Which, in case you wondered, was the name of a toy computer game.)
GeneralRe: Single visit of each pixelmemberJohn R. Shaw8 Jun '11 - 6:27 
Actually, I never tested Treuenfels code to see if it eliminated all revisits. Given my current knowledge and a quick review of the code presented in his article, I do not believe it does. Although this can only be verified by thorough testing.
 
My algorithm came to me some months after reading the articles cited and is a combination of the ideas presented in both of them; therefore it uses similar methods. At the time I was only concerned about efficiency/speed; because reading and writing directly to video memory can be very expensive. When testing other methods, including Treuenfels's, I simply could not get them to run fast enough to meet my minimum requirements.
 
For efficient flood filling, using solid colors, an occasional revisit of a single pixel is outweighed by the overall speed. Eliminating all revisits is only required if you need to flood fill an image with a pattern. Since pattern revisits can lead to an infinite loop of pushing and popping the same segment. Although I have analyzed QuickFill many times, it was only through testing that I found all the holes.
 
If I where going to test Truenfels algorithm, I would insert the code I used in the demo for reading, writing and tracking visits. Then just call it from the QuickFill method, allowing it to be tested with the demo application. That bitmap shown in the demo is not totally random, it covers every case I ever found where a revisit occurred during testing.
 
It looks like its time for me to revisit this algorithm. This version was actually written specifically for this article and influenced heavily by MFC, so it can be improved.
 
Happy Coding,
 
(5th attempt to post - 1st today)
 
INTP
"Program testing can be used to show the presence of bugs, but never to show their absence." - Edsger Dijkstra
"I have never been lost, but I will admit to being confused for several weeks. " - Daniel Boone
GeneralRe: Single visit of each pixelmemberThink-A-Tron8 Jun '11 - 20:10 
Thank you for your very helpful answer. I started looking again at the code in Treuenfels' article, and remembered something I'd noticed before but forgotten. The routine that seems to be the key difference between his method and the usual method, "removeoverlap," doesn't make sense to me. From the way many of the dashes in the listing are shown as two dashes, I assume it was converted using OCR, and wonder if there was some error in the conversion.
 
In any case, even if the particular implementation doesn't work, I'm fairly convinced that it's possible to avoid revisiting filled pixels, and hope to figure out exactly how. My motive is partly aesthetic -- I just think I'd be neat -- but also practical. I have a program that uses a flood fill to count the number of connected pixels with certain properties, and it annoys me to have to do an extra fill to clear 'visited' flags I set in the counting fill.
 
I'm sure I'll take advantage of your excellent test cases to test any fill method I write. I'll also write a Code Project article, assuming the method's useful enough to be worth sharing.
GeneralRe: Single visit of each pixelmemberMember 155606926 Jul '11 - 9:29 
Flood fill programmers:
 
I've updated the paper on my website to include a brief summary of the stack-checking algorithm required to eliminate all revisits. The paper, at http://www.crbond.com also refers to a companion paper which describes the stack checking in further detail. Because the project has taken on aspects of a PhD dissertation, I'm carefully rewriting the description of the required logic so it as as correct as possible.
 
I apologize for posting here before I had completed the papers, but I didn't expect the magnitude of the text to require so many graphics.
 
I'll post again when I finally post the companion paper.
GeneralRe: Single visit of each pixel [modified]memberThink-A-Tron28 Jul '11 - 21:04 
I have a caution: The stack can actually get fairly large, and checking it for overlaps can add substantial overhead. I'm pretty sure that for regions without holes, neither your algorithm without the stack check nor a well-written implementation of the standard scanline fill algorithm revisits already filled pixels. That means for such regions, any overhead to test for overlaps slows down the fill relative to the standard fill.
 
Let me give an example. Consider the following 10 pixel long, two-line pattern, where Xs represent nonfillable pixels and Os represent fillable pixels:
 
oooooooxxxoooooooxxxoooooooxxxoooooooxxx
ooxxxoooooooxxxoooooooxxxoooooooxxxooooo
oooooooxxxoooooooxxxoooooooxxxoooooooxxx
ooxxxoooooooxxxoooooooxxxoooooooxxxooooo
oooooooxxxoooooooxxxoooooooxxxoooooooxxx
ooxxxoooooooxxxoooooooxxxoooooooxxxooooo
 
It turns out that for your method of filling, the maximum combined stack depth when filling this pattern is about equal to one-seventh the number of pixels. For instance:
50 x 50: 352
100 x 100: 1405
400 x 500: 28823
 
Obviously checking over 28 thousand intervals for intersection with a filled span will have a negative effect on performance. While this example may seem rather contrived, something like it could occur, and even relatively tame examples can produce a stack depth of over a hundred.
 
BTW, I think there's a small error in your description. I believe the line,
7) if rnew < R-1 push_up(rnew, R, y-1)
should be,
7) if rnew < R-1 push_up(rnew+2, R, y-1).
The pixels at rnew and rnew+1 have already been tested, so they needn't be tested again.
 
For anyone who wishes to experiment with the algorithm (minus the stack overlap tests), I'll provide my implementation in C#. Please forgive any weakness in the coding style; I wrote it rather quickly, with no great effort at elegance or efficiency. My version is structured slightly differently than the description, but I believe it's functionally the same. The Fill4 routine is passed a delegate function that tests the pixel to see if it needs to be filled, fills it if it does, and returns a boolean that's true if the pixel was filled, and false otherwise. Obviously this sacrifices efficiency for generality.
 
        class FloodFill
        {
            private int xMin, yMin, xMax, yMax;
            TestAndModifyPixel testAndModifyPixel;
 
            // A routine that does the following:
            // Test the pixel at (x, y) to see if it hasn't already been
            // modified, and if not, whether it fits the criteria to
            // need modification.  If so, modify the pixel and return
            // true; otherwise, return false.
            public delegate bool TestAndModifyPixel(int x, int y);
            public delegate bool TestPixel(int x, int y);
            public delegate void ModifyPixel(int x, int y);
 
            public FloodFill()
            {
                DeallocateMemory();
            }
 
            private struct Statistics
            {
                public int ShadowCount;
 
                public void Clear()
                {
                    ShadowCount = 0;
                }
 
                public void IncrementShadowCount()
                {
                    ShadowCount++;
                }
            }
            Statistics Stats = new Statistics();
 
            private bool inUse = false;
            public bool InUse
            {
                get { return inUse; }
                private set { inUse = value; }
            }
 
            // Set the x and y range.
            public void SetRange(int XMin, int YMin, int XMax, int YMax)
            {
                xMin = XMin; yMin = YMin;
                xMax = XMax; yMax = YMax;
                int ySize = yMax - yMin + 1;
            }
 
            private class Shadow
            {
                public int left, right;
                public int y;
                public Shadow next, previous;
 
                public Shadow(int myLeft, int myRight, int myY)
                {
                    left = myLeft;
                    right = myRight;
                    y = myY;
                    next = previous = null;
                }
 
                public Shadow(int myLeft, int myRight, int myY, Shadow myNext)
                {
                    left = myLeft;
                    right = myRight;
                    y = myY;
                    next = myNext;
                    previous = null;
                }
            }
 
            Shadow upShadowsHead;
            Shadow upShadows;
            Shadow downShadowsHead;
            Shadow downShadows;
 
            public void DeallocateMemory()
            {
                upShadowsHead = new Shadow(0, 0, 0, null);
                upShadows = upShadowsHead;
                downShadowsHead = new Shadow(0, 0, 0, null);
                downShadows = downShadowsHead;
                Stats.Clear();
            }
 
            public int GetAllocationCount(int which)
            {
                switch (which)
                {
                    case 0:
                        return Stats.ShadowCount;
                    default:
                        return 0;
                }
            }
 
            public int ShadowCount
            {
                get { return Stats.ShadowCount; }
            }
 
            int upStackSize = 0;
            int downStackSize = 0;
 
            // The stack is stored as a double-linked list, with a dummy node on the end the
            // list as the top of the stack.  New nodes are added as needed, but not removed
            // except by calling DeallocateMemory().  Normally, pushing a shadow consists
            // moving to the previous node.  Popping a shadow consists of moving to the next node.
            void PushUpShadow(int left, int right, int y)
            {
                if ((upShadows = upShadows.previous) == null)
                {
                    Stats.IncrementShadowCount();
                    upShadows = new Shadow(left, right, y, upShadowsHead);
                    upShadowsHead.previous = upShadows;
                    upShadowsHead = upShadows;
                }
                else
                {
                    upShadows.left = left;
                    upShadows.right = right;
                    upShadows.y = y;
                }
                upStackSize++;
            }
 
            void PopUpShadow(out int left, out int right, out int y)
            {
                left = upShadows.left;
                right = upShadows.right;
                y = upShadows.y;
                upShadows = upShadows.next;
                upStackSize--;
            }
 
            void PushDownShadow(int left, int right, int y)
            {
                if ((downShadows = downShadows.previous) == null)
                {
                    Stats.IncrementShadowCount();
                    downShadows = new Shadow(left, right, y, downShadowsHead);
                    downShadowsHead.previous = downShadows;
                    downShadowsHead = downShadows;
                }
                else
                {
                    downShadows.left = left;
                    downShadows.right = right;
                    downShadows.y = y;
                }
                downStackSize++;
            }
 
            void PopDownShadow(out int left, out int right, out int y)
            {
                left = downShadows.left;
                right = downShadows.right;
                y = downShadows.y;
                downShadows = downShadows.next;
                downStackSize--;
            }
 
            // Fill4: set the pixel at (x ,y) and all of its 4-connected neighbors
            // with the same pixel value to the new pixel value.
            // A 4-connected neighbor is a pixel above, below, left, or right of a pixel.
            public void Fill4(int seedX, int seedY, TestAndModifyPixel argTestAndModifyPixel)
            {
                // Exit if the position is out of range.
                if ((seedX < xMin) || (seedY < yMin) ||
                    (seedX > xMax) || (seedY > yMax))
                    return;
 
                InUse = true;
                testAndModifyPixel = argTestAndModifyPixel;
 
                // Scan the seed span, wich consists of the seed pixel.
                // Exit if the pixel isn't filled.
                if (!LineFill(seedX, seedX, seedY))
                {
                    inUse = false;
                    return;
                }
 
                // Save the shadow for the previous and next lines.
                PushUpShadow(leftNew, rightNew, seedY + 1);
                PushDownShadow(leftNew, rightNew, seedY - 1);
 
                // Pop the stack with the most shadows, then move up or down until a shadow
                // contains no fillable pixels. Return when both stacks are empty.
                while (true)
                {
                    if (upStackSize <= downStackSize)
                    {
                        if (downStackSize == 0)
                            break;
 
                        ScanDown();
                    }
                    else
                    {
                        ScanUp();
                    }
                }
 
                InUse = false;
            }
 
            // Pop the next shadow from the up stack.  Fill the first fillable span within the
            // shadow, then move to the shadow the span cast on the next line.  Fill the first
            // fillable span on that line, and continue to the next line until encountering
            // a shadow with no fillable pixels.  If a fillable span extends more than one pixel
            // outside the shadow, additional pixels need to be checked on the parent line.  If
            // the fillable span ends more than one pixel before the end of the shadow, the
            // remaining pixels will need to be checked later.
            void ScanUp()
            {
                int left, right, y;
 
                PopUpShadow(out left, out right, out y);
                while ((y <= yMax) && LineFill(left, right, y))
                {
                    // If the span extends more than one pixel left of the the shadow that generated
                    // it, push a left parent shadow.  If it extends more than one pixel right, push a
                    // right parent shadow.  If it's more than one pixel from the right of the parent
                    // shadow, push the remanider of the parent shadow for later processing.
                    if (leftNew < left - 1)
                        PushDownShadow(leftNew, left - 2, y - 1);
                    if (rightNew > right + 1)
                        PushDownShadow(right + 2, rightNew, y - 1);
                    else if (rightNew < right - 1)
                        PushUpShadow(rightNew + 2, right, y);
 
                    left = leftNew;
                    right = rightNew;
                    y++;
                }
            }
 
            // Same as ScanUp, but pop the down stack, and move to the previous lines.
            void ScanDown()
            {
                int left, right, y;
 
                PopDownShadow(out left, out right, out y);
                while ((y >= yMin) && LineFill(left, right, y))
                {
                    if (leftNew < left - 1)
                        PushUpShadow(leftNew, left - 2, y + 1);
                    if (rightNew > right + 1)
                        PushUpShadow(right + 2, rightNew, y + 1);
                    else if (rightNew < right - 1)
                        PushDownShadow(rightNew + 2, right, y);
 
                    left = leftNew;
                    right = rightNew;
                    y--;
                }
            }
 
            // Fill the leftmost fillable span within the interval.  The span may extend
            // to the left and right of the interval.
            int leftNew, rightNew;
            bool LineFill(int left, int right, int y)
            {
                // Find the first interior pixel.
                int x = left;
                while (!testAndModifyPixel(x, y))
                {
                    if (++x > right)
                        return false;
                }
 
                // If the first pixel was filled, scan left.
                if (x == left)
                {
                    while ((--x >= yMin) && testAndModifyPixel(x, y))
                        ;
                    leftNew = x + 1;
                    x = left;
                }
                else
                {
                    leftNew = x;
                }
 
                // Find the end of the fillable span.
                while ((++x <= xMax) && testAndModifyPixel(x, y))
                    ;
 
                rightNew = x - 1;
                return true;
            }
        }

modified on Friday, July 29, 2011 3:20 AM

AnswerRe: Single visit of each pixelmemberMember 155606929 Jul '11 - 6:58 
Think-a-Tron:
 
Thanks for your post. It's probably a good time to 'zoom out' and review the current state of the art (a bit).
 
You are correct that there are situations, maybe many, where flood-filling does not require any stack checking. Unnecessary behind-the-scenes grinding should be avoided whenever possible. For the case of block graphics on a background, a simpler method is always appropriate.
 
However, there are situations in which the overhead of communication with the graphics device is very costly. That's one reason for an interest in single-visit methods. It isn't for everyone, or for every situation, but when the penalty for pixel testing is high, it may have advantages. (Note the number of entries in this thread.)
 
Your contribution is certainly very welcome. There are too many special cases possible to exhaustively test them all and proclaim any particular fill method the best. It all depends on the properties of the playfield and the platform in use. My effort has been simply to devise a single-visit method which is provably correct for any application. If the stack overhead is too high (recall Microsoft's early fill method in their original Paint program), another method, such as yours would be far better.
 
By the way, your implementation of the scan-and-fill operation and the stack management is very sleek. Good coding always gets high marks, and if you ever decide to tackle the problem of assuring single visits-per-pixel for the general case, you've got a ready-made audience.
GeneralRe: Single visit of each pixelmemberJohn R. Shaw30 Jul '11 - 5:08 
Member 1556069 wrote:
Good coding always gets high marks, and if you ever decide to tackle the problem of assuring single visits-per-pixel for the general case, you've got a ready-made audience.

That is true.
INTP
"Program testing can be used to show the presence of bugs, but never to show their absence." - Edsger Dijkstra
"I have never been lost, but I will admit to being confused for several weeks. " - Daniel Boone

Generalminor errormembermatthias.fauconneau7 Apr '09 - 6:54 
in the simple non-recursive scan line method
replace PUSH(y, left, x1-1, -dy); with PUSH( left, x1-1, y, -dy);
GeneralRe: minor errormemberNeil Roy18 Mar '11 - 7:29 
Thanks for catching this. I was wondering why the fill wouldn't work properly, I was just about to delete it and rewrite my own when I read your post.
 
Also POP and PUSH need the ML/ml and MR/mr changed to M1/m1 and M2/m2.
Generalhelp with debug error message: Loaded 'C:\WINDOWS\system32\kernel32.dll', no matching symbolic information found. [modified]memberjin_ming12 Nov '08 - 11:54 
Compile QuickFill is OK, but not working when running. The debug messge as in subject; but i check kernel32.dll was there.
 
By the way, can I ask for the non-window version(c source code)?
 
Thanks in advance
 
modified on Wednesday, November 12, 2008 6:09 PM

GeneralFlood fill of regions of specific size .memberAlexandros546 Oct '08 - 7:24 
I wonder how I could use your quick fill algorithm to fill certain size regions of a binary image.
 
Thank you!
GeneralRe: Flood fill of regions of specific size .memberJohn R. Shaw26 Nov '08 - 8:57 
If the image is loaded into a CBitmap call QuickFill(pBitmap, pRect, x, y, fill_color, border_color); where pRect is rectangular region, of the image, that you wish to fill. (See QuickFillDemoView.cpp for example of usage).
 
If for some reason the image is not a CBitmap, but you are still using MFC, then you can derive from CQuickFill and override the QuickFill, GetPixel, SetPixel, DrawHorizontalLine, ScanLeft, SearchLeft, ScanRight, and SearchRight methods to read from and write to your particular image type.
 
The limitation is the image type specific read/write operations, the rest of the code is non-specific and can remain unchanged.
 
INTP
"Program testing can be used to show the presence of bugs, but never to show their absence."Edsger Dijkstra

GeneralHelp! Some problems with the algorithmmemberluori22 Apr '06 - 5:14 
Hi! John
Your algorithm and code are very excellent. I want to use that to implement the MagicWand Tool like in Photoshop. A very simple change is like the follow(for only color image with the 'm_wBitsPerPixel==24'):
We add the 'Tolerance'
When in the ScanLeft(ScanRight)function,the code changeed to be this
case 24:
for( ; x <= xmax; ++x, pPixel += 3 )
{ clrSrc = 0x00FFFFFF & *(LPDWORD)pPixel;
if(abs((BYTE)((WORD)(GetRValue(clrSrc))-(WORD) (GetRValue(dwValue)))) >m_nTolerance||
abs((BYTE)((WORD)(GetGValue(clrSrc))-(WORD)(GetGValue(dwValue))))>m_nTolerance
||abs((BYTE)((WORD)(GetBValue(clrSrc))-(WORD)(GetBValue(dwValue))))>m_nTolerance)
//if( dwValue != (0x00FFFFFF & *(LPDWORD)pPixel) )
break;
}
break;
//In the SearchLeft(SeachRight)function,the code changed to be that
 
clrSrc = 0x00FFFFFF & *(LPDWORD)pPixel;
if(abs((BYTE)((WORD)(GetRValue(clrSrc))-(WORD)(GetRValue(dwValue))))<=m_nTolorance&&
abs((BYTE)((WORD)(GetGValue(clrSrc))-(WORD)(GetGValue(dwValue))))<=m_nTolorance
&&abs((BYTE)((WORD)(GetBValue(clrSrc))-(WORD)(GetBValue(dwValue))))<=m_nTolorance
//if( dwValue == (0x00FFFFFF & *(LPDWORD)pPixel) )
break;
)
 
Mosttimes the code works very well,but I found one problem that could not betackled by myself.sometims when the mouse clicked at some points on the image,the main memory busy working will increasing sharply.the computer is done! Then I use the "Slow Draw Mode",the computer stopped in the midway.I don't know why.Only find that there might be some problems in the big circle of '/* Now start flooding */ while( m_pLineList ) {}' in the code.Confused | :confused:
My email address is:xiangjianjian0@hotmail.com
GeneralScanline fill algomemberAnishta13 Dec '05 - 20:14 
May i know how to implement the scanline fill algorithm in c++ using mfc?
 
Anishta

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

Permalink | Advertise | Privacy | Mobile
Web02 | 2.6.130523.1 | Last Updated 13 Mar 2004
Article Copyright 2004 by John R. Shaw
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid