_GRAPHICS PROGRAMMING_ by Michael Abrash [LISTING ONE] ; Fast run length slice line drawing implementation for mode 0x13, the VGA's ; 320x200 256-color mode. ; Draws a line between the specified endpoints in color Color. ; C near-callable as: ; void LineDraw(int XStart, int YStart, int XEnd, int YEnd, int Color) ; Tested with TASM 3.0. SCREEN_WIDTH equ 320 SCREEN_SEGMENT equ 0a000h .model small .code ; Parameters to call. parms struc dw ? ;pushed BP dw ? ;pushed return address XStart dw ? ;X start coordinate of line YStart dw ? ;Y start coordinate of line XEnd dw ? ;X end coordinate of line YEnd dw ? ;Y end coordinate of line Color db ? ;color in which to draw line db ? ;dummy byte because Color is really a word parms ends ; Local variables. AdjUp equ -2 ;error term adjust up on each advance AdjDown equ -4 ;error term adjust down when error term turns over WholeStep equ -6 ;minimum run length XAdvance equ -8 ;1 or -1, for direction in which X advances LOCAL_SIZE equ 8 public _LineDraw _LineDraw proc near cld push bp ;preserve caller's stack frame mov bp,sp ;point to our stack frame sub sp,LOCAL_SIZE ;allocate space for local variables push si ;preserve C register variables push di push ds ;preserve caller's DS ; We'll draw top to bottom, to reduce the number of cases we have to handle, ; and to make lines between the same endpoints always draw the same pixels. mov ax,[bp].YStart cmp ax,[bp].YEnd jle LineIsTopToBottom xchg [bp].YEnd,ax ;swap endpoints mov [bp].YStart,ax mov bx,[bp].XStart xchg [bp].XEnd,bx mov [bp].XStart,bx LineIsTopToBottom: ; Point DI to the first pixel to draw. mov dx,SCREEN_WIDTH mul dx ;YStart * SCREEN_WIDTH mov si,[bp].XStart mov di,si add di,ax ;DI = YStart * SCREEN_WIDTH + XStart ; = offset of initial pixel ; Figure out how far we're going vertically (guaranteed to be positive). mov cx,[bp].YEnd sub cx,[bp].YStart ;CX = YDelta ; Figure out whether we're going left or right, and how far we're going ; horizontally. In the process, special-case vertical lines, for speed and ; to avoid nasty boundary conditions and division by 0. mov dx,[bp].XEnd sub dx,si ;XDelta jnz NotVerticalLine ;XDelta == 0 means vertical line ;it is a vertical line ;yes, special case vertical line mov ax,SCREEN_SEGMENT mov ds,ax ;point DS:DI to the first byte to draw mov al,[bp].Color VLoop: mov [di],al add di,SCREEN_WIDTH dec cx jns VLoop jmp Done ; Special-case code for horizontal lines. align 2 IsHorizontalLine: mov ax,SCREEN_SEGMENT mov es,ax ;point ES:DI to the first byte to draw mov al,[bp].Color mov ah,al ;duplicate in high byte for word access and bx,bx ;left to right? jns DirSet ;yes sub di,dx ;currently right to left, point to left end so we ; can go left to right (avoids unpleasantness with ; right to left REP STOSW) DirSet: mov cx,dx inc cx ;# of pixels to draw shr cx,1 ;# of words to draw rep stosw ;do as many words as possible adc cx,cx rep stosb ;do the odd byte, if there is one jmp Done ; Special-case code for diagonal lines. align 2 IsDiagonalLine: mov ax,SCREEN_SEGMENT mov ds,ax ;point DS:DI to the first byte to draw mov al,[bp].Color add bx,SCREEN_WIDTH ;advance distance from one pixel to next DLoop: mov [di],al add di,bx dec cx jns DLoop jmp Done align 2 NotVerticalLine: mov bx,1 ;assume left to right, so XAdvance = 1 ;***leaves flags unchanged*** jns LeftToRight ;left to right, all set neg bx ;right to left, so XAdvance = -1 neg dx ;|XDelta| LeftToRight: ; Special-case horizontal lines. and cx,cx ;YDelta == 0? jz IsHorizontalLine ;yes ; Special-case diagonal lines. cmp cx,dx ;YDelta == XDelta? jz IsDiagonalLine ;yes ; Determine whether the line is X or Y major, and handle accordingly. cmp dx,cx jae XMajor jmp YMajor ; X-major (more horizontal than vertical) line. align 2 XMajor: mov ax,SCREEN_SEGMENT mov es,ax ;point ES:DI to the first byte to draw and bx,bx ;left to right? jns DFSet ;yes, CLD is already set std ;right to left, so draw backwards DFSet: mov ax,dx ;XDelta sub dx,dx ;prepare for division div cx ;AX = XDelta/YDelta ; (minimum # of pixels in a run in this line) ;DX = XDelta % YDelta mov bx,dx ;error term adjust each time Y steps by 1; add bx,bx ; used to tell when one extra pixel should be mov [bp].AdjUp,bx ; drawn as part of a run, to account for ; fractional steps along the X axis per ; 1-pixel steps along Y mov si,cx ;error term adjust when the error term turns add si,si ; over, used to factor out the X step made at mov [bp].AdjDown,si ; that time ; Initial error term; reflects an initial step of 0.5 along the Y axis. sub dx,si ;(XDelta % YDelta) - (YDelta * 2) ;DX = initial error term ; The initial and last runs are partial, because Y advances only 0.5 for ; these runs, rather than 1. Divide one full run, plus the initial pixel, ; between the initial and last runs. mov si,cx ;SI = YDelta mov cx,ax ;whole step (minimum run length) shr cx,1 inc cx ;initial pixel count = (whole step / 2) + 1; ; (may be adjusted later). This is also the ; final run pixel count push cx ;remember final run pixel count for later ; If the basic run length is even and there's no fractional advance, we have ; one pixel that could go to either the initial or last partial run, which ; we'll arbitrarily allocate to the last run. ; If there is an odd number of pixels per run, we have one pixel that can't ; be allocated to either the initial or last partial run, so we'll add 0.5 to ; the error term so this pixel will be handled by the normal full-run loop. add dx,si ;assume odd length, add YDelta to error term ; (add 0.5 of a pixel to the error term) test al,1 ;is run length even? jnz XMajorAdjustDone ;no, already did work for odd case, all set sub dx,si ;length is even, undo odd stuff we just did and bx,bx ;is the adjust up equal to 0? jnz XMajorAdjustDone ;no (don't need to check for odd length, ; because of the above test) dec cx ;both conditions met; make initial run 1 ; shorter XMajorAdjustDone: mov [bp].WholeStep,ax ;whole step (minimum run length) mov al,[bp].Color ;AL = drawing color ; Draw the first, partial run of pixels. rep stosb ;draw the final run add di,SCREEN_WIDTH ;advance along the minor axis (Y) ; Draw all full runs. cmp si,1 ;are there more than 2 scans, so there are ; some full runs? (SI = # scans - 1) jna XMajorDrawLast ;no, no full runs dec dx ;adjust error term by -1 so we can use ; carry test shr si,1 ;convert from scan to scan-pair count jnc XMajorFullRunsOddEntry ;if there is an odd number of scans, ; do the odd scan now XMajorFullRunsLoop: mov cx,[bp].WholeStep ;run is at least this long add dx,bx ;advance the error term and add an extra jnc XMajorNoExtra ; pixel if the error term so indicates inc cx ;one extra pixel in run sub dx,[bp].AdjDown ;reset the error term XMajorNoExtra: rep stosb ;draw this scan line's run add di,SCREEN_WIDTH ;advance along the minor axis (Y) XMajorFullRunsOddEntry: ;enter loop here if there is an odd number ; of full runs mov cx,[bp].WholeStep ;run is at least this long add dx,bx ;advance the error term and add an extra jnc XMajorNoExtra2 ; pixel if the error term so indicates inc cx ;one extra pixel in run sub dx,[bp].AdjDown ;reset the error term XMajorNoExtra2: rep stosb ;draw this scan line's run add di,SCREEN_WIDTH ;advance along the minor axis (Y) dec si jnz XMajorFullRunsLoop ; Draw the final run of pixels. XMajorDrawLast: pop cx ;get back the final run pixel length rep stosb ;draw the final run cld ;restore normal direction flag jmp Done ; Y-major (more vertical than horizontal) line. align 2 YMajor: mov [bp].XAdvance,bx ;remember which way X advances mov ax,SCREEN_SEGMENT mov ds,ax ;point DS:DI to the first byte to draw mov ax,cx ;YDelta mov cx,dx ;XDelta sub dx,dx ;prepare for division div cx ;AX = YDelta/XDelta ; (minimum # of pixels in a run in this line) ;DX = YDelta % XDelta mov bx,dx ;error term adjust each time X steps by 1; add bx,bx ; used to tell when one extra pixel should be mov [bp].AdjUp,bx ; drawn as part of a run, to account for ; fractional steps along the Y axis per ; 1-pixel steps along X mov si,cx ;error term adjust when the error term turns add si,si ; over, used to factor out the Y step made at mov [bp].AdjDown,si ; that time ; Initial error term; reflects an initial step of 0.5 along the X axis. sub dx,si ;(YDelta % XDelta) - (XDelta * 2) ;DX = initial error term ; The initial and last runs are partial, because X advances only 0.5 for ; these runs, rather than 1. Divide one full run, plus the initial pixel, ; between the initial and last runs. mov si,cx ;SI = XDelta mov cx,ax ;whole step (minimum run length) shr cx,1 inc cx ;initial pixel count = (whole step / 2) + 1; ; (may be adjusted later) push cx ;remember final run pixel count for later ; If the basic run length is even and there's no fractional advance, we have ; one pixel that could go to either the initial or last partial run, which ; we'll arbitrarily allocate to the last run. ; If there is an odd number of pixels per run, we have one pixel that can't ; be allocated to either the initial or last partial run, so we'll add 0.5 to ; the error term so this pixel will be handled by the normal full-run loop. add dx,si ;assume odd length, add XDelta to error term test al,1 ;is run length even? jnz YMajorAdjustDone ;no, already did work for odd case, all set sub dx,si ;length is even, undo odd stuff we just did and bx,bx ;is the adjust up equal to 0? jnz YMajorAdjustDone ;no (don't need to check for odd length, ; because of the above test) dec cx ;both conditions met; make initial run 1 ; shorter YMajorAdjustDone: mov [bp].WholeStep,ax ;whole step (minimum run length) mov al,[bp].Color ;AL = drawing color mov bx,[bp].XAdvance ;which way X advances ; Draw the first, partial run of pixels. YMajorFirstLoop: mov [di],al ;draw the pixel add di,SCREEN_WIDTH ;advance along the major axis (Y) dec cx jnz YMajorFirstLoop add di,bx ;advance along the minor axis (X) ; Draw all full runs. cmp si,1 ;# of full runs. Are there more than 2 ; columns, so there are some full runs? ; (SI = # columns - 1) jna YMajorDrawLast ;no, no full runs dec dx ;adjust error term by -1 so we can use ; carry test shr si,1 ;convert from column to column-pair count jnc YMajorFullRunsOddEntry ;if there is an odd number of ; columns, do the odd column now YMajorFullRunsLoop: mov cx,[bp].WholeStep ;run is at least this long add dx,[bp].AdjUp ;advance the error term and add an extra jnc YMajorNoExtra ; pixel if the error term so indicates inc cx ;one extra pixel in run sub dx,[bp].AdjDown ;reset the error term YMajorNoExtra: ;draw the run YMajorRunLoop: mov [di],al ;draw the pixel add di,SCREEN_WIDTH ;advance along the major axis (Y) dec cx jnz YMajorRunLoop add di,bx ;advance along the minor axis (X) YMajorFullRunsOddEntry: ;enter loop here if there is an odd number ; of full runs mov cx,[bp].WholeStep ;run is at least this long add dx,[bp].AdjUp ;advance the error term and add an extra jnc YMajorNoExtra2 ; pixel if the error term so indicates inc cx ;one extra pixel in run sub dx,[bp].AdjDown ;reset the error term YMajorNoExtra2: ;draw the run YMajorRunLoop2: mov [di],al ;draw the pixel add di,SCREEN_WIDTH ;advance along the major axis (Y) dec cx jnz YMajorRunLoop2 add di,bx ;advance along the minor axis (X) dec si jnz YMajorFullRunsLoop ; Draw the final run of pixels. YMajorDrawLast: pop cx ;get back the final run pixel length YMajorLastLoop: mov [di],al ;draw the pixel add di,SCREEN_WIDTH ;advance along the major axis (Y) dec cx jnz YMajorLastLoop Done: pop ds ;restore caller's DS pop di pop si ;restore C register variables mov sp,bp ;deallocate local variables pop bp ;restore caller's stack frame ret _LineDraw endp end