Implement bucket-fill tool

This commit is contained in:
Ben Bridle 2022-06-11 13:34:40 +12:00
parent 3b00ba7ec7
commit 51e1e85f54
1 changed files with 245 additions and 18 deletions

View File

@ -344,6 +344,12 @@
%PRINTF(%b) { ;print_byte_binary CALL }
%PRINTF(%b\s) { PRINTF(%b) PRINTF(\s) }
%PRINTF(%b\n) { PRINTF(%b) PRINTF(\n) }
%PRINTF(%?) { ;print_bool CALL }
%PRINTF(%?\s) { PRINTF(%?) PRINTF(\s) }
%PRINTF(%?\n) { PRINTF(%?) PRINTF(\n) }
%PRINTF(%?*) { ;print_bool_short CALL }
%PRINTF(%?*\s) { PRINTF(%?) PRINTF(\s) }
%PRINTF(%?*\n) { PRINTF(%?) PRINTF(\n) }
@ -352,6 +358,14 @@
|0100
@program_start ( -- )
( #0008 /V.CANVAS.WIDTH!
#0008 /V.CANVAS.HEIGHT!
COL_1 #0001 #0002
;fill_canvas_from_point CALL
HALT
)
( Initialise variables )
#0080 /V.CANVAS.WIDTH!
#0060 /V.CANVAS.HEIGHT!
@ -362,25 +376,230 @@
( ---------------------------------------------------------------------------- )
( C A N V A S S T U F F )
( C A N V A S P R O C E D U R E S )
( ALL OF THESE ROUTINES USE CANVAS-SPACE COORDINATES )
( Get the colour of the canvas pixel at the given coordinates. )
@get_canvas_pixel_colour ( x* y* -- colour )
;get_mask_and_address CALL LDA AND TO_BOOL RETURN
@set_canvas_pixel_colour ( colour x* y* -- )
;get_mask_and_address CALL STHk* LDA ROT ,&col_1 JCN ( mask byte | row_addr* )
&col_0 EOR ,&end JMP
&col_1 ORA
&end STHr* STA RETURN
@check_in_canvas_bounds ( x* y* -- in_bounds? )
/V.CANVAS.HEIGHT? LTH* STH /V.CANVAS.WIDTH? LTH* STHr AND RETURN
( Return 1 if pixel is in-bounds and the given colour, otherwise return 0 )
@check_canvas_pixel_colour ( colour x* y* -- is_colour? )
OVR* OVR* ;check_in_canvas_bounds CALL ,&cont JCN
( bail out ) POP* POP* POP #00 RETURN
&cont ;get_canvas_pixel_colour CALL EQU RETURN
( Note: These routines all use canvas-space coordinates )
@get_mask_and_address ( x* y* -- mask row_address* )
OVR* OVR* ;get_sprite_address CALL ( x* y* sprite_addr* )
SWP* MOD8* ADD* ( x* row_addr* )
STH* ;get_mask CALL STHr* RETURN
@get_sprite_address ( x* y* -- sprite_address* )
SWP* DIV8* SWP* DIV8* ( spritex* spritey* )
/V.CANVAS.WIDTH? CEIL8DIV8* ( sprite_x* sprite_y* sprite_width* )
MUL* ADD* MUL8* ;canvas_buffer ADD* ( sprite_addr* )
RETURN
@get_mask ( x* -- mask )
NIP MOD8 #0107 ROT SUB SHL4 SFT RETURN
@fill_canvas_from_point ( colour x* y* )
( Store starting values )
STH* ROT ;&col_p STA ( x* | y* )
DUP* ;&paint_x STA* ( x* | y* )
STHr* DUP* ;&paint_y STA* ( x* y* )
( Return if coords not in bounds )
;check_in_canvas_bounds CALL ( in_bounds? )
IF_FALSE RETURN ( -- )
( Find target colour )
;&paint_x LDA* ;&paint_y LDA* ( x* y* )
;get_canvas_pixel_colour CALL ( target_colour )
DUP ;&col_t STA ( target_colour )
( Return if target and paint colours are the same )
;&col_p LDA IF_EQUAL RETURN ( -- )
( Clear variables )
#0000 OVRk ;&mark1_a STA ;&mark2_a STA
;&backtrack STA ;&findloop STA ;&paint_d STA
( Move the painter to a wall to start )
&move_into_place ( -- )
;&front? CALL NOT ,&START JCN
;&move_forward CALL ,&move_into_place JMP
&MAIN_LOOP
;&move_forward CALL
( if right-pixel is inside, then )
;&right? CALL NOT ,&START JCN
( if backtrack is true and findloop is false and either front-pixel or left-pixel is inside, then )
;&backtrack LDA ;&findloop LDA NOT AND ;&front? CALL ;&left? CALL ORA AND NOT ,&no_findloop JCN
#01 ;&findloop STA
&no_findloop
;&turn_right CALL
&PAINT
;&move_forward CALL
&START
;&update_neighbour_wall_count CALL
;&wall_count LDA #04 EQU ,&check_1 JCN
( Orient the painter to face right along a wall )
&orient_right
;&front? CALL NOT ,&orient_left JCN
;&turn_right CALL ,&orient_right JMP
&orient_left
;&front? CALL ,&check_1 JCN
;&turn_left CALL ,&orient_left JMP
&check_1
;&wall_count LDA #01 NEQ ,&check_2 JCN
( If backtrack is True, findloop = True )
&1_1 ;&backtrack LDA NOT ,&1_2 JCN
#01 ;&findloop STA ;&MAIN_LOOP GOTO
( Else if findloop is True, make mark1 active )
&1_2 ;&findloop LDA NOT ,&1_3 JCN
#01 ;&mark1_a STA ;&MAIN_LOOP GOTO
( Else if front-left and back-left are both inside )
&1_3 ;&front_left? CALL ;&back_left? CALL AND NOT ;&MAIN_LOOP JCN*
#00 ;&mark1_a STA
;&paint_here CALL
;&PAINT GOTO
&check_2
;&wall_count LDA #02 NEQ ;&check_3 JCN*
( If back is not inside )
&2_1 ;&back? CALL ,&2_2 JCN
( If front-left is inside )
;&front_left? CALL NOT ;&MAIN_LOOP JCN*
#00 ;&mark1_a STA
;&paint_here CALL
;&PAINT GOTO
( If mark1 is not active )
&2_2 ;&mark1_a LDA ,&2_3 JCN
;&paint_x LDA* ;&mark1_x STA*
;&paint_y LDA* ;&mark1_y STA*
;&paint_d LDA ;&mark1_d STA
#0001 ;&mark1_a STA ;&mark2_a STA
#0000 ;&backtrack STA ;&findloop STA
;&MAIN_LOOP GOTO
( Else if mark2 is not active )
&2_3 ;&mark2_a LDA ;&2_4 JCN*
( If painter is at mark1 )
&2_3_1 ;&mark1_a LDA NOT ,&2_3_2 JCN
;&paint_x LDA* ;&mark1_x LDA* NEQ* ,&2_3_2 JCN
;&paint_y LDA* ;&mark1_y LDA* NEQ* ,&2_3_2 JCN
( If painter and mark1 are facing in the same direction )
&2_3_1_1 ;&paint_d LDA ;&mark1_d LDA NEQ ,&2_3_1_else
#00 ;&mark1_a STA
;&turn_around CALL
;&paint_here CALL
;&PAINT GOTO
&2_3_1_else
#0001 ;&backtrack STA ;&findloop STA
;&mark1_d LDA ;&paint_d STA
;&MAIN_LOOP GOTO
( If findloop is True )
&2_3_2 ;&findloop LDA NOT ,&2_3_end JCN
;&paint_x LDA* ;&mark2_x STA*
;&paint_y LDA* ;&mark2_y STA*
;&paint_d LDA ;&mark2_d STA
#01 ;&mark2_a STA
&2_3_end ;&MAIN_LOOP GOTO
( Else if painter is at mark1 )
&2_4 ;&mark1_a LDA NOT ,&2_5 JCN
;&paint_x LDA* ;&mark1_x LDA* NEQ* ,&2_5 JCN
;&paint_y LDA* ;&mark1_y LDA* NEQ* ,&2_5 JCN
;&mark2_x LDA* ;&paint_x STA*
;&mark2_y LDA* ;&paint_y STA*
;&mark2_d LDA ;&paint_d STA
#0000 ;&mark1_a STA ;&mark2_a STA
#00 ;&backtrack STA
;&turn_around CALL
;&paint_here CALL
;&PAINT GOTO
( Else if painter is at mark2 )
&2_5 ;&mark2_a LDA NOT ,&2_end JCN
;&paint_x LDA* ;&mark2_x LDA* NEQ* ,&2_end JCN
;&paint_y LDA* ;&mark2_y LDA* NEQ* ,&2_end JCN
;&paint_x LDA* ;&mark1_x STA*
;&paint_y LDA* ;&mark1_y STA*
;&mark2_d LDA ;&mark1_d STA
;&mark2_d LDA ;&paint_d STA
#0001 ;&mark1_a STA ;&mark2_a STA
&2_end ;&MAIN_LOOP GOTO
&check_3
;&wall_count LDA #03 NEQ ,&check_4 JCN
#00 ;&mark1_a STA
;&paint_here CALL
;&PAINT GOTO
&check_4
;&wall_count LDA #04 NEQ ;&MAIN_LOOP JCN*
;&paint_here CALL_RET
( Update the variable tracking the number of walls around the painter )
&update_neighbour_wall_count ( -- wall_count )
#04 ,&front? JSR ,&back? JSR ,&left? JSR ,&right? JSR
ADD* ADD SUB ;&wall_count STA RETURN
( Return 1 if the pixel in the direction is inside the fill region, otherwise 0 )
&front? #00 ,&dir_inside? JMP
&back? #02 ,&dir_inside? JMP
&left? #03 ,&dir_inside? JMP
&right? #01 ,&dir_inside? JMP
&front_left? #03 ,&diag_inside? JMP
&back_left? #02 ,&diag_inside? JMP
&dir_inside? ;&col_t LDA SWP ,&get_dir_coords JSR ;check_canvas_pixel_colour CALL_RET
&diag_inside? ;&col_t LDA SWP ,&get_diag_coords JSR ;check_canvas_pixel_colour CALL_RET
( 0:front 1:right 2:back 3:left )
&get_dir_coords ( relative_d -- x* y* )
;&paint_d LDA ADD MOD4 ( d )
TO_SHORT DUP* INC MOD4 SWP* ( d+1* d* )
DOUBLE* ;&_dir_offsets ADD* LDA* SWP* ( d+1* | y_offset* )
DOUBLE* ;&_dir_offsets ADD* LDA* SWP* ( x_offset* | y_offset* )
,&to_abs_coords JMP
( 0:front-right 1:back-right 2:back-left 3:back-left )
&get_diag_coords ( relative_d -- x* y* )
;&paint_d LDA ADD MOD4 ( d )
#0003 ROT SUB INCk* MOD4 ( 3-d* 4-d* )
DOUBLE* ;&_diag_offsets ADD* LDA* SWP* ( y_offset* 3-d* )
DOUBLE* ;&_diag_offsets ADD* LDA* SWP* ( x_offset* y_offset* )
,&to_abs_coords JMP
&to_abs_coords ( x_offset* y_offset* -- x* y* )
SWP* ;&paint_x LDA* ADD* SWP* ( x* y_offset* )
;&paint_y LDA* ADD* RETURN ( x* y* )
&_dir_offsets ffff 0000 0001 0000
&_diag_offsets ffff ffff 0001 0001
&paint_here ,&col_p LDR ,&paint_x LDR* ,&paint_y LDR* ;set_canvas_pixel_colour CALL_RET
&move_forward #00 ;&get_dir_coords CALL ,&paint_y STR* ,&paint_x STR* RETURN
&turn_left #ff ,&_turn JMP
&turn_right #01 ,&_turn JMP
&turn_around #02 ,&_turn JMP
&_turn ,&paint_d LDR ADD MOD4 ,&paint_d STR RETURN
&paint_x $2 &mark1_x $2 &mark2_x $2 ( x-coords )
&paint_y $2 &mark1_y $2 &mark2_y $2 ( y-coords )
&paint_d $2 &mark1_d $1 &mark2_d $1 ( directions, 0:up 1:right 2:down 3:left )
&wall_count $1 &mark1_a $1 &mark2_a $1 ( neighbouring walls count, and mark active bools )
&col_p $1 &col_t $1 ( paint colour and target colour )
&backtrack $1 &findloop $1
( ---------------------------------------------------------------------------- )
( T O O L B O X )
@delay ( coarse* -- )
#0000 SWP* SUB* #0000 ( coarse* fine* )
&loop_fine
INC* DUP* ORA ,&loop_fine JCN
&loop_coarse
SWP* INC* SWP* OVR* ORA ,&loop_fine JCN
POP* POP* RETURN
@print_newline LIT <LINEFEED> /CONSOLE.WRITE! RETURN
@print_space LIT <SPACE> /CONSOLE.WRITE! RETURN
@ -393,7 +612,7 @@
&end POP* RETURN
( Print a single byte to the console )
( Print a single byte to the console in decimal )
@print_byte_decimal_signed ( byte -- )
DUP* IS_POSITIVE ,print_byte_decimal JCN
LIT "- /CONSOLE.WRITE! NEGATE
@ -401,7 +620,7 @@
;convert_byte_to_decimal_string CALL
PRINTF(%s) RETURN
( Print a single short to the console )
( Print a single short to the console in decimal )
@print_short_decimal_signed ( short* -- )
DUP* IS_POSITIVE* ,print_short_decimal JCN
LIT "- /CONSOLE.WRITE! NEGATE*
@ -414,9 +633,11 @@
&loop LDAk DUP ,&print JCN POP POP* RETURN
&print /CONSOLE.WRITE! INC* ,&loop JMP
( Print a byte as TRUE or FALSE )
( Print a value as TRUE or FALSE )
@print_bool_short ( bool* -- )
TO_BOOL*
@print_bool ( bool -- )
;&true ROT ,&print JCN POP* ;&false &print PRINTF(%s)
;&true ROT ,&print JCN POP* ;&false &print PRINTF(%s) RETURN
&true "TRUE <NULL> &false "FALSE <NULL>
@ -602,10 +823,15 @@
;ui__clear_memory_region CALL
( Clear all registered mouse zones )
;ui__zones ;ui__zones/end #0000
;ui__clear_memory_region CALL_RET
;ui__clear_memory_region CALL
( Set mouse colour to 0 )
COL_T1 ;ui__set_cursor_colour CALL_RET
&pop_null_callback POP
&null_callback RETURN
@ui__set_cursor_colour ( sprite_colour -- )
;ui__redraw_cursor/cursor_colour STA RETURN
( Fill a region of memory with a given value. )
@ui__clear_memory_region ( start_addr* end_addr* value* -- )
STH* SWP* ( end* addr* | val* )
@ -663,7 +889,7 @@
COL_00 AND_FG /SCREEN.SPRITE!
( Draw the mouse cursor under the current mouse position )
/MOUSE.X? /SCREEN.X! /MOUSE.Y? /SCREEN.Y!
COL_T1 AND_FG /SCREEN.SPRITE!
[ LIT &cursor_colour $1 ] AND_FG /SCREEN.SPRITE!
RETURN
@ui__restart_mouse1_drag ( -- )
@ -999,6 +1225,7 @@
@canvas_screen
;ui__clear CALL
;colour_palette_canvas #04 ;ui__set_colour_palette CALL
#03 ;ui__set_cursor_colour CALL
;canvas_screen__draw CALL
;canvas_screen__tool ;ui__register_mouse1_callback CALL
;canvas_screen__B ;ui__register_B_callback CALL
@ -1008,7 +1235,7 @@
@canvas_screen__B NORETURN ;tool_palette_screen GOTO
@canvas_screen__tool
( Dispatch control to the routine of the active tool )
/V.ACTIVE_TOOL? MUL4* JMP
/V.ACTIVE_TOOL? MUL4 JMP
;&pan GOTO ;&zoom GOTO ;&brush_1 GOTO ;&brush_2 GOTO
;&brush_3 GOTO ;&line GOTO ;&rect GOTO ;&fill GOTO
@ -1045,16 +1272,17 @@
/MOUSE.X? /SCREEN.X! /MOUSE.Y? /SCREEN.Y! COL_1 /SCREEN.PIXEL!
RETURN
&brush_2 RETURN
&brush_3 RETURN
&line RETURN
&rect RETURN
&fill RETURN
&fill ( released? -- )
IF_FALSE RETURN #01
/MOUSE.X? /V.CANVAS.X? SUB*
/MOUSE.Y? /V.CANVAS.Y? SUB*
;fill_canvas_from_point CALL
;draw_canvas CALL
RETURN
( Render the canvas to the screen from the raw program data )
@draw_canvas ( -- )
@ -1580,11 +1808,10 @@ e060 6060 6060 f000 ( l ) 0000 6cfe d6d6 c600 ( m )
0008 0008 0008 0008 0000 0000 0000 0000 0020 0020 0020 0020
0008 0d00 0000 0000 0000 5500 0000 0000 0020 6000 0000 0000
@canvas_buffer
(
This label needs to be at the end of the
program, to ensure that there is enough space
to work with the largest possible image.
)
ffa1 adad a1bf 81ff ff81 f5d1 f781 c1ff ffff 0000 ffff 0000