Rectangle routines (filled and border)

Here you can share sprites, code, sheets or ideas that you have
  • Author
  • Message
Offline
User avatar

chickendude

Staff Member

Topic Starter

Rectangle routines (filled and border)

Post03 February 2013, 11:57

The other day i wrote a small filled rectangle routine, perhaps it's not super fast but it gets the job done (it's currently at 70 bytes).

This is the first version after implementing some optimizations from Xeda. It fills the rectangle vertically:
Code: Select all
#ifdef TI83P
GBUF_LSB = $40
GBUF_MSB = $93
#else
GBUF_LSB = $29
GBUF_MSB = $8E
#endif

;b = # columns
;c = # rows
;d = starting x
;e = starting y
rectangle_filled_xor:
      ld a,$AE                ;xor (hl)
      jr rectangle_filled2
rectangle_filled_solid:
      ld a,$B6                ;or (hl)
rectangle_filled2:
      push de
      push bc
            ld (or_xor),a           ;use smc for xor/solid fill
            ld a,d                        ;starting x
            and $7                        ;what bit do we start on?
            ex af,af'
                  ld a,d                  ;starting x
                  ld l,e                  ;ld hl,e
                  ld h,0                  ; ..
                  ld d,h                  ;set d = 0
                  add hl,de         ;starting y * 12
                  add hl,de         ;x3
                  add hl,hl         ;x6
                  add hl,hl         ;x12
                  rra                     ;a = x coord / 8
                  rra                     ;
                  rra                     ;
                  and %00011111     ;starting x/8 (starting byte in gbuf)
                  add a,GBUF_LSB
                  ld e,a                  ;
                  ld d,GBUF_MSB     ;
                  add hl,de         ;hl = offset in gbuf
            ex af,af'               ;carry should be reset and z affected from and $7
            ld e,a
            ld a,%10000000
             jr z,$+6
                  rra
                  dec e
                  jr nz,$-2
            ld d,a                        ;starting bit to draw
rectangle_loop_y:
            push bc
            push hl
rectangle_loop_x:
                  ld e,a                  ;save a (overwritten with or (hl))
or_xor = $
                  or (hl)                 ;smc will modify this to or/xor
                  ld (hl),a
                  ld a,e                  ;recall a
                  rrca              ;rotate a to draw the next bit
                   jr nc,$+3
                        inc hl
                  djnz rectangle_loop_x
            pop hl                        ;hl = first column in gbuf row
            ld c,12                       ;b = 0, bc = 12
            add hl,bc               ;move down to next row
            pop bc                        ;restore b (# columns)
            ld a,d                        ;restore a (starting bit to draw)
            dec c
             jr nz,rectangle_loop_y
rectangle_end:
      pop bc
      pop de
      ret


This is a modified version of the other which i believe is a bit faster (i'd say around 5-15% depending on the dimensions of the rectangle). This one fills vertically:
Code: Select all
#ifdef TI83P
GBUF_LSB = $40
GBUF_MSB = $93
#else
GBUF_LSB = $29
GBUF_MSB = $8E
#endif

;b = # rows
;c = # columns
;d = starting x
;e = starting y
rectangle_filled_xor:
      ld a,$AE                ;xor (hl)
      jr rectangle_filled2
rectangle_filled_solid:
      ld a,$B6                ;or (hl)
rectangle_filled2:
      push de
      push bc
            ld (or_xor),a           ;use smc for xor/solid fill
            ld a,d                        ;starting x
            and $7                        ;what bit do we start on?
            ex af,af'
                  ld a,d                  ;starting x
                  ld l,e                  ;ld hl,e
                  ld h,0                  ; ..
                  ld d,h                  ;set d = 0
                  add hl,de         ;starting y * 12
                  add hl,de         ;x3
                  add hl,hl         ;x6
                  add hl,hl         ;x12
                  rra                     ;a = x coord / 8
                  rra                     ;
                  rra                     ;
                  and %00011111     ;starting x/8 (starting byte in gbuf)
                  add a,GBUF_LSB
                  ld e,a                  ;
                  ld d,GBUF_MSB     ;
                  add hl,de         ;hl = offset in gbuf
            ex af,af'               ;carry should be reset and z affected from and $7
            ld d,a
            ld a,%10000000
             jr z,$+6
                  rra
                  dec d
                  jr nz,$-2
            ld e,12
rectangle_loop_x:
            push af
            push bc
            push hl
                  ld c,a
rectangle_loop_y:
or_xor = $
                  or (hl)                 ;smc will modify this to or/xor
                  ld (hl),a
                  ld a,c
                  add hl,de
                  djnz rectangle_loop_y
            pop hl
            pop bc
            pop af
            rrca
             jr nc,$+3
                  inc hl
            dec c
             jr nz,rectangle_loop_x
rectangle_end:
      pop bc
      pop de
      ret


Note that for the first routine, b = the number of columns in the rectangle and c = the number of rows. In the second, they're switched: b = # rows and c = # columns. de is the same for both, though. Source is in the PD, do (or don't) whatever you want with it!

EDIT: The syntax highlighting is so pretty!
EDIT2: Found a small optimization.
Offline
User avatar

NanoWar

Site Admin

Re: Small filled rectangle routine

Post03 February 2013, 16:23

Cool, I have a much larger version, so I might use yours instead :) .

