$NOLIST DEBUG

NAME  Grafix

CGROUP GROUP CODE

PUBLIC GfxSetPixel, GfxClrPixel, GfxInvertPixel, GfxTestPixel
PUBLIC GfxDrawLine, GfxEraseLine, GfxInvertLine

PUBLIC InitWinParms, StartPosition, RestoreEmsSlot

EXTRN  emsBase:WORD

zero        EQU  0
evenMask    EQU  0FFFEH         ; make a word even
true        EQU  0FFFFH         
screenSeg   EQU  0B800H

PCFormat    EQU   0
GRIDFormat  EQU   1
$EJ
CODE SEGMENT PUBLIC 'CODE'
ASSUME  CS:CGROUP

;**************************************************
;*
;*    GfxSetpixel(screen, bytesPerLine, winHeight, x, y: Word)
;*
;* This will set the pixel at X = horizontal
;* and Y = vertical.
;* (0,0) is the upper lefthand corner.
;*
;* REGISTERS CHANGED: AX, BX, CX, DX, ES, DI, SI
;*
;**************************************************

;  Params for GfxSetPixel, GfxClrPixel, GfxInvertPixel
;  and GfxTestPixel

params STRUC
  oldDS        DW  ?
  oldBp        DW  ?
;-----------
  returnIP     DW  ?
  returnCS     DW  ?
;-----------
  y            DW  ?
  x            DW  ?
  winHeight    DW  ?
  bytesPerLine DW  ?
  screen       DW  ?
params ENDS

loc EQU [BP]
paramBytes EQU 10

GfxSetPixel PROC FAR
  PUSH  BP
  PUSH  DS
  MOV   BP, SP

  CALL  SetUpPixel
  MOV   AX, SI
  MOV   SI, 0080H               ; MSbit in LSByte
  ROR   SI, CL                  ; rotate to position

  OR    DS:[DI], SI             ; set the pixel

  INC   AX                      ; if old ems slot is -1
  JZ    GfxSetPixelRet          ; then don't restore

  DEC   AX                      ; old ems slot
  CALL  RestoreEmsSlot

GfxSetPixelRet:
  POP   DS
  POP   BP
  RET   paramBytes
GfxSetPixel ENDP



;***************************************************
;*
;*    GfxClrPixel(screen, bytesPerLine, winHeight, x, y : word)
;*
;* This is exactly the same as SetPixel except
;* for the last two statements
;*
;*****************************************************

GfxClrPixel PROC FAR
  PUSH  BP
  PUSH  DS
  MOV   BP, SP

  CALL  SetUpPixel
  MOV   AX, SI
  MOV   SI, 0FF7FH
  ROR   SI, CL                ; rotate to position

  AND   DS:[DI], SI

  INC   AX                    ; if old ems slot is -1
  JZ    GfxClrPixelRet        ; the don't restore

  DEC   AX                    ; old ems slot
  CALL  RestoreEmsSlot

GfxClrPixelRet:
  POP   DS
  POP   BP
  RET   paramBytes
GfxClrPixel ENDP
$EJ

;***************************************************
;*
;*    GfxInvertPixel(screen, bytesPerLine, winHeight, x, y : word)
;*
;* This is exactly the same as SetPixel except
;* for the last two statements
;*
;*****************************************************

GfxInvertPixel PROC FAR
  PUSH  BP
  PUSH  DS
  MOV   BP, SP

  CALL  SetUpPixel
  MOV   AX, SI
  MOV   SI, 0080H
  ROR   SI, CL               ; rotate to position

  XOR   DS:[DI], SI

  INC   AX                   ; if old ems slot is -1
  JZ    GfxInvertPixelRet    ; then don't restore

  DEC   AX                   ; old ems slot
  CALL  RestoreEmsSlot

GfxInvertPixelRet:
  POP   DS
  POP   BP
  RET   paramBytes
GfxInvertPixel ENDP


;***************************************************
;*
;*    GfxTestPixel(screen, bytesPerLine, winHeight, x, y) : word
;*
;* This is exactly the same as SetPixel except
;* for the last two statements
;*
;*****************************************************

