z80:Drawing Routines
There are a limited number of ways for your programs to communicate with a user. You can use text, graphics, and rarely sound. With text, the OS offers us many tools and it doesn't need to be too fast, but with graphics, the OS routines often do not do the job. Here are a few routines to get the job done.
Circle
A good circle routine is always sure to impress users, especially those accustomed to BASIC. This does not use formulas of the nature you may be used to for drawing circles. As well, this routine uses some SMC, so if you are using this in an app, be aware that the subroutine PlotPixel will need to be in RAM (or at least part of it).
gBufLSB = 40h ;different for the 83. Good for 83+/84+ and SE models. gBufMSB = 93h ;different for the 83. Good for 83+/84+ and SE models. FastCircle: ;Inputs: ; BC = (x,y) ; H = radius ; A = method ; 0 = Pixel Off ; 1 = Pixel On ; 2 = Pixel Change ld de,$A62F ;2F,A6 = 'cpl \ and (hl)' dec a jr z,$+5 ld de,$B600 ;00,B6 = 'nop \ or (hl)' dec a jr z,$+5 ld de,$AE00 ;00,B6 = 'nop \ xor (hl)' ld (smc_PlotType) ld a,h or a ret z ret m di ld e,0 ld d,a ld l,e add hl,hl jr Loop+4 Loop: ex af,af' call Plot4Pix call Plot4Pix inc d sub l jr nc,YIsGood DecY: dec e inc l add a,h YIsGood: ex af,af' ld a,e sub d jp p,Loop ret Plot4Pix: inc l push af push hl push bc ld a,e ld e,d ld d,a push de add a,b cp 96 call c,Plot2Pix ;DC**** ld a,b sub d call p,Plot2Pix pop de pop bc pop hl pop af ret plot2Pix: push bc ld b,a push bc ld a,c add a,e ld c,a ;c+e call p,PlotPixel pop bc ld a,c sub e ld c,a ;c-e call p,PlotPixel pop bc ret PlotPixel: ;Input: ; b is X ; c is y ;Output: ; HL points to byte cp 64 \ ret nc ld a,b cp 96 \ ret nc ld l,c ld h,0 ld b,h add hl,hl add hl,bc add hl,hl add hl,hl ld b,a rrca \ rrca \ rrca and 0Fh add a,gBufLSB ld c,a ld a,b ld b,gBufMSB add hl,bc and 7 ld b,a inc b ld a,1 rrca djnz $-1 PixelType: nop or (hl) ld (hl),a ret
Pixel Plotting
Sometimes, we need to plot pixels. This routine returns a pointer to the byte the pixel is located on, as well as a mask for the pixel. If it is out of bounds, it returns the c flag reset, else it is set. To use this routine, you can do:
;turn the pixel ON call GetPixelLoc ret nc or (hl) ld (hl),a ret ;turn the pixel OFF call GetPixelLoc ret nc cpl ;invert the bits of A and (hl) ld (hl),a ret ;invert the pixel call GetPixelLoc ret nc xor (hl) ret ;test the pixel (nz=ON, z=Off) call GetPixelLoc ret nc ;also happens to return nz if c is reset and (hl) ret
gBufLSB = 40h ;different for the 83. Good for 83+/84+ and SE models. gBufMSB = 93h ;different for the 83. Good for 83+/84+ and SE models. GetPixelLoc: ;Input: ; b is X ; c is y ;Output: ; HL points to byte ; A is the mask ; nc if not computed, c if computed cp 64 \ ret nc ld a,b cp 96 \ ret nc ld l,c ld h,0 ld b,h add hl,hl add hl,bc add hl,hl add hl,hl ld b,a rrca \ rrca \ rrca and 0Fh add a,gBufLSB ld c,a ld a,b ld b,gBufMSB add hl,bc and 7 ld b,a ld a,1 inc b rrca djnz $-1 scf ret
Rectangle
The OS routines automatically update the LCD area that it is drawn to, which can be a pain to assembly programmers. It also slows it down. Here is a quick routine, riddled with SMCL
gBufLSB = 40h ;different for the 83. Good for 83+/84+ and SE models. gBufMSB = 93h ;different for the 83. Good for 83+/84+ and SE models. RectData equ OP1 Rectangle: ;Inputs: ; D = x ; E = y ; B = height ; C = width ; A = Method ; 0=Erase ; 1=OR ; 2=XOR dec a jr nz,$+6 ld a,$B6 ;B6 = 'or (hl)' jr RectPattern dec a jr nz,$+6 ld a,$AE ;AE = 'xor (hl)' jr RectPattern ld a,-1 ld (smc_Erase0),a ld a,2Fh ;2F = CPL ld (smc_Erase1),a ld a,0Ch ;0C = INC C ld (smc_Erase2),a ld a,$A6 ;A6 = 'and (hl)' RectPattern: ld (smc_Logic),a push bc push de push bc ; First I want to clear the RectData buffer ld hl,RectData .db 1 smc_Erase0: .dw 0C00h ;changed to 0CFF for Erase ld (hl),c inc l djnz $-2 ld l,78h ; Now I want to make the rectangle pattern. ; Since D is the x coordinate, we need to find which bit ; the edge starts on and set each bit after that as well. ld a,d and 7 ld b,a ld a,80h jr z,$+5 rrca djnz $-1 add a,a dec a smc_Erase1: nop ld (hl),a ; Now we need to fill in the rest of the buffer ; First we need to know how many bits need to be set ex (sp),hl ld a,d neg and 7 ld b,a ld a,l smc_Erase2: dec c ;changed to 'inc c' for Erase sub b ex (sp),hl jr z,PatternFinished jr c,LoadLastByte inc l ld (hl),c sub 8 jr nc,$-4 LoadLastByte: and 7 ld b,a ld a,80h jr z,$+5 rrca djnz $-1 add a,a dec a xor (hl) smc_Erase3: nop ld (hl),a PatternFinished: ; At this point, the whole pattern is filled in. Bien. ; Now we can worry about where to start drawing the pattern. ; For this, we use DE for (x,y) ; B is 0, so I cheat. ld a,d ld d,b ld h,d ld l,e add hl,hl add hl,de add hl,hl add hl,hl and %11111000 rrca \ rrca \ rrca add a,gBufLSB ;plotSScreen = 9340h ld e,a ld d,gBufMSB add hl,de ; Now HL points to where to draw in the graph buffer. ; All we do now is treat the pattern as a 12-byte wide sprite with ; the same bytes repeated over and over. pop bc ;b is the height. ld d,84h ;RectData = 8478h DrawLoopStart: ld e,78h ld c,12 DrawLoop: ld a,(de) smc_Logic: or (hl) ld (hl),a inc hl inc e dec c jr nz,DrawLoop djnz DrawLoopStart pop de pop bc ret
Grayscale LCD Update
This code uses SMC, so it should be used in RAM. However, the SMC used can easily be removed for use in an app. This requires the use of a minor buffer and major buffer and it can use different levels of saturation from each buffer by changing GrayMask. This is designed to handle 2,3, or 4 levels of grayscale and each of the grayscale bit masks is meant to display each pixel for a certain percentage of the frames displayed. This routine must be continuously called to update the LCD in order for it to appear gray.
;=============================================================== BufferToLCD: ;=============================================================== ;Inputs: ; MajBuf points to the main buffer to use. Probably plotSScreen. ; MinBuf points to the backgfor round buffer to use. Probably saveSScreen. ; GrayMask holds the gray mask. Zero takes from the major, ; buffer, one takes from the minor buffer. ;Outputs: ; ;Defines: GrayMask50 equ %1010101010101010 ;1 1/2 1/2 0 GrayMask67 equ %1001001001001001 ;1 2/3 1/3 0 GrayMask75 equ %1000100010001000 ;1 3/4 1/4 0 GrayMask83 equ %1000001000001000 ;1 5/6 1/6 0 GrayMask92 equ %1000000000001000 ;1 11/12 1/12 0 Graymask100 equ -1 GrayMask0 equ 0 GrayMask8 equ 1-GrayMaks92 GrayMask17 equ 1-GrayMask83 GrayMask25 equ 1-GrayMask75 GrayMask33 equ 1-GrayMask67 #define LCDDelay() in a,(16) \ rlca \ jr c,$-3 ;=============================================================== .db 21h ;ld hl,** GrayMask: .dw GrayMask50 ;replace with whatever you like add hl,hl jr nc,$+4 set 4,l ld (GrayMask),hl ld hl,MajBuf ld ix,MinBuf setrow: LCDDelay() ld a,$80 out (16),a ld de,12 ld a,$20 col: ld (smc_Var2),a LCDDelay() ld a,(smc_Var2) out (10h),a ld b,64 row: ld (smc_Var1),hl ;16 .db 21h ;10 ld hl,** GrayMask: ; .dw GrayMask50 ;-- add hl,hl ;11 jr nc,$+4 ;12|15 set 4,l ;-- ld (GrayMask),hl ;16 ld a,(ix) ;19 .db 21h ;10 ld hl,** smc_Var1: ; .dw 0 ;-- xor (hl) ;7 and c ;4 xor (hl) ;7 add hl,de ;11 add ix,de ;15 ;======================================================== ;Not needed because the stuff before takes >130 cycles ; push af ; LCDDelay() ; pop af ;======================================================== out ($11),a djnz row .db 3Eh ;ld a,* smc_Var2: .db 20h inc a dec h dec h dec h inc hl dec ixh dec ixh dec ixh inc ix cp $2c jp nz,col ret