BTW: In SPASM you could do "GBUF >> 8" and "GBUF & $FF" instead of that #ifdef TI83P stuff.

We have the best syntax highlighter of all TI forums! :P
Offline
User avatar

chickendude

Staff Member

Topic Starter

Re: Small filled rectangle routine

Post04 February 2013, 08:29

Here's a generic rectangle routine (just an outlined rectangle):
Code: Select all
;b = # rows
;c = # columns
;d = starting x
;e = starting y
rectangle:
      push de
      push bc
            ld a,d                        ;starting x
            and $7                        ;what bit do we start on?
            ex af,af'
                  ld a,d                  ;starting x
                  ld l,e                  ;ld hl,e
                  ld h,0                  ; ..
                  ld d,h                  ;set d = 0
                  add hl,de         ;starting y * 12
                  add hl,de         ;x3
                  add hl,hl         ;x6
                  add hl,hl         ;x12
                  rra                     ;a = x coord / 8
                  rra                     ;
                  rra                     ;
                  and %00011111     ;starting x/8 (starting byte in gbuf)
                  add a,gbuf & $FF
                  ld e,a                  ;
                  ld d,gbuf >> 8    ;
                  add hl,de         ;hl = offset in gbuf
            ex af,af'               ;carry should be reset and z affected from and $7
            ld e,a
            ld a,%10000000
             jr z,$+6
                  rra
                  dec e
                  jr nz,$-2
            dec b                   ;you could adjust your input to take care of this, ie b = width-2, c = height-1 and save 3 bytes here
            dec b                   ;we draw the ends separately
            dec c                   ;we'll draw the last line at the end
            ld d,a                        ;starting bit to draw
;d = starting bit
rectangle_loop_y:
            push bc
                  push hl
                        call rectangle_loop_x
                  pop hl                        ;hl = first column in gbuf row
                  ld c,12                       ;b = 0, bc = 12
                  add hl,bc               ;move down to next row
            pop bc                        ;restore b (# columns)
            xor a
            ld (ld_hl),a            ;change ld (hl),a to nop
;           cp c                    ; # UNCOMMENT TO ALLOW LINES WITH A HEIGHT OF 1 PIXEL
;           jr z,rectangle_end      ; #
            ld a,d                        ;restore a (starting bit to draw)
            dec c
             jr nz,rectangle_loop_y
            ld a,$77                ;ld (hl),a
            ld (ld_hl),a            ;return nop to ld (hl),a
            ld a,d
            call rectangle_loop_x
rectangle_end:
      pop bc
      pop de
      ret

rectangle_loop_x:
      or (hl)                             ;first bit
      ld (hl),a
;     inc b                         ; # UNCOMMENT TO ALLOW LINES WITH A WIDTH OF 1 PIXEL
;      ret z                              ; #
;     dec b                         ; #
;      jr z,rectangle_loop_x_end    ; #
      ld a,d
rectangle_loop_x_inner:
      rrca                    ;rotate a to draw the next bit
       jr nc,$+3
            inc hl
      ld e,a                        ;save a (overwritten with or (hl))
      or (hl)                       ;smc will modify this to or/xor
ld_hl = $
      ld (hl),a
      ld a,e                        ;recall a
      djnz rectangle_loop_x_inner
rectangle_loop_x_end:
      rrca                    ;rotate a to draw the next bit
       jr nc,$+3
            inc hl
      or (hl)                       ;last bit
      ld (hl),a
      ret
It's currently at 90 bytes (or 98 if you want to be able to draw 1 pixel lines/dots vertically/horizontally). It doesn't feel that optimized to me but i couldn't think of a better way to handle the middle without jacking up the size too much.

EDIT: Found a small optimization in all of these codes. The or a was superfluous since the flags were still preserved from the and $7 above, so the routines should be one byte smaller now :)
Offline
User avatar