GfxTestPixel PROC FAR
  PUSH  BP
  PUSH  DS
  MOV   BP, SP

  CALL  SetUpPixel
  MOV   BX, SI          ; save ems slot in BX
  MOV   SI, 0080H
  ROR   SI, CL          ; rotate to position

  MOV   AX, DS:[DI]     ;Get the word
  AND   AX, SI          ;Mask out all other bits
  JZ    NotSet          ;do nothing if not set

  MOV   AX, true        ;Set to true if bit is set.

NotSet:
  INC   BX              ; if old ems slot = -1
  JZ    GfxTestPixelRet ; then don't restore

  PUSH  AX
  DEC   BX              ; ems slot
  XCHG  AX, BX          ; passed in AX
  CALL  RestoreEmsSlot  ; this call preserves BX
  POP   AX

GfxTestPixelRet:
  POP   DS
  POP   BP
  RET   paramBytes
GfxTestPixel ENDP
$EJ

;**************************************************
;*
;*    SetUpPixel
;*
;*  entry: 
;*          nothing
;*
;*  Does the setup for Set- and Clr- Pixel.
;*
;*  exit:  CL = rotate amount
;*         DI ^ word in display
;*         DS ^ start of display buffer
;*         SI = old ems slot (or none)
;*
;*************************************************

SetUpPixel PROC  NEAR
  MOV   AX, loc.screen
  MOV   DX, loc.bytesPerLine
  MOV   DI, loc.winHeight

  CALL  InitWinParms
  MOV   DS, AX

  MOV   AX, loc.y
  MOV   BX, loc.x

  CALL  StartPosition
  RET
SetUpPixel ENDP

  PURGE params
  PURGE oldBP
  PURGE oldDS
  PURGE returnIP
  PURGE returnCS
  PURGE x
  PURGE y
  PURGE bytesPerLine
  PURGE screen
  PURGE loc
  PURGE paramBytes
  PURGE winHeight
$EJ

;*********************************************************
;*
;*    GfxDrawLine(screen, bytesPerLine, winHeight, x1, y1, x2, y2)
;*
;* This will draw a line between the two points (x1, y1) and
;* (x2, y2).  It uses the DDA algorithm.  The act of
;* drawing a line is split into two cases for efficiency.
;*
;*  case AB: dx >= dy
;*  case CD: dx < dy
;*
;* During the inner loop of each case, the registers mean:
;*
;*  AX = dx            BX = dy
;*  CX = loop counter  DX = temp
;*  SI = mask          DI ^ display buffer
;*  BP = +- wordWidth
;*
;********************************************************

;  Params for GfxDrawLine, GfxEraseLine, GfxInvertLine

params STRUC
  dummy        DB  ?
  oddlineFlag  DB  ?
;-----------
  oldDS        DW  ?
  oldBp        DW  ?
;-----------
  returnIP     DW  ?
  returnCS     DW  ?
;-----------
  y2           DW  ?
  x2           DW  ?
  y1           DW  ?
  x1           DW  ?
  winHeight    DW  ?
  bytesPerLine DW  ?
  screen       DW  ?
params ENDS
localBytes EQU 2
loc EQU [BP-localBytes]
paramBytes EQU 14

GfxDrawLine PROC FAR
  PUSH  BP
  PUSH  DS
  MOV   BP, SP
  SUB   SP,localBytes

  MOV   DX, loc.y2          ; DX = y2
  MOV   CX, loc.x2          ; CX = x2
  MOV   BX, loc.y1          ; BX = y1
  MOV   AX, loc.x1          ; AX = x1

  CMP   AX, CX               ; x1 <= x2 ?
  JLE   Line10
  XCHG  AX, CX
  XCHG  BX, DX               ; now x1 <= x2
Line10:

  SUB   CX, AX               ; CX = dx ( >= 0)
  SUB   DX, BX               ; DX = dy
  CALL  AInitLine
  PUSH  SI                   ; save old ems slot
  MOV   SI, 0080H            ; SI = original mask
  ROR   SI, CL               ; rotate to starting pos
  OR    DS:[DI],SI
  CMP   AX, BX               ; dx < dy ?
  JL    CaseCD               ; yes -> case CD

