Fills on screen starting from a single point - 655 bytes (4%)


§1. Flood Fill.

einstein.png

 The flood fill feature sets any adjacent pixels (in the four cardinal directions) recursively
 starting from a given point within the graphics window, with options:

   * Fill until reaching a non-background colour.
   * Fill until reaching the current foreground colour.

 The filled area is set to either the current fill pattern or the current background colour.

 Limitations:

 To quote the manual...

 "Flooding a region with a colour which it already contains will not work. For example if you
 are attempting a flood to non-background with the background colour being black then you should
 not try to flood in black or a pattern which contains black pixels. If you do this then it will
 try to fill a region, find that pixels in it are still in the background colour and try again.
 When this occurs a small region of the screen will be flooded and then the flood fill will give
 up."

 Horizontal line fills:

 We can fill a single row, with options:

 * Fill left and right of the given pixel until a foreground pixel is met
 * Fill left and right of the given pixel until a non-background pixel is met
 * Fill right of the given pixel until a background pixel is met
 * Fill right of the given pixel until a non-foreground pixel is met

§2. Flood fill to foreground colour.

.floodFillToFg = $9ced
    LDA #1                                              fill background colour
    LDX #0                                              X = 0 for foreground graphics colour
    BEQ .floodFillInternal                              ALWAYS branch

§3. Flood fill to non-background colour.

.floodFillToNonBg = $9cf3
    LDA #0                                              A = 0 for 'non'
    LDX #1                                              X = 1 for fill background colour
.floodFillInternal = $9cf7
    STA .vduWorkspaceI                                  .vduWorkspaceI = 0 for non bg
    JSR .setupCurrentPattern                            

    LDY #.workspaceOffsetOptions                        
    LDA (.privateWorkspaceLow),Y                        
    BPL .finishedFloodFill                              if (flood disabled) then branch

    LDX #.vdu25ParameterXLow - .vduVariablesStart       
    JSR .testParameterPointForFloodFillBoundary         
    BNE .finishedFloodFill                              if (at boundary already) then branch

    LDA #0                                              
    STA .vduWorkspaceF                                  
    STA .vduWorkspaceG                                  
    LDA .vdu25ParameterXLow                             
    LDX .vdu25ParameterXHigh                            
    LDY .vdu25ParameterYLow                             
    JSR .fillLeftAndRightRow                            fill row
    JSR .addEntryToFloodFillBuffer                      write first row to circular buffer
                                                        of points to check

.floodFillLoop = $9d21
    BIT .escapeFlag                                     
    BMI .finishedFloodFill                              if (ESCAPE pressed) then branch
                                                        (finished)
    JSR .removeEntryFromFloodFillBuffer                 read row to check

    Check row+1
    LDY .vduWorkspaceC                                  
    INY                                                 
    BEQ .skipRow                                        if (edge of screen) then branch
    STY .vduWorkspaceC                                  
    JSR .checkAndFillRow                                
    BCC .finishedFloodFill                              

    Check row-1
    DEC .vduWorkspaceC                                  
.skipRow = $9d39
    LDY .vduWorkspaceC                                  
    BEQ .skipRow2                                       
    DEY                                                 
    STY .vduWorkspaceC                                  
    JSR .checkAndFillRow                                

    BCC .finishedFloodFill                              

.skipRow2 = $9d47
    LDX .vduWorkspaceG                                  check to see if the circular buffer
    CPX .vduWorkspaceF                                  is empty
    BNE .floodFillLoop                                  if not then branch back

.finishedFloodFill = $9d4f
    JMP .setGraphicsCursorPositionAndFinishPLOT         

§4. Add an entry to a circular buffer of coordinates during a flood fill.

 Used to store coordinates that we will revisit later when flood filling. Each entry holds
 two X coordinates and a Y coordinate, representing a row of pixels to check later.

 The data is stored in two pages. The first page stores the low byte of one X coordinate
 and the Y coordinate. The second page stores the other X coordinate and the high nybble
 of the first.

 On Entry:
   Where to write the data:
       .privateWorkspaceHigh: high byte of ROM private workspace
       .vduWorkspaceG: index to write to (range 0-127) (the 'write to buffer index')
       .vduWorkspaceF: index limit where we can write to (range 0-127) (the 'read buffer index')
   Data to write:
       .vduGraphicsCursorPixelsXLow/High:    pixel X coordinate
       .vduOldGraphicsCursorPixelsXLow/High: pixel X coordinate
       .vduOldGraphicsCursorPixelsYLow:      pixel Y coordinate
