This code appears in the following versions (click to see it in the source code):
Code variations between these versions are shown below.
Type: Subroutine Category: Drawing lines Summary: Draw a shallow line going right and up or left and down Deep dive: Bresenham's line algorithm
This routine draws a line from (X1, Y1) to (X2, Y2). It has multiple stages. If we get here, then: * The line is going right and up (no swap) or left and down (swap)
* Draw from (X1, Y1) at bottom left to (X2, Y2) at top right, omitting the first pixel
This variation is blank in the Cassette, Disc (flight), Disc (docked) and Electron versions.
This routine looks complex, but that's because the loop that's used in the BBC Micro cassette and disc versions has been unrolled to speed it up. The algorithm is unchanged, it's just a lot longer.
This variation is blank in the Cassette, Disc (flight), Disc (docked) and Electron versions.
LDA #%10001000 \ Modify the value in the LDA instruction at LI100 below AND COL \ to contain a pixel mask for the first pixel in the STA LI100+1 \ 4-pixel byte, in the colour COL, so that it draws in \ the correct colour LDA #%01000100 \ Modify the value in the LDA instruction at LI110 below AND COL \ to contain a pixel mask for the second pixel in the STA LI110+1 \ 4-pixel byte, in the colour COL, so that it draws in \ the correct colour LDA #%00100010 \ Modify the value in the LDA instruction at LI120 below AND COL \ to contain a pixel mask for the third pixel in the STA LI120+1 \ 4-pixel byte, in the colour COL, so that it draws in \ the correct colour LDA #%00010001 \ Modify the value in the LDA instruction at LI130 below AND COL \ to contain a pixel mask for the fourth pixel in the STA LI130+1 \ 4-pixel byte, in the colour COL, so that it draws in \ the correct colour
The original versions contain a bug where the last pixel is skipped instead of the first pixel, but only when drawing lines that go right and up or left and down. This leads to a messy line join between this kind of line and lines with different slopes. This bug was fixed in the advanced versions.
Tap on a block to expand it, and tap it again to revert.
This variation is blank in the Cassette, Disc (flight), Disc (docked) and Electron versions.
\ If we get here then we want to omit the first pixel LDA R \ Fetch the pixel byte from R, which we set in part 2 to \ the horizontal pixel number within the character block \ where the line starts (so it's 0, 1, 2 or 3) BEQ LI100+6 \ If R = 0, jump to LI100+6 to start plotting from the \ second pixel in this byte (LI100+6 points to the DEX \ instruction after the EOR/STA instructions, so the \ pixel doesn't get plotted but we join at the right \ point to decrement X correctly to plot the next three) CMP #2 \ If R < 2 (i.e. R = 1), jump to LI110+6 to skip the BCC LI110+6 \ first two pixels but plot the next two CLC \ Clear the C flag so it doesn't affect the additions \ below BEQ LI120+6 \ If R = 2, jump to LI120+6 to skip the first three \ pixels but plot the last one BNE LI130+6 \ If we get here then R must be 3, so jump to LI130+6 to \ skip plotting any of the pixels, but making sure we \ join the routine just after the plotting instructions .LI190
EOR (SC),Y \ Store R into screen memory at SC(1 0), using EOR STA (SC),Y \ logic so it merges with whatever is already on-screen .LI6 LSR R \ Shift the single pixel in R to the right to step along \ the x-axis, so the next pixel we plot will be at the \ next x-coordinate along BCC LI7 \ If the pixel didn't fall out of the right end of R \ into the C flag, then jump to LI7 ROR R \ Otherwise we need to move over to the next character \ block, so first rotate R right so the set C flag goes \ back into the left end, giving %10000000 LDA SC \ Add 8 to SC, so SC(1 0) now points to the next ADC #8 \ character along to the right STA SC
This variation is blank in the Cassette, Disc (flight), Disc (docked), 6502 Second Processor and Master versions.
BCC LI7 \ If the addition of the low bytes of SC overflowed, INC SC+1 \ increment the high byte
This variation is blank in the 6502 Second Processor and Master versions.
Tap on a block to expand it, and tap it again to revert.
This variation is blank in the Cassette, Disc (flight), Disc (docked) and Electron versions.
BEQ LI100 \ If R = 0, jump to LI100 to start plotting from the \ first pixel in this byte CMP #2 \ If R < 2 (i.e. R = 1), jump to LI110 to start plotting BCC LI110 \ from the second pixel in this byte CLC \ Clear the C flag so it doesn't affect the additions \ below BEQ LI120 \ If R = 2, jump to LI120 to start plotting from the \ third pixel in this byte JMP LI130 \ If we get here then R must be 3, so jump to LI130 to \ start plotting from the fourth pixel in this byte .LI100 LDA #%10001000 \ Set a mask in A to the first pixel in the 4-pixel byte \ (note that this value is modified by the code at the \ start of this section to be a bit mask for the colour \ in COL) EOR (SC),Y \ Store A into screen memory at SC(1 0), using EOR STA (SC),Y \ logic so it merges with whatever is already on-screen DEX \ Decrement the counter in X .LIEXS BEQ LIEX \ If we have just reached the right end of the line, \ jump to LIEX to return from the subroutine LDA S \ Set S = S + Q to update the slope error ADC Q STA S BCC LI110 \ If the addition didn't overflow, jump to LI110 CLC \ Otherwise we just overflowed, so clear the C flag and DEY \ decrement Y to move to the pixel line above BMI LI101 \ If Y is negative we need to move up into the character \ block above, so jump to LI101 to decrement the screen \ address accordingly (jumping back to LI110 afterwards) .LI110 LDA #%01000100 \ Set a mask in A to the second pixel in the 4-pixel \ byte (note that this value is modified by the code at \ the start of this section to be a bit mask for the \ colour in COL) EOR (SC),Y \ Store A into screen memory at SC(1 0), using EOR STA (SC),Y \ logic so it merges with whatever is already on-screen DEX \ Decrement the counter in X BEQ LIEX \ If we have just reached the right end of the line, \ jump to LIEX to return from the subroutine LDA S \ Set S = S + Q to update the slope error ADC Q STA S BCC LI120 \ If the addition didn't overflow, jump to LI120 CLC \ Otherwise we just overflowed, so clear the C flag and DEY \ decrement Y to move to the pixel line above BMI LI111 \ If Y is negative we need to move up into the character \ block above, so jump to LI111 to decrement the screen \ address accordingly (jumping back to LI120 afterwards) .LI120 LDA #%00100010 \ Set a mask in A to the third pixel in the 4-pixel byte \ (note that this value is modified by the code at the \ start of this section to be a bit mask for the colour \ in COL) EOR (SC),Y \ Store A into screen memory at SC(1 0), using EOR STA (SC),Y \ logic so it merges with whatever is already on-screen DEX \ Decrement the counter in X BEQ LIEX \ If we have just reached the right end of the line, \ jump to LIEX to return from the subroutine LDA S \ Set S = S + Q to update the slope error ADC Q STA S BCC LI130 \ If the addition didn't overflow, jump to LI130 CLC \ Otherwise we just overflowed, so clear the C flag and DEY \ decrement Y to move to the pixel line above BMI LI121 \ If Y is negative we need to move up into the character \ block above, so jump to LI121 to decrement the screen \ address accordingly (jumping back to LI130 afterwards) .LI130 LDA #%00010001 \ Set a mask in A to the fourth pixel in the 4-pixel \ byte (note that this value is modified by the code at \ the start of this section to be a bit mask for the \ colour in COL) EOR (SC),Y \ Store A into screen memory at SC(1 0), using EOR STA (SC),Y \ logic so it merges with whatever is already on-screen LDA S \ Set S = S + Q to update the slope error ADC Q STA S BCC LI140 \ If the addition didn't overflow, jump to LI140 CLC \ Otherwise we just overflowed, so clear the C flag and DEY \ decrement Y to move to the pixel line above BMI LI131 \ If Y is negative we need to move up into the character \ block above, so jump to LI131 to decrement the screen \ address accordingly (jumping back to LI140 afterwards) .LI140 DEX \ Decrement the counter in X BEQ LIEX \ If we have just reached the right end of the line, \ jump to LIEX to return from the subroutine LDA SC \ Add 8 to SC, so SC(1 0) now points to the next ADC #8 \ character along to the right STA SC BCC LI100 \ If the addition didn't overflow, jump back to LI100 \ to plot the next pixel INC SC+1 \ Otherwise the low byte of SC(1 0) just overflowed, so \ increment the high byte SC+1 as we just crossed over \ into the right half of the screen CLC \ Clear the C flag to avoid breaking any arithmetic BCC LI100 \ Jump back to LI100 to plot the next pixel .LI101 DEC SC+1 \ If we get here then we need to move up into the DEC SC+1 \ character block above, so we decrement the high byte LDY #7 \ of the screen twice (as there are two pages per screen \ row) and set the pixel line to the last line in \ that character block BPL LI110 \ Jump back to the instruction after the BMI that called \ this routine .LI111 DEC SC+1 \ If we get here then we need to move up into the DEC SC+1 \ character block above, so we decrement the high byte LDY #7 \ of the screen twice (as there are two pages per screen \ row) and set the pixel line to the last line in \ that character block BPL LI120 \ Jump back to the instruction after the BMI that called \ this routine .LI121 DEC SC+1 \ If we get here then we need to move up into the DEC SC+1 \ character block above, so we decrement the high byte LDY #7 \ of the screen twice (as there are two pages per screen \ row) and set the pixel line to the last line in \ that character block BPL LI130 \ Jump back to the instruction after the BMI that called \ this routine .LI131 DEC SC+1 \ If we get here then we need to move up into the DEC SC+1 \ character block above, so we decrement the high byte LDY #7 \ of the screen twice (as there are two pages per screen \ row) and set the pixel line to the last line in \ that character block BPL LI140 \ Jump back to the instruction after the BMI that called \ this routine .LIEX
RTS \ Return from the subroutine