NanoWar

Site Admin

Re: Rectangle routines (filled and border)

Post04 February 2013, 23:47

If you use shadow registers, shouldn't you disable interrupts? I mean you could just use push af/pop af, right?

I need an implementation for a filled xor rectangle function without clipping, but with every possible pixel width. Optimized in size rather than in speed.
Offline
User avatar

chickendude

Staff Member

Topic Starter

Re: Rectangle routines (filled and border)

Post05 February 2013, 09:14

Ah yeah, i always have them disabled by default so it didn't even cross my mind. Well you can use either of the first two routines i posted, then. And if you just need an XOR routine you can get rid of the SMC stuff at the top and just use xor (hl) in rectangle_loop_y and save another 9 bytes (or 8, if you add di at the start). You could just push/pop, but using ex af,af' is much faster (4 clocks vs 11/10). That routine should work with every pixel width from 1-96 and heights from 1-64.
Offline
User avatar

chickendude

Staff Member

Topic Starter

Re: Rectangle routines (filled and border)

Post16 May 2013, 07:00

I just wrote a much faster routine that would let you potentially add special borders (at the price of a few bytes). It's for an app so could be optimized a bit with SMC. Currently it's about twice the size of the previous routine:
Code: Select all
;b = height
;c = width
;d = starting x
;e = starting y
drawBox:
      dec b
      dec b
      dec b
      dec b
      ld a,d
      and $7
      ex af,af'
            ld a,d
            ld l,e
            ld h,0
            ld d,h
            add hl,hl
            add hl,de
            add hl,hl
            add hl,de
            add hl,hl         ;y*14
            rra
            rra
            rra
            and %00011111
            ld e,a
            add hl,de         ;+x
            ld de,gbuf
            add hl,de         ;position in gbuf
      ex af,af'
      ld de,%1111111011111111
      ld (scratchSpace),de
      ld e,%01111111
      call drawLine
      ld de,%1111111111111111
      ld (scratchSpace),de
      call drawLine
middleLoop:
      ld de,%0000000100000000
      ld (scratchSpace),de
      ld e,%10000000
      call drawLine
      djnz middleLoop

      ld de,%1111111111111111
      ld (scratchSpace),de
      call drawLine
      ld de,%1111111011111111
      ld (scratchSpace),de
      ld e,%01111111
drawLine:
      push bc
      push af
            ld b,a
            or a
            ld a,$FF
             jr z,$+9         ;check if aligned
                  inc c
                  srl a
                  srl e
                  djnz $-5
            cpl
            and (hl)
            or e
            ld (hl),a         ;store in gbuf
            inc hl
            ld a,(scratchSpace)
            ld e,a                  ;e = the pattern to draw in the middle of the line
            ld a,c                  ;width+xOff
            sub 16                  ;-16 for the first two bytes in the line
             jr c,exitLine
lineLoop:
            ld (hl),e         ;draw 8 pixels at a time until we've got
            inc hl                  ; fewer than 8 pixels to draw
            sub 8
             jr nc,lineLoop
exitLine:
            neg
            cp 8
            ld b,a
            ld a,(scratchSpace+1)
             jr nz,unAlined
                  dec hl
                  ld (hl),a
                  inc hl
                  jr nextRow
