Omnimaga
Calculator Community => TI Calculators => ASM => Topic started by: aeTIos on March 24, 2011, 04:17:05 am
-
Hi,
How do you set a pixel on the screen on/ off?
How do you draw a line ?
Thanks!
-
I'm assuming you mean without using the built in ROM calls? Manipulating pixels is fairly straightforward. Drawing lines, however, is not. In order to draw lines without the use of b_calls you'll have to write an algorithm in assembly to do that yourself. I'd suggest Bresenham's line algorithm for that. I'm fairly new to ASM myself, so I don't think I could easily do or explain how to do that. Pixels, on the other hand, I can do.
Here's an explanation for manipulating pixels for the graph buffer (not directly plotting to the LCD):
Because of the nature of monochrome graphics, instead of representing each pixel with one or more bytes, each byte represents 8 pixels: one pixel for each bit. So in order to manipulate a specific pixel, you have to first find two things: The byte that contains the pixel, and which bit in that byte represents the pixel.
Getting the pixel:
GetPixel:
; Inputs:
; A = x-coordinate, L = y-coordinate
; Outputs:
; A = a bitmask for the pixel, HL = the address of the byte containing the pixel
ld h, 0
ld d, h
ld e, l
add hl, hl ; the graph screen is arranged as an array of 12 x 64 bytes (12 bytes per row, 64 rows)
add hl, de ; so in order to find the correct byte we first multiply the y coordinate by 12...
add hl, hl
add hl, hl
ld e, a ; ...then add the x-coordinate/8 (because there are 8 pixels per byte)
srl e
srl e
srl e
add hl, de
ld de, PlotSScreen
add hl, de ; now hl contains the address of the byte
and 7 ; this is the tricky part: finding the bit. Because there are 8 bits in each byte, we can do x-coordinate modulo 8
ld b, a ; and that will return a number (0-7). That number tells us which bit to use (where "7" represents bit 0, and "0" represents bit 7)
ld a, $80 ; here we load the initial bitmask into A
ret z ; if the "and 7" command returned 0, then the mask doesn't need to be rotated, so we can just return now.
_loop:
rrca ; otherwise, rotate the bitmask B times (remember we loaded the result of "and 7" into B earlier)
djnz _loop
ret
Turning a pixel on:
call GetPixel
or (hl) ; HL holds the byte, and A holds the mask, so we simply use or to turn on the masked pixel...
ld (hl), a ;...then load it into the buffer
Inverting a pixel:
call GetPixel
xor (hl) ; Same idea as before only using xor to invert
ld (hl), a
Turning a pixel off:
call GetPixel
cpl ; because AND clears any pixels that are already set, you have to invert the mask first
and (hl)
ld (hl), a
NOTE: These only plot pixels in the graph buffer. You have to update the display before they will actually show up. Easiest way is just with B_CALL(_GrBufCpy), or you could write a custom routine, but that's a bit harder. :P
Hope this helped!
-Zippy Dee
EDIT: Bresenham's line algorithm: http://en.wikipedia.org/wiki/Bresenham's_line_algorithm
-
thanx!
-
No problem! You can use the GetPixel routine for drawing vertical lines pretty easily, too.
Vertical lines:
; Inputs:
; D is the x-coordinate, L is the starting y-coordinate and E is the ending y-coordinate
; make sure L is less than E, otherwise it'll go KABLOOIE!! or something...
ld a, e
sub l ; get the length of the line by subtracting L from E
ret z ; if the length is zero, we don't need to draw it, so just return
push af ; save the length of the line into the stack so we don't lose it
ld a, d ; put the x-coordinate in A
call GetPixel ; now A is the mask that we'll just use for all the rows and HL is the address of the starting byte for the line
pop BC ; we pushed AF before, so now we pop to BC, so now B is the line length
ld de, 12
ld c, a ; save the bitmask to C so we don't lose it
_vloop:
ld a, c ; restore the mask
or (hl) ; turn the pixel on
ld (hl), a
add hl, de ; each row is 12 bytes, so to go to the next row we can just add 12 (which we stored in DE) to HL
djnz _vloop
ret
You could potentially just loop GetPixel for each point in horizontal lines, too, but because the bytes group pixels horizontally, there are more efficient ways to do it.
Unfortunately, I don't have a tested horizontal line routine, and I don't know that I'm good enough to really write an efficient one myself yet without doing tests first.
Anyway, hope this helped even more!
-Zippy Dee
EDIT: I wrote out this routine just now, haven't tested it, but it should work. Maybe someone else can confirm. I'm not guaranteeing 100% efficiency, but it should be fine for almost all cases.
; Inputs:
; L =is the y-coordinate, D is the starting x-coordinate and E is the ending x-coordinate
; again, make sure D is less than E
ld a, e ; get length, return if 0
sub d
ret z
srl a ; divide line length by 8 to find the number of bytes this line will span across (minus 1)
srl a
srl a
push af ; save it for later
ld h, 0 ; get the address of the byte containing the first pixel of the line
ld b, h ; same code as in GetPixel, just using BC instead of DE because DE is currently occupied by our precious x-coordinates
ld c, l
add hl, hl
add hl, bc
add hl, hl
add hl, hl
ld c, d
srl c
srl c
srl c
add hl, bc
ld bc, PlotSScreen
add hl, bc ; now hl is the starting byte of the line
call GetMask ;get the mask for the left end of the line
pop bc ; length of the line goes into b
ld c, a ; save the mask to c
ld a, b
or a ; if b is 0, mask the end of the line as well
jr z, _lastbyte
ld a, c
_hloop:
or (hl) ; apply the mask
ld (hl), a
inc hl
ld a, $FF ; after the first byte, the other bytes (except the last) will be masked with $FF so all pixels are turned on
djnz _hloop
_lastbyte:
ld d, e
dec d
call GetMask ; get the mask for the right end of the line
srl a ; because GetMask gives us the mask for the left end, we shift it one to the left and invert it to get the mask for the right end
cpl
and c ; get the similar bits between the previous mask and the current one (remember that if the length is less than 8, the masks for both the left and right ends have to be applied to the same byte)
or (hl) ; now we apply the mask
ld (hl), a
ret ; and we're done!
GetMask:
; this is also the same idea as getting the mask in GetPixel, only instead of rotating $80 we do a logical shift right on $FF to retrieve the mask for the ends of the line
ld a, d
and 7
ld b, a
ld a, $FF
ret z
_maskloop:
sra a
djnz _maskloop:
ret
-
EDIT: As a note, the vertical and horizontal line commands draw across the whole screen
The easiest horizontal line I could come up with...
;============================================
DrawHoriz:
;============================================
;Input:
; l is the row to draw to
; a is the type of line to draw
; 0 is a white line
; 1 is a black line
; 2 is an inverted line
;============================================
ld h,0 ;2600 7
ld b,h ;44 4
ld c,l ;4D 4
add hl,bc ;09 11
add hl,bc ;09 11
add hl,hl ;29 11
add hl,hl ;29 11
ld bc,9340h ;014093 10
add hl,bc ;09 11
ld b,12 ;060C 7
or a ;B7 4 Total: 91
jr z,DrawHoriz ;2804 White: 12+318+91
dec a ;3C 4
jr nz,DrawInvH ;2006 Black: 4+4+7+7+318+91
dec a ;3C 4
DrawHoriz: Total: 5 bytes, 318 cycles
ld (hl),a ;77 7 84
inc hl ;23 6 72
djnz DrawHoriz ;10FC 13*11+8 152
ret ;C9 10 10
DrawInv: Total: 7 bytes, 450 cycles Invert: 4+7+12+450+91
ld a,(hl) ;7E 7 84
cpl ;2F 4 48
ld (hl),a ;77 7 84
inc hl ;23 6 72
djnz DrawInv ;10FA 13*11+8 152
ret ;C9 10 10
;============================================
;Stats:
;============================================
;Size: 33 bytes
;Speed:
; White: 12+318+91 421 cycles
; Black: 4+4+7+7+318+91 431 cycles
; Inv: 4+7+12+450+91 564 cycles
;============================================
Here is a vertical line:
;============================================
DrawVert:
;============================================
;Inputs:
; a is the column
; c is the method
; 0 draws a white line
; 1 draws a black line
; 2 draws an inverted line
;============================================
ld b,a ;47
rrca \ rrca \ rrca ;0F0F0F
and 1Fh ;E60F
add 40h ;C640
ld l,a ;6F
ld h,93h ;2693
ld a,b ;78
and 7 ;E607
inc a ;3C
ld b,a ;47
ld a,1 ;3E01
GetPixelMask:
rrca ;0F
djnz GetPixelMask ;10FD
dec c ;0D
jr nz,DrawVertW ;200F
ld e,a ;5F
ld bc,400Ch ;010C40
DrawVertBLoop:
ld a,e ;7B
or (hl) ;B6
ld (hl),a ;77
ld a,b ;78
ld b,0 ;0600
add hl,bc ;09
ld b,a ;47
djnz DrawVertBLoop ;10F6
ret ;C9
DrawVertW:
dec c ;0D
jr z,DrawVertI ;2001
cpl a ;2F
DrawVertI:
ld e,a ;5F
ld bc,400Ch ;010C40
DrawVertILoop:
ld a,e ;7B
xor (hl) ;AE
ld (hl),a ;77
ld a,b ;78
ld b,0 ;0600
add hl,bc ;09
ld b,a ;47
djnz DrawVertBLoop ;10F6
ret ;C9
;============================================
;Stats:
;============================================
;Size: 58 bytes
;Speed: ... too lazy to calculate at the moment
; White:
; Black:
; Inv:
;============================================
Here is a pixel routine:
;============================================
PlotPixel: ;45 bytes
;============================================
;Inputs:
; b is the x coordinate
; c is the y coordinate
; e is the method
; 0 draws a white line
; 1 draws a black line
; 2 draws an inverted line
;============================================
ld a,b ;78
ld b,0 ;0600
ld h,b ;60
ld l,c ;69
add hl,hl ;29
add hl,bc ;09
add hl,hl ;29
add hl,hl ;29
ld bc,9340h ;014093
add hl,bc ;09
ld b,a ;47
rrca ;0F
rrca ;0F
rrca ;0F
and 1Fh ;E60F
ld c,a ;4F
ld a,b ;78
ld b,0 ;0600
add hl,bc ;09
and 7 ;E607
ld b,a ;47
inc b ;04
ld a,1 ;3E01
GetMask:
rrca ;0F
djnz GetMask ;10FD
dec e ;1D
jr nz,DrawWPix ;2003
or (hl) ;B6
ld (hl),a ;77
ret ;C9
DrawWPix:
dec e ;1D
jr z,DrawIPix ;2801
cpl ;2F
DrawIPix:
xor (hl) ;AE
ld (hl),a ;77
ret ;C9
These can probably be optimised, but I just typed them up :/ I am fairly sure the last can be optimised more.
-
[code] tags in [spoilers] :(
-
Oh, sorry about that :/ Sometime I need to see what happens in other browsers :/
-
That's something the Chrome guys need to fix :P
-
huh, for horizontal lines, isnt there just the B_CALL _HorizCmd?
Oh wait, that requires OP1. I dont use OP1 atm lol.
Thanks for those neat routines!
-
Ah, usually using bcalls is slow, so I try to make routines that replace them. As an example, the horizontal commands can run over 10000 times in a second at 6MHz
EDIT: And I stress "usually" because some cases aren't too bad. Also, the faster horizontal command can execute over 14 000 times in a second :D
-
wow.
-
It is pretty impressive when you put it into perspective like that, right ? :D That is why sometimes the difference is negligible when using OS routines.
-
Yes, I cee. Nut if you use it a very big program it can just differ like a minute, I think
-
Nobody's given a diagonal line routine yet ;) This one comes from Missile.
You need to define (increment) somewhere. I believe you also need a getpixel that takes de = xy for input and doesn't destroy anything.
;###############################
;input: de = xy
; hl = xy
line:
push hl
push de
ld b, 2
fastLinePreLoop:
ld a, d
call xClipCheck
ld d, a
ld a, e
call yClipCheck
ld e, a
ex de, hl
djnz fastLinePreLoop
or a
sbc hl, de
add hl, de
jr nc, noSwitch
ex de, hl
noSwitch:
ld a, h
sub d
ld b, a
ld a, l
sub e
ld c, 0
jp p, positivoz
neg
ld c, 1
positivoz:
cp b
jr nc, yMajor
xMajor:
inc b
ld h, a
ld a, b
call setUpForLoop
bit 0, c
jr z, notNeggaz
inc h
notNeggaz:
ld c, d
xMajorLoop:
push bc
ld de, (increment)
add hl, de
push hl
ld d, c
ld e, h
call getPixel
ld a, c
call middlePartOfLoop
pop hl
pop bc
inc c
djnz xMajorLoop
pop de
pop hl
ret
yMajor:
bit 0, c
jr z, alreadySetUp
ex de, hl
alreadySetUp:
inc a
ld h, b
ld b, a
call setUpForLoop
bit 0, c
jr z, notNegga
inc d
notNegga:
ld c, h
ld h, d
yMajorLoop:
push bc
ld de, (increment)
add hl, de
push hl
ld e, c
ld d, h
ld b, h
call getPixel
ld a, b
call middlePartOfLoop
pop hl
pop bc
inc c
djnz yMajorLoop
pop de
pop hl
ret
middlePartOfLoop:
ld c, 128
and %00000111
jr z, alreadyDone
ld b, a
byteLoop:
srl c
djnz byteLoop
alreadyDone:
ld a, c
or (hl)
ld (hl), a
ret
setUpForLoop:
ld l, $FF
push de
push bc
call divHLbyA
pop bc
bit 0, c
call nz, negHL
ld (increment), hl
pop hl
ld d, h
ld h, l
ld l, 0
ret
yClipCheck: ;super efficient, no rets
call isANeg
cp 64
ret c
ld a, 63
xClipCheck:
call isANeg
cp 96
ret c
ld a, 95
isANeg:
or a
ret p
xor a
ret
-
Is that bresenham's?
Also, can someone who really knows what they're talking about confirm that my horizontal line routine is actually correct? :P
-
Hey thepenguin77 mind if I use that in my contest entry? (I may or may not end up actually using it.)
-
Sure, go for it. I spent a long time making it, so you might as well use it.