Re: Factor

Factor: the language, the theory, and the practice.

Flood fill

Tuesday, November 18, 2025

#ui

Yesterday, Rodrigo Girão Serrão wrote an article about implementing the Floodfill algorithm in Python. He included a Javascript demonstration you can click on, as well as a step-by-step example at the end of his blog post to go over how Flood fill works.

We are going to implement this in Factor and then extend the images.viewer vocabulary:

When working with bitmap pixel data, we typically store colors using integers in the range [0..255]. We can generate a random color simply:

:: random-color ( -- color )
    255 random 255 random 255 random 255 random 4byte-array ;

This allows us to implement the flood fill four-way algorithm:

  1. Start from a specified pixel in an image.
  2. Choose a random but different color to assign.
  3. If the pixel is not the initial color, you’re done.
  4. If the pixel is, then change it’s color.
  5. For each surrounding pixel, continue from step 3.
CONSTANT: neighbors { { 1 0 } { 0 1 } { -1 0 } { 0 -1 } }

:: floodfill ( x y image -- ? )
    image dim>> first2 :> ( w h )
    {
        [ x 0 >= ] [ x w < ]
        [ y 0 >= ] [ y h < ]
    } 0&& [
        x y image pixel-at :> initial
        f [ drop random-color dup initial = ] loop :> color

        color x y image set-pixel-at
        V{ { x y } } :> queue

        [ queue empty? ] [
            queue pop first2 :> ( tx ty )
            neighbors [
                first2 :> ( dx dy )
                tx dx + :> nx
                ty dy + :> ny
                {
                    [ nx 0 >= ] [ nx w < ]
                    [ ny 0 >= ] [ ny h < ]
                    [ nx ny image pixel-at initial = ]
                } 0&& [
                    color nx ny image set-pixel-at
                    { nx ny } queue push
                ] when
            ] each
        ] until t
    ] [ f ] if ;

Note: as implemented, we change every pixel that matches the first click to a different color. It might be more aesthetic to allow for anti-aliasing to adjust colors that are fairly close to the original color.

Now, we’ll extend the image-gadget:

TUPLE: floodfill-gadget < image-gadget ;

: <floodfill-gadget> ( image -- gadget )
    floodfill-gadget new-image-gadget* ;

We implement a click handler that performs a flood fill and if it changed the image, cleans up the texture object and re-renders the gadget.

:: on-click ( gadget -- )
    gadget hand-rel first2 [ gl-scale >integer ] bi@ :> ( x y )
    x y gadget image>> floodfill [
        gadget delete-current-texture
        gadget relayout-1
    ] when ;

That word is assigned as a gesture on button-up mouse clicks:

floodfill-gadget "gestures" f {
    { T{ button-up { # 1 } } on-click }
} define-command-map

And, for convenience, make a main window word to launch the user interface with an example image:

MAIN-WINDOW: floodfill-window { { title "Floodfill" } }
    "vocab:floodfill/logo.png" <floodfill-gadget> >>gadgets ;

This is available on my GitHub.