CaseAB:            ; case AB
  MOV   CX, AX               ; loop 'dx' times
  MOV   DX, AX
  CMP   DX, 1
  JLE   NoShift
  SAR   DX, 1
NoShift:
  NEG   DX                   ; DX = -dx/2; DX > 0!!
  JCXZ  LoopDone

LoopOnAB:
  ADD   DX, BX               ; temp += dy
  JS    A10
  SUB   DX, AX               ; temp -= dx
;
  XOR   loc.oddlineFlag,1
  JNZ   NextLineOdd
  SUB   DI, loc.winHeight
  CMP   loc.bytesPerLine,0
  JL    A10
AddBytesPerLine:
  ADD   DI,loc.bytesPerLine
  JMP   A10
NextLineOdd:
  ADD    DI,loc.WinHeight
  CMP    loc.bytesPerLine,0
  JL     AddBytesPerLine
A10:

  ROR   SI, 1                ; x += 1
;  ROR   SI, 1                ; x += 1
  TEST  SI,80H
  JZ    SameX
  ADD   DI, 2                ; DI ^ next word
SameX:
  OR    DS:[DI], SI             ; set the bit
  LOOP  LoopOnAB
  JMP   LoopDone

CaseCD:            ; case CD
  MOV   CX, BX               ; loop 'dy' times
  MOV   DX, BX
  CMP   DX, 1
  JLE   NoShift2
  SAR   DX, 1
NoShift2:
  NEG   DX                   ; DX = -dy / 2
  JCXZ  LoopDone

LoopOnCD:
  ADD   DX, AX               ; temp += dx
  JS    C10
  SUB   DX, BX               ; temp -= dy
  ROR   SI, 1                ; x += 1
;  ROR   SI, 1                ; x += 1
  TEST  SI,80H
  JZ    C10
  ADD   DI, 2                ; DI ^ next word

C10:
  XOR   loc.oddlineFlag,1
  JNZ   NextLineOdd2
  SUB   DI, loc.winHeight
  CMP   loc.bytesPerLine,0
  JL    C11
AddBytesPerLine2:
  ADD   DI,loc.bytesPerLine
  JMP   C11
NextLineOdd2:
  ADD    DI,loc.WinHeight
  CMP    loc.bytesPerLine,0
  JL     AddBytesPerLine2
C11:
  OR    DS:[DI], SI             ; set the bit
  LOOP  LoopOnCD

LoopDone:
  POP   AX
  INC   AX
  JZ    GfxDrawLineRet

  DEC   AX
  CALL  RestoreEmsSlot

GfxDrawLineRet:
  MOV   SP,BP
  POP   DS
  POP   BP
  RET   paramBytes
GfxDrawLine ENDP

$EJ
;******************************************
;*                                        *
;*  GfxEraseLine(screen, bytesPerLine,    *
;*               windowHeight,            *
;*               x1, y1, x2, y2)          *
;*                                        *
;******************************************

GfxEraseLine PROC FAR
  PUSH  BP
  PUSH  DS
  MOV   BP, SP
  SUB   SP,localBytes
  MOV   DX, loc.y2          ; DX = y2
  MOV   CX, loc.x2          ; CX = x2
  MOV   BX, loc.y1          ; BX = y1
  MOV   AX, loc.x1          ; AX = x1

  CMP   AX, CX               ; x1 <= x2 ?
  JLE   ELine10
  XCHG  AX, CX
  XCHG  BX, DX               ; now x1 < x2

ELine10:
  SUB   CX, AX               ; CX = dx ( >= 0 )
  SUB   DX, BX               ; DX = dy
  CALL  AInitLine
  PUSH  SI
  MOV   SI, 0FF7FH           ; SI = original mask
  ROR   SI, CL               ; rotate to starting pos
  AND   DS:[DI], SI
  CMP   AX, BX               ; dx < dy ?
  JL    ECaseCD              ; yes -> case CD

ECaseAB:           ; case AB
  MOV   CX, AX               ; loop 'dx' times
  MOV   DX, AX
  CMP   DX, 1
  JLE   ENoShift
  SHR   DX, 1
