; Fast assembler implementation of Bresenham's line drawing algorithm ; for the EGA and VGA. Works in modes 0Eh, 0Fh, 10h, and 12h. ; C near-callable. ; Bit mask accumulation technique when |DeltaX| >= |DeltaY| ; suggested by Jim Mackraz. ; ; Assembled with MASM ; ; By Michael Abrash ; ;**************************************************************** ; C-compatible line-drawing entry point at _EVGALine. * ; Near C-callable as: * ; EVGALine(X0, Y0, X1, Y1, Color); * ;**************************************************************** ; .model small .code ; ; Equates. ; EVGA_SCREEN_WIDTH_IN_BYTES equ 80 ;memory offset from start of ; one row to start of next ; in display memory EVGA_SCREEN_SEGMENT equ 0a000h ;display memory segment GC_INDEX equ 3ceh ;Graphics Controller ; Index register port SET_RESET_INDEX equ 0 ;indexes of needed ENABLE_SET_RESET_INDEX equ 1 ; Graphics Controller BIT_MASK_INDEX equ 8 ; registers ; ; Stack frame. ; EVGALineParms struc dw ? ;pushed BP dw ? ;pushed return address (make double ; word for far call) X0 dw ? ;starting X coordinate of line Y0 dw ? ;starting Y coordinate of line X1 dw ? ;ending X coordinate of line Y1 dw ? ;ending Y coordinate of line Color db ? ;color of line db ? ;dummy to pad to word size EVGALineParms ends ;**************************************************************** ; Line drawing macros. * ;**************************************************************** ; ; Macro to loop through length of line, drawing each pixel in turn. ; Used for case of |DeltaX| >= |DeltaY|. ; Input: ; MOVE_LEFT: 1 if DeltaX < 0, 0 else ; AL: pixel mask for initial pixel ; BX: |DeltaX| ; DX: address of GC data register, with index register set to ; index of Bit Mask register ; SI: DeltaY ; ES:DI: display memory address of byte containing initial ; pixel ; LINE1 macro MOVE_LEFT local LineLoop, MoveXCoord, NextPixel, Line1End local MoveToNextByte, ResetBitMaskAccumulator mov cx,bx ;# of pixels in line jcxz Line1End ;done if there are no more pixels ; (there's always at least the one pixel ; at the start location) shl si,1 ;DeltaY * 2 mov bp,si ;error term sub bp,bx ;error term starts at DeltaY * 2 - DeltaX shl bx,1 ;DeltaX * 2 sub si,bx ;DeltaY * 2 - DeltaX * 2 (used in loop) add bx,si ;DeltaY * 2 (used in loop) mov ah,al ;set aside pixel mask for initial pixel ; with AL (the pixel mask accumulator) set ; for the initial pixel LineLoop: ; ; See if it's time to advance the Y coordinate yet. ; and bp,bp ;see if error term is negative js MoveXCoord ;yes, stay at the same Y coordinate ; ; Advance the Y coordinate, first writing all pixels in the current ; byte, then move the pixel mask either left or right, depending ; on MOVE_LEFT. ; out dx,al ;set up bit mask for pixels in this byte xchg byte ptr [di],al ;load latches and write pixels, with bit mask ; preserving other latched bits. Because ; set/reset is enabled for all planes, the ; value written actually doesn't matter add di,EVGA_SCREEN_WIDTH_IN_BYTES ;increment Y coordinate add bp,si ;adjust error term back down ; ; Move pixel mask one pixel (either right or left, depending ; on MOVE_LEFT), adjusting display memory address when pixel mask wraps. ; if MOVE_LEFT rol ah,1 ;move pixel mask 1 pixel to the left else ror ah,1 ;move pixel mask 1 pixel to the right endif jnc ResetBitMaskAccumulator ;didn't wrap to next byte jmp short MoveToNextByte ;did wrap to next byte ; ; Move pixel mask one pixel (either right or left, depending ; on MOVE_LEFT), adjusting display memory address and writing pixels ; in this byte when pixel mask wraps. ; MoveXCoord: add bp,bx ;increment error term & keep same if MOVE_LEFT rol ah,1 ;move pixel mask 1 pixel to the left else ror ah,1 ;move pixel mask 1 pixel to the right endif jnc NextPixel ;if still in same byte, no need to ; modify display memory yet out dx,al ;set up bit mask for pixels in this byte. xchg byte ptr [di],al ;load latches and write pixels, with bit mask ; preserving other latched bits. Because ; set/reset is enabled for all planes, the ; value written actually doesn't matter MoveToNextByte: if MOVE_LEFT dec di ;next pixel is in byte to left else inc di ;next pixel is in byte to right endif ResetBitMaskAccumulator: sub al,al ;reset pixel mask accumulator NextPixel: or al,ah ;add the next pixel to the pixel mask ; accumulator loop LineLoop ; ; Write the pixels in the final byte. ; Line1End: out dx,al ;set up bit mask for pixels in this byte. xchg byte ptr [di],al ;load latches and write pixels, with bit mask ; preserving other latched bits. Because ; set/reset is enabled for all planes, the ; value written actually doesn't matter endm ; ; Macro to loop through length of line, drawing each pixel in turn. ; Used for case of DeltaX < DeltaY. ; Input: ; MOVE_LEFT: 1 if DeltaX < 0, 0 else ; AL: pixel mask for initial pixel ; BX: |DeltaX| ; DX: address of GC data register, with index register set to ; index of Bit Mask register ; SI: DeltaY ; ES:DI: display memory address of byte containing initial ; pixel ; LINE2 macro MOVE_LEFT local LineLoop, MoveYCoord, ETermAction, Line2End mov cx,si ;# of pixels in line jcxz Line2End ;done if there are no more pixels shl bx,1 ;DeltaX * 2 mov bp,bx ;error term sub bp,si ;error term starts at DeltaX * 2 - DeltaY shl si,1 ;DeltaY * 2 sub bx,si ;DeltaX * 2 - DeltaY * 2 (used in loop) add si,bx ;DeltaX * 2 (used in loop) ; ; Set up initial bit mask & write initial pixel. ; out dx,al xchg byte ptr [di],ah ;load latches and write pixel, with bit mask ; preserving other latched bits. Because ; set/reset is enabled for all planes, the ; value written actually doesn't matter LineLoop: ; ; See if it's time to advance the X coordinate yet. ; and bp,bp ;see if error term is negative jns ETermAction ;no, advance X coordinate add bp,si ;increment error term & keep same jmp short MoveYCoord; X coordinate ETermAction: ; ; Move pixel mask one pixel (either right or left, depending ; on MOVE_LEFT), adjusting display memory address when pixel mask wraps. ; if MOVE_LEFT rol al,1 sbb di,0 else ror al,1 adc di,0 endif out dx,al ;set new bit mask add bp,bx ;adjust error term back down ; ; Advance Y coordinate. ; MoveYCoord: add di,EVGA_SCREEN_WIDTH_IN_BYTES ; ; Write the next pixel. ; xchg byte ptr [di],ah ;load latches and write pixel, with bit mask ; preserving other latched bits. Because ; set/reset is enabled for all planes, the ; value written actually doesn't matter ; loop LineLoop Line2End: endm ;**************************************************************** ; Line drawing routine. * ;**************************************************************** public _EVGALine _EVGALine proc near push bp mov bp,sp push si ;preserve register variables push di push ds ; ; Point DS to display memory. ; mov ax,EVGA_SCREEN_SEGMENT mov ds,ax ; ; Set the Set/Reset and Set/Reset Enable registers for ; the selected color. ; mov dx,GC_INDEX mov al,SET_RESET_INDEX out dx,al inc dx mov al,[bp+Color] out dx,al dec dx mov al,ENABLE_SET_RESET_INDEX out dx,al inc dx mov al,0ffh out dx,al ; ; Get DeltaY. ; mov si,[bp+Y1] ;line Y start mov ax,[bp+Y0] ;line Y end, used later in ;calculating the start address sub si,ax ;calculate DeltaY jns CalcStartAddress;if positive, we're set ; ; DeltaY is negative -- swap coordinates so we're always working ; with a positive DeltaY. ; mov ax,[bp+Y1] ;set line start to Y1, for use ; in calculating the start address mov dx,[bp+X0] xchg dx,[bp+X1] mov [bp+X0],dx ;swap X coordinates neg si ;convert to positive DeltaY ; ; Calculate the starting address in display memory of the line. ; Hardwired for a screen width of 80 bytes. ; CalcStartAddress: shl ax,1 ;Y0 * 2 ;Y0 is already in AX shl ax,1 ;Y0 * 4 shl ax,1 ;Y0 * 8 shl ax,1 ;Y0 * 16 mov di,ax shl ax,1 ;Y0 * 32 shl ax,1 ;Y0 * 64 add di,ax ;Y0 * 80 mov dx,[bp+X0] mov cl,dl ;set aside lower 3 bits of column for and cl,7 ; pixel masking shr dx,1 shr dx,1 shr dx,1 ;get byte address of column (X0/8) add di,dx ;offset of line start in display segment ; ; Set up GC Index register to point to the Bit Mask register. ; mov dx,GC_INDEX mov al,BIT_MASK_INDEX out dx,al inc dx ;leave DX pointing to the GC Data register ; ; Set up pixel mask (in-byte pixel address). ; mov al,80h shr al,cl ; ; Calculate DeltaX. ; mov bx,[bp+X1] sub bx,[bp+X0] ; ; Handle correct one of four octants. ; js NegDeltaX cmp bx,si jb Octant1 ; ; DeltaX >= DeltaY >= 0. ; LINE1 0 jmp EVGALineDone ; ; DeltaY > DeltaX >= 0. ; Octant1: LINE2 0 jmp short EVGALineDone ; NegDeltaX: neg bx ;|DeltaX| cmp bx,si jb Octant2 ; ; |DeltaX| >= DeltaY and DeltaX < 0. ; LINE1 1 jmp short EVGALineDone ; ; |DeltaX| < DeltaY and DeltaX < 0. ; Octant2: LINE2 1 ; EVGALineDone: ; ; Restore EVGA state. ; mov al,0ffh out dx,al ;set Bit Mask register to 0ffh dec dx mov al,ENABLE_SET_RESET_INDEX out dx,al inc dx sub al,al out dx,al ;set Enable Set/Reset register to 0 ; pop ds pop di pop si pop bp ret _EVGALine endp end