; C near-callable function to draw an antialiased line from
; (X0,Y0) to (X1,Y1), in mode 13h, the VGA's standard 320x200 256-color
; mode. Uses an antialiasing approach published by Xiaolin Wu in the July
; 1991 issue of Computer Graphics. Requires that the palette be set up so
; that there are NumLevels intensity levels of the desired drawing color,
; starting at color BaseColor (100% intensity) and followed by (NumLevels-1)
; levels of evenly decreasing intensity, with color (BaseColor+NumLevels-1)
; being 0% intensity of the desired drawing color (black). No clipping is
; performed in DrawWuLine. Handles a maximum of 256 intensity levels per
; antialiased color. This code is suitable for use at screen resolutions,
; with lines typically no more than 1K long; for longer lines, 32-bit error
; arithmetic must be used to avoid problems with fixed-point inaccuracy.
; Tested with TASM 4.0 by Jim Mischel 12/16/94.
;
; C near-callable as:
;   void DrawWuLine(int X0, int Y0, int X1, int Y1, int BaseColor,
;      int NumLevels, unsigned int IntensityBits);

SCREEN_WIDTH_IN_BYTES equ 320   ;# of bytes from the start of one scan line
            ; to the start of the next
SCREEN_SEGMENT   equ   0a000h   ;segment in which screen memory resides

; Parameters passed in stack frame.
parms   struc
   dw   2 dup (?) ;pushed BP and return address
X0   dw   ?   		;X coordinate of line start point
Y0   dw   ?   		;Y coordinate of line start point
X1   dw   ?   		;X coordinate of line end point
Y1   dw   ?   		;Y coordinate of line end point
BaseColor dw   ?		;color # of first color in block used for
         	 	;antialiasing, the 100% intensity version of the
         	 	;drawing color
NumLevels dw   ?  	;size of color block, with BaseColor+NumLevels-1
         		; being the 0% intensity version of the drawing color
         		; (maximum NumLevels = 256)
IntensityBits dw ?   	;log base 2 of NumLevels; the # of bits used to
         		; describe the intensity of the drawing color.
         		; 2**IntensityBits==NumLevels
         		; (maximum IntensityBits = 8)
parms   ends

.model   small
.code
; Screen dimension globals, used in main program to scale.
_ScreenWidthInPixels    dw      320
_ScreenHeightInPixels   dw      200

   .code
   public   _DrawWuLine
_DrawWuLine proc near
   push   bp   		;preserve caller's stack frame
   mov   bp,sp   	;point to local stack frame
   push   si   		;preserve C's register variables
   push   di
   push   ds   		;preserve C's default data segment
   cld      		;make string instructions increment their pointers

; Make sure the line runs top to bottom.
   mov   si,[bp].X0
   mov   ax,[bp].Y0
   cmp   ax,[bp].Y1   	;swap endpoints if necessary to ensure that
   jna   NoSwap      	; Y0 <= Y1
   xchg   [bp].Y1,ax
   mov   [bp].Y0,ax
   xchg   [bp].X1,si
   mov   [bp].X0,si
NoSwap:

; Draw the initial pixel, which is always exactly intersected by the line
; and so needs no weighting.
   mov   dx,SCREEN_SEGMENT
   mov   ds,dx      	;point DS to the screen segment
   mov   dx,SCREEN_WIDTH_IN_BYTES
   mul   dx      	;Y0 * SCREEN_WIDTH_IN_BYTES yields the offset
            		; of the start of the row start the initial
            		; pixel is on
   add   si,ax      			;point DS:SI to the initial pixel
   mov   al,byte ptr [bp].BaseColor 	;color with which to draw
   mov   [si],al      			;draw the initial pixel

   mov   bx,1      	;XDir = 1; assume DeltaX >= 0
   mov   cx,[bp].X1
   sub   cx,[bp].X0   	;DeltaX; is it >= 1?
   jns   DeltaXSet   	;yes, move left->right, all set
            		;no, move right->left
   neg   cx      	;make DeltaX positive
   neg   bx      	;XDir = -1
DeltaXSet:

; Special-case horizontal, vertical, and diagonal lines, which require no
; weighting because they go right through the center of every pixel.
   mov   dx,[bp].Y1
   sub   dx,[bp].Y0   	;DeltaY; is it 0?
   jnz   NotHorz      	;no, not horizontal
            		;yes, is horizontal, special case
   and   bx,bx      	;draw from left->right?
   jns   DoHorz      	;yes, all set
   std         		;no, draw right->left
DoHorz:
   lea   di,[bx+si]   	;point DI to next pixel to draw
   mov   ax,ds
   mov   es,ax      	;point ES:DI to next pixel to draw
   mov   al,byte ptr [bp].BaseColor ;color with which to draw
            		;CX = DeltaX at this point
   rep   stosb      	;draw the rest of the horizontal line
   cld         		;restore default direction flag
   jmp   Done      	;and we're done

   align   2
NotHorz:
   and   cx,cx      	;is DeltaX 0?
   jnz   NotVert      	;no, not a vertical line
            		;yes, is vertical, special case
   mov   al,byte ptr [bp].BaseColor ;color with which to draw
VertLoop:
   add   si,SCREEN_WIDTH_IN_BYTES ;point to next pixel to draw
   mov   [si],al      	;draw the next pixel
   dec   dx      	;--DeltaY
   jnz   VertLoop
   jmp   Done      	;and we're done

   align   2
NotVert:
   cmp   cx,dx      	;DeltaX == DeltaY?
   jnz   NotDiag      	;no, not diagonal
            		;yes, is diagonal, special case
   mov   al,byte ptr [bp].BaseColor ;color with which to draw
DiagLoop:
   lea   si,[si+SCREEN_WIDTH_IN_BYTES+bx]
            		;advance to next pixel to draw by
            		; incrementing Y and adding XDir to X
   mov   [si],al      	;draw the next pixel
   dec   dx      	;--DeltaY
   jnz   DiagLoop
   jmp   Done      	;and we're done

; Line is not horizontal, diagonal, or vertical.
   align   2
NotDiag:
; Is this an X-major or Y-major line?
   cmp   dx,cx
   jb   XMajor         	;it's X-major

; It's a Y-major line. Calculate the 16-bit fixed-point fractional part of a
; pixel that X advances each time Y advances 1 pixel, truncating the result
; to avoid overrunning the endpoint along the X axis.
   xchg   dx,cx      	;DX = DeltaX, CX = DeltaY
   sub   ax,ax      	;make DeltaX 16.16 fixed-point value in DX:AX
   div   cx      	;AX = (DeltaX << 16) / DeltaY. Won't overflow
            		; because DeltaX < DeltaY
   mov   di,cx      	;DI = DeltaY (loop count)
   sub   si,bx      	;back up the start X by 1, as explained below
   mov   dx,-1      	;initialize the line error accumulator to -1,
            		; so that it will turn over immediately and
            		; advance X to the start X. This is necessary
            		; properly to bias error sums of 0 to mean
            		; "advance next time" rather than "advance
            		; this time," so that the final error sum can
            		; never cause drawing to overrun the final X
            		; coordinate (works in conjunction with
            		; truncating ErrorAdj, to make sure X can't
            		; overrun)
   mov   cx,8         	;CL = # of bits by which to shift
   sub   cx,[bp].IntensityBits   	; ErrorAcc to get intensity level (8
               			; instead of 16 because we work only
               			; with the high byte of ErrorAcc)
   mov   ch,byte ptr [bp].NumLevels ;mask used to flip all bits in an
   dec   ch            		; intensity weighting, producing
                  		; result (1 - intensity weighting)
   mov   bp,BaseColor[bp]   	;***stack frame not available***
               			;***from now on              ***
   xchg   bp,ax         		;BP = ErrorAdj, AL = BaseColor,
               			; AH = scratch register

; Draw all remaining pixels.
YMajorLoop:
   add   dx,bp         	;calculate error for next pixel
   jnc     NoXAdvance    ;not time to step in X yet
                         ;the error accumulator turned over,
                         ;so advance the X coord
   add     si,bx         ;add XDir to the pixel pointer
NoXAdvance:
   add   si,SCREEN_WIDTH_IN_BYTES ;Y-major, so always advance Y