ENoShift:
  NEG   DX                   ; DX = - dx / 2
  JCXZ  EallDone

ELoopOnAB:
  ADD   DX, BX               ; temp += dy
  JS    ESameY               ; next y value ?
  SUB   DX, AX               ; temp -= dx
;
  XOR   loc.oddlineFlag,1
  JNZ   NextLineOdd3
  SUB   DI, loc.winHeight
  CMP   loc.bytesPerLine,0
  JL    ESameY
AddBytesPerLine3:
  ADD   DI,loc.bytesPerLine
  JMP   ESameY
NextLineOdd3:
  ADD    DI,loc.WinHeight
  CMP    loc.bytesPerLine,0
  JL     AddBytesPerLine3

ESameY:
  ROR   SI, 1                ; x += 1
;  ROR   SI, 1                ; x += 1
  NOT   SI
  TEST  SI, 80H
  JZ    ESameX               ; on to next word?
  ADD   DI, 2                ; yes
ESameX:
  NOT   SI
  AND   DS:[DI], SI             ; erase the bit
  LOOP  ELoopOnAB
EallDone:
  JMP   ELoopDone            ; all finished

ECaseCD:           ; case CD
  MOV   CX, BX               ; loop 'dy' times
  MOV   DX, BX
  CMP   DX, 1
  JLE   ENoShift2
  SHR   DX, 1
ENoShift2:
  NEG   DX                   ; DX = -dy / 2
  JCXZ  ELoopDone
ELoopOnCD:
  ADD   DX, AX               ; temp += dx
  JS    EC10
  SUB   DX, BX               ; temp -= dy
  ROR   SI, 1                ; x += 1
;  ROR   SI, 1                ; x += 1
  NOT   SI
  TEST  SI,80H
  JZ    EC9
  ADD   DI, 2                ; DI ^ next word
EC9:
  NOT   SI
EC10:
;
  XOR   loc.oddlineFlag,1
  JNZ   NextLineOdd4
  SUB   DI, loc.winHeight
  CMP   loc.bytesPerLine,0
  JL    adjustDone4
AddBytesPerLine4:
  ADD   DI,loc.bytesPerLine
  JMP   adjustDone4
NextLineOdd4:
  ADD    DI,loc.WinHeight
  CMP    loc.bytesPerLine,0
  JL     AddBytesPerLine4
adjustDone4:
;
  AND   DS:[DI], SI             ; clear the bit
  LOOP  ELoopOnCD

ELoopDone:
  POP   AX
  INC   AX
  JZ    GfxEraseLineRet

  DEC   AX
  CALL  RestoreEmsSlot

GfxEraseLineRet:
  MOV   SP,BP
  POP   DS
  POP   BP
  RET   paramBytes
GfxEraseLine ENDP

$EJ
;****************************************
;*
;*    GfxInvertLine(screen, bytesPerLine, 
;*                  windowHeight, X, Y, X1, Y1)
;*
;****************************************

GfxInvertLine PROC FAR
  PUSH  BP
  PUSH  DS
  MOV   BP,SP
  SUB   SP,localBytes

  MOV   DX, loc.y2          ; DX = y2
  MOV   CX, loc.x2          ; CX = x2
  MOV   BX, loc.y1          ; BX = y1
  MOV   AX, loc.x1          ; AX = x1

  CMP  AX, CX    ; IS X <= X1
  JLE  XLine10    ; YES -> JUMP

  XCHG  AX, CX
  XCHG  BX, DX    ; NOW X < X1
XLine10:
  SUB  CX, AX    ; CX = dx (dx >= 0)
  SUB  DX, BX    ; DX = dy

  CALL  AInitLine
  PUSH  SI                  ; save old ems slot
  MOV   SI, 0080H           ; SI = original mask
  ROR   SI, CL              ; rotate to starting pos
  XOR   DS:[DI], SI

  CMP  AX, BX    ; IS dx < dy ?
  JL  XCaseCD    ; YES -> GOTO CASES C,D

XCaseAB:      ; MUST BE CASE A OR B
  MOV  CX, AX    ; LOOP dx TIMES
  MOV  DX, AX
  CMP   DX, 1
  JLE   XNoShift
  SHR   DX, 1
