269 lines
9.9 KiB
NASM
269 lines
9.9 KiB
NASM
; 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.
|
|
;
|
|
; 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
|