; The IntensityBits most significant bits of ErrorAcc give us the intensity
; weighting for this pixel, and the complement of the weighting for the
; paired pixel.
   mov   ah,dh   	;msb of ErrorAcc
   shr   ah,cl   	;Weighting = ErrorAcc >> IntensityShift;
   add   ah,al   	;BaseColor + Weighting
   mov   [si],ah   	;DrawPixel(X, Y, BaseColor + Weighting);
   mov   ah,dh   	;msb of ErrorAcc
   shr   ah,cl   	;Weighting = ErrorAcc >> IntensityShift;
   xor   ah,ch   	;Weighting ^ WeightingComplementMask
   add   ah,al   	;BaseColor + (Weighting ^ WeightingComplementMask)
   mov   [si+bx],ah 	;DrawPixel(X+XDir, Y,
; BaseColor + (Weighting ^ WeightingComplementMask));
   dec   di   		;--DeltaY
   jnz   YMajorLoop
   jmp   Done    	;we're done with this line

; It's an X-major line.
   align   2
XMajor:
; Calculate the 16-bit fixed-point fractional part of a pixel that Y advances
; each time X advances 1 pixel, truncating the result to avoid overrunning
; the endpoint along the X axis.
   sub   ax,ax      	;make DeltaY 16.16 fixed-point value in DX:AX
   div   cx      	;AX = (DeltaY << 16) / Deltax. Won't overflow
            		; because DeltaY < DeltaX
   mov   di,cx      	;DI = DeltaX (loop count)
   sub   si,SCREEN_WIDTH_IN_BYTES ;back up the start X by 1, as
            		; explained below
   mov   dx,-1      	;initialize the line error accumulator to -1,
            		; so that it will turn over immediately and
            		; advance Y to the start Y. This is necessary
            		; properly to bias error sums of 0 to mean
            		; "advance next time" rather than "advance
            		; this time," so that the final error sum can
            		; never cause drawing to overrun the final Y
            		; coordinate (works in conjunction with
            		; truncating ErrorAdj, to make sure Y can't
            		; overrun)
   mov   cx,8         	;CL = # of bits by which to shift
   sub   cx,[bp].IntensityBits   	; ErrorAcc to get intensity level (8
               			; instead of 16 because we work only
               			; with the high byte of ErrorAcc)
   mov   ch,byte ptr [bp].NumLevels ;mask used to flip all bits in an
   dec   ch            		; intensity weighting, producing
                  		; result (1 - intensity weighting)
   mov   bp,BaseColor[bp]   	;***stack frame not available***
               			;***from now on              ***
   xchg   bp,ax         		;BP = ErrorAdj, AL = BaseColor,
               			; AH = scratch register
; Draw all remaining pixels.
XMajorLoop:
   add   dx,bp         		;calculate error for next pixel
   jnc   NoYAdvance      	;not time to step in Y yet
                                	;the error accumulator turned over,
                               	; so advance the Y coord
   add     si,SCREEN_WIDTH_IN_BYTES ;advance Y
NoYAdvance:
   add   si,bx      	;X-major, so add XDir to the pixel pointer

; The IntensityBits most significant bits of ErrorAcc give us the intensity
; weighting for this pixel, and the complement of the weighting for the
; paired pixel.
   mov   ah,dh   		;msb of ErrorAcc
   shr   ah,cl   		;Weighting = ErrorAcc >> IntensityShift;
   add   ah,al   		;BaseColor + Weighting
   mov   [si],ah   		;DrawPixel(X, Y, BaseColor + Weighting);
   mov   ah,dh   		;msb of ErrorAcc
   shr   ah,cl   		;Weighting = ErrorAcc >> IntensityShift;
   xor   ah,ch   		;Weighting ^ WeightingComplementMask
   add   ah,al   		;BaseColor + (Weighting ^ WeightingComplementMask)
   mov   [si+SCREEN_WIDTH_IN_BYTES],ah
 ;DrawPixel(X, Y+SCREEN_WIDTH_IN_BYTES,
 ; BaseColor + (Weighting ^ WeightingComplementMask));
   dec   di   ;--DeltaX
   jnz   XMajorLoop

Done:              	;we're done with this line
   pop   ds   		;restore C's default data segment
   pop   di   		;restore C's register variables
   pop   si
   pop   bp   		;restore caller's stack frame
   ret      		;done
_DrawWuLine endp
   end