XNoShift:
  NEG  DX    ; DX = - dx /2
  JCXZ  XLoopDone
XLoopOnAB:
  ADD  DX, BX    ; temp += dy
  JS  XA10    ; JUMP IF RESULT < 0
  SUB  DX, AX    ; temp -= dx
;
  XOR   loc.oddlineFlag,1
  JNZ   NextLineOdd5
  SUB   DI, loc.winHeight
  CMP   loc.bytesPerLine,0
  JL    adjustDone5
AddBytesPerLine5:
  ADD   DI,loc.bytesPerLine
  JMP   adjustDone5
NextLineOdd5:
  ADD    DI,loc.WinHeight
  CMP    loc.bytesPerLine,0
  JL     AddBytesPerLine5
adjustDone5:
;
;
XA10:
  ROR  SI,1    ; X += 1
;  ROR  SI,1    ; X += 1
  TEST SI,80H
  JZ   XA20
  ADD  DI,2    ; DI ^ next word
XA20:
  XOR  DS:[DI],SI    ; SET THE BIT
  LOOP  XLoopOnAB

  JMP   XLoopDone

XCaseCD:      ; MUST BE CASE C OR D
  MOV  CX, BX    ; LOOP dy TIMES
  MOV  DX, BX
  CMP   DX, 1
  JLE   XNoShift2
  SHR   DX, 1
XNoShift2:
  NEG  DX    ; DX = -dy / 2
  JCXZ  XLoopDone
XLoopOnCD:
  ADD  DX, AX    ; temp += dx
  JS  XC10    ; JUMP IF RESULT < 0
  SUB  DX, BX    ; temp -= dy
  ROR  SI,1    ; X += 1
;  ROR  SI,1    ; X += 1
  TEST SI,80H
  JZ   XC10
  ADD  DI,2    ; DI ^ next word
XC10:
  XOR   loc.oddlineFlag,1
  JNZ   NextLineOdd6
  SUB   DI, loc.winHeight
  CMP   loc.bytesPerLine,0
  JL    adjustDone6
AddBytesPerLine6:
  ADD   DI,loc.bytesPerLine
  JMP   adjustDone6
NextLineOdd6:
  ADD    DI,loc.WinHeight
  CMP    loc.bytesPerLine,0
  JL     AddBytesPerLine6
adjustDone6:

  XOR  DS:[DI],SI    ; SET THE BIT
  LOOP  XLoopOnCD

XLoopDone:
  POP   AX
  INC   AX
  JZ    GfxInvertLineRet

  DEC   AX
  CALL  RestoreEmsSlot

GfxInvertLineRet:
  MOV  SP,BP
  POP  DS
  POP  BP
  RET  paramBytes
GfxInvertLine ENDP

$EJ
;*************************************************
;*
;*    AInitLine
;*
;*  entry:  AX = x
;*          BX = y
;*          CX = dx    (dx >= 0)
;*          DX = dy
;*  
;*  exit:   AX = dx
;*          BX = dy
;*          CL = rotate amount
;*          DI ^ word in display
;*          SI = ems slot to restore
;*          loc.bytesPerLine = +- wordWidth
;*
;************************************************

AInitLine PROC NEAR
  PUSH  CX
  PUSH  DX

  PUSH  AX              ; save xPos for later 
  PUSH  BX              ; save yPos for later

  MOV   AX, loc.screen
  MOV   DX, loc.bytesPerLine
  MOV   DI, loc.winHeight

  CALL  InitWinParms

  MOV   DS, AX
  MOV   loc.winHeight, DI

  POP   AX              ; y Pos
  POP   BX              ; x Pos
  CALL  StartPosition

  MOV   loc.oddlineFlag, AL

  POP   BX              ; BX = dy
  POP   AX              ; AX = dx

  CMP   BX, zero
  JGE   DYGE0           ; dy >= 0

  NEG   BX              ; dy = |dy|
  NEG   loc.bytesPerLine

DYGE0:
  RET