.addEntryToFloodFillBuffer = $9d52
    Increment .vduWorkspaceG, checking if we have completely filled the buffer already
    LDX .vduWorkspaceG                                  
    INX                                                 
    TXA                                                 
    AND #$7F                                            
    CMP .vduWorkspaceF                                  
    BEQ .clearCarryAndReturn                            
    STA .vduWorkspaceG                                  
    TAY                                                 Y=new incremented index to write to

    .vduTempStoreDC/DD = private workspace + 1 page = start address of flood fill data
    LDA .privateWorkspaceHigh                           
    STA .vduTempStoreDD                                 
    INC .vduTempStoreDD                                 +1 page
    LDA #0                                              
    STA .vduTempStoreDC                                 

    Store old graphics cursor X low in the flood fill data in the first page
    Store     graphics cursor X low in the flood fill data in the second page
    LDA .vduOldGraphicsCursorPixelsXLow                 
    STA (.vduTempStoreDC),Y                             
    INC .vduTempStoreDD                                 second page
    LDA .vduGraphicsCursorPixelsXLow                    
    STA (.vduTempStoreDC),Y                             

    In the second half of those two pages...
    LDA #$80                                            
    STA .vduTempStoreDC                                 

    In the second half of the second page: store the graphics cursor
                                             and old graphics cursor X (high nybbles)
    LDA .vduGraphicsCursorPixelsXHigh                   
    ASL                                                 
    ASL                                                 
    ASL                                                 
    ASL                                                 
    ORA .vduOldGraphicsCursorPixelsXHigh                
    STA (.vduTempStoreDC),Y                             

    In the second half of the first page: store the old graphics cursor Y (low byte)
    DEC .vduTempStoreDD                                 
    LDA .vduOldGraphicsCursorPixelsYLow                 
    STA (.vduTempStoreDC),Y                             
    SEC                                                 set carry (all OK) and return
    RTS                                                 

.clearCarryAndReturn = $9d91
    CLC                                                 
    RTS                                                 

§5. Remove an entry out of the circular flood fill buffer.

 Each entry is a horizontal row of pixels for use during flood filling.

 On Exit:
   .vduWorkspaceA: X1 low
   .vduWorkspaceB: X1 high
   .vduWorkspaceC: Y coordinate
   .vduWorkspaceD: X2 low
   .vduWorkspaceE: X2 high
.removeEntryFromFloodFillBuffer = $9d93
    Increment .vduWorkspaceF, wrapping at half a page
    INC .vduWorkspaceF                                  
    LDA .vduWorkspaceF                                  
    AND #$7F                                            
    STA .vduWorkspaceF                                  
    TAY                                                 

    Get start address of flood fill data (DC/DD = private workspace + 1 page)
    LDA .privateWorkspaceHigh                           
    STA .vduTempStoreDD                                 
    INC .vduTempStoreDD                                 
    LDA #0                                              
    STA .vduTempStoreDC                                 

    Read from flood fill memory and parse coordinates
    LDA (.vduTempStoreDC),Y                             
    STA .vduWorkspaceA                                  first half of first page (X1 low
                                                        byte)
    INC .vduTempStoreDD                                 
    LDA (.vduTempStoreDC),Y                             
    STA .vduWorkspaceD                                  first half of second page (X2 low
                                                        byte)

    Look at second half of the two pages
    LDA #$80                                            
    STA .vduTempStoreDC                                 
    LDA (.vduTempStoreDC),Y                             read top nybble of second half of
                                                        second page
    LSR                                                 
    LSR                                                 
    LSR                                                 
    LSR                                                 
    STA .vduWorkspaceE                                  store X2 high byte
    LDA (.vduTempStoreDC),Y                             
    AND #$0F                                            
    STA .vduWorkspaceB                                  store X1 high byte
    DEC .vduTempStoreDD                                 
    LDA (.vduTempStoreDC),Y                             
    STA .vduWorkspaceC                                  store Y
    RTS                                                 

§6. Fills a row of pixels during flood fill.

 Set the current and old graphics cursors to the given coordinates, clips to the graphics
 window, if within bounds then flood fill.

 On Entry:
   A is the X coordinate to set (low)
   X is the X coordinate to set (high)
   Y is the Y coordinate to set (low) with 0 high.