unAlined:                     ;)
                  ld e,a
                  ld a,$FF
                  sla a
                  sla e
                   djnz $-4
                  cpl
                  and (hl)    ;a holds the mask
                  or e
                  ld (hl),a
nextRow:
            ld a,c
            rra
            rra
            rra
            and %00011111
            ld e,a
            ld a,12
            sub e
            ld e,a
            ld d,0
            add hl,de         ;+x
      pop af
      pop bc
      ret

Here's a sample in action:
Image

Actually, it was written for my tilemapping routine which is a bit ... non-standard. I'll need to double-check that it works for a regular 12-byte wide gbuf...
Offline
User avatar

NanoWar

Site Admin

Re: Rectangle routines (filled and border)

Post16 May 2013, 15:57

This is my current version. I looks horrible, but it doesn't do the horizontal line drawing, so it might be pretty quick. It saves the scanlines in RAM for later usage (see rectangle.smooth).

Code: Select all
;rectangle

      .clear
      .module "rectangle"
     
      ; Ok, you need 3 bytes in RAM somewhere. I do it this way:
      .var "scanline1"
      .var "scanline2"
      .var "scanline3"
     
      .import

#macro _drawRectangle(_drawRectangle_down, _drawRectangle_right, _drawRectangle_height, _drawRectangle_length)
      ld    l, _drawRectangle_down
      ld    a, _drawRectangle_right
      ld    bc, _drawRectangle_height * 256 + _drawRectangle_length
      call  rectangle
#endmacro
#define rect _drawRectangle(

#macro _drawRectangleSmooth(_drawRectangleSmooth_down, _drawRectangleSmooth_right, _drawRectangleSmooth_height, _drawRectangleSmooth_length)
      #ifdef _drawRectangleSmooth_down
            ld    l, _drawRectangleSmooth_down
            ld    a, _drawRectangleSmooth_right
            ld    bc, _drawRectangleSmooth_height * 256 + _drawRectangleSmooth_length
      #endif
      call  rectangle.smooth
#endmacro
#define rects _drawRectangleSmooth(

rectangle
      ;inputs: l=Y, a=X, b=height, c=length
      ;save coords & stuff
      ld    h, a
      push  hl
      push  bc
            call  rectangle.calc
      pop   bc
      pop   hl
rectangle.display
; inputs: h=X, l=Y, b=height
      ld    a, h
      ld    e, l
      ld    h, $00
      ld    d, h
      add   hl, de
      add   hl, de
      add   hl, hl
      add   hl, hl      ;l*12
      ld    e, a
      srl   e
      srl   e
      srl   e     ;x/8
      add   hl, de
      ld    de, gbuf
      add   hl, de
rectangle.display.loop:
      push  bc
      push  hl
            ld    a, (hl)
            ld    c, a
            ld    a, (rectangle.scanline1)
            xor   c
            ld    (hl), a
            inc   hl
            ld    a, (rectangle.scanline2)
            or    a
            jr    z, rectangle.display.noloop2
            ld    b, a
rectangle.display.loop2:
            ld    a, (hl)
            xor   $FF
            ld    (hl), a
            inc   hl
            djnz  rectangle.display.loop2
rectangle.display.noloop2:
            ld    a, (hl)
            ld    c, a
            ld    a, (rectangle.scanline3)
            xor   c
            ld    (hl), a
      pop   hl
      pop   bc
      ld    de, 12
      add   hl, de
      djnz  rectangle.display.loop
      ret

rectangle.calc
;inputs:
;     a = x
;     b = height
;     c = length
      ld    d, a
            ld    a, $FF
            ld    (rectangle.scanline1), a
            xor   a
            ld    (rectangle.scanline2), a
            ld    (rectangle.scanline3), a
      ld    a, d
      and   7
      ld    d, a
            or    a
            jr    z, rectangle.skipShift1
            ld    e, $FF
rectangle.shift1
            srl   e
            dec   a
            or    a
            jr    nz, rectangle.shift1
            ld    a, e
            ld    (rectangle.scanline1), a
rectangle.skipShift1
      ld    a, d  ; a = shift right
      ld    h, a  ; save
            add   a, c  ; a + c
            ld    b, a  ; save b = a + c
            and   7     ; /8 Rest?
            ld    d, a  ; Rest
            ld    a, 8
            sub   d     ; 8 - Rest
            ld    d, a  ; = d
            ld    e, $FF