AInitLine ENDP

  PURGE params
  PURGE oldBP
  PURGE oldDS
  PURGE returnIP
  PURGE returnCS
  PURGE x1
  PURGE x2
  PURGE y1
  PURGE y2
  PURGE bytesPerLine
  PURGE screen
  PURGE loc
  PURGE paramBytes
  PURGE winHeight
  PURGE oddLineFlag
  PURGE dummy
$EJECT

; Init Win Parms

; INPUT
;   AX = screen address segment
;   DX = bytes Per Line
;   DI = windowHeight

; OUTPUT
;   AX = screen address segment (maybe changed if in EMS)
;   BX = 0
;   CX = scrFormat = pcFormat = 0
;   DX = bytes Per Line
;   DI = bankHeight, actually bankHeightInBytes
;   SI = ems slot to be restored (or -1 if not swapped out)

InitWinParms PROC NEAR
  MOV  CX, AX                 ; Save screen seg in CX
  MOV  SI, 0FFFFH             ; ems slot for "not swapped" case
  CMP  AX, screenSeg
  MOV  AX, 2000H
  JE   InitWinUsingScreen

  MOV  AX, CX                 ; Restore AX to screen seg again
  AND  AX, 0FF80H
  CMP  AX, CS:emsBase
  JNE  AltScreenNotInEms

  PUSHF                       ; Saving direction flag (just in case)
  PUSH ES
  PUSH DI
  PUSH DX

  AND  CX, 07FH               ; low 7 bits of window addr has ems slot

  PUSH CX                     ; Pass slot to set (BYTE parameter)
  INT  71H                    ; Cp Subsystem Call
  DB   4EH                    ; CpSetActiveSlot

  MOV  AH, 0
  MOV  SI, AX                 ; Save old slot in SI

  POP  DX
  POP  DI
  POP  ES
  POPF

  MOV  CX, CS:emsBase

AltScreenNotInEms:
  MOV  AX, DI
  SHR  AX, 1
  ADC  AX, 0
  MOV  BX, DX                 ; Save bytesPerLine
  MUL  DX                     ; bankHeight = (winHeight DIV 4) * bytesPerLine
  MOV  DX, BX                 ; Restore bytesPerLine

InitWinUsingScreen:
  XCHG DI, AX                 ; DI now has bankHeight
  XOR  BX, BX                 ; return BX as 0
  XOR  AX, AX                 ; AX will become CX = pcFormat
  XCHG CX, AX                 ; AX = scr seg (or EMS base); CX = 0 (pcFormat)
  RET
InitWinParms ENDP
$EJECT

; Restore EMS Slot

; INPUT
;   AX = ems slot to be restored
;   IT BETTER BE VALID, TOO! (i.e. check for AX=-1 before calling here)

; OUTPUT
;   Total destruction (within limits)

RestoreEmsSlot PROC NEAR
  PUSH AX                     ; Pass slot to restore (BYTE parameter)
  INT  71H                    ; Cp Subsystem Call
  DB   4EH                    ; CpSetActiveSlot
  RET
RestoreEmsSlot ENDP
$EJECT

;----------------------
StartPosition PROC NEAR
;----------------------

;computes word address & shift count, given x & y

;INPUT: 
;   AX = y
;   BX = x
;   DX = bytesPerLine
;   DI = odd/evenline offset
;   CX = screenFormat

;OUTPUT:
;   DI = offset to word
;   CX = shift count
;   DX = bytesPerLine
;   AX = 0 for an even line , 1 for an oddline
;

  PUSH DX
  CLC
  CMP  CX,PCformat
  JNZ  S0

  SHR  AX, 1           ; alternating scan lines

S0:
  PUSHF
  JC   S1

  MOV  DI, 0

S1:
  IMUL DX
  ADD  DI, AX          ;offset to beginning of line
  MOV  AX, BX		        ; get x
  SAR  AX, 1           ; account for bits/pel
  SAR  AX, 1           
  SAR  AX, 1           
  SAR  AX, 1           ; x/16 pels/word
  MOV  CX,0FH

S2:
  SHL  AX, 1           ; make it a byte count
  ADD  DI,AX
  AND  CX,BX           ;  pels per word
  XOR  AX,AX
  POPF
  ADC  AX,0
  POP  DX
  RET
StartPosition ENDP


CODE ENDS

  END