.fillLeftAndRightRow = $9dd1
    Store X and Y coordinates to the current and old graphics cursor positions
    STA .vduGraphicsCursorPixelsXLow                    
    STA .vduOldGraphicsCursorPixelsXLow                 
    STX .vduGraphicsCursorPixelsXHigh                   
    STX .vduOldGraphicsCursorPixelsXHigh                
    STY .vduGraphicsCursorPixelsYLow                    
    STY .vduOldGraphicsCursorPixelsYLow                 
    LDA #0                                              
    STA .vduGraphicsCursorPixelsYHigh                   
    STA .vduOldGraphicsCursorPixelsYHigh                

.fillLeftRight = $9deb
    LDX #.vduOldGraphicsCursorPixelsXLow - .vduVariablesStart         
    JSR .gxrCheckInBoundsThenSetScreenAddressAndSetGraphicsColourMask 
    BEQ .checkForFloodBoundary                          
    RTS                                                 

.checkForFloodBoundary = $9df3
    JSR .readPixelAndTestForFloodFillBoundary           
    BEQ .fillLeftAndRight                               
    RTS                                                 

§7. Fill left and right.

.fillLeftAndRightLoop = $9df9
    JSR .readPixelAndTestForFloodFillBoundary           
    BNE .doneFillingLeft                                
    LDA .vduCurrentPlotByteMask                         
    ORA .vduWorkspaceH                                  

.fillLeftAndRight = $9e03
    STA .vduWorkspaceH                                  
    LDA .vduOldGraphicsCursorPixelsXHigh                
    CMP .vduGraphicsWindowPixelsLeftHigh                
    BNE +                                               

    LDA .vduOldGraphicsCursorPixelsXLow                 
    CMP .vduGraphicsWindowPixelsLeftLow                 
    BEQ .fillPointAndFillRight                          if (done filling left) then branch
                                                        (fill right)

    Move left
+
    LDA .vduOldGraphicsCursorPixelsXLow                 
    BNE +                                               
    DEC .vduOldGraphicsCursorPixelsXHigh                
+
    DEC .vduOldGraphicsCursorPixelsXLow                 

    Update byte mask
    ASL .vduCurrentPlotByteMask                         
    BCC .fillLeftAndRightLoop                           

    Plot byte
    JSR .gxrPlotByteWithinBoundsAtY                     

    Move left
    LDA #0                                              
    STA .vduWorkspaceH                                  
    SEC                                                 
    JSR .moveGraphicsCursorAddressTotheLeftAndUpdateMask 
    JMP .fillLeftAndRightLoop                           

§8. Done filling left, now fill right.

.doneFillingLeft = $9e34
    INC .vduOldGraphicsCursorPixelsXLow                 }
    BNE .fillPointAndFillRight                          } increment X
    INC .vduOldGraphicsCursorPixelsXHigh                }

.fillPointAndFillRight = $9e3c
    JSR .gxrPlotByteWithinBoundsAtY                     plot point at X

.fillRight = $9e3f
    LDA #0                                              
    STA .vduWorkspaceH                                  build up a byte to write to the
                                                        screen

    LDX #.vduGraphicsCursorPixelsXLow - .vduVariablesStart 
    JSR .gxrCheckInBoundsThenSetScreenAddressAndSetGraphicsColourMask 
    BNE .return4                                        if (out of graphics window) then
                                                        branch (return)

.fillNextPixel = $9e4b
    JSR .readPixelAndTestForFloodFillBoundary           
    BNE .floodFillReachedBoundaryRight                  if (reached boundary) then branch

    LDA .vduCurrentPlotByteMask                         }
    ORA .vduWorkspaceH                                  } OR in the current byte mask into
    STA .vduWorkspaceH                                  } the byte to write to the screen

    LDA .vduGraphicsCursorPixelsXHigh                   }
    CMP .vduGraphicsWindowPixelsRightHigh               }
    BNE +                                               }
    LDA .vduGraphicsCursorPixelsXLow                    }
    CMP .vduGraphicsWindowPixelsRightLow                }
    BEQ .gxrPlotByteWithinBoundsAtY                     } if (at right edge of graphics
                                                        } window) then branch (plot final
                                                        } byte)

+
    INC .vduGraphicsCursorPixelsXLow                    }
    BNE +                                               } move graphics cursor position
    INC .vduGraphicsCursorPixelsXHigh                   } one pixel right
+                                                       }

    LSR .vduCurrentPlotByteMask                         
    BCC .fillNextPixel                                  if (we have not finished this byte
                                                        yet) then branch (loop back)

    JSR .gxrPlotByteWithinBoundsAtY                     write byte

    LDA #0                                              }
    STA .vduWorkspaceH                                  } clear byte to write
    SEC                                                 
    JSR .moveGraphicsCursorAddressTotheRightAndUpdateMask   move to the next byte right

    JMP .fillNextPixel                                  

