Fills on screen starting from a single point - 655 bytes (4%)
- §1. Flood Fill
- §2. Flood fill to foreground colour
- §3. Flood fill to non-background colour
- §4. Add an entry to a circular buffer of coordinates during a flood fill
- §5. Remove an entry out of the circular flood fill buffer
- §6. Fills a row of pixels during flood fill
- §7. Fill left and right
- §8. Done filling left, now fill right
- §9. Reached right boundary, fill final pixel
- §10. Write a byte to the screen (for flood fill)
- §11. Look along a row of pixels to check for any we can flood fill
- §12. Check pixels along row, filling if needed
- §13. Check if PLOT parameter pixel is a boundary pixel
- §14. Check if pixel is a boundary pixel
- §15. Plot row left and right to non-background colour
- §16. Plot row left and right to foreground colour
- §17. Plot row right to background colour
- §18. Plot row right to non-foreground colour
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
.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