; 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