§9. Reached right boundary, fill final pixel.

.floodFillReachedBoundaryRight = $9e83
    LDA .vduGraphicsCursorPixelsXLow                    }
    BNE +                                               }
    DEC .vduGraphicsCursorPixelsXHigh                   } move graphics cursor one pixel left
+                                                       }
    DEC .vduGraphicsCursorPixelsXLow                    }
    fall through...

§10. Write a byte to the screen (for flood fill).

 On Entry:
   .vduWorkspaceH: byte to write
   .vduGraphicsColourByteOR:  } mask values needed for writing in the current GCOL mode
   .vduGraphicsColourByteEOR: }
   .vduScreenAddressOfGraphicsCursorCellLow: address of current graphics cursor cell
   Y: offset within current graphics cursor cell (0-7)
.gxrPlotByteWithinBoundsAtY = $9e8e
!if (MACHINE = BBC_B) | (MACHINE = ELECTRON) {
    LDA .vduWorkspaceH                                  }
    AND .vduGraphicsColourByteOR                        }
    ORA (.vduScreenAddressOfGraphicsCursorCellLow),Y    }
    STA .vduTempStoreDA                                 } DA = (H AND ByteOR) OR screen
    LDA .vduGraphicsColourByteEOR                       } screen = (H AND ByteEOR) EOR DA
    AND .vduWorkspaceH                                  }
    EOR .vduTempStoreDA                                 }
    STA (.vduScreenAddressOfGraphicsCursorCellLow),Y    }
} else if MACHINE = BBC_B_PLUS {
    LDX .vduCurrentPlotByteMask                         
    LDA .vduWorkspaceH                                  
    STA .vduCurrentPlotByteMask                         
    JSR .plotPointWithinBoundsAtY                       
    STX .vduCurrentPlotByteMask                         
} else {
    +unknown_machine
}

.return4 = $9ea0
    RTS                                                 

§11. Look along a row of pixels to check for any we can flood fill.

 On Entry:
   A: initial X coordinate (low)
   X: initial X coordinate (high)
   Y: initial Y coordinate
 On Exit:
   Carry set if no more points to fill are found
.findNextPointInRowToFill = $9ea1
    STA .vdu25ParameterXLow                             
    STX .vdu25ParameterXHigh                            
    STY .vdu25ParameterYLow                             low byte of Y coordinate
    LDA #0                                              
    STA .vduQueueEndByte                                high byte of Y coordinate = 0

    Check point is in bounds
    LDX #.vdu25ParameterXLow - .vduVariablesStart       
    JSR .gxrCheckInBoundsThenSetScreenAddressAndSetGraphicsColourMask 

    BNE .exitWithCarrySet                               if (not in bounds) then return with
                                                        carry set

.checkRowForFillingLoop = $9eb6
    JSR .readPixelAndTestForFloodFillBoundary           read pixel

    BEQ .exitWithCarryClear                             if (suitable for filling) then
                                                        return with carry clear

    Check if we are done with this row
    LDY .vdu25ParameterXLow                             
    CPY .vduWorkspaceD                                  
    BNE +                                               
    LDY .vdu25ParameterXHigh                            
    CPY .vduWorkspaceE                                  
    BEQ .exitWithCarrySet                               if (row finished) then return with
                                                        carry set

    Move to the right to continue checking
+
    INC .vdu25ParameterXLow                             
    BNE +                                               
    INC .vdu25ParameterXHigh                            
+
    Update byte mask
    LSR .vduCurrentPlotByteMask                         
    BCC .checkRowForFillingLoop                         
    JSR .moveGraphicsCursorAddressTotheRightAndUpdateMask 
    JMP .checkRowForFillingLoop                         

.exitWithCarryClear = $9edd
    CLC                                                 
    RTS                                                 

.exitWithCarrySet = $9edf
    SEC                                                 
    RTS                                                 

§12. Check pixels along row, filling if needed.

 Each row of pixels we fill is added to our flood fill buffer. (We will later read those
 rows back out and check the row above and below it for any more to be filled).
.checkAndFillRow = $9ee1
    LDA .vduWorkspaceA                                  X coordinate (low byte)
    LDX .vduWorkspaceB                                  X coordinate (high byte)
    LDY .vduWorkspaceC                                  Y coordinate
    JSR .findNextPointInRowToFill                       find next point to fill
    BCS .return9                                        if (not found) then return