rectangle.shift2
            sla   e
            dec   a
            or    a
            jr    nz, rectangle.shift2
            ld    a, e
            ld    (rectangle.scanline3), a
            ld    a, 16
      ld    e, h  ; a
      sub   e     ; 16 - a
      sub   d     ; -d
      srl   a
      srl   a
      srl   a
      ld    d, a
      ;
      ld    a, c
      srl   a
      srl   a
      srl   a     ; /8
      sub   d
      ld    d, a
      ld    a, (rectangle.scanline2)
      add   a, d
      ld    (rectangle.scanline2), a
      ;
      ld    a, b
      and   %11111000
      or    a     ; if (shift_right + length)<8, do (rectangle.scanline1 & rectangle.scanline3)
      ret   nz
      ld    a, (rectangle.scanline1)
      ld    d, a
      ld    a, (rectangle.scanline3)
      and   d
      ld    (rectangle.scanline1), a
      xor   a
      ld    (rectangle.scanline2), a
      ld    (rectangle.scanline3), a
      ret


; This draws a rounded, filled rectangle.
rectangle.smooth
;inputs: l=Y, a=X, b=height, c=length
      push  af
      push  hl
      push  bc
            dec   c
            dec   c
            inc   a
            call  rectangle
      pop   bc
      pop   hl
      pop   af
      ;
      push  af
      push  hl
      push  bc
            dec   b
            dec   b
            inc   l
            call  rectangle
      pop   bc
      pop   hl
      pop   af
      ;
      dec   c
      dec   c
      inc   a
      dec   b
      dec   b
      inc   l
      call  rectangle
      ret
Offline
User avatar

chickendude

Staff Member

Topic Starter

Re: Rectangle routines (filled and border)

Post17 May 2013, 08:42

I like mine because it'll clear what's behind it. That way if i want to update what's in the box, i can just redraw the rectangle to clear it. That's how my scrolling menus work, though i don't know if you can see them in the screenshot. This version draws the first byte assuming it's not aligned, then updates 8 pixels at a time until it reaches the last byte, where it draws just what's necessary.

I haven't followed through your code all the way, but i think scanline1 is the first byte, scanline2 i'm not quite sure, i thought it'd be the middle but i haven't followed the variables closely enough to figure out, and then scanline3 is the right side of the line? Btw, "and" and "dec" (for 8-bit registers) both update the z-flag:
Code: Select all
     and   %11111000
;      or    a     ; and %11111000 will set the z flag, no need for "or a"
      ret   nz


Code: Select all
     and   7
      ld    d, a
;            or    a      ;again, and 7 will set the z flag if the result is 0
            jr    z, rectangle.skipShift1


Code: Select all
rectangle.shift1
            srl   e
            dec   a
;            or    a      ;dec a will set the z flag (but won't set the c flag)
            jr    nz, rectangle.shift1


Code: Select all
           ld    a, $FF
            ld    (rectangle.scanline1), a
            xor   a
            ld    (rectangle.scanline2), a
            ld    (rectangle.scanline3), a
...is the same as:
Code: Select all
           xor a
            ld hl,rectangle.scanline1
            ld (hl),$FF
            inc hl
            ld (hl),a
            inc hl
            ld (hl),a
Though i'm really not sure why you initialize scanline2/3, it seems like you just overwrite them anyway. Also:
Code: Select all
     ld    a, d  ; a = shift right
      ld    h, a  ; save
Why not just use h as a scratch variable instead of d? And later you've got ld e, h \ sub e, why not just sub h? I like the idea of precalculating the end bytes, maybe i'll do that in my own routine :)
Offline
User avatar

NanoWar

Site Admin

Re: Rectangle routines (filled and border)

Post20 May 2013, 09:44

YES, a good code review, love that! I'll will post an updated version later maybe.

scanline2 holds the middle byte, which is always $FF, so I decided to put the amount of those bytes there.

Return to Resources and Ideas

Who is online

Users browsing this forum: No registered users and 1 guest

cron