.fillRowLoop = $9eef
    LDA .vdu25ParameterXLow                             
    LDX .vdu25ParameterXHigh                            
    LDY .vdu25ParameterYLow                             
    JSR .fillLeftAndRightRow                            fill row
    JSR .addEntryToFloodFillBuffer                      add row to buffer
    BCC .return9                                        if (buffer full) then return

    If (reached right hand end of row) then return
    LDA .vduGraphicsCursorPixelsXLow                    
    CMP .vduWorkspaceD                                  
    LDA .vduGraphicsCursorPixelsXHigh                   
    SBC .vduWorkspaceE                                  
    BCS .return9                                        

    Find next point to fill
    LDA .vduGraphicsCursorPixelsXLow                    
    LDX .vduGraphicsCursorPixelsXHigh                   
    LDY .vduGraphicsCursorPixelsYLow                    
    JSR .findNextPointInRowToFill                       

    BCC .fillRowLoop                                    

.return9 = $9f1c
    RTS                                                 

§13. Check if PLOT parameter pixel is a boundary pixel.

.testParameterPointForFloodFillBoundary = $9f1d
    LDX #.vdu25ParameterXLow - .vduVariablesStart       
    JSR .gxrCheckInBoundsThenSetScreenAddressAndSetGraphicsColourMask 
    BNE .return8                                        if (outside graphics window) then
                                                        branch
    fall through...

§14. Check if pixel is a boundary pixel.

.readPixelAndTestForFloodFillBoundary = $9f24
    LDY .vduGraphicsCursorVerticalOffsetInCell          

    get screen byte
!if (MACHINE = BBC_B) | (MACHINE = ELECTRON) {
    LDA (.vduScreenAddressOfGraphicsCursorCellLow),Y    
} else if MACHINE = BBC_B_PLUS {
    JSR .checkPixelIsBackgroundColourFast               read byte from screen
    EOR .vduBackgroundGraphicsColour                    undo the unwanted eor from
                                                        subroutine call
} else {
    +unknown_machine
}
    EOR .gxrCurrentPattern,Y                            EOR with pattern
    AND .vduCurrentPlotByteMask                         AND with byte mask
    BEQ +                                               
    LDA #1                                              
+
    EOR .vduWorkspaceI                                  boundary test
.return8 = $9f35
    RTS                                                 

§15. Plot row left and right to non-background colour.

.plotHorizontalFillLRToNonBg = $9f36
    LDA #0                                              A = 0 means fill to 'non'-
    LDX #1                                              X = 1 means fill to background pixels
.plotHorizontalFillLR = $9f3a
    STA .vduWorkspaceI                                  .vduWorkspaceI means fill to:
                                                        0: 'non-foreground or non-background'
                                                        1: 'foreground or background'
    JSR .setupCurrentPattern                            

    LDY #3                                              loop counter
-
    LDA .vdu25ParameterXLow,Y                           
    STA .vduOldGraphicsCursorPixelsXLow,Y               
    STA .vduGraphicsCursorPixelsXLow,Y                  graphics cursor = old graphics
                                                        cursor = vdu 25 parameter
    DEY                                                 
    BPL -                                               

    JSR .fillLeftRight                                  
    JMP .copyWorkspaceCacheBackIntoCharacterDefinitons  

§16. Plot row left and right to foreground colour.

.plotHorizontalFillLRToFg = $9f54
    LDA #1                                              A = 1
    LDX #0                                              X = 0 means fill to foreground pixels
    BEQ .plotHorizontalFillLR                           ALWAYS branch

§17. Plot row right to background colour.

.plotHorizontalFillRToBg = $9f5a
    LDA #1                                              A = 1
    TAX                                                 X = 1 means fill to background pixels
    BNE .plotHorizontalFillR                            

§18. Plot row right to non-foreground colour.

.plotHorizontalFillRToNonFg = $9f5f
    LDA #0                                              A = 0 means fill to 'non-'
    TAX                                                 X = 0 means fill to foreground pixels
.plotHorizontalFillR = $9f62
    STA .vduWorkspaceI                                  .vduWorkspaceI = 0 meaning
                                                        'non-foreground or non-background'
                                                                      or 1 meaning
                                                        'foreground or background'
    JSR .setupCurrentPattern                            

    LDY #3                                              loop counter
-
    LDA .vdu25ParameterXLow,Y                           }
    STA .vduOldGraphicsCursorPixelsXLow,Y               }
    STA .vduGraphicsCursorPixelsXLow,Y                  } set the old graphics cursor and
    DEY                                                 } the current graphics cursor
    BPL -                                               } to the parameter point

    JSR .fillRight                                      
    JMP .copyWorkspaceCacheBackIntoCharacterDefinitons