Unit ModeX;
{$F+}
Interface

Uses Vectors;

Const
  SCREEN_SEG            = $A000; { Segment of display memory in mode X      }
  SCREEN_WIDTH          = 320;
  SCREEN_HEIGHT         = 240;
  PAGE0_START_OFFSET    = 0;
  PAGE1_START_OFFSET    = (SCREEN_HEIGHT * SCREEN_WIDTH) Div 4;
  BG_START_OFFSET       = (SCREEN_HEIGHT * SCREEN_WIDTH * 2) Div 4;
  DOWNLOAD_START_OFFSET = (SCREEN_HEIGHT * SCREEN_WIDTH * 3) Div 4;
  SC_INDEX       = $03C4;     { Sequence Controller Index                   }
  CRTC_INDEX     = $03D4;     { CRT Controller Index                        }
  MISC_OUTPUT    = $03C2;     { Miscellaneous Output Register               }
  MAP_MASK       = 2;         { Index in SC of Map Mask register            }
  GC_INDEX       = $03CE;     { Graphics Controller Index register port     }
  BIT_MASK       = 8;         { Index in GC of Bit Mask register            }
  READ_MAP       = 4;         { Index in GC of Read Map Select register     }

  _32BIT         = $66;       { Used for 32-bit instructions; overrides the }
                              {  operand size attributes for the following  }
                              {  instruction to force it to 32 bits.        }
                              {                                             }
                              { Example:                                    }
                              {                                             }
                              {    DB    _32BIT                             }
                              {    MOV   AX,WORD PTR I                      }
                              {                                             }
                              { Really looks like when run:                 }
                              {                                             }
                              {    MOV   EAX,DWORD PTR I                    }
                              {                                             }
                              { This allows 32-bit instructions to be used  }
                              {  in BASM, without using "InLine"s.          }
Type
  APtr = ^AlignedMaskedImage;
  AlignedMaskedImage = Record
    ImageWidth : Word;         { Image width in addresses (also mask width  }
                               {  in bytes)                                 }
    ImagePtr   : Pointer;      { Offset of image bitmap in display memory   }
    MaskPtr    : Pointer;      { Pointer to mask bitmap in DS               }
  End;
  MPtr = ^MaskedIMage;
  MaskedImage = Record
    Alignments: Array[0..3] Of APtr;  { Pointers to AlignedMaskedImages for }
                                      { the 4 possible destination image    }
                                      { alignments                          }
  End;
  BPtr = ^Byte;
  FPtr = ^FormattedImage;
  FormattedImage = Record
    ImageSize : Word;
    IMagePtr  : Pointer;
  End;
  EdgeScan = Record
    Direction      : Integer; { Through edge list; 1 for a right edge       }
                              {  (forward through vertex list), -1 for a    }
                              {  left edge (backward through vertex list)   }
    RemainingScans : Integer; { Height left to scan out in dest             }
    CurrentEnd     : Byte;    { Vertex of end of current edge               }
    SourceX        : LongInt; { X location in source for this edge          }
    SourceY        : LongInt; { Y location in source for this edge          }
    SourceStepX    : LongInt; { X step in source for Y step in dest of 1    }
    SourceStepY    : LongInt; { Y step in source for Y step in dest of 1    }
                              { Variables used for all-integer Bresenham's- }
                              {  type X stepping through the dest, needed   }
                              {  for precise pixel placement to avoid gaps  }
    DSourceStepX   : LongInt;
    DSourceStepY   : LongInt;
    EndSourceX     : LongInt;
    EndSourceY     : LongInt;
    DestX          : Integer; { Current X location in dest for this edge    }
    DestXIntStep   : Integer; { Whole part of dest X step per scan-line Y   }
                              { step                                        }
    DestXDirection : Integer; { -1 or 1 to indicate which way X steps (left }
                              {  or right)                                  }
    DestXErrTerm   : Integer; { Current error term for dest X stepping      }
    DestXAdjUp     : Integer; { Amount to add to error term per scan line   }
                              {  move                                       }
    DestXAdjDown   : Integer; { Amount to subtract from error term when the }
                              {  error term turns over                      }
  End;

Procedure Set320x240Mode;
Procedure FillRectangleX(StartX,StartY,EndX,EndY: Integer; PageBase: Word; Color: Integer);
Procedure DrawLineX(X0,Y0,X1,Y1: Integer; PageBase: Word; Color: Word);
Procedure RectangleX(StartX,StartY,EndX,EndY: Integer; PageBase: Word; Color: Word);
Procedure PutPixel(X,Y: Integer; PageBase: Word; Color: Integer);
Procedure FillPatternX(StartX,StartY,EndX,EndY: Integer; PageBase: Word; Pattern: Pointer);
Procedure CopyScreenToScreenX(SourceStartX,SourceStartY,SourceEndX,SourceEndY,
  DestStartX,DestStartY: Integer; SourcePageBase,DestPageBase: Word;
  SourceBitmapWidth,DestBitmapWidth: Integer);
Procedure CopySystemToScreenX(SourceStartX,SourceStartY,SourceEndX,SourceEndY,
  DestStartX,DestStartY: Integer; SourcePtr: Pointer; DestPageBase: Word;
  SourceBitmapWidth,DestBitmapWidth: Integer);
Procedure CopySystemToScreenMaskedX(SourceStartX,SourceStartY,SourceEndX,
  SourceEndY,DestStartX,DestStartY: Integer; SourcePtr: Pointer;
  DestPageBase: Word; SourceBitmapWidth,DestBitmapWidth: Integer;
  MaskPtr: Pointer);
Procedure CopyScreenToScreenMaskedX(SourceStartX,SourceStartY,SourceEndX,
  SourceEndY,DestStartX,DestStartY: Integer; Source: MPtr;
  DestPageBase: Word; DestBitmapWidth: Integer);
Function CreateAlignedMaskedImage(ImageToSet: MPtr;
 DispMemStart: Word; Image: BPtr; ImageWidth,ImageHeight: Integer; Mask: BPtr): Word;
Procedure ShowPage(StartOffset: Word);
Procedure ShowPageNoWait(StartOffset: Word);
Procedure ShowPageIRQ(StartOffset: Word);
Procedure ZapSystemToScreenMaskedX(SourceStartX,SourceStartY,SourceEndX,
  SourceEndY,DestStartX,DestStartY: Integer; Source: MPtr;
  DestPageBase: Word; DestBitmapWidth: Integer);
Procedure ZapSystemToScreenMaskedX1(SourceStartX,SourceStartY,SourceEndX,
  SourceEndY,DestStartX,DestStartY: Integer; SourcePtr: Pointer;
  DestPageBase: Word; SourceBitmapWidth,DestBitmapWidth: Integer;
  MaskPtr: Pointer);
Procedure ScaleZapSystemToScreenMaskedX(SourceStartX,SourceStartY,SourceEndX,
  SourceEndY,DestStartX,DestStartY: Integer; SourcePtr: Pointer;
  DestPageBase: Word; SourceBitmapWidth,DestBitmapWidth: Integer;
  MaskPtr: Pointer; Scale: Word);

Procedure Scale3DZapSystemToScreenMaskedX(SourceStartX,SourceStartY,SourceEndX,
  SourceEndY,DestStartX,DestStartY: Integer; SourcePtr: Pointer;
  DestPageBase: Word; SourceBitmapWidth,DestBitmapWidth: Integer;
  MaskPtr: Pointer; Height1,Height2,Width: Word; Skew: Integer; Vertical: Boolean);

Procedure RotateScaleZapSystemToScreenMaskedX(SourceStartX,SourceStartY,SourceEndX,
  SourceEndY,DestStartX,DestStartY: Integer; SourcePtr: Pointer;
  DestPageBase: Word; SourceBitmapWidth,DestBitmapWidth: Integer;
  MaskPtr: Pointer; Scale: Word; Degrees: Word; RotateX,RotateY: Word;
  Var X1,Y1,X2,Y2: Integer);
Function CreateZapMaskedImage(ImageToSet: MPtr;
 Image: BPtr; ImageWidth,ImageHeight: Integer; Mask: BPtr): Word;
Function CreateZapMaskedImage1(ImageToSet: MPtr;
 Image: BPtr; ImageWidth,ImageHeight: Integer; Mask: BPtr): Word;
Function ValSize(Scale: Word; Value: Word): Word;
Function BoundScale(Var SourceStartX,SourceStartY,SourceEndX,SourceEndY,
 DestStartX,DestStartY: Integer; Scale: Word; X1,Y1,X2,Y2: Integer): Boolean;
Function BoundScale3D(Var SourceStartX,SourceStartY,SourceEndX,SourceEndY,
 DestStartX,DestStartY: Integer; Var Height1,Height2,Width: Word; Var Skew: Integer; Vertical: Boolean;
 X1,Y1,X2,Y2: Integer): Boolean;
Procedure ScanOutLine(LeftEdge,RightEdge: EdgeScan; TexMapWidth,
 DestY,CurrentPageBase,ClipMinX,ClipMaxX: Word; TexMapBits: Pointer);
Procedure ScanOutLineVert(LeftEdge,RightEdge: EdgeScan; TexMapWidth,
 DestX,CurrentPageBase,ClipMinY,ClipMaxY: Word; TexMapBits: Pointer);
Procedure ScanOutLineBuf(LeftEdge,RightEdge: EdgeScan; TexMapWidth,
 DestX,BufSeg,ClipMinX,ClipMaxX: Word; TexMapBits: Pointer);
Procedure ScanOutLineBufVert(LeftEdge,RightEdge: EdgeScan; TexMapWidth,
 DestX,BufSeg,ClipMinY,ClipMaxY: Word; TexMapBits: Pointer);
Procedure ScanOutLineBufWhole(ClipMinX,ClipMinY,ClipMaxX,ClipMaxY: Word;
 SourceLimits,DestLimits: Pointer; Top,DestY: Word;
 TexMapWidth: Word; TexMapBits: Pointer; BufSeg: Word);
Procedure ScanOutLineBufWholeLight(ClipMinX,ClipMinY,ClipMaxX,ClipMaxY: Word;
 SourceLimits,DestLimits: Pointer; Top,DestY: Word;
 TexMapWidth: Word; TexMapBits: Pointer; BufSeg: Word; LightData: Pointer;
 Light: Byte);
Procedure ScanOutLineBufWholeLightVert(ClipMinX,ClipMinY,ClipMaxX,ClipMaxY: Word;
 SourceLimits,DestLimits: Pointer; Left,DestX: Word;
 TexMapWidth: Word; TexMapBits: Pointer; BufSeg: Word; LightData: Pointer;
 Light: Byte);

Implementation

Const
  _SCREEN_WIDTH  = SCREEN_WIDTH Div 4; { Width of screen in bytes from one  }
                                       {  scan line to the next             }
  PATTERN_BUFFER = $FFFC;     { Offset in screen memory of the buffer used  }
                              {  to store each pattern during drawing       }
  INPUT_STATUS_1 =  $03DA;    { Input Status 1 register                     }
  START_ADDRESS_HIGH = $0C;   { Bitmap start address high byte              }
  START_ADDRESS_LOW  = $0D;   { Bitmap start address low byte               }

  { Plane masks for clipping left and right edges of rectangle. }

  LeftClipPlaneMask  : Array[0..3] of Byte = ($0F,$0E,$0C,$08);
  RightClipPlaneMask : Array[0..3] Of Byte = ($0F,$01,$03,$07);

Var
  _AddValX  : Array[0..359] Of Word;
  _AddValY  : Array[0..359] Of Word;
  _XMult    : Array[0..359] Of Real;
  _YMult    : Array[0..359] Of Real;
  _Cos      : Array[0..359] Of Real;
  _Sin      : Array[0..359] Of Real;
  I         : Word;
  R,S       : Real;

Procedure Set320x240Mode;
{ ------------------------------------------------------------------------- }
{ Mode X (320x240, 256 colors) mode set routine. Works on all VGAs.         }
{ Pascal near-callable as:                                                  }
{       Set320x240Mode;                                                     }
{ Tested with TASM 2.0.                                                     }
{ Modified from public-domain mode set code by John Bridges.                }
{ ------------------------------------------------------------------------- }

Const

  { Index/data pairs for CRT Controller registers that differ between       }
  { mode 13h and mode X.                                                    }

  CRTParms : Array[0..9] Of Word =
  ($0D06,     { Vertical total                      }
   $3E07,     { Overflow (bit 8 of vertical counts) }
   $4109,     { Cell height (2 to double-scan)      }
   $EA10,     { V sync start                        }
   $AC11,     { V sync end and protect cr0-cr7      }
   $DF12,     { Vertical displayed                  }
   $0014,     { Turn off dword mode                 }
   $E715,     { V blank start                       }
   $0616,     { V blank end                         }
   $E317);    { Turn on byte mode                   }

  CRT_PARM_LENGTH = SizeOf(CRTParms) Shr 1;

Var CRT_PARM_ADDR : Pointer;

Begin
  CRT_PARM_ADDR := Addr(CRTParms);
  Asm
    CLI
    PUSH    DS
    PUSH    SI         { Preserve C register vars                    }
    PUSH    DI         {  (don't count on BIOS preserving anything)  }

    MOV     AX,13h     { Let the BIOS set standard 256-color mode    }
    INT     10h        {  (320x200 linear)                           }

    MOV     DX,SC_INDEX
    MOV     AX,0604h
    OUT     DX,AX      { Disable chain-4 mode                        }
    MOV     AX,0100h
    OUT     DX,AX      { Synchronous reset while switching clocks    }

    MOV     DX,MISC_OUTPUT
    MOV     AL,0E3h {0A7h}
    OUT     DX,AL      { Select 28MHz dot clock & 60Hz scanning rate }

    MOV     DX,SC_INDEX
    MOV     AX,0300h
    OUT     DX,AX      { Undo reset (restart sequencer)              }

    MOV     DX,CRTC_INDEX  { Reprogram the CRT controller            }
    MOV     AL,11h     { VSync End reg contains register write       }
    OUT     DX,AL      {  protect bit                                }
    INC     DX         { CRT Controller Data register                }
    IN      AL,DX      { Get current VSync End register setting      }
    AND     AL,7Fh     { Remove write protect on various CRTC        }
    OUT     DX,AL      {  registers                                  }
    DEC     DX         { CRT Controller Index                        }
    CLD
    LDS     SI,DWORD PTR CRT_PARM_ADDR
    MOV     CX,CRT_PARM_LENGTH  { Number of table entries            }

@SetCRTParmsLoop:

    LODSW              { Get the next CRT Index/Data pair            }
    OUT     DX,AX      { Set the next CRT Index/Data pair            }
    LOOP    @SetCRTParmsLoop

    MOV     DX,SC_INDEX
    MOV     AX,0F02h
    OUT     DX,AX      { Enable writes to all four planes            }
    MOV     AX,SCREEN_SEG  { Now clear all display memory, 8 pixels  }
    MOV     ES,AX          {  at a time                              }
    SUB     DI,DI      { Point ES:DI to display memory               }
    SUB     AX,AX      { Clear to zero-value pixels                  }
    MOV     CX,8000h   { Number of words in display memory           }
    REP     STOSW      { Clear all of display memory                 }

    POP     DI         { Restore C register vars                     }
    POP     SI
    POP     DS
    STI
  End; { Asm }
End; { Set320x240Mode }


Procedure FillRectangleX(StartX,StartY,EndX,EndY: Integer; PageBase: Word; Color: Integer);
{ ------------------------------------------------------------------------- }
{ Mode X (320x240, 256 colors) rectangle fill routine. Works on all VGAs.   }
{ Uses fast approach that fans data out to up to four planes at once to     }
{ draw up to four pixels at once. Fills up to but not including the column  }
{ atr EndX and the row at EndY. No clipping is performed.                   }
{ Pascal near-callable as:                                                  }
{    FillRectangleX(StartX,StartY,EndX,EndY,PageBase,Color);                }
{ ------------------------------------------------------------------------- }

Begin
  Asm
    CLI
    PUSH   SI               { Preserve caller's register variables          }
    PUSH   DI

    CLD
    MOV    AX,_SCREEN_WIDTH
    MUL    WORD PTR StartY  { Offset in page of top rectangle scan line     }
    MOV    DI,WORD PTR StartX
    SHR    DI,1             { X/4 = Offset of first rectangle pixel in scan }
    SHR    DI,1             {  line                                         }
    ADD    DI,AX            { Offset of first rectangle pixel in page       }
    ADD    DI,WORD PTR PageBase { Offset of first rectangle pixel in display}
                            {  memory                                       }
    MOV    AX,SCREEN_SEG    { Point ES:DI to the first rectangle pixel's    }
    MOV    ES,AX            {  address                                      }
    MOV    DX,SC_INDEX      { Set the Sequence Controller Index to point to }
    MOV    AL,MAP_MASK      {  the Map Mask register                        }
    OUT    DX,AL
    INC    DX               { Point DX to the SC Data register              }
    MOV    SI,WORD PTR StartX
    AND    SI,0003h                           { Look up left edge plane mask}
    MOV    BH,BYTE PTR LeftClipPlaneMask[SI]  {  to clip and put in BH      }
    MOV    SI,WORD PTR EndX
    AND    SI,0003h                           { Look up right edge plane    }
    MOV    BL,BYTE PTR RightClipPlaneMask[SI] {  mask to clip and put in BL }

    MOV    CX,WORD PTR EndX { Calculate number of addresses across rect     }
    MOV    SI,WORD PTR StartX
    CMP    CX,SI
    JLE    @FillDone        { Skip if 0 or negative width                   }
    DEC    CX
    AND    SI,NOT 011b
    SUB    CX,SI
    SHR    CX,1
    SHR    CX,1             { # of addresses across rectangle to fill - 1   }
    JNZ    @MasksSet        { There's more than one byte to draw            }
    AND    BH,BL            { There's only one byte, so combine the left    }
                            {  and right edge clip masks                    }
@MasksSet:
    MOV    SI,WORD PTR EndY
    SUB    SI,WORD PTR StartY { BX = height of rectangle                    }
    JLE    @FillDone        { Skip if 0 or negative height                  }
    MOV    AH,BYTE PTR Color { Color with which to fill                     }

    PUSH   BP

    MOV    BP,_SCREEN_WIDTH  { Stack frame isn't needed anymore              }
    SUB    BP,CX            { Distance from end of one scan line to start   }
    DEC    BP               {  of next                                      }
@FillRowsLoop:
    PUSH   CX               { Remember width in addresses - 1               }
    MOV    AL,BH            { Put left-edge clip mask in AL                 }
    OUT    DX,AL            { Set the left-edge plane (clip) mask           }
    MOV    AL,AH            { Put color in AL                               }
    STOSB                   { Draw the left edge                            }
    DEC    CX               { Count off left edge byte                      }
    JS     @FillLoopBottom  { That's the only byte                          }
    JZ     @DoRightEdge     { There are only two bytes                      }
    MOV    AL,00Fh          { Middle addresses are drawn 4 pixels at a pop  }
    OUT    DX,AL            { Set the middle pixel mask to no clip          }
    MOV    AL,AH            { Put color in AL                               }
    REP    STOSB            { Draw the middle addresses four pixels apiece  }
@DoRightEdge:
    MOV    AL,BL            { Put right-edge clip mask in AL                }
    OUT    DX,AL            { Set the right-edge plane (clip) mask          }
    MOV    AL,AH            { Put color in AL                               }
    STOSB                   { Draw the right edge                           }
@FillLoopBottom:
    ADD    DI,BP            { Point to the start of the next scan line of   }
                            {  the rectangle                                }
    POP    CX               { Retrieve width in addresses - 1               }
    DEC    SI               { Count down scan lines                         }
    JNZ    @FillRowsLoop

    POP    BP

@FillDone:
    POP    DI               { Restore caller's register variables           }
    POP    SI
    STI
  End; { Asm }
End; { FillRectangleX }


Procedure DrawLineX(X0,Y0,X1,Y1: Integer; PageBase: Word; Color: Word);
{ ------------------------------------------------------------------------- }
{ Mode X (320x240, 256 colors) line draw routine.  Fast assembler           }
{ implementation of Bresenham's line-drawing algorithm.                     }
{ ------------------------------------------------------------------------- }
Begin
  Asm
        MOV     DX,SC_INDEX     { Point to SC Index register                }
        MOV     AL,MAP_MASK
        OUT     DX,AL           { Point SC Index reg to the Map Mask        }
        PUSH    DS
        MOV     AX,SCREEN_SEG
        MOV     DS,AX
        MOV     SI,WORD PTR Y1 { Line Y start                          }
        MOV     AX,WORD PTR Y0 { Line Y end, used later in calculating }
                               { the start address                     }
        SUB     SI,AX          { Calculate DeltaY                      }
        JNS     @CalcStartAddress

{ DeltaY is negative -- swap coordinates so we're always working with a }
{ positive DeltaY.                                                      }

        MOV     AX,WORD PTR Y1 { Set line start to Y1, for use in }
                               { calculating the start address    }
        MOV     DX,WORD PTR X0
        XCHG    DX,WORD PTR X1
        MOV     WORD PTR X0,DX { Swap X coordinates               }
        NEG     SI             { Convert to positive DeltaY       }

{ Calculate the starting address in display memory of the line. }

@CalcStartAddress:
        MOV     CX,_SCREEN_WIDTH
        MUL     CX
        MOV     DI,AX
        MOV     CX,WORD PTR X0
        MOV     AX,CX
        SHR     CX,1
        SHR     CX,1
        ADD     DI,CX
        ADD     DI,WORD PTR PageBase

        AND     AX,3
        MOV     CX,AX
        MOV     AX,11h
        ROL     AL,CL
        MOV     DX,SC_INDEX+1
        OUT     DX,AL
        MOV     CX,AX
        MOV     AX,WORD PTR Color
        MOV     AH,AL
        MOV     AL,CL

        MOV     BX,WORD PTR X1 { Calculate DeltaX. }
        SUB     BX,WORD PTR X0

        PUSH    BP

        JS      @NegDeltaX { Handle correct one of four octants. }
        CMP     BX,SI
        JB      @Octant1

{ DeltaX >= DeltaY >= 0. }

{ ------------------------------------------------------------------------- }
{ LINE1 0                                                                   }
{ ------------------------------------------------------------------------- }
        MOV     CX,BX         { # of pixels in line                      }
        JCXZ    @Line1End1    { Done if there are no more pixels         }
                              {  (there's always at least 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)                }
@LineLoop1:

{ See if it's time to advance the Y coordinate yet. }

        AND     BP,BP         { See if error term is negative      }
        JS      @MoveXCoord1  { Yes, stay at the same Y coordinate }

{ Advance the Y coordinate, first writing all pixels in the current byte. }

        MOV     BYTE PTR [DI],AH
        ADD     DI,_SCREEN_WIDTH
        ADD     BP,SI         { Adjust error term back down }
        JMP     @MoveToNextByte1

{ Adjust display memory address. }

@MoveXCoord1:
        ADD     BP,BX         { Increment error term & keep same Y coord }
        MOV     BYTE PTR [DI],AH
@MoveToNextByte1:
        ROL     AL,1          { Next pixel is in byte to right }
        ADC     DI,0
        OUT     DX,AL
        LOOP    @LineLoop1
@Line1End1:
        MOV     BYTE PTR [DI],AH
        JMP     @VGALineDone

{ DeltaY > DeltaX >= 0. }

@Octant1:
{ -------------------------------------------------------------------- }
{ LINE2 0                                                              }
{ -------------------------------------------------------------------- }
{ Macro to loop through length of line, drawing each pixel in turn.    }
{ Used for case of DeltaX < DeltaY.                                    }
{ |DeltaY|+1 points are drawn.                                         }
{                                                                      }
{ Input:                                                               }
{       MOVE_LEFT: 1 if DeltaX < 0, 0 else                             }
{       AL: Line color                                                 }
{       BX: |DeltaX| (X distance between start and end points)         }
{       SI: DeltaY   (Y distance between start and end points)         }
{       ES:DI: Display-memory address of byte containing initial pixel }
{                                                                      }
{ Output: none                                                         }

        MOV     CX,SI         { # of pixels in line                      }
        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)                }

{ Draw initial pixel. }

        MOV     BYTE PTR [DI],AH
        JCXZ    @Line2End3
@LineLoop3:

{ See if it's time to advance the X coordinate yet. }

        AND     BP,BP         { See if error term is negative            }
        JNS     @ETermAction3 { No, advance X coordinate                 }
        ADD     BP,SI         { Increment error term & keep same X coord }
        JMP     @MoveYCoord3
@ETermAction3:

{ Adjust display memory address. }

        ROL     AL,1
        ADC     DI,0
        OUT     DX,AL
        ADD     BP,BX         { Adjust error term back down }

{ Advance the Y coordinate. }

@MoveYCoord3:
        ADD     DI,_SCREEN_WIDTH

{ Write the next pixel. }

        MOV     BYTE PTR [DI],AH
        LOOP    @LineLoop3
@Line2End3:
        JMP     @VGALineDone

@NegDeltaX:
        NEG     BX            { |DeltaX| }
        CMP     BX,SI
        JB      @Octant2

{ |DeltaX| >= DeltaY and DeltaX < 0. }

{ ------------------------------------------------------------------------- }
{ LINE1 1                                                                   }
{ ------------------------------------------------------------------------- }
        MOV     CX,BX         { # of pixels in line                      }
        JCXZ    @Line1End2    { Done if there are no more pixels         }
                              {  (there's always at least 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)                }
@LineLoop2:

{ See if it's time to advance the Y coordinate yet. }

        AND     BP,BP         { See if error term is negative      }
        JS      @MoveXCoord2  { Yes, stay at the same Y coordinate }

{ Advance the Y coordinate, first writing all pixels in the current byte. }

        MOV     BYTE PTR [DI],AH
        ADD     DI,_SCREEN_WIDTH
        ADD     BP,SI         { Adjust error term back down }
        JMP     @MoveToNextByte2

{ Adjust display memory address. }

@MoveXCoord2:
        ADD     BP,BX         { Increment error term & keep same Y coord }
        MOV     BYTE PTR [DI],AH
@MoveToNextByte2:
        ROR     AL,1
        SBB     DI,0          { Next pixel is in byte to left  }
        OUT     DX,AL
        LOOP    @LineLoop2
@Line1End2:
        MOV     BYTE PTR [DI],AH
        JMP     @VGALineDone

{ |DeltaX| < DeltaY and DeltaX < 0. }

@Octant2:
{ -------------------------------------------------------------------- }
{ LINE2 1                                                              }
{ -------------------------------------------------------------------- }
{ Macro to loop through length of line, drawing each pixel in turn.    }
{ Used for case of DeltaX < DeltaY.                                    }
{ |DeltaY|+1 points are drawn.                                         }
{                                                                      }
{ Input:                                                               }
{       MOVE_LEFT: 1 if DeltaX < 0, 0 else                             }
{       AL: Line color                                                 }
{       BX: |DeltaX| (X distance between start and end points)         }
{       SI: DeltaY   (Y distance between start and end points)         }
{       ES:DI: Display-memory address of byte containing initial pixel }
{                                                                      }
{ Output: none                                                         }

        MOV     CX,SI         { # of pixels in line                      }
        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)                }

{ Draw initial pixel. }

        MOV     BYTE PTR [DI],AH
        JCXZ    @Line2End4
@LineLoop4:

{ See if it's time to advance the X coordinate yet. }

        AND     BP,BP         { See if error term is negative            }
        JNS     @ETermAction4 { No, advance X coordinate                 }
        ADD     BP,SI         { Increment error term & keep same X coord }
        JMP     @MoveYCoord4
@ETermAction4:

{ Adjust display memory address. }

        ROR     AL,1
        SBB     DI,0
        OUT     DX,AL
        ADD     BP,BX         { Adjust error term back down }

{ Advance the Y coordinate. }

@MoveYCoord4:
        ADD     DI,_SCREEN_WIDTH

{ Write the next pixel. }

        MOV     BYTE PTR [DI],AH
        LOOP    @LineLoop4
@Line2End4:
@VGALineDone:

{ Pop registers and exit. }

        POP     BP
        POP     DS
  End; { Asm }
End; { DrawLineX }


Procedure RectangleX(StartX,StartY,EndX,EndY: Integer; PageBase: Word; Color: Word);
{ ------------------------------------------------------------------------- }
{ Mode X (320x240, 256 colors) rectangle plot routine.                      }
{ ------------------------------------------------------------------------- }
Begin
  DrawLineX(StartX,StartY,EndX  ,StartY,PageBase,Color);
  DrawLineX(EndX  ,StartY,EndX  ,EndY  ,PageBase,Color);
  DrawLineX(EndX  ,EndY  ,StartX,EndY  ,PageBase,Color);
  DrawLineX(StartX,EndY  ,StartX,StartY,PageBase,Color);
End; { RectangleX }


Procedure PutPixel(X,Y: Integer; PageBase: Word; Color: Integer);
{ ------------------------------------------------------------------------- }
{ Mode X (320x240, 256 colors) pixel plot routine.                          }
{ ------------------------------------------------------------------------- }
Begin
  Asm
    MOV    AX,SCREEN_SEG
    MOV    ES,AX

    MOV    AX,_SCREEN_WIDTH
    MOV    DI,WORD PTR Y
    MUL    DI
    MOV    DI,AX
    ADD    DI,WORD PTR PageBase
    MOV    BX,WORD PTR X
    MOV    CX,BX
    SHR    BX,1
    SHR    BX,1
    ADD    DI,BX
    AND    CX,3

    MOV    DX,SC_INDEX      { Set the Sequence Controller Index to point to }
    MOV    AL,MAP_MASK      {  the Map Mask register                        }
    OUT    DX,AL
    INC    DX               { Point DX to the SC Data register              }
    MOV    AL,11h
    ROL    AL,CL
    OUT    DX,AL
    MOV    AX,WORD Ptr Color
    MOV    BYTE PTR ES:[DI],AL
  End; { Asm }
End; { PutPixel }


Procedure FillPatternX(StartX,StartY,EndX,EndY: Integer; PageBase: Word; Pattern: Pointer);
{ ------------------------------------------------------------------------- }
{ Mode X (320x240, 256 colors) rectangle 4x4 pattern fill routine.  Upper   }
{ left corner of pattern is always aligned to a multiple-of-4 row & column. }
{ Works on all VGAs. Uses approach of copying the pattern to off-screen     }
{ display memory, then loading the latches with the pattern for each scan   }
{ line four pixels at a time. Fills up to but not including the column at   }
{ EndX and the row at EndY. No clipping is performed. All ASM code tested   }
{ with TASM 2. Pascal near-callable as:                                     }
{    FillPatternX(StartX,StartY,EndX,EndY,PageBase,Pattern);                }
{ ------------------------------------------------------------------------- }

Var
  NextScanOffset : Word;    { Local storage for distance from one end of    }
                            {  one scan line to start of next               }
  RectAddrWidth  : Word;    { Local storage for address width of rectangle  }
  Height         : Word;    { Local storage for height of rectangle         }

Begin
  Asm
    CLI
    PUSH    DS
    PUSH    SI              { Preserve caller's register variables          }
    PUSH    DI

    CLD
    MOV     AX,SCREEN_SEG   { Point ES to display memory                    }
    MOV     ES,AX

    LDS     SI,DWORD PTR Pattern { Point to pattern fill with               }
    MOV     DI,PATTERN_BUFFER  { Point ES:DI to pattern buffer              }
    MOV     DX,SC_INDEX     { Point Sequence Controller Index to Map Mask   }
    MOV     AL,MAP_MASK
    OUT     DX,AL
    INC     DX              { Point to SC Data register                     }
    MOV     CX,4            { 4 pixel quadruplets in pattern                }
@DownloadPatternLoop:
    MOV     AL,1
    OUT     DX,AL           { Select plane 0 for writes                     }
    MOVSB                   { Copy over next plane 0 pattern pixel          }
    DEC     DI              { Stay at same address for next plane           }
    MOV     AL,2
    OUT     DX,AL           { Select plane 1 for writes                     }
    MOVSB                   { Copy over next plane 1 pattern pixel          }
    DEC     DI              { Stay at same address for next plane           }
    MOV     AL,4
    OUT     DX,AL           { Select plane 2 for writes                     }
    MOVSB                   { Copy over next plane 2 pattern pixel          }
    DEC     DI              { Stay at same address for next plane           }
    MOV     AL,8
    OUT     DX,AL           { Select plane 3 for writes                     }
    MOVSB                   { Copy over next plane 3 pattern pixel and      }
                            {  advance address                              }
    LOOP    @DownloadPatternLoop

    MOV     DX,GC_INDEX         { Set the bit mask to select all bits from  }
    MOV     AX,00000h+BIT_MASK  {  the latches and none from the CPU, so    }
    OUT     DX,AX               {  that we can write the latch contents     }
                                {  directly to memory                       }
    MOV     AX,WORD PTR StartY  { Top rectangle scan line                   }
    MOV     SI,AX
    AND     SI,011b         { Top rect scan line modulo 4                   }
    ADD     SI,PATTERN_BUFFER  { Point to pattern scan line that maps to    }
                               {  top line of rect to draw                  }
    MOV     DX,_SCREEN_WIDTH
    MUL     DX              { Offset in page of top rectangle scan line     }
    MOV     DI,WORD PTR StartX
    MOV     BX,DI
    SHR     DI,1            { X/4 = offset of first rectangle pixel in scan }
    SHR     DI,1            {  line                                         }
    ADD     DI,AX           { Offset of first rectangle pixel in page       }
    ADD     DI,WORD PTR PageBase  { Offset of first rectangle pixel in      }
                                  {  display memory                         }
    AND     BX,0003h        { Look up left edge plane mask to clip          }
    MOV     AH,BYTE PTR LeftClipPlaneMask[BX]
    MOV     BX,WORD PTR EndX
    AND     BX,0003h        { Look up right edge plane mask to clip         }
    MOV     AL,BYTE PTR RightClipPlaneMask[BX]
    MOV     BX,AX           { Put the masks in BX                           }

    MOV     CX,WORD PTR EndX    { Calculate # of addresses across rectangle }
    MOV     AX,WORD PTR StartX
    CMP     CX,AX
    JLE     @FillDone       { Skip if 0 or negative width                   }
    DEC     CX
    AND     AX,NOT 011b
    SUB     CX,AX
    SHR     CX,1
    SHR     CX,1            { # of addresses across rectangle to fill - 1   }
    JNZ     @MasksSet       { There's more than one pixel to draw           }
    AND     BH,BL           { There's only one pixel, so combine the left   }
                            {  and right edge clip masks                    }
@MasksSet:
    MOV     AX,WORD PTR EndY
    SUB     AX,WORD PTR StartY  { AX=height of rectangle                    }
    JLE     @FillDone       { Skip if 0 or negative height                  }
    MOV     WORD PTR Height,AX
    MOV     AX,_SCREEN_WIDTH
    SUB     AX,CX           { Distance from end of one scan line to start   }
    DEC     AX              {  of next                                      }
    MOV     WORD PTR NextScanOffset,AX
    MOV     WORD PTR RectAddrWidth,CX  { Remember width in addresses - 1    }
    MOV     DX,SC_INDEX+1   { Point to Sequence Controller Data reg         }
                            {  (SC Index still points to Map Mask)          }
@FillRowsLoop:
    MOV     CX,WORD PTR RectAddrWidth  { Width across - 1                   }
    MOV     AL,ES:[SI]      { read display memory to latch this scan line's }
                            {  pattern                                      }
    INC     SI              { Point to the next pattern scan line,          }
                            {  wrapping back to the start of the pattern if }
                            {  we've run off the end                        }
    JNZ     @NoWrap
    SUB     SI,4
@NoWrap:
    MOV     AL,BH           { Put left-edge clip mask in AL                 }
    OUT     DX,AL           { Set the left-edge plane (clip) mask           }
    STOSB                   { Draw the left edge (pixels come from latches; }
                            {  value written by CPU doesn't matter)         }
    DEC     CX              { Count off left edge address                   }
    JS      @FillLoopBottom { That's the only address                       }
    JZ      @DoRightEdge    { There are only rwo addresses                  }
    MOV     AL,00Fh         { Middle addresses are drawn 4 pixels at a pop  }
    OUT     DX,AL           { Set the middle pixel mask to no clip          }
    REP     STOSB           { Draw the middle addresses four pixels apiece  }
                            { (from latches; value written doesn't matter)  }
@DoRightEdge:
    MOV     AL,BL           { Put right-edge clip mask in AL                }
    OUT     DX,AL           { Set the right-edge plane (clip) mask          }
    STOSB                   { Draw the right edge (from latches; value      }
                            {  written doesn't matter)                      }
@FillLoopBottom:
    ADD     DI,WORD PTR NextScanOffset  { Point to the start of the next    }
                                        {  scan line of the rectangle       }
    DEC     WORD PTR Height { Count down scan lines                         }
    JNZ     @FillRowsLoop
@FillDone:
    MOV     DX,GC_INDEX+1   { Restore the bit mask to its default, which    }
    MOV     AL,0FFh         {  selects all bits from the CPU and none from  }
    OUT     DX,AL           {  the latches (the GC Index still points to    }
                            {  Bit Mask)                                    }
    POP     DI              { Restore caller's register variables           }
    POP     SI
    POP     DS
    STI
  End; { Asm }
End; { FillPatternX }


Procedure CopyScreenToScreenX(SourceStartX,SourceStartY,SourceEndX,SourceEndY,
  DestStartX,DestStartY: Integer; SourcePageBase,DestPageBase: Word;
  SourceBitmapWidth,DestBitmapWidth: Integer);
{ ------------------------------------------------------------------------- }
{ Mode X (320x240, 256 colors) display memory to display memory copy        }
{ routine.  Left edge of source rectangle modulo 4 must equal left edge of  }
{ destination rectangle modulo 4. Works on all VGAs. Uses approach of       }
{ reading 4 pixels at a time from the source into the latches, then writing }
{ the latches to the destination. Copies up to but not including the column }
{ at SourceEndX and the row at SourceEndY. No clipping is performed.        }
{ Results are not guaranteed if the source and destination overlap.         }
{ Pascal near-callable as:                                                  }
{    CopyScreenToScreenX(SourceStartX,SourceStartY,SourceEndX,SourceEndY,   }
{       DestStartX,DestStartY,SourcePageBase,DestPageBase,                  }
{       SourceBitmapWidth,DestBitmapWidth);                                 }
{ ------------------------------------------------------------------------- }

Var
  SourceNextScanOffset : Word; { Local storage for distance from end of one }
                               {  source scan line to start of next         }
  DestNextScanOffset   : Word; { Local storage for distance from end of one }
                               {  dest scan line to start of next           }
  RectAddrWidth        : Word; { Local storage for address width of rectangle}
  Height               : Word; { Local storage for height of rectangle      }

Begin
  Asm
{    CLI}
    PUSH     SI             { Preserve caller's register variables          }
    PUSH     DI
    PUSH     DS

    CLD
    MOV      DX,GC_INDEX         { Set the bit mask to select all bits from }
    MOV      AX,00000h+BIT_MASK  {  the latches and none from the CPU, so   }
    OUT      DX,AX               {  that we can write the latch contents    }
                                 { directly to memory.                      }
    MOV      AX,SCREEN_SEG  { Point ES to display memory                    }
    MOV      ES,AX
    MOV      AX,WORD PTR DestBitmapWidth
    SHR      AX,1           { Convert to width in addresses                 }
    SHR      AX,1
    MUL      WORD PTR DestStartY { Top dest rect scan line                  }
    MOV      DI,WORD PTR DestStartX
    SHR      DI,1           { X/4=offset of first dest rect pixel in scan   }
    SHR      DI,1           {  line                                         }
    ADD      DI,AX          { Offset of first dest rect pixel in page       }
    ADD      DI,WORD PTR DestPageBase { Offset of first dest rect pixel in  }
                                   {  display memory                        }
    MOV      AX,WORD PTR SourceBitmapWidth
    SHR      AX,1           { Convert to width in addresses                 }
    SHR      AX,1
    MUL      WORD PTR SourceStartY { Top source rect scan line              }
    MOV      SI,WORD PTR SourceStartX
    MOV      BX,SI
    SHR      SI,1           { X/4=offset of first source rect pixel in scan }
    SHR      SI,1           {  line                                         }
    ADD      SI,AX          { Offset of first source rect pixel in page     }
    ADD      SI,WORD PTR SourcePageBase { Offset of first source rect pixel }
                                        {  in display memory                }
    AND      BX,0003h       { Look up left edge plane mask to clip          }
    MOV      AH,BYTE PTR LeftClipPlaneMask[BX]
    MOV      BX,WORD PTR SourceEndX
    AND      BX,0003h       { Look up right edge plane mask to clip         }
    MOV      AL,BYTE PTR RightClipPlaneMask[BX]
    MOV      BX,AX          { Put the masks in BX                           }
    MOV      CX,WORD PTR SourceEndX { calculate # of addresses across rect  }
    MOV      AX,WORD PTR SourceStartX
    CMP      CX,AX
    JLE      @CopyDone      { Skip if 0 or negative width                   }
    DEC      CX
    AND      AX,NOT 011b
    SUB      CX,AX
    SHR      CX,1
    SHR      CX,1           { # of addresses across rectangle to copy - 1   }
    JNZ      @MasksSet      { There's more than one address to draw         }
    AND      BH,BL          { There's only one address, so combine the left }
                            {  and right edge clip masks                    }
@MasksSet:
    MOV      AX,WORD PTR SourceEndY
    SUB      AX,WORD PTR SourceStartY { AX=height of rectangle              }
    JLE      @CopyDone      { Skip if 0 or negative height                  }
    MOV      WORD PTR Height,AX
    MOV      AX,WORD PTR DestBitmapWidth
    SHR      AX,1           { Convert to width in addresses                 }
    SHR      AX,1
    SUB      AX,CX          { Distance from end of one dest scan line to    }
    DEC      AX             {  start of next                                }
    MOV      WORD PTR DestNextScanOffset,AX
    MOV      AX,WORD PTR SourceBitmapWidth
    SHR      AX,1           { Convert to width in addresses                 }
    SHR      AX,1
    SUB      AX,CX          { Distance from end of one dest scan line to    }
    DEC      AX             {  start of next                                }
    MOV      WORD PTR SourceNextScanOffset,AX
    MOV      WORD PTR RectAddrWidth,CX  { Remember width in addresses - 1   }
    MOV      DX,SC_INDEX    { Point to Sequence Controller Data reg         }
    MOV      AL,MAP_MASK
    OUT      DX,AL          { Point SC Index reg to Map Mask                }
    INC      DX             { Point to SC Data reg                          }
    MOV      AX,ES          { DS=ES=screen segment for MOVS                 }
    MOV      DS,AX
@CopyRowsLoop:
    MOV      CX,WORD PTR RectAddrWidth { Width across - 1                   }
    MOV      AL,BH          { Put left-edge clip mask in AL                 }
    OUT      DX,AL          { Set the left-edge plane (clip) mask           }
    MOVSB                   { Copy the left edge (pixels go through latches)}

    DEC      CX             { Count off left edge address                   }
    JS       @CopyLoopBottom { That's the only address                      }
    JZ       @DoRightEdge   { There are only two addresses                  }
    MOV      AL,00Fh        { Middle addresses are drawn 4 pixels at a pop  }
    OUT      DX,AL          { Set the middle pixel mask to no clip          }
    REP      MOVSB          { Draw the middle addresses four pixels apiece  }
                            {  (pixels copied through latches)              }
@DoRightEdge:
    MOV      AL,BL          { Put right-edge clip mask in AL                }
    OUT      DX,AL          { Set the right-edge plane (clip) mask          }
    MOVSB                   { draw the right edge (pixels copied through    }
                            {  latches)                                     }
@CopyLoopBottom:
    ADD      SI,WORD PTR SourceNextScanOffset { Point to the start of       }
    ADD      DI,WORD PTR DestNextScanOffset   {  next source & dest lines   }
    DEC      WORD PTR Height { Count down scan lines                        }
    JNZ      @CopyRowsLoop
@CopyDone:
    MOV      DX,GC_INDEX+1  { Restore the bit mask to its default, which    }
    MOV      AL,0FFh        {  selects all bits from the CPU and none from  }
    OUT      DX,AL          {  the latches (the GC Index still points to Bit}
                            {  Mask)                                        }
    POP      DS
    POP      DI             { Restore caler's register variables            }
    POP      SI
{    STI}
  End; { Asm }
End; { CopyScreenToScreenX }


Procedure CopySystemToScreenX(SourceStartX,SourceStartY,SourceEndX,SourceEndY,
  DestStartX,DestStartY: Integer; SourcePtr: Pointer; DestPageBase: Word;
  SourceBitmapWidth,DestBitmapWidth: Integer);
{ ------------------------------------------------------------------------- }
{ Mode X (320x240,256 colors) system memory to display memory copy routine. }
{ Uses approach of changing the plane for each pixel copied; this is slower }
{ than copying all pixels in one plane, then all pixels in the next plane,  }
{ and so on, but it is simpler; besides, images for which performance is    }
{ critical should be stored in off-screen memory and copied to the screen   }
{ via the latches. Copies up to but not including the column at SourceEndX  }
{ and the row at SourceEndY. No clipping is performed.                      }
{ Pascal near-callable as:                                                  }
{    CopySystemToScreenX(SourceStartX, SourceStartY,SourceEndX,SourceEndY,  }
{       DestStartX,DestStartY,SourcePtr,DestPageBase,SourceBitmapWidth,     }
{       DestBitmapWidth);                                                   }
{ ------------------------------------------------------------------------- }

Var
  RectWidth : Word;         { Local storage for width of rectangle          }
  LeftMask  : Byte;         { Local storage for left rect edge plane mask   }
  Counter0  : Word;
  Counter   : Word;

Begin
  Asm
    CLI
    PUSH    DS
    PUSH    SI              { Preserve caller's register variables          }
    PUSH    DI

    CLD
    MOV     AX,SCREEN_SEG   { Point ES to display memory                    }
    MOV     ES,AX
    MOV     AX,WORD PTR SourceBitmapWidth
    MUL     WORD PTR SourceStartY { Top source rect scan line               }
    ADD     AX,WORD PTR SourceStartX

    LDS     SI,DWORD PTR SourcePtr { Offset of first source rect pixel in DS}
    ADD     SI,AX

    MOV     AX,WORD PTR DestBitmapWidth
    SHR     AX,2            { Convert to width in addresses                 }
    MOV     WORD PTR DestBitmapWidth,AX  { Remember address width           }
    MUL     WORD PTR DestStartY { Top dest rect scan line                   }
    MOV     DI,WORD PTR DestStartX
    MOV     CX,DI
    SHR     DI,2          { X/4=offset of first dest rect pixel in scan line}
    ADD     DI,AX           { offset of first dest rect pixel in page       }
    ADD     DI,WORD PTR DestPageBase { Offset of first dest rect pixel in   }
                                     {  display memory                      }
    AND     CL,011b         { CL=first dest pixel's plane                   }
    MOV     AL,11h          { upper nibble comes into play when plane wraps }
                            {  from 3 back to 0                             }
    SHL     AL,CL                { Set the bit for the first dest pixel's   }
    MOV     BYTE PTR LeftMask,AL {  plane in each nibble to 1               }

    MOV     CX,WORD PTR SourceEndX  { Calculate # of pixels across rect     }
    SUB     CX,WORD PTR SourceStartX
    JLE     @CopyDone       { Skip if 0 or negative width                   }
    MOV     WORD PTR RectWidth,CX
    MOV     BX,WORD PTR SourceEndY
    SUB     BX,WORD PTR SourceStartY  { BX=height of rectangle              }
    JLE     @CopyDone       { Skip if 0 or negative height                  }
    MOV     WORD PTR Counter,BX
    MOV     DX,SC_INDEX     { Point to SC Index register                    }
    MOV     AL,MAP_MASK
    OUT     DX,AL           { Point SC Index reg to the Map Mask            }
    INC     DX              { Point DX to SC Data reg                       }

    MOV     AX,WORD PTR Counter
    MOV     WORD PTR Counter0,AX
    PUSH    SI              { Remember the start offset in the source       }
    PUSH    DI              { Remember the start offset in the dest         }
    MOV     AL,BYTE PTR LeftMask
    OUT     DX,AL
@CopyRowsLoop1:
    MOV     CX,WORD PTR RectWidth
    PUSH    SI              { Remember the start offset in the source       }
    PUSH    DI              { Remember the start offset in the dest         }
    ADD     CX,3
    SHR     CX,2
    TEST    CX,3
    JZ      @Plane1A
    MOV     AX,CX
@Plane1C:
    AND     CX,3
    MOVSB
    ADD     SI,3
    DEC     CX
    JNZ     @Plane1C
    MOV     CX,AX
@Plane1A:
    SHR     CX,2
    JZ      @Plane1B
@Plane1:
    MOV     AL,BYTE PTR [SI + 8]
    MOV     AH,BYTE PTR [SI + 12]
    DB      _32BIT
    SHL     AX,16
    MOV     AL,BYTE PTR [SI]
    MOV     AH,BYTE PTR [SI + 4]
    DB      _32BIT
    STOSW
    ADD     SI,16
    DEC     CX
    JNZ     @Plane1         { This is actually faster than a LOOP on a 386  }
@Plane1B:
    POP     DI              { Retrieve the dest start offset                }
    ADD     DI,WORD PTR DestBitmapWidth { Point to the start of the next    }
                                        {  scan line of the dest            }
    POP     SI              { Retrieve the source start offset              }
    ADD     SI,WORD PTR SourceBitmapWidth { Point to the start of the next  }
                                          {  scan line of the source        }
    DEC     WORD PTR Counter  { Count down scan lines                       }
    JNZ     @CopyRowsLoop1
    MOV     AX,WORD PTR Counter0
    MOV     WORD PTR Counter,AX

    POP     DI
    POP     SI
    INC     SI
    MOV     AL,BYTE PTR LeftMask
    ROL     AL,1
    ADC     DI,0
    MOV     BYTE PTR LeftMask,AL
    OUT     DX,AL
    PUSH    SI              { Remember the start offset in the source       }
    PUSH    DI              { Remember the start offset in the dest         }
@CopyRowsLoop2:
    PUSH    SI
    PUSH    DI
    MOV     CX,WORD PTR RectWidth
    ADD     CX,2
    SHR     CX,2
    TEST    CX,3
    JZ      @Plane2A
    MOV     AX,CX
@Plane2C:
    AND     CX,3
    MOVSB
    ADD     SI,3
    DEC     CX
    JNZ     @Plane2C
    MOV     CX,AX
@Plane2A:
    SHR     CX,2
    JZ      @Plane2B
@Plane2:
    MOV     AL,BYTE PTR [SI + 8]
    MOV     AH,BYTE PTR [SI + 12]
    DB      _32BIT
    SHL     AX,16
    MOV     AL,BYTE PTR [SI]
    MOV     AH,BYTE PTR [SI + 4]
    DB      _32BIT
    STOSW
    ADD     SI,16
    DEC     CX
    JNZ     @Plane2         { This is actually faster than a LOOP on a 386  }
@Plane2B:
    POP     DI              { Retrieve the dest start offset                }
    ADD     DI,WORD PTR DestBitmapWidth { Point to the start of the next    }
                                        {  scan line of the dest            }
    POP     SI              { Retrieve the source start offset              }
    ADD     SI,WORD PTR SourceBitmapWidth { Point to the start of the next  }
                                          {  scan line of the source        }
    DEC     WORD PTR Counter  { Count down scan lines                       }
    JNZ     @CopyRowsLoop2
    MOV     AX,WORD PTR Counter0
    MOV     WORD PTR Counter,AX

    POP     DI
    POP     SI
    INC     SI
    MOV     AL,BYTE PTR LeftMask
    ROL     AL,1
    ADC     DI,0
    MOV     BYTE PTR LeftMask,AL
    OUT     DX,AL
    PUSH    SI              { Remember the start offset in the source       }
    PUSH    DI              { Remember the start offset in the dest         }
@CopyRowsLoop3:
    PUSH    SI
    PUSH    DI
    MOV     CX,WORD PTR RectWidth
    INC     CX
    SHR     CX,2
    TEST    CX,3
    JZ      @Plane3A
    MOV     AX,CX
@Plane3C:
    AND     CX,3
    MOVSB
    ADD     SI,3
    DEC     CX
    JNZ     @Plane3C
    MOV     CX,AX
@Plane3A:
    SHR     CX,2
    JZ      @Plane3B
@Plane3:
    MOV     AL,BYTE PTR [SI + 8]
    MOV     AH,BYTE PTR [SI + 12]
    DB      _32BIT
    SHL     AX,16
    MOV     AL,BYTE PTR [SI]
    MOV     AH,BYTE PTR [SI + 4]
    DB      _32BIT
    STOSW
    ADD     SI,16
    DEC     CX
    JNZ     @Plane3         { This is actually faster than a LOOP on a 386  }
@Plane3B:
    POP     DI              { Retrieve the dest start offset                }
    ADD     DI,WORD PTR DestBitmapWidth { Point to the start of the next    }
                                        {  scan line of the dest            }
    POP     SI              { Retrieve the source start offset              }
    ADD     SI,WORD PTR SourceBitmapWidth { Point to the start of the next  }
                                          {  scan line of the source        }
    DEC     WORD PTR Counter  { Count down scan lines                       }
    JNZ     @CopyRowsLoop3
    MOV     AX,WORD PTR Counter0
    MOV     WORD PTR Counter,AX

    POP     DI
    POP     SI
    INC     SI
    MOV     AL,BYTE PTR LeftMask
    ROL     AL,1
    ADC     DI,0
    OUT     DX,AL
    PUSH    SI              { Remember the start offset in the source       }
    PUSH    DI              { Remember the start offset in the dest         }
@CopyRowsLoop4:
    PUSH    SI
    PUSH    DI
    MOV     CX,WORD PTR RectWidth
    SHR     CX,2
    TEST    CX,3
    JZ      @Plane4A
    MOV     AX,CX
@Plane4C:
    AND     CX,3
    MOVSB
    ADD     SI,3
    DEC     CX
    JNZ     @Plane4C
    MOV     CX,AX
@Plane4A:
    SHR     CX,2
    JZ      @Plane4B
@Plane4:
    MOV     AL,BYTE PTR [SI + 8]
    MOV     AH,BYTE PTR [SI + 12]
    DB      _32BIT
    SHL     AX,16
    MOV     AL,BYTE PTR [SI]
    MOV     AH,BYTE PTR [SI + 4]
    DB      _32BIT
    STOSW
    ADD     SI,16
    DEC     CX              { This is actually faster than a LOOP on a 386  }
    JNZ     @Plane4
@Plane4B:
    POP     DI              { Retrieve the dest start offset                }
    ADD     DI,WORD PTR DestBitmapWidth { Point to the start of the next    }
                                        {  scan line of the dest            }
    POP     SI              { Retrieve the source start offset              }
    ADD     SI,WORD PTR SourceBitmapWidth { Point to the start of the next  }
                                          {  scan line of the source        }
    DEC     WORD PTR Counter  { Count down scan lines                       }
    JNZ     @CopyRowsLoop4

    POP     DI
    POP     SI
(*
@CopyScanLineLoop:
    OUT     DX,AL           { Set the plane for this pixel                  }
    MOVSB                   { Copy the pixel to the screen                  }
    ROL     AL,1            { Set mask for next pixel's plane               }
    CMC                     { advance destination address only when wrapping}
    SBB     DI,0            {  from plane 3 to plane 0                      }
                            {  (else undo INC DI done by MOVSB)             }
    LOOP    @CopyScanLineLoop
    POP     DI              { Retrieve the dest start offset                }
    ADD     DI,WORD PTR DestBitmapWidth { Point to the start of the next    }
                                        {  scan line of the dest            }
    POP     SI              { Retrieve the source start offset              }
    ADD     SI,WORD PTR SourceBitmapWidth { Point to the start of the next  }
                                          {  scan line of the source        }
    DEC     WORD PTR Counter  { Count down scan lines                       }
    JNZ     @CopyRowsLoop
*)
@CopyDone:
    POP     DI              { Restore caller's register variables           }
    POP     SI
    POP     DS
    STI
  End; { Asm }
End; { CopySystemToScreenX }


Procedure CopySystemToScreenMaskedX(SourceStartX,SourceStartY,SourceEndX,
  SourceEndY,DestStartX,DestStartY: Integer; SourcePtr: Pointer;
  DestPageBase: Word; SourceBitmapWidth,DestBitmapWidth: Integer;
  MaskPtr: Pointer);
{ ------------------------------------------------------------------------- }
{ Mode X (320x240,256 colors) system memory-to-display memory masked copy   }
{ routine. Not particularly fast; images for which performance is critical  }
{ should be stored in off-screen memory and copied to screen via latches.   }
{ Works on all VGAs. Copies up to but not including column at SourceEndX and}
{ row at SourceEndY. No clipping is performed. Mask and source image are    }
{ both byte-per-pixel, and must be of same widths and reside at same        }
{ coordinates in their respective bitmaps. Assembly code tested with TASM   }
{ 2.0.                                                                      }
{ Pascal near-callable as:                                                  }
{    void CopySystemToScreenMaskedX(int SourceStartX, int SourceStartY,     }
{       int SourceEndX, int SourceEndY, int DestStartX, int DestStartY,     }
{       char * SourcePtr, unsigned int DestPageBase, int SourceBitmapWidth, }
{       int DestBitmapWidth, char * MaskPtr);                               }
{ ------------------------------------------------------------------------- }

Var
  RectWidth  : Word;        { Local storage for width of rectangle          }
  RectHeight : Word;        { Local storage for height of rectangle         }
  LeftMask   : Word;        { Local storage for left rect edge plane mask   }
  SourceSeg  : Word;
  MaskSeg    : Word;

Begin
  Asm
    CLI
    PUSH    SI              { Preserve caller's register variables          }
    PUSH    DI

    MOV     AX,SCREEN_SEG   { Point ES to display memory                    }
    MOV     ES,AX
    MOV     AX,WORD PTR SourceBitmapWidth
    MUL     WORD PTR SourceStartY { Top source rect scan line               }
    ADD     AX,WORD PTR SourceStartX
    MOV     BX,AX
    ADD     AX,WORD PTR SourcePtr { Offset of first source rect pixel in DS }
    MOV     SI,AX
    ADD     BX,WORD PTR MaskPtr { Offset of first mask pixel in DS          }

    MOV     AX,WORD PTR DestBitmapWidth
    SHR     AX,1            { Convert to width in addresses                 }
    SHR     AX,1
    MOV     WORD PTR DestBitmapWidth,AX  { Remember address width           }
    MUL     WORD PTR DestStartY { Top dest rect scan line                   }
    MOV     DI,WORD PTR DestStartX
    MOV     CX,DI
    SHR     DI,1            { X/4=offset of first dest rect pixel in scan   }
    SHR     DI,1            {  line                                         }
    ADD     DI,AX           { Offset of first dest rect pixel in page       }
    ADD     DI,WORD PTR DestPageBase  { Offset of first dest rect pixel     }
                                      {  in display memory                  }
    AND     CL,011b         { CL=first dest pixel's plane                   }
    MOV     AL,11h          { Upper nibble comes into play when plane wraps }
                            {  from 3 back to 0                             }
    SHL     AL,CL                { Set the bit for the first dest pixel's   }
    MOV     BYTE PTR LeftMask,AL {  plane in each nibble to 1               }

    MOV     AX,WORD PTR SourceEndX { Calculate # of pixels across rect      }
    SUB     AX,WORD PTR SourceStartX
    JLE     @CopyDone       { Skip if 0 or negative width                   }
    MOV     WORD PTR RectWidth,AX
    SUB     WORD PTR SourceBitmapWidth,AX
                            { Distance from end of one source scan line to  }
                            {  start of next                                }
    MOV     AX,WORD PTR SourceEndY
    SUB     AX,WORD PTR SourceStartY  { Height of rectangle                 }
    JLE     @CopyDone       { Skip if 0 or negative height                  }
    MOV     WORD PTR RectHeight,AX
    MOV     DX,SC_INDEX     { Point to SC Index register                    }
    MOV     AL,MAP_MASK
    OUT     DX,AL           { Point SC Index reg to the Map Mask            }
    INC     DX              { Point DX to SC Data reg                       }
@CopyRowsLoop:

    PUSH    DS
    MOV     CX,WORD PTR SourcePtr[2]
    MOV     WORD PTR SourceSeg,CX
    MOV     CX,WORD PTR MaskPtr[2]
    MOV     WORD PTR MaskSeg,CX

    MOV     AL,BYTE PTR LeftMask
    MOV     CX,WORD PTR RectWidth
    PUSH    DI              { Remember the start offset in the dest         }
@CopyScanLineLoop:
    MOV     DS,WORD PTR MaskSeg
    CMP     BYTE PTR [BX],0 { Is this pixel mask-enabled?                   }
    JZ      @MaskOff        { No, so don't draw it                          }
                            { Yes, draw the pixel                           }
    OUT     DX,AL           { Set the plane for this pixel                  }
    MOV     DS,WORD PTR SourceSeg
    MOV     AH,[SI]         { Get the pixel from the source                 }
    MOV     ES:[DI],AH      { Copy the pixel to the screen                  }
@MaskOff:
    INC     BX              { Advance the mask pointer                      }
    INC     SI              { Advance the source pointer                    }
    ROL     AL,1            { Set mask for next pixel's plane               }
    ADC     DI,0            { Advance destination address only when wrapping}
                            {  from plane 3 to plane 0                      }
    LOOP    @CopyScanLineLoop
    POP     DI
    POP     DS              { Retrieve the dest start offset                }
    ADD     DI,WORD PTR DestBitmapWidth  { Point to the start of the next   }
                                         {  scan line of the dest           }
    ADD     SI,WORD PTR SourceBitmapWidth  { Point to the start of the next }
                                           {  scan line of the source       }
    ADD     BX,WORD PTR SourceBitmapWidth  { Point to the start of the next }
                                           {  scan line of the mask         }
    DEC     WORD PTR RectHeight  { Count down scan lines                    }
    JNZ     @CopyRowsLoop
@CopyDone:
    POP     DI              { Restore caller's register variables           }
    POP     SI
    STI
  End; { Asm }
End; { CopySystemToScreenMaskedX }


Procedure CopyScreenToScreenMaskedX(SourceStartX,SourceStartY,SourceEndX,
  SourceEndY,DestStartX,DestStartY: Integer; Source: MPtr;
  DestPageBase: Word; DestBitmapWidth: Integer);
{ ------------------------------------------------------------------------- }
{ Mode X (320x240, 256 colors) display memory to display memory masked copy }
{ routine. Works on all VGAs. Uses approach of reading 4 pixels at a time   }
{ from source into latches, then writing latches to destination, using Map  }
{ Mask register to perform masking. Copies up to but not including column at}
{ SourceEndX and row at SourceEndY. No clipping is performed. Results are   }
{ not guaranteed if source and destination overlap.                         }
{ Pascal near-callable as:                                                  }
{    CopyScreenToScreenMaskedX(SourceStartX,SourceStartY,SourceEndX,        }
{       SourceEndY,DestStartX,DestStartY,Source,DestPageBase,               }
{       DestBitmapWidth);                                                   }
{ ------------------------------------------------------------------------- }

Var
  SourceNextScanOffset : Word; { Local storage for distance from end of one }
                               {  source scan line to start of next         }
  DestNextScanOffset   : Word; { Local storage for distance from end of one }
                               {  dest scan line to start of next           }
  RectAddrWidth        : Word; { Local storage for address width of rectangle}
  RectHeight           : Word; { Local storage for height of rectangle      }
  SourceBitmapWidth    : Word; { Local storage for width of source bitmap (in}
                               {  addresses)                                }
  Save_DS              : Word;

Begin
  Asm
    CLI
    PUSH    DS
    PUSH    SI              { Preserve caller's register variables          }
    PUSH    DI

    CLD
    MOV     DX,GC_INDEX         { Set the bit mask to select all bits from  }
    MOV     AX,00000h+BIT_MASK  {  the latches and none from the CPU, so    }
    OUT     DX,AX               {  that we can write the latch contents     }
                                {  directly to memory                       }
    MOV     AX,SCREEN_SEG   { Point ES to display memory                    }
    MOV     ES,AX
    MOV     AX,WORD PTR DestBitmapWidth
    SHR     AX,1            { Convert to width in addresses                 }
    SHR     AX,1
    MUL     WORD PTR DestStartY { Top dest rect scan line                   }
    MOV     DI,WORD PTR DestStartX
    MOV     SI,DI
    SHR     DI,1            { X/4=Offset of first dest rect pixel in scan   }
    SHR     DI,1            {  line                                         }
    ADD     DI,AX           { Offset of first dest rect pixel in page       }
    ADD     DI,WORD PTR DestPageBase  { Offset of first dest rect pixel in  }
                                      {  display memory. Now look up the    }
                                      {  image that's aligned to match left-}
                                      {  edge alignment of destination      }

    AND     SI,3            { DestStartX modulo 4                           }
    MOV     CX,SI           { Set aside alignment for later                 }
    SHL     SI,1            { Prepare for dword look-up                     }
    SHL     SI,1

    PUSH    DS

    LDS     BX,DWORD PTR Source { Point to source MaskedImage structure     }
    LDS     BX,DWORD PTR [BX+SI] { Point to AlignedMaskedImage struc for    }
                                 {  current left edge alignment             }
    MOV     AX,WORD PTR [BX]    { Image width in addresses                  }


    MOV     SI,DS
    POP     DS
    MOV     WORD PTR Save_DS,SI


    MOV     WORD PTR SourceBitMapWidth,AX  { Remember image width in        }
                                           {  addresses                     }
    MUL     WORD PTR SourceStartY  { Top source rect scan line              }
    MOV     SI,WORD PTR SourceStartX
    SHR     SI,1            { X/4=address of first source rect pixel in scan}
    SHR     SI,1            {  line                                         }
    ADD     SI,AX           { Offset of first source rect pixel in image    }
    MOV     AX,SI


    PUSH    DS
    MOV     DS,WORD PTR Save_DS


    ADD     SI,WORD PTR [BX+6] { Point to mask offset of first mask pixel   }
                               {  in DS                                     }
    ADD     AX,WORD PTR [BX+2] { Offset of first source rect pixel in    }
                               {  display memory                         }
    MOV     BX,WORD PTR [BX+8]

    POP     DS
    MOV     WORD PTR Save_DS,BX
    MOV     BX,AX


    MOV     AX,WORD PTR SourceStartX  { Calculate # of addresses across     }
    ADD     AX,CX                     {  rect, shifting if necessary to     }
    ADD     CX,WORD PTR SourceEndX    {  account for alignment              }
    CMP     CX,AX
    JLE     @CopyDone       { Skip if 0 or negative width                   }
    ADD     CX,3
    AND     AX,NOT 011b
    SUB     CX,AX
    SHR     CX,1
    SHR     CX,1            { # of addresses across rectangle to copy       }
    MOV     AX,WORD PTR SourceEndY
    SUB     AX,WORD PTR SourceStartY  { AX=height of retcangle              }
    JLE     @CopyDone       { Skip if 0 or negative height                  }
    MOV     WORD PTR RectHeight,AX
    MOV     AX,WORD PTR DestBitmapWidth
    SHR     AX,1            { Convert to width in addresses                 }
    SHR     AX,1
    SUB     AX,CX           { Distance from end of one dest scan line to    }
                            {  start of next                                }
    MOV     WORD PTR DestNextScanOffset,AX
    MOV     AX,WORD PTR SourceBitmapWidth  { Width in addresses             }
    SUB     AX,CX           { Distance from end of source scan line to      }
                            {  start of next                                }
    MOV     WORD PTR SourceNextScanOffset,AX
    MOV     WORD PTR RectAddrWidth,CX  { Remember width in addresses        }

    MOV     DX,SC_INDEX
    MOV     AL,MAP_MASK
    OUT     DX,AL           { Point SC Index register to Map Mask           }
    INC     DX              { Point to SC Data register                     }
@CopyRowsLoop:
    MOV     CX,WORD PTR RectAddrWidth  { Width across                       }


    PUSH    DS
    MOV     DS,WORD PTR Save_DS


@CopyScanLineLoop:
    LODSB                   { Get the mask for this four-pixel set and      }
                            {  advance the mask pointer                     }
    OUT     DX,AL           { Set the mask                                  }
    MOV     AL,ES:[BX]      { Load the latches with 4-pixel set from source }
    MOV     ES:[DI],AL      { Copy the four-pixel set to the dest           }
    INC     BX              { Advance the source pointer                    }
    INC     DI              { Advance the destination pointer               }
(*
    DEC     CX              { Count off four-pixel sets                     }
    JNZ     @CopyScanLineLoop
*)
    LOOP    @CopyScanLineLoop

    POP     DS


    MOV     AX,WORD PTR SourceNextScanOffset
    ADD     SI,AX           { Point to the start of the next source, mask,  }
    ADD     BX,AX           {  and dest lines                               }
    ADD     DI,WORD PTR DestNextScanOffset
    DEC     WORD PTR RectHeight  { Count down scan lines                    }
    JNZ     @CopyRowsLoop
@CopyDone:
    MOV     DX,GC_INDEX+1   { Restore the bit mask to its default, which    }
    MOV     AL,0FFh         {  selects all bits from the CPU and none from  }
    OUT     DX,AL           {  the latches (the GC Index still points to    }
                            {  Bit Mask)                                    }
    POP     DI              { Restore caller's register variables           }
    POP     SI
    POP     DS
    STI
  End; { Asm }
End; { CopyScreenToScreenMaskedX }


Function CreateAlignedMaskedImage(ImageToSet: MPtr;
 DispMemStart: Word; Image: BPtr; ImageWidth,ImageHeight: Integer; Mask: BPtr): Word;
{ ------------------------------------------------------------------------- }
{ Generates all four possible mode X image/mask alignments, stores image    }
{ alignments in display memory, allocates memory for and generates mask     }
{ alignments, and fills out an Aligned Masked Image structure. Image and    }
{ mask must both be in byte-per-pixel form, and must both be of width       }
{ ImageWidth.  Mask maps isomorphically (one to one) onto image, with each  }
{ 0-byte in mask masking off corresponding image pixel (causing it not to be}
{ drawn), and each non-0-byte allowing corresponding image pixel to be      }
{ drawn. Returns 0 if failure, or # of display memory addresses (4-pixel    }
{ sets) used if success.                                                    }
{ ------------------------------------------------------------------------- }

Var
  Align,ScanLine,BitNum,Size,TempImageWidth : Integer;
  MaskTemp                                  : Byte;
  DispMemOffset                             : Word;
  WorkingAMImage                            : APtr;
  NewMaskPtr,OldMaskPtr                     : BPtr;
  B                                         : Byte;

Begin
  DispMemOffset := DispMemStart;

  { Generate each of the four alignments in turn }

  For Align := 0 To 3 Do
  Begin

    { Allocate space for the AlignedMaskedImage struct for this alignment }

    GetMem(WorkingAMImage,SizeOf(AlignedMaskedImage));
    ImageToSet^.Alignments[Align] := WorkingAMImage;
    If WorkingAMImage = Nil Then
    Begin
      CreateAlignedMaskedImage := 0;
      Exit;
    End;

    WorkingAMImage^.ImageWidth := (ImageWidth + Align + 3) Div 4; { width in 4-pixel sets }
    WorkingAMImage^.ImagePtr   := Ptr(SCREEN_SEG,DispMemOffset);  { image dest }

    { Download this alignment of the image }

    CopySystemToScreenX(0,0,ImageWidth,ImageHeight,Align,0,Image,
     DispMemOffset,ImageWidth,WorkingAMImage^.ImageWidth * 4);

    { Calculate the number of bytes needed to store the mask in nibble }
    {  (Map Mask-ready) form, then allocate that space                 }

    Size := WorkingAMImage^.ImageWidth * ImageHeight;
    GetMem(WorkingAMImage^.MaskPtr,Size);
    If WorkingAMImage^.MaskPtr = Nil Then
    Begin
      CreateAlignedMaskedImage := 0;
      Exit;
    End;

    { Generate this nibble oriented (Map Mask-ready) alignment of the mask, }
    {  one scan line at a time                                              }

    OldMaskPtr := Mask;
    NewMaskPtr := BPtr(WorkingAMImage^.MaskPtr);
    For ScanLine := 0 To ImageHeight - 1 Do
    Begin
      BitNum         := Align;
      MaskTemp       := 0;
      TempImageWidth := ImageWidth;
      Repeat

        { Set the mask bit for next pixel according to its alignment }

        If OldMaskPtr^ <> 0 Then B := 1 Else B := 0;
        OldMaskPtr  := Ptr(Seg(OldMaskPtr^),Ofs(OldMaskPtr^) + 1);
        MaskTemp := MaskTemp Or (B Shl BitNum);
        Inc(BitNum);
        If BitNum > 3 Then
        Begin
          NewMaskPtr^ := MaskTemp;
          NewMaskPtr  := Ptr(Seg(NewMaskPtr^),Ofs(NewMaskPtr^) + 1);
          MaskTemp    := 0;
          BitNum      := 0;
        End;
        Dec(TempImageWidth);
      Until TempImageWidth = 0;

      { Set any partial final mask on this scan line }

      If BitNum <> 0 Then
      Begin
        NewMaskPtr^ := MaskTemp;
        NewMaskPtr  := Ptr(Seg(NewMaskPtr^),Ofs(NewMaskPtr^) + 1);
      End;
    End; { For ScanLine }
    Inc(DispMemOffset,Size);           { mark off the space we just used }
  End; { For Align }
  CreateAlignedMaskedImage := DispMemOffset - DispMemStart;
End; { CreateAlignedMaskedImage }


Procedure ShowPage(StartOffset: Word);
{ ------------------------------------------------------------------------- }
{ Shows the page at the specified offset in the bitmap. Page is displayed   }
{ when this routine returns. This code first appeared in PC-Techniques.     }
{ Pascal near-callable as: ShowPage(StartOffset);                           }
{ ------------------------------------------------------------------------- }

Begin
  Asm
{    CLI}
    { Wait for display enable to be active (status is active low), to be }
    { sure both halves of the start address will take in the same frame. }

    MOV     BL,START_ADDRESS_LOW           { Preload for fastest    }
    MOV     BH,BYTE PTR StartOffset        {  flipping once display }
    MOV     CL,START_ADDRESS_HIGH          {  enable is detected    }
    MOV     CH,BYTE PTR StartOffset[1]
    MOV     DX,INPUT_STATUS_1
@WaitDE:
    IN      AL,DX
    TEST    AL,01h
    JNZ     @WaitDE    { Display enable is active low (0=active) }

    { Set the start offset in display memory of the page to display. }

    MOV     DX,CRTC_INDEX
    MOV     AX,BX
    OUT     DX,AX      { Start address low  }
    MOV     AX,CX
    OUT     DX,AX      { Start address high }

    { Now wait for vertical sync, so the other page will be invisible when }
    { we start drawing to it.                                              }

    MOV     DX,INPUT_STATUS_1
@WaitVS:
    IN      AL,DX
    TEST    AL,08h
    JZ      @WaitVS    { Vertical sync is active high (1=active) }
{    STI}
  End; { Asm }
End; { ShowPage }


Procedure ShowPageNoWait(StartOffset: Word);
{ ------------------------------------------------------------------------- }
{ Shows the page at the specified offset in the bitmap. Page is displayed   }
{ when this routine returns.  Doesn't wait for display enable, by assuming  }
{ the low byte of the offset doesn't change.                                }
{ Pascal near-callable as: ShowPageNoWait(StartOffset);                     }
{ ------------------------------------------------------------------------- }

Begin
  Asm
{    CLI}
    { Wait for display enable to be active (status is active low), to be }
    { sure both halves of the start address will take in the same frame. }
(*
    MOV     BL,START_ADDRESS_LOW           { Preload for fastest    }
    MOV     BH,BYTE PTR StartOffset        {  flipping once display }
*)
    MOV     CL,START_ADDRESS_HIGH          {  enable is detected    }
    MOV     CH,BYTE PTR StartOffset[1]
{    MOV     DX,INPUT_STATUS_1}
@WaitDE:
(*
    IN      AL,DX
    TEST    AL,01h
    JNZ     @WaitDE    { Display enable is active low (0=active) }
*)
    { Set the start offset in display memory of the page to display. }

    MOV     DX,CRTC_INDEX
(*
    MOV     AX,BX
    OUT     DX,AX      { Start address low  }
*)
    MOV     AX,CX
    OUT     DX,AX      { Start address high }

    { Now wait for vertical sync, so the other page will be invisible when }
    { we start drawing to it.                                              }

    MOV     DX,INPUT_STATUS_1
@WaitVS:
    IN      AL,DX
    TEST    AL,08h
    JZ      @WaitVS    { Vertical sync is active high (1=active) }
{    STI}
  End; { Asm }
End; { ShowPageNoWait }


Procedure ShowPageIRQ(StartOffset: Word);
{ ------------------------------------------------------------------------- }
{ Shows the page at the specified offset in the bitmap. Page is displayed   }
{ when this routine returns.  Doesn't wait for anything at all; assumes that}
{ it was called as a result of a VGA-generated IRQ2.                        }
{ Pascal near-callable as: ShowPageIRQ(StartOffset);                        }
{ ------------------------------------------------------------------------- }

Begin
  Asm
    MOV     CL,START_ADDRESS_HIGH          {  enable is detected    }
    MOV     CH,BYTE PTR StartOffset[1]

    { Set the start offset in display memory of the page to display. }

    MOV     DX,CRTC_INDEX
    MOV     AX,CX
    OUT     DX,AX      { Start address high }
  End; { Asm }
End; { ShowPageIRQ }


Procedure ZapSystemToScreenMaskedX(SourceStartX,SourceStartY,SourceEndX,
  SourceEndY,DestStartX,DestStartY: Integer; Source: MPtr;
  DestPageBase: Word; DestBitmapWidth: Integer);
{ ------------------------------------------------------------------------- }
{ Mode X (320x240, 256 colors) display memory to display memory masked copy }
{ routine. Works on all VGAs. Uses approach of reading 4 pixels at a time   }
{ from source into latches, then writing latches to destination, using Map  }
{ Mask register to perform masking. Copies up to but not including column at}
{ SourceEndX and row at SourceEndY. No clipping is performed. Results are   }
{ not guaranteed if source and destination overlap.                         }
{ Pascal near-callable as:                                                  }
{    ZapSystemToScreenMaskedX(SourceStartX,SourceStartY,SourceEndX,        }
{       SourceEndY,DestStartX,DestStartY,Source,DestPageBase,               }
{       DestBitmapWidth);                                                   }
{ ------------------------------------------------------------------------- }

Var
  SourceNextScanOffset : Word; { Local storage for distance from end of one }
                               {  source scan line to start of next         }
  DestNextScanOffset   : Word; { Local storage for distance from end of one }
                               {  dest scan line to start of next           }
  RectAddrWidth        : Word; { Local storage for address width of rectangle}
  RectHeight           : Word; { Local storage for height of rectangle      }
  SourceBitmapWidth    : Word; { Local storage for width of source bitmap (in}
                               {  addresses)                                }
  DestStartAddr        : Word; { Local storage for start of dest rect       }
  Pages                : Word; { Counter for number of pages to run         }
  CurPage              : Word;
  Save_DS              : Word;
  MaskSeg              : Word;
  ImageSeg             : Word;

Begin
  Asm
    CLI
    PUSH    DS
    PUSH    SI              { Preserve caller's register variables          }
    PUSH    DI

    CLD
(*
    MOV     DX,GC_INDEX         { Set the bit mask to select all bits from  }
    MOV     AX,00000h+BIT_MASK  {  the latches and none from the CPU, so    }
    OUT     DX,AX               {  that we can write the latch contents     }
                                {  directly to memory                       }

*)
    MOV     DX,GC_INDEX
    MOV     AX,0FFh + BIT_MASK
    OUT     DX,AX

    MOV     AX,SCREEN_SEG   { Point ES to display memory                    }
    MOV     ES,AX
    MOV     AX,WORD PTR DestBitmapWidth
    SHR     AX,1            { Convert to width in addresses                 }
    SHR     AX,1
    MUL     WORD PTR DestStartY { Top dest rect scan line                   }
    MOV     DI,WORD PTR DestStartX
    MOV     SI,DI
    SHR     DI,1            { X/4=Offset of first dest rect pixel in scan   }
    SHR     DI,1            {  line                                         }
    ADD     DI,AX           { Offset of first dest rect pixel in page       }
    ADD     DI,WORD PTR DestPageBase  { Offset of first dest rect pixel in  }
                                      {  display memory. Now look up the    }
                                      {  image that's aligned to match left-}
                                      {  edge alignment of destination      }
    MOV     WORD PTR DestStartAddr,DI { Save start address                  }

    MOV     WORD PTR Pages,4
    AND     SI,3
    MOV     WORD PTR CurPage,SI
@PageLoop:
    MOV     DI,WORD PTR DestStartAddr
    MOV     SI,4
    SUB     SI,WORD PTR Pages
    MOV     CX,SI           { Set aside alignment for later                 }
    ADD     SI,WORD PTR SourceStartX
    AND     SI,3
    SHL     SI,1            { Prepare for dword look-up                     }
    SHL     SI,1

    PUSH    DS

    LDS     BX,DWORD PTR Source { Point to source MaskedImage structure     }
    LDS     BX,DWORD PTR [BX+SI] { Point to AlignedMaskedImage struc for    }
                                 {  current left edge alignment             }
    MOV     AX,WORD PTR [BX]    { Image width in addresses                  }

    MOV     SI,DS
    POP     DS

    MOV     WORD PTR Save_DS,SI

    MOV     WORD PTR SourceBitMapWidth,AX  { Remember image width in        }
                                           {  addresses                     }
    MUL     WORD PTR SourceStartY  { Top source rect scan line              }
    MOV     SI,WORD PTR SourceStartX
    SHR     SI,1            { X/4=address of first source rect pixel in scan}
    SHR     SI,1            {  line                                         }
    ADD     SI,AX           { Offset of first source rect pixel in image    }
    MOV     AX,SI

    MOV     DX,WORD PTR SourceStartX
    AND     DX,3
    CMP     WORD PTR Pages,DX
    JA      @NoAdd
    INC     SI
    INC     AX
@NoAdd:

    PUSH    CX
    PUSH    DS
    MOV     DS,WORD PTR Save_DS


    ADD     SI,WORD PTR [BX+6] { Point to mask offset of first mask pixel   }
                               {  in DS                                     }
    ADD     AX,WORD PTR [BX+2] { Offset of first source rect pixel in    }
                               {  display memory                         }
    MOV     CX,WORD PTR [BX+4]

    MOV     BX,WORD PTR [BX+8]

    POP     DS
    MOV     WORD PTR ImageSeg,CX
    POP     CX
    MOV     WORD PTR MaskSeg,BX
    MOV     BX,AX

    MOV     AX,WORD PTR SourceEndX     { # of dots to do                    }
    SUB     AX,WORD PTR SourceStartX
    ADD     AX,3
    SUB     AX,CX
    SHR     AX,1
    SHR     AX,1
    CMP     AX,0
    JLE     @CopyDone       { Skip if 0 or negative width                   }
    MOV     CX,AX           { # of addresses across rectangle to copy       }

    MOV     AX,WORD PTR SourceEndY
    SUB     AX,WORD PTR SourceStartY  { AX=height of retcangle              }
    JLE     @CopyDone       { Skip if 0 or negative height                  }

    MOV     WORD PTR RectHeight,AX
    MOV     AX,WORD PTR DestBitmapWidth
    SHR     AX,1            { Convert to width in addresses                 }
    SHR     AX,1
    SUB     AX,CX           { Distance from end of one dest scan line to    }
                            {  start of next                                }
    MOV     WORD PTR DestNextScanOffset,AX
    MOV     AX,WORD PTR SourceBitmapWidth  { Width in addresses             }
    SUB     AX,CX           { Distance from end of source scan line to      }
                            {  start of next                                }
    MOV     WORD PTR SourceNextScanOffset,AX
    MOV     WORD PTR RectAddrWidth,CX  { Remember width in addresses        }

    MOV     DX,SC_INDEX
    MOV     AL,MAP_MASK
    OUT     DX,AL           { Point SC Index register to Map Mask           }
    INC     DX              { Point to SC Data register                     }
    MOV     CX,WORD PTR CurPage
    MOV     AL,11h
    SHL     AL,CL
    OUT     DX,AL
@CopyRowsLoop:
    MOV     CX,WORD PTR RectAddrWidth  { Width across                       }

    PUSH    DS
    MOV     DX,WORD PTR ImageSeg
    MOV     DS,WORD PTR MaskSeg
    PUSH    BP
    MOV     BP,DS

@CopyScanLineLoop:
    LODSB                   { Get the mask for this four-pixel set and      }
                            {  advance the mask pointer                     }
    OR      AL,AL
    JNZ     @NoWrite
    MOV     DS,DX
    MOV     AL,[BX]         { Load the latches with 4-pixel set from source }
    MOV     ES:[DI],AL      { Copy the four-pixel set to the dest           }
    MOV     DS,BP
@NoWrite:
    INC     BX              { Advance the source pointer                    }
    INC     DI
    LOOP    @CopyScanLineLoop

    POP     BP
    POP     DS

    MOV     AX,WORD PTR SourceNextScanOffset
    ADD     SI,AX           { Point to the start of the next source, mask,  }
    ADD     BX,AX           {  and dest lines                               }
    ADD     DI,WORD PTR DestNextScanOffset
    DEC     WORD PTR RectHeight  { Count down scan lines                    }
    JNZ     @CopyRowsLoop
@CopyDone:
    INC     WORD PTR CurPage
    AND     WORD PTR CurPage,3
    CMP     WORD PTR CurPage,0
    JNE     @NoWrap
    INC     WORD PTR DestStartAddr
@NoWrap:
    DEC     WORD PTR Pages
    JNZ     @PageLoop
(*
    MOV     DX,GC_INDEX+1   { Restore the bit mask to its default, which    }
    MOV     AL,0FFh         {  selects all bits from the CPU and none from  }
    OUT     DX,AL           {  the latches (the GC Index still points to    }
                            {  Bit Mask)                                    }
*)
    POP     DI              { Restore caller's register variables           }
    POP     SI
    POP     DS
    STI
  End; { Asm }
End; { ZapSystemToScreenMaskedX }


Procedure ZapSystemToScreenMaskedX1(SourceStartX,SourceStartY,SourceEndX,
  SourceEndY,DestStartX,DestStartY: Integer; SourcePtr: Pointer;
  DestPageBase: Word; SourceBitmapWidth,DestBitmapWidth: Integer;
  MaskPtr: Pointer);
{ ------------------------------------------------------------------------- }
{ Mode X (320x240,256 colors) system memory-to-display memory masked copy   }
{ routine. Not particularly fast; images for which performance is critical  }
{ should be stored in off-screen memory and copied to screen via latches.   }
{ Works on all VGAs. Copies up to but not including column at SourceEndX and}
{ row at SourceEndY. No clipping is performed. Mask and source image are    }
{ both byte-per-pixel, and must be of same widths and reside at same        }
{ coordinates in their respective bitmaps. Assembly code tested with TASM   }
{ 2.0.                                                                      }
{ Pascal near-callable as:                                                  }
{    void CopySystemToScreenMaskedX(int SourceStartX, int SourceStartY,     }
{       int SourceEndX, int SourceEndY, int DestStartX, int DestStartY,     }
{       char * SourcePtr, unsigned int DestPageBase, int SourceBitmapWidth, }
{       int DestBitmapWidth, char * MaskPtr);                               }
{ ------------------------------------------------------------------------- }

Var
  RectWidth  : Word;        { Local storage for width of rectangle          }
  RectHeight : Word;        { Local storage for height of rectangle         }
  LeftMask   : Word;        { Local storage for left rect edge plane mask   }
  SourceSeg  : Word;
  MaskSeg    : Word;

Begin
  Asm
{    CLI}
    PUSH    SI              { Preserve caller's register variables          }
    PUSH    DI

    MOV     AX,SCREEN_SEG   { Point ES to display memory                    }
    MOV     ES,AX
    MOV     AX,WORD PTR SourceBitmapWidth
    MUL     WORD PTR SourceStartY { Top source rect scan line               }
    ADD     AX,WORD PTR SourceStartX
    MOV     BX,AX
    ADD     AX,WORD PTR SourcePtr { Offset of first source rect pixel in DS }
    MOV     SI,AX
    ADD     BX,WORD PTR MaskPtr { Offset of first mask pixel in DS          }

    MOV     AX,WORD PTR DestBitmapWidth
    SHR     AX,1            { Convert to width in addresses                 }
    SHR     AX,1
    MOV     WORD PTR DestBitmapWidth,AX  { Remember address width           }
    MUL     WORD PTR DestStartY { Top dest rect scan line                   }
    MOV     DI,WORD PTR DestStartX
    MOV     CX,DI
    SHR     DI,1            { X/4=offset of first dest rect pixel in scan   }
    SHR     DI,1            {  line                                         }
    ADD     DI,AX           { Offset of first dest rect pixel in page       }
    ADD     DI,WORD PTR DestPageBase  { Offset of first dest rect pixel     }
                                      {  in display memory                  }
    AND     CL,011b         { CL=first dest pixel's plane                   }
    MOV     AL,11h          { Upper nibble comes into play when plane wraps }
                            {  from 3 back to 0                             }
    SHL     AL,CL                { Set the bit for the first dest pixel's   }
    MOV     BYTE PTR LeftMask,AL {  plane in each nibble to 1               }

    MOV     AX,WORD PTR SourceEndX { Calculate # of pixels across rect      }
    SUB     AX,WORD PTR SourceStartX
    JLE     @CopyDone       { Skip if 0 or negative width                   }
    MOV     WORD PTR RectWidth,AX

    SUB     WORD PTR SourceBitmapWidth,AX
                            { Distance from end of one source scan line to  }
                            {  start of next                                }
    MOV     AX,WORD PTR SourceEndY
    SUB     AX,WORD PTR SourceStartY  { Height of rectangle                 }
    JLE     @CopyDone       { Skip if 0 or negative height                  }
    MOV     WORD PTR RectHeight,AX
    MOV     DX,SC_INDEX     { Point to SC Index register                    }
    MOV     AL,MAP_MASK
    OUT     DX,AL           { Point SC Index reg to the Map Mask            }
    INC     DX              { Point DX to SC Data reg                       }

    MOV     CX,WORD PTR SourcePtr[2]
    MOV     WORD PTR SourceSeg,CX
    MOV     CX,WORD PTR MaskPtr[2]
    MOV     WORD PTR MaskSeg,CX
@CopyRowsLoop:
    PUSH    DS
    PUSH    DI              { Remember the start offset in the dest         }
    MOV     AL,BYTE PTR LeftMask
    MOV     CX,WORD PTR RectWidth
@CopyScanLineLoop:
    MOV     DS,WORD PTR MaskSeg
    CMP     BYTE PTR [BX],0 { Is this pixel mask-enabled?                   }
    JNZ     @MaskOff        { No, so don't draw it                          }
                            { Yes, draw the pixel                           }
    OUT     DX,AL           { Set the plane for this pixel                  }
    MOV     DS,WORD PTR SourceSeg
    MOV     AH,[SI]         { Get the pixel from the source                 }
    MOV     ES:[DI],AH      { Copy the pixel to the screen                  }
@MaskOff:
    ROL     AL,1            { Set mask for next pixel's plane               }
    ADC     DI,0            { Advance destination address only when wrapping}
                            {  from plane 3 to plane 0                      }
    INC     BX              { Advance the mask pointer                      }
    INC     SI              { Advance the source pointer                    }
    LOOP    @CopyScanLineLoop

    POP     DI
    POP     DS              { Retrieve the dest start offset                }
    ADD     DI,WORD PTR DestBitmapWidth  { Point to the start of the next   }
                                         {  scan line of the dest           }
    ADD     SI,WORD PTR SourceBitmapWidth  { Point to the start of the next }
                                           {  scan line of the source       }
    ADD     BX,WORD PTR SourceBitmapWidth  { Point to the start of the next }
                                           {  scan line of the mask         }
    DEC     WORD PTR RectHeight  { Count down scan lines                    }
    JNZ     @CopyRowsLoop
@CopyDone:
    POP     DI              { Restore caller's register variables           }
    POP     SI
{    STI}
  End; { Asm }
End; { ZapSystemToScreenMaskedX1 }


Procedure ScaleZapSystemToScreenMaskedX(SourceStartX,SourceStartY,SourceEndX,
  SourceEndY,DestStartX,DestStartY: Integer; SourcePtr: Pointer;
  DestPageBase: Word; SourceBitmapWidth,DestBitmapWidth: Integer;
  MaskPtr: Pointer; Scale: Word);
{ ------------------------------------------------------------------------- }
{ Mode X (320x240,256 colors) system memory-to-display memory masked copy   }
{ routine. Not particularly fast; images for which performance is critical  }
{ should be stored in off-screen memory and copied to screen via latches.   }
{ Works on all VGAs. Copies up to but not including column at SourceEndX and}
{ row at SourceEndY. No clipping is performed. Mask and source image are    }
{ both byte-per-pixel, and must be of same widths and reside at same        }
{ coordinates in their respective bitmaps. Assembly code tested with TASM   }
{ 2.0.                                                                      }
{ Pascal near-callable as:                                                  }
{    void CopySystemToScreenMaskedX(int SourceStartX, int SourceStartY,     }
{       int SourceEndX, int SourceEndY, int DestStartX, int DestStartY,     }
{       char * SourcePtr, unsigned int DestPageBase, int SourceBitmapWidth, }
{       int DestBitmapWidth, char * MaskPtr);                               }
{ ------------------------------------------------------------------------- }

Var
  RectWidth  : Word;        { Local storage for width of rectangle          }
  RectHeight : Word;        { Local storage for height of rectangle         }
  LeftMask   : Word;        { Local storage for left rect edge plane mask   }
  SourceSeg  : Word;
  MaskSeg    : Word;
  Count      : Word;
  Count1     : Word;
  Count2     : Word;
  RemainderX : Word;
  RemainderY : Word;
  ScaleHI    : Word;

Begin
  ScaleHi := Scale Shr 9;
  Asm
{    CLI}
    PUSH    SI              { Preserve caller's register variables          }
    PUSH    DI

    MOV     AX,SCREEN_SEG   { Point ES to display memory                    }
    MOV     ES,AX
    MOV     AX,WORD PTR SourceBitmapWidth
    MUL     WORD PTR SourceStartY { Top source rect scan line               }
    ADD     AX,WORD PTR SourceStartX
    MOV     BX,AX
    ADD     AX,WORD PTR SourcePtr { Offset of first source rect pixel in DS }
    MOV     SI,AX
    ADD     BX,WORD PTR MaskPtr { Offset of first mask pixel in DS          }

    MOV     AX,WORD PTR DestBitmapWidth
    SHR     AX,1            { Convert to width in addresses                 }
    SHR     AX,1
    MOV     WORD PTR DestBitmapWidth,AX  { Remember address width           }
    MUL     WORD PTR DestStartY { Top dest rect scan line                   }
    MOV     DI,WORD PTR DestStartX
    MOV     CX,DI
    SHR     DI,1            { X/4=offset of first dest rect pixel in scan   }
    SHR     DI,1            {  line                                         }
    ADD     DI,AX           { Offset of first dest rect pixel in page       }
    ADD     DI,WORD PTR DestPageBase  { Offset of first dest rect pixel     }
                                      {  in display memory                  }
    AND     CL,011b         { CL=first dest pixel's plane                   }
    MOV     AL,11h          { Upper nibble comes into play when plane wraps }
                            {  from 3 back to 0                             }
    SHL     AL,CL                { Set the bit for the first dest pixel's   }
    MOV     BYTE PTR LeftMask,AL {  plane in each nibble to 1               }

    MOV     AX,WORD PTR SourceEndX { Calculate # of pixels across rect      }
    SUB     AX,WORD PTR SourceStartX
    JLE     @CopyDone       { Skip if 0 or negative width                   }
    MOV     WORD PTR RectWidth,AX

    SUB     WORD PTR SourceBitmapWidth,AX
                            { Distance from end of one source scan line to  }
                            {  start of next                                }
    MOV     AX,WORD PTR SourceEndY
    SUB     AX,WORD PTR SourceStartY  { Height of rectangle                 }
    JLE     @CopyDone       { Skip if 0 or negative height                  }
    MOV     WORD PTR RectHeight,AX
    MOV     DX,SC_INDEX     { Point to SC Index register                    }
    MOV     AL,MAP_MASK
    OUT     DX,AL           { Point SC Index reg to the Map Mask            }
    INC     DX              { Point DX to SC Data reg                       }

    MOV     CX,WORD PTR ScaleHI
    MOV     WORD PTR Count2,CX
    MOV     WORD PTR RemainderY,0

    MOV     CX,WORD PTR SourcePtr[2]
    MOV     WORD PTR SourceSeg,CX
    MOV     CX,WORD PTR MaskPtr[2]
    MOV     WORD PTR MaskSeg,CX
@CopyRowsLoop:


    CMP     WORD PTR Count2,0
    JE      @NoLoopY
@GrowLoopY:
    PUSH    DS
    PUSH    DI              { Remember the start offset in the dest         }
    MOV     AL,BYTE PTR LeftMask

    MOV     CX,WORD PTR RectWidth
    MOV     WORD PTR Count,CX
    MOV     CX,WORD PTR ScaleHI
    MOV     WORD PTR Count1,CX
    MOV     WORD PTR RemainderX,0
@CopyScanLineLoop:

    CMP     WORD PTR Count1,0
    JE      @NoLoopX
@GrowLoopX:
    MOV     DS,WORD PTR MaskSeg
    CMP     BYTE PTR [BX],0 { Is this pixel mask-enabled?                   }
    JNZ     @MaskOff        { No, so don't draw it                          }
                            { Yes, draw the pixel                           }
    OUT     DX,AL           { Set the plane for this pixel                  }
    MOV     DS,WORD PTR SourceSeg
    MOV     AH,[SI]         { Get the pixel from the source                 }
    MOV     ES:[DI],AH      { Copy the pixel to the screen                  }
@MaskOff:
    ROL     AL,1            { Set mask for next pixel's plane               }
    ADC     DI,0            { Advance destination address only when wrapping}
                            {  from plane 3 to plane 0                      }
    DEC     WORD PTR Count1
    JNZ     @GrowLoopX

@NoLoopX:
    INC     BX              { Advance the mask pointer                      }
    INC     SI              { Advance the source pointer                    }

    MOV     CX,WORD PTR RemainderX
    ADD     CX,WORD PTR Scale
    MOV     WORD PTR RemainderX,CX
    AND     WORD PTR RemainderX,01FFh
    MOV     WORD PTR Count1,CX
    MOV     CX,9
    SHR     WORD PTR Count1,CL


    DEC     WORD PTR Count
    JNZ     @CopyScanLineLoop

    POP     DI
    POP     DS              { Retrieve the dest start offset                }
    ADD     DI,WORD PTR DestBitmapWidth  { Point to the start of the next   }
                                         {  scan line of the dest           }
    SUB     SI,WORD PTR RectWidth
    SUB     BX,WORD PTR RectWidth

    DEC     WORD PTR Count2
    JNZ     @GrowLoopY
@NoLoopY:
    ADD     SI,WORD PTR RectWidth
    ADD     BX,WORD PTR RectWidth

    ADD     SI,WORD PTR SourceBitmapWidth  { Point to the start of the next }
                                           {  scan line of the source       }
    ADD     BX,WORD PTR SourceBitmapWidth  { Point to the start of the next }
                                           {  scan line of the mask         }

    MOV     CX,WORD PTR RemainderY
    ADD     CX,WORD PTR Scale
    MOV     WORD PTR RemainderY,CX
    AND     WORD PTR RemainderY,01FFh
    MOV     WORD PTR Count2,CX
    MOV     CX,9
    SHR     WORD PTR Count2,CL

    DEC     WORD PTR RectHeight  { Count down scan lines                    }
    JNZ     @CopyRowsLoop
@CopyDone:
    POP     DI              { Restore caller's register variables           }
    POP     SI
{    STI}
  End; { Asm }
End; { ScaleZapSystemToScreenMaskedX }


Procedure Scale3DZapSystemToScreenMaskedX(SourceStartX,SourceStartY,SourceEndX,
  SourceEndY,DestStartX,DestStartY: Integer; SourcePtr: Pointer;
  DestPageBase: Word; SourceBitmapWidth,DestBitmapWidth: Integer;
  MaskPtr: Pointer; Height1,Height2,Width: Word; Skew: Integer; Vertical: Boolean);
{ ------------------------------------------------------------------------- }
{ Mode X (320x240,256 colors) system memory-to-display memory masked copy   }
{ routine. Not particularly fast; images for which performance is critical  }
{ should be stored in off-screen memory and copied to screen via latches.   }
{ Works on all VGAs. Copies up to but not including column at SourceEndX and}
{ row at SourceEndY. No clipping is performed. Mask and source image are    }
{ both byte-per-pixel, and must be of same widths and reside at same        }
{ coordinates in their respective bitmaps. Assembly code tested with TASM   }
{ 2.0.                                                                      }
{ Pascal near-callable as:                                                  }
{    void CopySystemToScreenMaskedX(int SourceStartX, int SourceStartY,     }
{       int SourceEndX, int SourceEndY, int DestStartX, int DestStartY,     }
{       char * SourcePtr, unsigned int DestPageBase, int SourceBitmapWidth, }
{       int DestBitmapWidth, char * MaskPtr);                               }
{ ------------------------------------------------------------------------- }

Var
  RectWidth  : Word;        { Local storage for width of rectangle          }
  RectHeight : Word;        { Local storage for height of rectangle         }
  LeftMask   : Word;        { Local storage for left rect edge plane mask   }
  SourceSeg  : Word;
  MaskSeg    : Word;
  Count      : Word;
  Count1     : Word;
  Count2     : Word;
  RemainderX : Word;
  RemainderY : Word;
  ScaleHIX   : Word;
  ScaleLOX   : Word;
  ScaleHIY   : Word;
  ScaleLOY   : Word;
  ScaleXInc  : Integer;
  ScaleYInc  : Integer;
  ScaleX     : Word;
  ScaleY     : Word;
  Creep      : Word;
  CreepInc   : Word;
  DotAdd     : Word;
  DotInc     : Integer;
  InitDotAdd : Integer;

Label _Vertical;

Begin
  If (Height1 = 0) Or (Height2 = 0) Or (Width = 0) Then Exit;
  If Vertical Then Goto _Vertical;
{ ------------------------------------------------------------------------- }
{ HORIZONTAL                                                                }
{ ------------------------------------------------------------------------- }
  ScaleHiY  := Round(Height1 / (SourceEndY - SourceStartY) * 512);
  ScaleLoY  := Round(Height2 / (SourceEndY - SourceStartY) * 512);
  ScaleHiX  := Round(Width   / (SourceEndX - SourceStartX) * 512);
  ScaleXInc := 0;
  ScaleYInc := Round((Integer(ScaleLoY) - Integer(ScaleHiY)) / ((ScaleHiX / 512) * Integer(SourceEndX - SourceStartX)));
  ScaleX    := ScaleHiX;
  ScaleY    := ScaleHiY;
  ScaleHiX  := ScaleHiX Shr 9;
  ScaleHiY  := ScaleHiY Shr 9;
  Creep     := 0;
  CreepInc  := Round(Abs(Abs(Integer(Height1) - Integer(Height2)) / 2 + Integer(Skew)) / Integer(Width) * 512);
  DotAdd    := 0;
  If Height1 > Height2 Then
  Begin
    If (((Integer(Height1) - Integer(Height2)) Div 2) + Integer(Skew)) > 0
     Then DotInc := 1 Else DotInc := -1;
  End
  Else
  Begin
    If (((Integer(Height1) - Integer(Height2)) Div 2) - Integer(Skew)) > 0
     Then DotInc := 1 Else DotInc := -1;
  End;
  InitDotAdd := 0;
  Asm
    PUSH    SI                                { Preserve caller's registers }
    PUSH    DI
    MOV     AX,SCREEN_SEG                     { Point ES to display memory  }
    MOV     ES,AX
    MOV     AX,WORD PTR SourceBitmapWidth
    MUL     WORD PTR SourceStartY             { Top source rect scan line   }
    ADD     AX,WORD PTR SourceStartX
    MOV     BX,AX
    ADD     AX,WORD PTR SourcePtr             { Offset of first source rect }
    MOV     SI,AX                             {  pixel in DS                }
    ADD     BX,WORD PTR MaskPtr               { Offset of first mask pixel  }
                                              {  in DS                      }
    MOV     AX,WORD PTR DestBitmapWidth
    SHR     AX,1                              { Change to width in addresses}
    SHR     AX,1
    MOV     WORD PTR DestBitmapWidth,AX       { Remember address width      }
    MUL     WORD PTR DestStartY               { Top dest rect scan line     }
    MOV     DI,WORD PTR DestStartX
    MOV     CX,DI
    SHR     DI,1                              { X/4=offset of first dest    }
    SHR     DI,1                              {  rect pixel in scan line    }
    ADD     DI,AX                             { Offset of first dest rect   }
                                              {  pixel in page              }
    ADD     DI,WORD PTR DestPageBase          { Offset of first dest rect   }
                                              {  pixel in display memory    }
    AND     CL,011b                           { CL=first dest pixel's plane }
    MOV     AL,11h                            { Upper nibble comes into play}
                                              {  when plane wraps from 3    }
                                              {  back to 0                  }
    SHL     AL,CL                             { Set the bit for the first   }
    MOV     BYTE PTR LeftMask,AL              {  dest pixel'  plane in each }
                                              {  nibble to 1                }
    MOV     AX,WORD PTR SourceEndX            { Calculate # of pixels across}
    SUB     AX,WORD PTR SourceStartX          {  rect                       }
    JLE     @CopyDone                         { Skip if 0 or negative width }
    MOV     WORD PTR RectWidth,AX
    MOV     AX,WORD PTR SourceEndY
    SUB     AX,WORD PTR SourceStartY          { Height of rectangle         }
    JLE     @CopyDone                         { Skip if 0 or negative height}
    MOV     WORD PTR RectHeight,AX
    MOV     DX,SC_INDEX                       { Point to SC Index register  }
    MOV     AL,MAP_MASK
    OUT     DX,AL                             { Point SC Index reg to the   }
                                              {  Map Mask                   }
    INC     DX                                { Point DX to SC Data reg     }
    MOV     CX,WORD PTR ScaleHIX
    MOV     WORD PTR Count2,CX
    MOV     WORD PTR RemainderX,0

    MOV     CX,WORD PTR SourcePtr[2]
    MOV     WORD PTR SourceSeg,CX
    MOV     CX,WORD PTR MaskPtr[2]
    MOV     WORD PTR MaskSeg,CX

    MOV     CX,WORD PTR InitDotAdd            { Initial creep }
    OR      CX,CX
    JZ      @NoDotAdd
@DotAddLoop:
    ROL     BYTE PTR LeftMask,1
    ADD     DI,WORD PTR DestBitmapWidth
    LOOP    @DotAddLoop
@NoDotAdd:

@CopyRowsLoop:
    CMP     WORD PTR Count2,0
    JE      @NoLoopY
@GrowLoopY:
    PUSH    DS

    MOV     CX,WORD PTR DotAdd                { Creep }
    OR      CX,CX
    JZ      @NoAdd
    CMP     CX,0
    JL      @CreepLeft
@CreepRight:
    ADD     DI,WORD PTR DestBitmapWidth
    LOOP    @CreepRight
    JMP     @NoAdd
@CreepLeft:
    NEG     CX
@CreepLeftLoop:
    SUB     DI,WORD PTR DestBitmapWidth
    LOOP    @CreepLeftLoop
@NoAdd:

    PUSH    DI                                { Remember the start offset in}
                                              {  the dest                   }
    PUSH    BX
    PUSH    SI

    MOV     AL,BYTE PTR LeftMask
    MOV     CX,WORD PTR RectHeight
    MOV     WORD PTR Count,CX
    MOV     CX,WORD PTR ScaleY
    MOV     WORD PTR ScaleHIY,CX
    MOV     CX,9
    SHR     WORD PTR ScaleHIY,CL
    MOV     CX,WORD PTR ScaleHIY
    MOV     WORD PTR Count1,CX
    MOV     WORD PTR RemainderY,0

    OUT     DX,AL                             { Set the plane for this pixel}

@CopyScanLineLoop:
    CMP     WORD PTR Count1,0
    JE      @NoLoopX
@GrowLoopX:
    MOV     DS,WORD PTR MaskSeg
    CMP     BYTE PTR [BX],0                   { Is this pixel mask-enabled? }
    JNZ     @MaskOff                          { No, so don't draw it        }
                                              { Yes, draw the pixel         }
    MOV     DS,WORD PTR SourceSeg
    MOV     AH,[SI]                           { Get the pixel from source   }
    MOV     ES:[DI],AH                        { Copy the pixel to the screen}
@MaskOff:
    ADD     DI,WORD PTR DestBitmapWidth       { Point to the start of the   }
                                              {  next scan line of the dest }
    DEC     WORD PTR Count1
    JNZ     @GrowLoopX
@NoLoopX:
    ADD     SI,WORD PTR SourceBitmapWidth  { Point to the start of the next }
                                           {  scan line of the source       }
    ADD     BX,WORD PTR SourceBitmapWidth  { Point to the start of the next }
                                           {  scan line of the mask         }

    MOV     CX,WORD PTR RemainderY
    ADD     CX,WORD PTR ScaleY
    MOV     WORD PTR RemainderY,CX
    AND     WORD PTR RemainderY,01FFh
    MOV     WORD PTR Count1,CX
    MOV     CX,9
    SHR     WORD PTR Count1,CL
    DEC     WORD PTR Count
    JNZ     @CopyScanLineLoop

    MOV     WORD PTR DotAdd,0
    MOV     CX,WORD PTR Creep                 { Handle creep here }
    ADD     CX,WORD PTR CreepInc
@TryAgain:
    CMP     CX,512
    JB      @NoNewDot
    SUB     CX,512
    MOV     WORD PTR Creep,CX
    MOV     CX,WORD PTR DotInc
    ADD     WORD PTR DotAdd,CX
    MOV     CX,WORD PTR Creep
    JMP     @TryAgain
@NoNewDot:
    MOV     WORD PTR Creep,CX
@EndCreep:
    POP     SI
    POP     BX
    POP     DI
    POP     DS                                { Retrieve the dest start     }
                                              {  offset                     }
    ROL     BYTE PTR LeftMask,1               { Set mask for next pixel's   }
                                              {  plane                      }
    ADC     DI,0                              { Advance destination address }
                                              {  only when wrapping from    }
                                              {  plane 3 to plane 0         }
    MOV     CX,WORD PTR ScaleYInc
    ADD     WORD PTR ScaleY,CX

    DEC     WORD PTR Count2
    JNZ     @GrowLoopY

    MOV     CX,WORD PTR ScaleXInc
    ADD     WORD PTR ScaleX,CX
@NoLoopY:
    INC     SI
    INC     BX
    MOV     CX,WORD PTR RemainderX
    ADD     CX,WORD PTR ScaleX
    MOV     WORD PTR RemainderX,CX
    AND     WORD PTR RemainderX,01FFh
    MOV     WORD PTR Count2,CX
    MOV     CX,9
    SHR     WORD PTR Count2,CL

    DEC     WORD PTR RectWidth        { Count down scan lines          }
    JNZ     @CopyRowsLoop
@CopyDone:
    POP     DI                        { Restore caller's register variables }
    POP     SI
  End; { Asm }
  Exit;
{ ------------------------------------------------------------------------- }
{ VERTICAL                                                                  }
{ ------------------------------------------------------------------------- }
_Vertical:
  ScaleHiX  := Round(Height1 / (SourceEndX - SourceStartX) * 512);
  ScaleLoX  := Round(Height2 / (SourceEndX - SourceStartX) * 512);
  ScaleHiY  := Round(Width   / (SourceEndY - SourceStartY) * 512);
  ScaleXInc := Round((Integer(ScaleLoX) - Integer(ScaleHiX)) / ((ScaleHiY / 512) * (SourceEndY - SourceStartY)));
  ScaleYInc := 0;
  ScaleX    := ScaleHiX;
  ScaleY    := ScaleHiY;
  ScaleHiX  := ScaleHiX Shr 9;
  ScaleHiY  := ScaleHiY Shr 9;
  Creep     := 0;
  CreepInc  := Round(Abs(Abs(Integer(Height1) - Integer(Height2)) / 2 + Integer(Skew)) / Integer(Width) * 512);
  DotAdd    := 0;
  If Height1 > Height2 Then
  Begin
    If (((Integer(Height1) - Integer(Height2)) Div 2) + Integer(Skew)) > 0
     Then DotInc := 1 Else DotInc := -1;
  End
  Else
  Begin
    If (((Integer(Height1) - Integer(Height2)) Div 2) - Integer(Skew)) > 0
     Then DotInc := 1 Else DotInc := -1;
  End;
  InitDotAdd := 0;
  Asm
    PUSH    SI                                { Preserve caller's registers }
    PUSH    DI
    MOV     AX,SCREEN_SEG                     { Point ES to display memory  }
    MOV     ES,AX
    MOV     AX,WORD PTR SourceBitmapWidth
    MUL     WORD PTR SourceStartY             { Top source rect scan line   }
    ADD     AX,WORD PTR SourceStartX
    MOV     BX,AX
    ADD     AX,WORD PTR SourcePtr             { Offset of first source rect }
    MOV     SI,AX                             {  pixel in DS                }
    ADD     BX,WORD PTR MaskPtr               { Offset of first mask pixel  }
                                              {  in DS                      }
    MOV     AX,WORD PTR DestBitmapWidth
    SHR     AX,1                              { Change to width in addresses}
    SHR     AX,1
    MOV     WORD PTR DestBitmapWidth,AX       { Remember address width      }
    MUL     WORD PTR DestStartY               { Top dest rect scan line     }
    MOV     DI,WORD PTR DestStartX
    MOV     CX,DI
    SHR     DI,1                              { X/4=offset of first dest    }
    SHR     DI,1                              {  rect pixel in scan line    }
    ADD     DI,AX                             { Offset of first dest rect   }
                                              {  pixel in page              }
    ADD     DI,WORD PTR DestPageBase          { Offset of first dest rect   }
                                              {  pixel in display memory    }
    AND     CL,011b                           { CL=first dest pixel's plane }
    MOV     AL,11h                            { Upper nibble comes into play}
                                              {  when plane wraps from 3    }
                                              {  back to 0                  }
    SHL     AL,CL                             { Set the bit for the first   }
    MOV     BYTE PTR LeftMask,AL              {  dest pixel'  plane in each }
                                              {  nibble to 1                }
    MOV     AX,WORD PTR SourceEndX            { Calculate # of pixels across}
    SUB     AX,WORD PTR SourceStartX          {  rect                       }
    JLE     @CopyDone                         { Skip if 0 or negative width }
    MOV     WORD PTR RectWidth,AX
    SUB     WORD PTR SourceBitmapWidth,AX     { Distance from end of one    }
                                              {  source scan line to start  }
                                              {  of next                    }
    MOV     AX,WORD PTR SourceEndY
    SUB     AX,WORD PTR SourceStartY          { Height of rectangle         }
    JLE     @CopyDone                         { Skip if 0 or negative height}
    MOV     WORD PTR RectHeight,AX
    MOV     DX,SC_INDEX                       { Point to SC Index register  }
    MOV     AL,MAP_MASK
    OUT     DX,AL                             { Point SC Index reg to the   }
                                              {  Map Mask                   }
    INC     DX                                { Point DX to SC Data reg     }
    MOV     CX,WORD PTR ScaleHIY
    MOV     WORD PTR Count2,CX
    MOV     WORD PTR RemainderY,0

    MOV     CX,WORD PTR SourcePtr[2]
    MOV     WORD PTR SourceSeg,CX
    MOV     CX,WORD PTR MaskPtr[2]
    MOV     WORD PTR MaskSeg,CX

    MOV     CX,WORD PTR InitDotAdd            { Initial creep }
    OR      CX,CX
    JZ      @NoDotAdd
@DotAddLoop:
    ROL     BYTE PTR LeftMask,1
    ADC     DI,0
    LOOP    @DotAddLoop
@NoDotAdd:

@CopyRowsLoop:
    CMP     WORD PTR Count2,0
    JE      @NoLoopY
@GrowLoopY:
    PUSH    DS

    MOV     CX,WORD PTR DotAdd                { Creep }
    OR      CX,CX
    JZ      @NoAdd
    CMP     CX,0
    JL      @CreepLeft
@CreepRight:
    ROL     BYTE PTR LeftMask,1
    ADC     DI,0
    LOOP    @CreepRight
    JMP     @NoAdd
@CreepLeft:
    NEG     CX
@CreepLeftLoop:
    ROR     BYTE PTR LeftMask,1
    SBB     DI,0
    LOOP    @CreepLeftLoop
@NoAdd:

    PUSH    DI                                { Remember the start offset in}
                                              {  the dest                   }
    MOV     AL,BYTE PTR LeftMask
    MOV     CX,WORD PTR RectWidth
    MOV     WORD PTR Count,CX
    MOV     CX,WORD PTR ScaleX
    MOV     WORD PTR ScaleHIX,CX
    MOV     CX,9
    SHR     WORD PTR ScaleHIX,CL
    MOV     CX,WORD PTR ScaleHIX
    MOV     WORD PTR Count1,CX
    MOV     WORD PTR RemainderX,0
@CopyScanLineLoop:
    CMP     WORD PTR Count1,0
    JE      @NoLoopX
@GrowLoopX:
    MOV     DS,WORD PTR MaskSeg
    CMP     BYTE PTR [BX],0                   { Is this pixel mask-enabled? }
    JNZ     @MaskOff                          { No, so don't draw it        }
                                              { Yes, draw the pixel         }
    OUT     DX,AL                             { Set the plane for this pixel}
    MOV     DS,WORD PTR SourceSeg
    MOV     AH,[SI]                           { Get the pixel from source   }
    MOV     ES:[DI],AH                        { Copy the pixel to the screen}
@MaskOff:
    ROL     AL,1                              { Set mask for next pixel's   }
                                              {  plane                      }
    ADC     DI,0                              { Advance destination address }
                                              {  only when wrapping from    }
                                              {  plane 3 to plane 0         }
    DEC     WORD PTR Count1
    JNZ     @GrowLoopX
@NoLoopX:
    INC     BX                                { Advance the mask pointer    }
    INC     SI                                { Advance the source pointer  }
    MOV     CX,WORD PTR RemainderX
    ADD     CX,WORD PTR ScaleX
    MOV     WORD PTR RemainderX,CX
    AND     WORD PTR RemainderX,01FFh
    MOV     WORD PTR Count1,CX
    MOV     CX,9
    SHR     WORD PTR Count1,CL
    DEC     WORD PTR Count
    JNZ     @CopyScanLineLoop

    MOV     WORD PTR DotAdd,0
    MOV     CX,WORD PTR Creep                 { Handle creep here }
    ADD     CX,WORD PTR CreepInc
@TryAgain:
    CMP     CX,512
    JB      @NoNewDot
    SUB     CX,512
    MOV     WORD PTR Creep,CX
    MOV     CX,WORD PTR DotInc
    ADD     WORD PTR DotAdd,CX
    MOV     CX,WORD PTR Creep
    JMP     @TryAgain
@NoNewDot:
    MOV     WORD PTR Creep,CX
@EndCreep:

    POP     DI
    POP     DS                                { Retrieve the dest start     }
                                              {  offset                     }
    ADD     DI,WORD PTR DestBitmapWidth       { Point to the start of the   }
                                              {  next scan line of the dest }
    SUB     SI,WORD PTR RectWidth
    SUB     BX,WORD PTR RectWidth

    MOV     CX,WORD PTR ScaleXInc
    ADD     WORD PTR ScaleX,CX

    DEC     WORD PTR Count2
    JNZ     @GrowLoopY

    MOV     CX,WORD PTR ScaleYInc
    ADD     WORD PTR ScaleY,CX
@NoLoopY:
    ADD     SI,WORD PTR RectWidth
    ADD     BX,WORD PTR RectWidth

    ADD     SI,WORD PTR SourceBitmapWidth  { Point to the start of the next }
                                           {  scan line of the source       }
    ADD     BX,WORD PTR SourceBitmapWidth  { Point to the start of the next }
                                           {  scan line of the mask         }
    MOV     CX,WORD PTR RemainderY
    ADD     CX,WORD PTR ScaleY
    MOV     WORD PTR RemainderY,CX
    AND     WORD PTR RemainderY,01FFh
    MOV     WORD PTR Count2,CX
    MOV     CX,9
    SHR     WORD PTR Count2,CL

    DEC     WORD PTR RectHeight            { Count down scan lines          }
    JNZ     @CopyRowsLoop
@CopyDone:
    POP     DI                        { Restore caller's register variables }
    POP     SI
  End; { Asm }
End; { Scale3DZapSystemToScreenMaskedX }


Procedure RotateScaleZapSystemToScreenMaskedX(SourceStartX,SourceStartY,SourceEndX,
  SourceEndY,DestStartX,DestStartY: Integer; SourcePtr: Pointer;
  DestPageBase: Word; SourceBitmapWidth,DestBitmapWidth: Integer;
  MaskPtr: Pointer; Scale: Word; Degrees: Word; RotateX,RotateY: Word;
  Var X1,Y1,X2,Y2: Integer);
{ ------------------------------------------------------------------------- }
{ Mode X (320x240,256 colors) system memory-to-display memory masked copy   }
{ routine. Not particularly fast; images for which performance is critical  }
{ should be stored in off-screen memory and copied to screen via latches.   }
{ Works on all VGAs. Copies up to but not including column at SourceEndX and}
{ row at SourceEndY. No clipping is performed. Mask and source image are    }
{ both byte-per-pixel, and must be of same widths and reside at same        }
{ coordinates in their respective bitmaps. Assembly code tested with TASM   }
{ 2.0.                                                                      }
{ Pascal near-callable as:                                                  }
{    void CopySystemToScreenMaskedX(int SourceStartX, int SourceStartY,     }
{       int SourceEndX, int SourceEndY, int DestStartX, int DestStartY,     }
{       char * SourcePtr, unsigned int DestPageBase, int SourceBitmapWidth, }
{       int DestBitmapWidth, char * MaskPtr);                               }
{                                                                           }
{      1 - 0-45ø        2 - 46-90ø       3 - 91-135ø      4 - 136-180ø      }
{      5 - 181-225ø     6 - 226-270ø     7 - 271-315ø     8 - 315-359ø      }
{ ------------------------------------------------------------------------- }

Var
  RectWidth       : Word;   { Local storage for width of rectangle          }
  RectHeight      : Word;   { Local storage for height of rectangle         }
  LeftMask        : Word;   { Local storage for left rect edge plane mask   }
  SourceSeg       : Word;
  MaskSeg         : Word;
  Count           : Word;
  Count1          : Word;
  Count2          : Word;
  RCount1         : Word;
  RCount2         : Word;
  RemainderX      : Word;
  RemainderY      : Word;
  ScaleHIX        : Word;
  ScaleHiY        : Word;
  ScaleX          : Word;
  ScaleY          : Word;
  AddValX         : Word;
  AddValY         : Word;
  LeftRightOfs    : Word;
  UpDownOfs       : Word;
  AddOfs          : LongInt;
  AddOfsHi        : Integer;
  AddOfsLo        : Integer;
  S,_X1,_Y1       : Real;
  A1,A2,A3,A4     : Word;
  _A1,_A2,_A3,_A4 : Word;
  GotA2           : Word;

Begin
  GotA2    := 0;
  Degrees  := Degrees Mod 360;
  ScaleX   := Round(Scale * _XMult[Degrees]);
  ScaleY   := Round(Scale * _YMult[Degrees]);
  ScaleHiX := ScaleX Shr 9;
  ScaleHiY := ScaleY Shr 9;
  AddValX  := _AddValX[Degrees];
  AddValY  := _AddValY[Degrees];
  Asm
    MOV     BX,_SCREEN_WIDTH
    SHL     BX,1
    SHL     BX,1
    MOV     AX,BX
    MUL     WORD PTR RotateY
    ADD     AX,WORD PTR RotateX
    MOV     WORD PTR LeftRightOfs,AX
    MOV     AX,BX
    MUL     WORD PTR RotateX
    SUB     AX,WORD PTR RotateY
    MOV     WORD PTR UpDownOfs,AX
  End; { Asm }
  S := Scale / 512;
  _X1 := -RotateX * _Cos[Degrees] - RotateY * _Sin[Degrees] + RotateX;
  _Y1 :=  RotateX * _Sin[Degrees] - RotateY * _Cos[Degrees] + RotateY;
{  XOfs := Round(S * _X1);
  YOfs := Round(S * _Y1);}
  AddOfs   := (_SCREEN_WIDTH * 4) * Round(S * _Y1) + Round(S * _X1);
  AddOfsHi := AddOfs Shr 2;
  AddOfsLo := AddOfs And 3;
  Asm
{    CLI}
    PUSH    SI              { Preserve caller's register variables          }
    PUSH    DI

    MOV     AX,SCREEN_SEG   { Point ES to display memory                    }
    MOV     ES,AX
    MOV     AX,WORD PTR SourceBitmapWidth
    MUL     WORD PTR SourceStartY { Top source rect scan line               }
    ADD     AX,WORD PTR SourceStartX
    MOV     BX,AX
    ADD     AX,WORD PTR SourcePtr { Offset of first source rect pixel in DS }
    MOV     SI,AX
    ADD     BX,WORD PTR MaskPtr { Offset of first mask pixel in DS          }

    MOV     AX,WORD PTR DestBitmapWidth
    SHR     AX,1            { Convert to width in addresses                 }
    SHR     AX,1
    MOV     WORD PTR DestBitmapWidth,AX  { Remember address width           }
    MUL     WORD PTR DestStartY { Top dest rect scan line                   }
    MOV     DI,WORD PTR DestStartX
    ADD     AX,WORD PTR AddOfsHi
    ADD     DI,WORD PTR AddOfsLo
    MOV     CX,DI
    SHR     DI,1            { X/4=offset of first dest rect pixel in scan   }
    SHR     DI,1            {  line                                         }
    ADD     DI,AX           { Offset of first dest rect pixel in page       }
    ADD     DI,WORD PTR DestPageBase  { Offset of first dest rect pixel     }
                                      {  in display memory                  }

    MOV     WORD PTR A1,DI  { Save address of upper left corner             }
    MOV     WORD PTR A2,DI  { Initialize the other addresses                }
    MOV     WORD PTR A3,DI
    MOV     WORD PTR A4,DI

    AND     CL,011b         { CL=first dest pixel's plane                   }
    MOV     AL,11h          { Upper nibble comes into play when plane wraps }
                            {  from 3 back to 0                             }
    SHL     AL,CL                { Set the bit for the first dest pixel's   }
    MOV     BYTE PTR LeftMask,AL {  plane in each nibble to 1               }

    MOV     AX,WORD PTR SourceEndX { Calculate # of pixels across rect      }
    SUB     AX,WORD PTR SourceStartX
    JG      @Continue       { Skip if 0 or negative width                   }
    JMP     @CopyDone
@Continue:
    MOV     WORD PTR RectWidth,AX

    SUB     WORD PTR SourceBitmapWidth,AX
                            { Distance from end of one source scan line to  }
                            {  start of next                                }
    MOV     AX,WORD PTR SourceEndY
    SUB     AX,WORD PTR SourceStartY  { Height of rectangle                 }
    JG      @Continue1      { Skip if 0 or negative height                  }
    JMP     @CopyDone
@Continue1:
    MOV     WORD PTR RectHeight,AX
    MOV     DX,SC_INDEX     { Point to SC Index register                    }
    MOV     AL,MAP_MASK
    OUT     DX,AL           { Point SC Index reg to the Map Mask            }
    INC     DX              { Point DX to SC Data reg                       }

    MOV     CX,WORD PTR ScaleHIY
    MOV     WORD PTR Count2,CX
    MOV     WORD PTR RemainderY,0
    MOV     WORD PTR RCount2,0

    MOV     CX,WORD PTR SourcePtr[2]
    MOV     WORD PTR SourceSeg,CX
    MOV     CX,WORD PTR MaskPtr[2]
    MOV     WORD PTR MaskSeg,CX

    MOV     CX,WORD PTR Degrees
    CMP     CX,45
    JA      @CheckPart2
    JMP     @CopyRowsLoop1
@CheckPart2:
    CMP     CX,90
    JA      @CheckPart3
    JMP     @CopyRowsLoop2
@CheckPart3:
    CMP     CX,135
    JA      @CheckPart4
    JMP     @CopyRowsLoop3
@CheckPart4:
    CMP     CX,180
    JA      @CheckPart5
    JMP     @CopyRowsLoop4
@CheckPart5:
    CMP     CX,225
    JA      @CheckPart6
    JMP     @CopyRowsLoop5
@CheckPart6:
    CMP     CX,270
    JA      @CheckPart7
    JMP     @CopyRowsLoop6
@CheckPart7:
    CMP     CX,315
    JA      @CheckPart8
    JMP     @CopyRowsLoop7
@CheckPart8:
    JMP     @CopyRowsLoop8

 { --------------------------- Part 1: 0-45ø ------------------------------ }

@CopyRowsLoop1:
    CMP     WORD PTR Count2,0
    JE      @NoLoopY1
@GrowLoopY1:
    PUSH    DS
    PUSH    DI              { Remember the start offset in the dest         }
    MOV     AL,BYTE PTR LeftMask
    MOV     CX,WORD PTR RectWidth
    MOV     WORD PTR Count,CX
    MOV     CX,WORD PTR ScaleHIX
    MOV     WORD PTR Count1,CX
    MOV     WORD PTR RemainderX,0
    MOV     WORD PTR RCount1,0
    MOV     WORD PTR A3,DI  { Save address of lower left corner             }
@CopyScanLineLoop1:
    CMP     WORD PTR Count1,0
    JE      @NoLoopX1
@GrowLoopX1:
    MOV     DS,WORD PTR MaskSeg
    CMP     BYTE PTR [BX],0 { Is this pixel mask-enabled?                   }
    JNZ     @MaskOff1       { No, so don't draw it                          }
                            { Yes, draw the pixel                           }
    OUT     DX,AL           { Set the plane for this pixel                  }
    MOV     DS,WORD PTR SourceSeg
    MOV     AH,[SI]         { Get the pixel from the source                 }
    MOV     ES:[DI],AH      { Copy the pixel to the screen                  }
@MaskOff1:
    ROL     AL,1            { Set mask for next pixel's plane               }
    ADC     DI,0            { Advance destination address only when wrapping}
                            {  from plane 3 to plane 0                      }
    MOV     CX,WORD PTR AddValX
    ADD     WORD PTR RCount1,CX
    CMP     WORD PTR RCount1,512
    JB      @SkipHorz1
    SUB     DI,WORD PTR DestBitmapWidth
    SUB     WORD PTR RCount1,512
@SkipHorz1:
    DEC     WORD PTR Count1
    JNZ     @GrowLoopX1
@NoLoopX1:
    INC     BX              { Advance the mask pointer                      }
    INC     SI              { Advance the source pointer                    }
    MOV     CX,WORD PTR RemainderX
    ADD     CX,WORD PTR ScaleX
    MOV     WORD PTR RemainderX,CX
    AND     WORD PTR RemainderX,01FFh
    MOV     WORD PTR Count1,CX
    MOV     CX,9
    SHR     WORD PTR Count1,CL
    DEC     WORD PTR Count
    JNZ     @CopyScanLineLoop1
    CMP     WORD PTR GotA2,0
    JNE     @AlreadyGot1
    MOV     WORD PTR GotA2,1
    MOV     WORD PTR A2,DI
@AlreadyGot1:
    MOV     WORD PTR A4,DI  { Save address of lower right corner            }
    POP     DI
    POP     DS              { Retrieve the dest start offset                }
    ADD     DI,WORD PTR DestBitmapWidth  { Point to the start of the next   }
                                         {  scan line of the dest           }
    MOV     CX,WORD PTR AddValY
    ADD     WORD PTR RCount2,CX
    CMP     WORD PTR RCount2,512
    JB      @SkipVert1
    ROL     BYTE PTR LeftMask,1
    ADC     DI,0
    SUB     DI,WORD PTR DestBitmapWidth
    SUB     WORD PTR RCount2,512
@SkipVert1:
    SUB     SI,WORD PTR RectWidth
    SUB     BX,WORD PTR RectWidth
    DEC     WORD PTR Count2
    JNZ     @GrowLoopY1
@NoLoopY1:
    ADD     SI,WORD PTR RectWidth
    ADD     BX,WORD PTR RectWidth
    ADD     SI,WORD PTR SourceBitmapWidth  { Point to the start of the next }
                                           {  scan line of the source       }
    ADD     BX,WORD PTR SourceBitmapWidth  { Point to the start of the next }
                                           {  scan line of the mask         }
    MOV     CX,WORD PTR RemainderY
    ADD     CX,WORD PTR ScaleY
    MOV     WORD PTR RemainderY,CX
    AND     WORD PTR RemainderY,01FFh
    MOV     WORD PTR Count2,CX
    MOV     CX,9
    SHR     WORD PTR Count2,CL

    DEC     WORD PTR RectHeight  { Count down scan lines                    }
    JNZ     @CopyRowsLoop1
    JMP     @CopyDone

 { --------------------------- Part 2: 46-90ø ----------------------------- }

@CopyRowsLoop2:
    CMP     WORD PTR Count2,0
    JE      @NoLoopY2
@GrowLoopY2:
    PUSH    DS
    PUSH    DI              { Remember the start offset in the dest         }
    MOV     AL,BYTE PTR LeftMask
    MOV     CX,WORD PTR RectWidth
    MOV     WORD PTR Count,CX
    MOV     CX,WORD PTR ScaleHIX
    MOV     WORD PTR Count1,CX
    MOV     WORD PTR RemainderX,0
    MOV     WORD PTR RCount1,0
    MOV     WORD PTR A3,DI  { Save address of lower left corner             }
@CopyScanLineLoop2:
    CMP     WORD PTR Count1,0
    JE      @NoLoopX2
@GrowLoopX2:
    MOV     DS,WORD PTR MaskSeg
    CMP     BYTE PTR [BX],0 { Is this pixel mask-enabled?                   }
    JNZ     @MaskOff2       { No, so don't draw it                          }
                            { Yes, draw the pixel                           }
    OUT     DX,AL           { Set the plane for this pixel                  }
    MOV     DS,WORD PTR SourceSeg
    MOV     AH,[SI]         { Get the pixel from the source                 }
    MOV     ES:[DI],AH      { Copy the pixel to the screen                  }
@MaskOff2:
    SUB     DI,WORD PTR DestBitmapWidth
    MOV     CX,WORD PTR AddValX
    ADD     WORD PTR RCount1,CX
    CMP     WORD PTR RCount1,512
    JB      @SkipHorz2
    ROL     AL,1            { Set mask for next pixel's plane               }
    ADC     DI,0            { Advance destination address only when wrapping}
                            {  from plane 3 to plane 0                      }
    SUB     WORD PTR RCount1,512
@SkipHorz2:
    DEC     WORD PTR Count1
    JNZ     @GrowLoopX2
@NoLoopX2:
    INC     BX              { Advance the mask pointer                      }
    INC     SI              { Advance the source pointer                    }
    MOV     CX,WORD PTR RemainderX
    ADD     CX,WORD PTR ScaleX
    MOV     WORD PTR RemainderX,CX
    AND     WORD PTR RemainderX,01FFh
    MOV     WORD PTR Count1,CX
    MOV     CX,9
    SHR     WORD PTR Count1,CL
    DEC     WORD PTR Count
    JNZ     @CopyScanLineLoop2
    CMP     WORD PTR GotA2,0
    JNE     @AlreadyGot2
    MOV     WORD PTR GotA2,1
    MOV     WORD PTR A2,DI
@AlreadyGot2:
    MOV     WORD PTR A4,DI  { Save address of lower right corner            }
    POP     DI
    POP     DS              { Retrieve the dest start offset                }
    ROL     BYTE PTR LeftMask,1
    ADC     DI,0
    MOV     CX,WORD PTR AddValY
    ADD     WORD PTR RCount2,CX
    CMP     WORD PTR RCount2,512
    JB      @SkipVert2
    ROR     BYTE PTR LeftMask,1
    SBB     DI,0
    ADD     DI,WORD PTR DestBitmapWidth
    SUB     WORD PTR RCount2,512
@SkipVert2:
    SUB     SI,WORD PTR RectWidth
    SUB     BX,WORD PTR RectWidth
    DEC     WORD PTR Count2
    JNZ     @GrowLoopY2
@NoLoopY2:
    ADD     SI,WORD PTR RectWidth
    ADD     BX,WORD PTR RectWidth
    ADD     SI,WORD PTR SourceBitmapWidth  { Point to the start of the next }
                                           {  scan line of the source       }
    ADD     BX,WORD PTR SourceBitmapWidth  { Point to the start of the next }
                                           {  scan line of the mask         }
    MOV     CX,WORD PTR RemainderY
    ADD     CX,WORD PTR ScaleY
    MOV     WORD PTR RemainderY,CX
    AND     WORD PTR RemainderY,01FFh
    MOV     WORD PTR Count2,CX
    MOV     CX,9
    SHR     WORD PTR Count2,CL

    DEC     WORD PTR RectHeight  { Count down scan lines                    }
    JNZ     @CopyRowsLoop2
    JMP     @CopyDone

 { --------------------------- Part 3: 91-135ø ---------------------------- }

@CopyRowsLoop3:
    CMP     WORD PTR Count2,0
    JE      @NoLoopY3
@GrowLoopY3:
    PUSH    DS
    PUSH    DI              { Remember the start offset in the dest         }
    MOV     AL,BYTE PTR LeftMask
    MOV     CX,WORD PTR RectWidth
    MOV     WORD PTR Count,CX
    MOV     CX,WORD PTR ScaleHIX
    MOV     WORD PTR Count1,CX
    MOV     WORD PTR RemainderX,0
    MOV     WORD PTR RCount1,0
    MOV     WORD PTR A3,DI  { Save address of lower left corner             }
@CopyScanLineLoop3:
    CMP     WORD PTR Count1,0
    JE      @NoLoopX3
@GrowLoopX3:
    MOV     DS,WORD PTR MaskSeg
    CMP     BYTE PTR [BX],0 { Is this pixel mask-enabled?                   }
    JNZ     @MaskOff3       { No, so don't draw it                          }
                            { Yes, draw the pixel                           }
    OUT     DX,AL           { Set the plane for this pixel                  }
    MOV     DS,WORD PTR SourceSeg
    MOV     AH,[SI]         { Get the pixel from the source                 }
    MOV     ES:[DI],AH      { Copy the pixel to the screen                  }
@MaskOff3:
    SUB     DI,WORD PTR DestBitmapWidth
    MOV     CX,WORD PTR AddValX
    ADD     WORD PTR RCount1,CX
    CMP     WORD PTR RCount1,512
    JB      @SkipHorz3
    ROR     AL,1            { Set mask for previous pixel's plane           }
    SBB     DI,0            { Decrement destination address only when       }
                            {  wrapping from plane 0 to plane 3             }
    SUB     WORD PTR RCount1,512
@SkipHorz3:
    DEC     WORD PTR Count1
    JNZ     @GrowLoopX3
@NoLoopX3:
    INC     BX              { Advance the mask pointer                      }
    INC     SI              { Advance the source pointer                    }
    MOV     CX,WORD PTR RemainderX
    ADD     CX,WORD PTR ScaleX
    MOV     WORD PTR RemainderX,CX
    AND     WORD PTR RemainderX,01FFh
    MOV     WORD PTR Count1,CX
    MOV     CX,9
    SHR     WORD PTR Count1,CL
    DEC     WORD PTR Count
    JNZ     @CopyScanLineLoop3
    CMP     WORD PTR GotA2,0
    JNE     @AlreadyGot3
    MOV     WORD PTR GotA2,1
    MOV     WORD PTR A2,DI
@AlreadyGot3:
    MOV     WORD PTR A4,DI  { Save address of lower right corner            }
    POP     DI
    POP     DS              { Retrieve the dest start offset                }
    ROL     BYTE PTR LeftMask,1
    ADC     DI,0
    MOV     CX,WORD PTR AddValY
    ADD     WORD PTR RCount2,CX
    CMP     WORD PTR RCount2,512
    JB      @SkipVert3
    ROR     BYTE PTR LeftMask,1
    SBB     DI,0
    SUB     DI,WORD PTR DestBitmapWidth
    SUB     WORD PTR RCount2,512
@SkipVert3:
    SUB     SI,WORD PTR RectWidth
    SUB     BX,WORD PTR RectWidth
    DEC     WORD PTR Count2
    JNZ     @GrowLoopY3
@NoLoopY3:
    ADD     SI,WORD PTR RectWidth
    ADD     BX,WORD PTR RectWidth
    ADD     SI,WORD PTR SourceBitmapWidth  { Point to the start of the next }
                                           {  scan line of the source       }
    ADD     BX,WORD PTR SourceBitmapWidth  { Point to the start of the next }
                                           {  scan line of the mask         }
    MOV     CX,WORD PTR RemainderY
    ADD     CX,WORD PTR ScaleY
    MOV     WORD PTR RemainderY,CX
    AND     WORD PTR RemainderY,01FFh
    MOV     WORD PTR Count2,CX
    MOV     CX,9
    SHR     WORD PTR Count2,CL

    DEC     WORD PTR RectHeight  { Count down scan lines                    }
    JNZ     @CopyRowsLoop3
    JMP     @CopyDone

 { -------------------------- Part 4: 136-180ø ---------------------------- }

@CopyRowsLoop4:
    CMP     WORD PTR Count2,0
    JE      @NoLoopY4
@GrowLoopY4:
    PUSH    DS
    PUSH    DI              { Remember the start offset in the dest         }
    MOV     AL,BYTE PTR LeftMask
    MOV     CX,WORD PTR RectWidth
    MOV     WORD PTR Count,CX
    MOV     CX,WORD PTR ScaleHIX
    MOV     WORD PTR Count1,CX
    MOV     WORD PTR RemainderX,0
    MOV     WORD PTR RCount1,0
    MOV     WORD PTR A3,DI  { Save address of lower left corner             }
@CopyScanLineLoop4:
    CMP     WORD PTR Count1,0
    JE      @NoLoopX4
@GrowLoopX4:
    MOV     DS,WORD PTR MaskSeg
    CMP     BYTE PTR [BX],0 { Is this pixel mask-enabled?                   }
    JNZ     @MaskOff4       { No, so don't draw it                          }
                            { Yes, draw the pixel                           }
    OUT     DX,AL           { Set the plane for this pixel                  }
    MOV     DS,WORD PTR SourceSeg
    MOV     AH,[SI]         { Get the pixel from the source                 }
    MOV     ES:[DI],AH      { Copy the pixel to the screen                  }
@MaskOff4:
    ROR     AL,1            { Set mask for previous pixel's plane           }
    SBB     DI,0            { Decrement destination address only when       }
                            {  wrapping from plane 0 to plane 3             }
    MOV     CX,WORD PTR AddValX
    ADD     WORD PTR RCount1,CX
    CMP     WORD PTR RCount1,512
    JB      @SkipHorz4
    SUB     DI,WORD PTR DestBitmapWidth
    SUB     WORD PTR RCount1,512
@SkipHorz4:
    DEC     WORD PTR Count1
    JNZ     @GrowLoopX4
@NoLoopX4:
    INC     BX              { Advance the mask pointer                      }
    INC     SI              { Advance the source pointer                    }
    MOV     CX,WORD PTR RemainderX
    ADD     CX,WORD PTR ScaleX
    MOV     WORD PTR RemainderX,CX
    AND     WORD PTR RemainderX,01FFh
    MOV     WORD PTR Count1,CX
    MOV     CX,9
    SHR     WORD PTR Count1,CL
    DEC     WORD PTR Count
    JNZ     @CopyScanLineLoop4
    CMP     WORD PTR GotA2,0
    JNE     @AlreadyGot4
    MOV     WORD PTR GotA2,1
    MOV     WORD PTR A2,DI
@AlreadyGot4:
    MOV     WORD PTR A4,DI  { Save address of lower right corner            }
    POP     DI
    POP     DS              { Retrieve the dest start offset                }
    SUB     DI,WORD PTR DestBitmapWidth  { Point to the start of the next   }
                                         {  scan line of the dest           }
    MOV     CX,WORD PTR AddValY
    ADD     WORD PTR RCount2,CX
    CMP     WORD PTR RCount2,512
    JB      @SkipVert4
    ROL     BYTE PTR LeftMask,1
    ADC     DI,0
    ADD     DI,WORD PTR DestBitmapWidth
    SUB     WORD PTR RCount2,512
@SkipVert4:
    SUB     SI,WORD PTR RectWidth
    SUB     BX,WORD PTR RectWidth
    DEC     WORD PTR Count2
    JNZ     @GrowLoopY4
@NoLoopY4:
    ADD     SI,WORD PTR RectWidth
    ADD     BX,WORD PTR RectWidth
    ADD     SI,WORD PTR SourceBitmapWidth  { Point to the start of the next }
                                           {  scan line of the source       }
    ADD     BX,WORD PTR SourceBitmapWidth  { Point to the start of the next }
                                           {  scan line of the mask         }
    MOV     CX,WORD PTR RemainderY
    ADD     CX,WORD PTR ScaleY
    MOV     WORD PTR RemainderY,CX
    AND     WORD PTR RemainderY,01FFh
    MOV     WORD PTR Count2,CX
    MOV     CX,9
    SHR     WORD PTR Count2,CL

    DEC     WORD PTR RectHeight  { Count down scan lines                    }
    JNZ     @CopyRowsLoop4
    JMP     @CopyDone

 { -------------------------- Part 5: 181-225ø ---------------------------- }

@CopyRowsLoop5:
    CMP     WORD PTR Count2,0
    JE      @NoLoopY5
@GrowLoopY5:
    PUSH    DS
    PUSH    DI              { Remember the start offset in the dest         }
    MOV     AL,BYTE PTR LeftMask
    MOV     CX,WORD PTR RectWidth
    MOV     WORD PTR Count,CX
    MOV     CX,WORD PTR ScaleHIX
    MOV     WORD PTR Count1,CX
    MOV     WORD PTR RemainderX,0
    MOV     WORD PTR RCount1,0
    MOV     WORD PTR A3,DI  { Save address of lower left corner             }
@CopyScanLineLoop5:
    CMP     WORD PTR Count1,0
    JE      @NoLoopX5
@GrowLoopX5:
    MOV     DS,WORD PTR MaskSeg
    CMP     BYTE PTR [BX],0 { Is this pixel mask-enabled?                   }
    JNZ     @MaskOff5       { No, so don't draw it                          }
                            { Yes, draw the pixel                           }
    OUT     DX,AL           { Set the plane for this pixel                  }
    MOV     DS,WORD PTR SourceSeg
    MOV     AH,[SI]         { Get the pixel from the source                 }
    MOV     ES:[DI],AH      { Copy the pixel to the screen                  }
@MaskOff5:
    ROR     AL,1            { Set mask for previous pixel's plane           }
    SBB     DI,0            { Decrement destination address only when       }
                            {  wrapping from plane 0 to plane 3             }
    MOV     CX,WORD PTR AddValX
    ADD     WORD PTR RCount1,CX
    CMP     WORD PTR RCount1,512
    JB      @SkipHorz5
    ADD     DI,WORD PTR DestBitmapWidth
    SUB     WORD PTR RCount1,512
@SkipHorz5:
    DEC     WORD PTR Count1
    JNZ     @GrowLoopX5
@NoLoopX5:
    INC     BX              { Advance the mask pointer                      }
    INC     SI              { Advance the source pointer                    }
    MOV     CX,WORD PTR RemainderX
    ADD     CX,WORD PTR ScaleX
    MOV     WORD PTR RemainderX,CX
    AND     WORD PTR RemainderX,01FFh
    MOV     WORD PTR Count1,CX
    MOV     CX,9
    SHR     WORD PTR Count1,CL
    DEC     WORD PTR Count
    JNZ     @CopyScanLineLoop5
    CMP     WORD PTR GotA2,0
    JNE     @AlreadyGot5
    MOV     WORD PTR GotA2,1
    MOV     WORD PTR A2,DI
@AlreadyGot5:
    MOV     WORD PTR A4,DI  { Save address of lower right corner            }
    POP     DI
    POP     DS              { Retrieve the dest start offset                }
    SUB     DI,WORD PTR DestBitmapWidth  { Point to the start of the next   }
                                         {  scan line of the dest           }
    MOV     CX,WORD PTR AddValY
    ADD     WORD PTR RCount2,CX
    CMP     WORD PTR RCount2,512
    JB      @SkipVert5
    ROR     BYTE PTR LeftMask,1
    SBB     DI,0
    ADD     DI,WORD PTR DestBitmapWidth
    SUB     WORD PTR RCount2,512
@SkipVert5:
    SUB     SI,WORD PTR RectWidth
    SUB     BX,WORD PTR RectWidth
    DEC     WORD PTR Count2
    JNZ     @GrowLoopY5
@NoLoopY5:
    ADD     SI,WORD PTR RectWidth
    ADD     BX,WORD PTR RectWidth
    ADD     SI,WORD PTR SourceBitmapWidth  { Point to the start of the next }
                                           {  scan line of the source       }
    ADD     BX,WORD PTR SourceBitmapWidth  { Point to the start of the next }
                                           {  scan line of the mask         }
    MOV     CX,WORD PTR RemainderY
    ADD     CX,WORD PTR ScaleY
    MOV     WORD PTR RemainderY,CX
    AND     WORD PTR RemainderY,01FFh
    MOV     WORD PTR Count2,CX
    MOV     CX,9
    SHR     WORD PTR Count2,CL

    DEC     WORD PTR RectHeight  { Count down scan lines                    }
    JNZ     @CopyRowsLoop5
    JMP     @CopyDone

 { -------------------------- Part 6: 226-270ø ---------------------------- }

@CopyRowsLoop6:
    CMP     WORD PTR Count2,0
    JE      @NoLoopY6
@GrowLoopY6:
    PUSH    DS
    PUSH    DI              { Remember the start offset in the dest         }
    MOV     AL,BYTE PTR LeftMask
    MOV     CX,WORD PTR RectWidth
    MOV     WORD PTR Count,CX
    MOV     CX,WORD PTR ScaleHIX
    MOV     WORD PTR Count1,CX
    MOV     WORD PTR RemainderX,0
    MOV     WORD PTR RCount1,0
    MOV     WORD PTR A3,DI  { Save address of lower left corner             }
@CopyScanLineLoop6:
    CMP     WORD PTR Count1,0
    JE      @NoLoopX6
@GrowLoopX6:
    MOV     DS,WORD PTR MaskSeg
    CMP     BYTE PTR [BX],0 { Is this pixel mask-enabled?                   }
    JNZ     @MaskOff6       { No, so don't draw it                          }
                            { Yes, draw the pixel                           }
    OUT     DX,AL           { Set the plane for this pixel                  }
    MOV     DS,WORD PTR SourceSeg
    MOV     AH,[SI]         { Get the pixel from the source                 }
    MOV     ES:[DI],AH      { Copy the pixel to the screen                  }
@MaskOff6:
    ADD     DI,WORD PTR DestBitmapWidth
    MOV     CX,WORD PTR AddValX
    ADD     WORD PTR RCount1,CX
    CMP     WORD PTR RCount1,512
    JB      @SkipHorz6
    ROR     AL,1            { Set mask for previous pixel's plane           }
    SBB     DI,0            { Decrement destination address only when       }
                            {  wrapping from plane 0 to plane 3             }
    SUB     WORD PTR RCount1,512
@SkipHorz6:
    DEC     WORD PTR Count1
    JNZ     @GrowLoopX6
@NoLoopX6:
    INC     BX              { Advance the mask pointer                      }
    INC     SI              { Advance the source pointer                    }
    MOV     CX,WORD PTR RemainderX
    ADD     CX,WORD PTR ScaleX
    MOV     WORD PTR RemainderX,CX
    AND     WORD PTR RemainderX,01FFh
    MOV     WORD PTR Count1,CX
    MOV     CX,9
    SHR     WORD PTR Count1,CL
    DEC     WORD PTR Count
    JNZ     @CopyScanLineLoop6
    CMP     WORD PTR GotA2,0
    JNE     @AlreadyGot6
    MOV     WORD PTR GotA2,1
    MOV     WORD PTR A2,DI
@AlreadyGot6:
    MOV     WORD PTR A4,DI  { Save address of lower right corner            }
    POP     DI
    POP     DS              { Retrieve the dest start offset                }
    ROR     BYTE PTR LeftMask,1
    SBB     DI,0
    MOV     CX,WORD PTR AddValY
    ADD     WORD PTR RCount2,CX
    CMP     WORD PTR RCount2,512
    JB      @SkipVert6
    ROL     BYTE PTR LeftMask,1
    ADC     DI,0
    SUB     DI,WORD PTR DestBitmapWidth
    SUB     WORD PTR RCount2,512
@SkipVert6:
    SUB     SI,WORD PTR RectWidth
    SUB     BX,WORD PTR RectWidth
    DEC     WORD PTR Count2
    JNZ     @GrowLoopY6
@NoLoopY6:
    ADD     SI,WORD PTR RectWidth
    ADD     BX,WORD PTR RectWidth
    ADD     SI,WORD PTR SourceBitmapWidth  { Point to the start of the next }
                                           {  scan line of the source       }
    ADD     BX,WORD PTR SourceBitmapWidth  { Point to the start of the next }
                                           {  scan line of the mask         }
    MOV     CX,WORD PTR RemainderY
    ADD     CX,WORD PTR ScaleY
    MOV     WORD PTR RemainderY,CX
    AND     WORD PTR RemainderY,01FFh
    MOV     WORD PTR Count2,CX
    MOV     CX,9
    SHR     WORD PTR Count2,CL

    DEC     WORD PTR RectHeight  { Count down scan lines                    }
    JNZ     @CopyRowsLoop6
    JMP     @CopyDone

 { -------------------------- Part 7: 271-315ø ---------------------------- }

@CopyRowsLoop7:
    CMP     WORD PTR Count2,0
    JE      @NoLoopY7
@GrowLoopY7:
    PUSH    DS
    PUSH    DI              { Remember the start offset in the dest         }
    MOV     AL,BYTE PTR LeftMask
    MOV     CX,WORD PTR RectWidth
    MOV     WORD PTR Count,CX
    MOV     CX,WORD PTR ScaleHIX
    MOV     WORD PTR Count1,CX
    MOV     WORD PTR RemainderX,0
    MOV     WORD PTR RCount1,0
    MOV     WORD PTR A3,DI  { Save address of lower left corner             }
@CopyScanLineLoop7:
    CMP     WORD PTR Count1,0
    JE      @NoLoopX7
@GrowLoopX7:
    MOV     DS,WORD PTR MaskSeg
    CMP     BYTE PTR [BX],0 { Is this pixel mask-enabled?                   }
    JNZ     @MaskOff7       { No, so don't draw it                          }
                            { Yes, draw the pixel                           }
    OUT     DX,AL           { Set the plane for this pixel                  }
    MOV     DS,WORD PTR SourceSeg
    MOV     AH,[SI]         { Get the pixel from the source                 }
    MOV     ES:[DI],AH      { Copy the pixel to the screen                  }
@MaskOff7:
    ADD     DI,WORD PTR DestBitmapWidth
    MOV     CX,WORD PTR AddValX
    ADD     WORD PTR RCount1,CX
    CMP     WORD PTR RCount1,512
    JB      @SkipHorz7
    ROL     AL,1            { Set mask for next pixel's plane               }
    ADC     DI,0            { Advance destination address only when wrapping}
                            {  from plane 3 to plane 0                      }
    SUB     WORD PTR RCount1,512
@SkipHorz7:
    DEC     WORD PTR Count1
    JNZ     @GrowLoopX7
@NoLoopX7:
    INC     BX              { Advance the mask pointer                      }
    INC     SI              { Advance the source pointer                    }
    MOV     CX,WORD PTR RemainderX
    ADD     CX,WORD PTR ScaleX
    MOV     WORD PTR RemainderX,CX
    AND     WORD PTR RemainderX,01FFh
    MOV     WORD PTR Count1,CX
    MOV     CX,9
    SHR     WORD PTR Count1,CL
    DEC     WORD PTR Count
    JNZ     @CopyScanLineLoop7
    CMP     WORD PTR GotA2,0
    JNE     @AlreadyGot7
    MOV     WORD PTR GotA2,1
    MOV     WORD PTR A2,DI
@AlreadyGot7:
    MOV     WORD PTR A4,DI  { Save address of lower right corner            }
    POP     DI
    POP     DS              { Retrieve the dest start offset                }
    ROR     BYTE PTR LeftMask,1
    SBB     DI,0
    MOV     CX,WORD PTR AddValY
    ADD     WORD PTR RCount2,CX
    CMP     WORD PTR RCount2,512
    JB      @SkipVert7
    ROL     BYTE PTR LeftMask,1
    ADC     DI,0
    ADD     DI,WORD PTR DestBitmapWidth
    SUB     WORD PTR RCount2,512
@SkipVert7:
    SUB     SI,WORD PTR RectWidth
    SUB     BX,WORD PTR RectWidth
    DEC     WORD PTR Count2
    JNZ     @GrowLoopY7
@NoLoopY7:
    ADD     SI,WORD PTR RectWidth
    ADD     BX,WORD PTR RectWidth
    ADD     SI,WORD PTR SourceBitmapWidth  { Point to the start of the next }
                                           {  scan line of the source       }
    ADD     BX,WORD PTR SourceBitmapWidth  { Point to the start of the next }
                                           {  scan line of the mask         }
    MOV     CX,WORD PTR RemainderY
    ADD     CX,WORD PTR ScaleY
    MOV     WORD PTR RemainderY,CX
    AND     WORD PTR RemainderY,01FFh
    MOV     WORD PTR Count2,CX
    MOV     CX,9
    SHR     WORD PTR Count2,CL

    DEC     WORD PTR RectHeight  { Count down scan lines                    }
    JNZ     @CopyRowsLoop7
    JMP     @CopyDone

 { -------------------------- Part 8: 316-359ø ---------------------------- }

@CopyRowsLoop8:
    CMP     WORD PTR Count2,0
    JE      @NoLoopY8
@GrowLoopY8:
    PUSH    DS
    PUSH    DI              { Remember the start offset in the dest         }
    MOV     AL,BYTE PTR LeftMask
    MOV     CX,WORD PTR RectWidth
    MOV     WORD PTR Count,CX
    MOV     CX,WORD PTR ScaleHIX
    MOV     WORD PTR Count1,CX
    MOV     WORD PTR RemainderX,0
    MOV     WORD PTR RCount1,0
    MOV     WORD PTR A3,DI  { Save address of lower left corner             }
@CopyScanLineLoop8:
    CMP     WORD PTR Count1,0
    JE      @NoLoopX8
@GrowLoopX8:
    MOV     DS,WORD PTR MaskSeg
    CMP     BYTE PTR [BX],0 { Is this pixel mask-enabled?                   }
    JNZ     @MaskOff8       { No, so don't draw it                          }
                            { Yes, draw the pixel                           }
    OUT     DX,AL           { Set the plane for this pixel                  }
    MOV     DS,WORD PTR SourceSeg
    MOV     AH,[SI]         { Get the pixel from the source                 }
    MOV     ES:[DI],AH      { Copy the pixel to the screen                  }
@MaskOff8:
    ROL     AL,1            { Set mask for next pixel's plane               }
    ADC     DI,0            { Advance destination address only when wrapping}
                            {  from plane 3 to plane 0                      }
    MOV     CX,WORD PTR AddValX
    ADD     WORD PTR RCount1,CX
    CMP     WORD PTR RCount1,512
    JB      @SkipHorz8
    ADD     DI,WORD PTR DestBitmapWidth
    SUB     WORD PTR RCount1,512
@SkipHorz8:
    DEC     WORD PTR Count1
    JNZ     @GrowLoopX8
@NoLoopX8:
    INC     BX              { Advance the mask pointer                      }
    INC     SI              { Advance the source pointer                    }
    MOV     CX,WORD PTR RemainderX
    ADD     CX,WORD PTR ScaleX
    MOV     WORD PTR RemainderX,CX
    AND     WORD PTR RemainderX,01FFh
    MOV     WORD PTR Count1,CX
    MOV     CX,9
    SHR     WORD PTR Count1,CL
    DEC     WORD PTR Count
    JNZ     @CopyScanLineLoop8
    CMP     WORD PTR GotA2,0
    JNE     @AlreadyGot8
    MOV     WORD PTR GotA2,1
    MOV     WORD PTR A2,DI
@AlreadyGot8:
    MOV     WORD PTR A4,DI  { Save address of lower right corner            }
    POP     DI
    POP     DS              { Retrieve the dest start offset                }
    ADD     DI,WORD PTR DestBitmapWidth  { Point to the start of the next   }
                                         {  scan line of the dest           }
    MOV     CX,WORD PTR AddValY
    ADD     WORD PTR RCount2,CX
    CMP     WORD PTR RCount2,512
    JB      @SkipVert8
    ROR     BYTE PTR LeftMask,1
    SBB     DI,0
    SUB     DI,WORD PTR DestBitmapWidth
    SUB     WORD PTR RCount2,512
@SkipVert8:
    SUB     SI,WORD PTR RectWidth
    SUB     BX,WORD PTR RectWidth
    DEC     WORD PTR Count2
    JNZ     @GrowLoopY8
@NoLoopY8:
    ADD     SI,WORD PTR RectWidth
    ADD     BX,WORD PTR RectWidth
    ADD     SI,WORD PTR SourceBitmapWidth  { Point to the start of the next }
                                           {  scan line of the source       }
    ADD     BX,WORD PTR SourceBitmapWidth  { Point to the start of the next }
                                           {  scan line of the mask         }
    MOV     CX,WORD PTR RemainderY
    ADD     CX,WORD PTR ScaleY
    MOV     WORD PTR RemainderY,CX
    AND     WORD PTR RemainderY,01FFh
    MOV     WORD PTR Count2,CX
    MOV     CX,9
    SHR     WORD PTR Count2,CL

    DEC     WORD PTR RectHeight  { Count down scan lines                    }
    JNZ     @CopyRowsLoop8
@CopyDone:
    POP     DI              { Restore caller's register variables           }
    POP     SI
{    STI}
  End; { Asm }
  Dec(A1,DestPageBase);
  Dec(A2,DestPageBase);
  Dec(A3,DestPageBase);
  Dec(A4,DestPageBase);
  Case Degrees Of
       0..89: Begin
                _A1 := A1;
                _A2 := A2;
                _A4 := A4;
                _A3 := A3;
              End;
     90..179: Begin
                _A1 := A2;
                _A2 := A4;
                _A4 := A3;
                _A3 := A1;
              End;
    180..269: Begin
                _A1 := A4;
                _A2 := A3;
                _A4 := A1;
                _A3 := A2;
              End;
    270..359: Begin
                _A1 := A3;
                _A2 := A1;
                _A4 := A2;
                _A3 := A4;
              End;
  End; { Case }
  Dec(_A1);
  Inc(_A4);
  X1 := (_A1 Mod _SCREEN_WIDTH) Shl 2;
  Y1 := _A2 Div _SCREEN_WIDTH;
  X2 := (_A4 Mod _SCREEN_WIDTH) Shl 2;
  Y2 := _A3 Div _SCREEN_WIDTH;
  If X2 < X1 Then
  Begin
    If X1 > (DestStartX + _X1) Then Dec(X1,SCREEN_WIDTH);
    If X2 < (DestStartX + _X1) Then Inc(X2,SCREEN_WIDTH);
  End;
  If Y2 < Y1 Then Y1 := Integer(_A2 - 65536) Div _SCREEN_WIDTH;
  Dec(Y1);
  Inc(Y2);
End; { RotateScaleZapSystemToScreenMaskedX }


Function CreateZapMaskedImage(ImageToSet: MPtr;
 Image: BPtr; ImageWidth,ImageHeight: Integer; Mask: BPtr): Word;
{ ------------------------------------------------------------------------- }
{ Generates all four possible mode X image/mask alignments, stores image    }
{ alignments in display memory, allocates memory for and generates mask     }
{ alignments, and fills out an Aligned Masked Image structure. Image and    }
{ mask must both be in byte-per-pixel form, and must both be of width       }
{ ImageWidth.  Mask maps isomorphically (one to one) onto image, with each  }
{ 0-byte in mask masking off corresponding image pixel (causing it not to be}
{ drawn), and each non-0-byte allowing corresponding image pixel to be      }
{ drawn. Returns 0 if failure, or # of display memory addresses (4-pixel    }
{ sets) used if success.                                                    }
{ ------------------------------------------------------------------------- }

Var
  Align,Size     : Integer;
  WorkingAMImage : APtr;
  P              : Pointer;
  I,J            : Word;
  Wid            : Word;
  BytesUsed      : Word;

Begin
  BytesUsed := 0;

  { Generate each of the four alignments in turn }

  For Align := 0 To 3 Do
  Begin

    { Allocate space for the AlignedMaskedImage struct for this alignment }

    GetMem(WorkingAMImage,SizeOf(AlignedMaskedImage));
    ImageToSet^.Alignments[Align] := WorkingAMImage;
    If WorkingAMImage = Nil Then
    Begin
      CreateZapMaskedImage := 0;
      Exit;
    End;

    WorkingAMImage^.ImageWidth := (ImageWidth - Align + 3) Div 4; { width in 4-pixel sets }
    Size := WorkingAMImage^.ImageWidth * ImageHeight;
    If Size = 0 Then
    Begin
      WorkingAMImage^.ImagePtr := Nil;
      WorkingAMImage^.MaskPtr  := Nil;
    End;
    If Size > 0 Then
    Begin
      GetMem(P,Size);
      WorkingAMImage^.ImagePtr := P;                  { Image dest }
      If P = Nil Then
      Begin
        CreateZapMaskedImage := 0;
        Exit;
      End;
      GetMem(P,Size);
      WorkingAMImage^.MaskPtr := P;                   { Mask dest }
      If P = Nil Then
      Begin
        CreateZapMaskedImage := 0;
        Exit;
      End;

      { Download this alignment of the image }

      Wid := WorkingAMImage^.ImageWidth;
      For I := 0 To ImageHeight - 1 Do
      Begin
        For J := 0 To Wid - 1 Do
        Begin
          P := WorkingAMImage^.ImagePtr;
          Mem[Seg(P^):Ofs(P^) + J + (I * Wid)] :=
           Mem[Seg(Image^):Ofs(Image^) + (J * 4) + (I * ImageWidth) + Align];
          P := WorkingAMImage^.MaskPtr;
          Mem[Seg(P^):Ofs(P^) + J + (I * Wid)] :=
           Mem[Seg(Mask^):Ofs(Mask^) + (J * 4) + (I * ImageWidth) + Align];
        End; { For J }
      End; { For I }

      Inc(BytesUsed,Size * 2);        { mark off the space we just used }
    End;
  End; { For Align }
  CreateZapMaskedImage := BytesUsed;
End; { CreateZapMaskedImage }


Function CreateZapMaskedImage1(ImageToSet: MPtr;
 Image: BPtr; ImageWidth,ImageHeight: Integer; Mask: BPtr): Word;
{ ------------------------------------------------------------------------- }
{ Generates all four possible mode X image/mask alignments, stores image    }
{ alignments in display memory, allocates memory for and generates mask     }
{ alignments, and fills out an Aligned Masked Image structure. Image and    }
{ mask must both be in byte-per-pixel form, and must both be of width       }
{ ImageWidth.  Mask maps isomorphically (one to one) onto image, with each  }
{ 0-byte in mask masking off corresponding image pixel (causing it not to be}
{ drawn), and each non-0-byte allowing corresponding image pixel to be      }
{ drawn. Returns 0 if failure, or # of display memory addresses (4-pixel    }
{ sets) used if success.                                                    }
{ ------------------------------------------------------------------------- }

Var
  Size           : Integer;
  WorkingAMImage : APtr;
  P              : Pointer;
  I,J            : Word;
  Wid            : Word;
  BytesUsed      : Word;

Begin
  BytesUsed := 0;

  { Allocate space for the AlignedMaskedImage struct for this alignment }

  GetMem(WorkingAMImage,SizeOf(AlignedMaskedImage));
  ImageToSet^.Alignments[0] := WorkingAMImage;
  If WorkingAMImage = Nil Then
  Begin
    CreateZapMaskedImage1 := 0;
    Exit;
  End;

  WorkingAMImage^.ImageWidth := ImageWidth; { width in 4-pixel sets }
  Size := WorkingAMImage^.ImageWidth * ImageHeight;
  If Size = 0 Then
  Begin
    WorkingAMImage^.ImagePtr := Nil;
    WorkingAMImage^.MaskPtr  := Nil;
  End;
  If Size > 0 Then
  Begin
    GetMem(P,Size);
    WorkingAMImage^.ImagePtr := P;                  { Image dest }
    If P = Nil Then
    Begin
      CreateZapMaskedImage1 := 0;
      Exit;
    End;
    GetMem(P,Size);
    WorkingAMImage^.MaskPtr := P;                   { Mask dest }
    If P = Nil Then
    Begin
      CreateZapMaskedImage1 := 0;
      Exit;
    End;

    { Download this alignment of the image }

    Wid := WorkingAMImage^.ImageWidth;
    For I := 0 To ImageHeight - 1 Do
    Begin
      For J := 0 To Wid - 1 Do
      Begin
        P := WorkingAMImage^.ImagePtr;
        Mem[Seg(P^):Ofs(P^) + J + (I * Wid)] :=
         Mem[Seg(Image^):Ofs(Image^) + J + (I * ImageWidth)];
        P := WorkingAMImage^.MaskPtr;
        Mem[Seg(P^):Ofs(P^) + J + (I * Wid)] :=
         Mem[Seg(Mask^):Ofs(Mask^) + J + (I * ImageWidth)];
      End; { For J }
    End; { For I }

    Inc(BytesUsed,Size * 2);        { mark off the space we just used }
  End;
  CreateZapMaskedImage1 := BytesUsed;
End; { CreateZapMaskedImage1 }


Function ValSize(Scale: Word; Value: Word): Word;
Var Work: Word;
Begin
  Work := 0;
  If Scale > 512 Then
  Begin
    Asm
      MOV   AX,0
      MOV   BX,0
      MOV   CX,9
      MOV   DX,WORD PTR Value
@Loop1:
      MOV   AX,BX
      ADD   AX,WORD PTR Scale
      MOV   BX,AX
      AND   BX,01FFh
      SHR   AX,CL
      ADD   WORD PTR Work,AX
      DEC   DX
      JNZ   @Loop1
    End; { Asm }
  End
  Else
  Begin
    Asm
      MOV   AX,0
      MOV   CX,WORD PTR Value
@Loop1:
      CMP   AX,0
      JG    @Skip
      INC   WORD PTR Work
      ADD   AX,512
@Skip:
      SUB   AX,WORD PTR Scale
      LOOP  @Loop1
    End; { Asm }
  End;
  ValSize := Work + 1;
End; { ValSize }


(*
Function CreateFormattedImage(Image,Mask: BPtr; ImageWidth,
 ImageHeight: Word): FPtr;
Const BufSize = 60 * 1024;               { 60K buffer }
Var
  Buffer : Pointer;
  I,J,K  : Word;

Begin
  GetMem(Buffer,BufSize);
  If Buffer = Nil Then                   { Exit if no memory }
  Begin
    CreateFormattedImage := Nil;
    Exit;
  End;

  { Proceed one scan line at a time }

  For I := 0 To ImageHeight - 1 Do
  Begin

    { Break up into four planes }

    For J := 0 To 3 Do
    Begin
      Asm
        MOV    DX,WORD PTR J

        MOV    CX,WORD PTR ImageWidth    { Number of dots across }
        DEC    CX
        SUB    CX,DX
        SHR    CX,1                      { Do just the ones in this plane }
        SHR    CX,1

        LES    DI,DWORD PTR Buffer
        PUSH   DS
        LDS    BX,DWORD PTR Mask
        ADD    BX,DX
@MoveAcrossLoop:

        CMP    BYTE PTR [BX],0           { Look for the first dot }
        JE     @NotFound
        MOV    SI,BX                     { See how many dots there are }
        SUB    AX,AX
@CountDotsLoop:
        INC    SI
        INC    AX



        STOSW


@NotFound:
        ADD    BX,4
        LOOP   @MoveAcrossLoop

      End; { Asm }
    End; { For J }
  End; { For I }
End; { CreateFormattedImage }
*)

Function BoundScale(Var SourceStartX,SourceStartY,SourceEndX,SourceEndY,
 DestStartX,DestStartY: Integer; Scale: Word; X1,Y1,X2,Y2: Integer): Boolean;
{ ------------------------------------------------------------------------- }
{ Performs bounds-checking for a scaled flat bitmap, to fit within a given  }
{ bounding box.  Will allow the edges to move outside the box by 1 scaled   }
{ pixel.                                                                    }
{ ------------------------------------------------------------------------- }
Var
  Inc1      : Integer;
  Inc2      : Integer;
  ImageX2   : Integer;
  ImageY2   : Integer;

Begin
  ImageX2 := Round((SourceEndX - SourceStartX) * Scale / 512) + DestStartX;
  ImageY2 := Round((SourceEndY - SourceStartY) * Scale / 512) + DestStartY;
  If (ImageX2    < X1) Or (ImageY2    < Y1) Or
     (DestStartX > X2) Or (DestStartY > Y2) Then
  Begin
    BoundScale := False;
    Exit;
  End;
  If DestStartX < X1 Then
  Begin
    Inc1 := Trunc((X1 - DestStartX) / (Scale / 512));
    Inc2 := Round(Inc1 * Scale / 512);
    Inc(SourceStartX,Inc1);
    Inc(DestStartX,Inc2);
  End;
  If DestStartY < Y1 Then
  Begin
    Inc1 := Trunc((Y1 - DestStartY) / (Scale / 512));
    Inc2 := Round(Inc1 * Scale / 512);
    Inc(SourceStartY,Inc1);
    Inc(DestStartY,Inc2);
  End;
  If ImageX2 > X2 Then
   Dec(SourceEndX,Trunc((ImageX2 - X2) / (Scale / 512)));
  If ImageY2 > Y2 Then
   Dec(SourceEndY,Trunc((ImageY2 - Y2) / (Scale / 512)));
  BoundScale := True;
End; { BoundScale }


Function BoundScale3D(Var SourceStartX,SourceStartY,SourceEndX,SourceEndY,
 DestStartX,DestStartY: Integer; Var Height1,Height2,Width: Word; Var Skew: Integer; Vertical: Boolean;
 X1,Y1,X2,Y2: Integer): Boolean;
{ ------------------------------------------------------------------------- }
{ Performs bounds-checking for a scaled 3D bitmap, to fit within a given    }
{ bounding box.                                                             }
{ ------------------------------------------------------------------------- }
Var
  H1        : Integer;
  H2        : Integer;
  ImageX1   : Integer;
  ImageY1   : Integer;
  ImageX2   : Integer;
  ImageY2   : Integer;
  ImageX3   : Integer;
  ImageY3   : Integer;
  ImageX4   : Integer;
  ImageY4   : Integer;

Begin
  H1 := Height1;
  H2 := Height2;
  If Vertical Then
  Begin
    ImageX1 := DestStartX;
    ImageY1 := DestStartY;
    ImageX2 := ImageX1 + ((H1 - H2) Div 2) + H2 + Skew;
    ImageY2 := ImageY1 + Width;
    ImageX3 := ImageX1 + H1;
    ImageY3 := ImageY1;
    ImageX4 := ImageX2 - H2;
    ImageY4 := ImageY2;
    If (ImageX1 > X2) Or (ImageX4 > X2) Or
       (ImageX3 < X1) Or (ImageX2 < X1) Or
       (ImageY1 > Y2) Or (ImageY2 < Y1) Then
    Begin
      BoundScale3D := False;
      Exit;
    End;
    BoundScale3D := True;
  End
  Else
  Begin
    ImageX1 := DestStartX;
    ImageY1 := DestStartY;
    ImageX2 := ImageX1 + Width;
    ImageY2 := ImageY1 + ((H1 - H2) Div 2) + H2 + Skew;
    ImageX3 := ImageX2;
    ImageY3 := ImageY2 - H2;
    ImageX4 := ImageX1;
    ImageY4 := ImageY1 + H1;
    If (ImageX1 > X2) Or (ImageX4 > X2) Or
       (ImageX3 < X1) Or (ImageX2 < X1) Or
       (ImageY1 > Y2) Or (ImageY2 < Y1) Then
    Begin
      BoundScale3D := False;
      Exit;
    End;
    BoundScale3D := True;
  End;
End; { BoundScale3D }


Procedure _FixedMul; Far; External;
{ ------------------------------------------------------------------------- }
{ Multiples two fixed-point values together; 386-ONLY                       }
{ ------------------------------------------------------------------------- }


Procedure _FixedDiv; Far; External;
{ ------------------------------------------------------------------------- }
{ Multiples two fixed-point values together; 386-ONLY                       }
{ ------------------------------------------------------------------------- }
{$L FIXED.OBJ}


Procedure ScanOutLine(LeftEdge,RightEdge: EdgeScan; TexMapWidth,
 DestY,CurrentPageBase,ClipMinX,ClipMaxX: Word; TexMapBits: Pointer);
{ ------------------------------------------------------------------------- }
{ Draws all pixels in the specified scan line, with the pixel colors taken  }
{ from the specified texture map.  Uses approach of pre-stepping 1/2 pixel  }
{ into the source image and rounding to the nearest source pixel at each    }
{ step, so that texture maps will appear reasonably similar at all angles.  }
{ This routine is specific to 320-pixel-wide planar (non-Chain4) 256-color  }
{ modes, such as mode X, which is a planar (non-Chain4) 256-color mode with }
{ a resolution of 320x240.                                                  }
{ ------------------------------------------------------------------------- }
Var
  LSourceX       : LongInt; { Current X coordinate in source image          }
  LSourceY       : LongInt; { Current Y coordinate in source image          }
  LSourceStepX   : LongInt; { X step in source image for X dest step of 1   }
  LSourceStepY   : LongInt; { Y step in source image for X dest step of 1   }
  _LSourceStepX  : LongInt; { X step in source image for X dest step of 1   }
  _LSourceStepY  : LongInt; { Y step in source image for X dest step of 1   }
  LXAdvanceByOne : Word;    { Used to step source pointer 1 pixel           }
                            {  incrementally in X                           }
  LXBaseAdvance  : Word;    { Use to step source pointer minimum number of  }
                            {  pixels incrementally in X                    }
  _LXBaseAdvance : Word;    { Use to step source pointer minimum number of  }
                            {  pixels incrementally in X                    }
  LYAdvanceByOne : Word;    { Used to step source pointer 1 pixel           }
                            {  incrementally in Y                           }
  LYBaseAdvance  : Word;    { Use to step source pointer minimum number of  }
                            {  pixels incrementally in Y                    }
  _LYBaseAdvance : Word;    { Use to step source pointer minimum number of  }
                            {  pixels incrementally in Y                    }
  LXYBaseAdvance : Word;
  _LXYBaseAdvance: Word;
  SaveSourceX    : Word;
  SaveSourceY    : Word;

Label ScanDone;
Begin
  Asm
    JMP    @Start
@ToScanDone:
    JMP    ScanDone
@Start:

    { Nothing to do if destination is fully X clipped. }

    MOV    SI,WORD PTR RightEdge.DestX
    CMP    SI,WORD PTR ClipMinX
    JLE    @ToScanDone        { Right edge is to left of clip rect, so done }
    MOV    DX,WORD PTR LeftEdge.DestX
    CMP    DX,WORD PTR ClipMaxX
    JGE    @ToScanDone        { Left edge is to right of clip rect, so done }
    SUB    SI,DX
    JLE    @ToScanDone        { Null or negative full width, so done        }

    MOV    AX,WORD PTR LeftEdge.SourceX       { Initial source X coordinate }
    MOV    WORD PTR LSourceX,AX
    MOV    AX,WORD PTR LeftEdge.SourceX + 2
    MOV    WORD PTR LSourceX + 2,AX
    MOV    AX,WORD PTR LeftEdge.SourceY       { Initial source Y coordinate }
    MOV    WORD PTR LSourceY,AX
    MOV    AX,WORD PTR LeftEdge.SourceY + 2
    MOV    WORD PTR LSourceY + 2,AX

    { Calculate source steps that correspond to each 1-pixel destination X  }
    { step (across the destination scan line).                              }

    PUSH   SI                 { Push dest X width, in fixedpoint form       }
    SUB    AX,AX
    PUSH   AX                 { Push 0 as fractional part of dest X width   }
    MOV    AX,WORD PTR RightEdge.SourceX
    SUB    AX,WORD PTR LSourceX               { Low word of source X width  }
    MOV    DX,WORD PTR RightEdge.SourceX + 2
    SBB    DX,WORD PTR LSourceX + 2           { High word of source X width }
    PUSH   DX                 { Push source X width, in fixedpoint form     }
    PUSH   AX
    CALL   FAR PTR _FixedDiv  { Scale source X width to dest X width        }
    ADD    SP,8               { Clear parameters from stack                 }
    MOV    WORD PTR LSourceStepX,AX           { Remember source X step for  }
    MOV    WORD PTR LSourceStepX + 2,DX       {  1-pixel destination X step }

    MOV    BX,AX
    MOV    CX,DX
    OR     CX,CX
    JNS    @Pos1
    NOT    AX
    NOT    CX
    ADD    AX,1
    ADC    CX,0
    ADD    AX,AX
    ADC    CX,CX
    ADD    AX,AX
    ADC    CX,CX
    SUB    AX,1
    SBB    CX,0
    NOT    AX
    NOT    CX
    JMP    @Mul1
@Pos1:
    ADD    AX,AX
    ADC    CX,CX
    ADD    AX,AX
    ADC    CX,CX
@Mul1:
    MOV    WORD PTR _LSourceStepX,AX
    MOV    WORD PTR _LSourceStepX + 2,CX
    MOV    AX,BX
    MOV    BX,CX

    MOV    CX,1               { Assume source X advances non-negative       }
    AND    DX,DX              { Which way does source X advance?            }
    JNS    @SourceXNonNeg     { Non-negative                                }
    NEG    CX                 { Negative                                    }
    CMP    AX,0               { Is the whole step exactly an integer?       }
    JZ     @SourceXNonNeg     { Yes                                         }
    INC    DX                 { No, truncate to integer in the direction of }
                              {  0, because otherwise we'll end up with a   }
                              {  whole step of 1-too-large magnitude        }
    INC    BX
@SourceXNonNeg:
    MOV    WORD PTR LXAdvanceByOne,CX { Amount to add to source pointer to  }
                                      {  move by one in X                   }
    MOV    WORD PTR LXBaseAdvance,DX  { Minimum amount to add to source     }
                                      {  pointer to advance in X each time  }
                                      {  the dest advances one in X         }
    MOV    WORD PTR _LXBaseAdvance,BX

    PUSH   SI                 { Push dest Y height, in fixedpoint form      }
    SUB    AX,AX
    PUSH   AX                 { Push 0 as fractional part of dest Y height  }
    MOV    AX,WORD PTR RightEdge.SourceY
    SUB    AX,WORD PTR LSourceY              { Low word of source Y height  }
    MOV    DX,WORD PTR RightEdge.SourceY + 2
    SBB    DX,WORD PTR LSourceY + 2          { High word of source Y height }
    PUSH   DX                 { Push source Y height, in fixedpoint form    }
    PUSH   AX
    CALL   FAR PTR _FixedDiv  { Scale source Y height to dest X width       }
    ADD    SP,8               { Clear parameters from stack                 }
    MOV    WORD PTR LSourceStepY,AX           { Remember source Y step for  }
    MOV    WORD PTR LSourceStepY + 2,DX       {  1-pixel destination X step }

    MOV    BX,AX
    MOV    CX,DX
    OR     CX,CX
    JNS    @Pos2
    NOT    AX
    NOT    CX
    ADD    AX,1
    ADC    CX,0
    ADD    AX,AX
    ADC    CX,CX
    ADD    AX,AX
    ADC    CX,CX
    SUB    AX,1
    SBB    CX,0
    NOT    AX
    NOT    CX
    JMP    @Mul2
@Pos2:
    ADD    AX,AX
    ADC    CX,CX
    ADD    AX,AX
    ADC    CX,CX
@Mul2:
    MOV    WORD PTR _LSourceStepY,AX
    MOV    WORD PTR _LSourceStepY + 2,CX
    MOV    AX,BX
    MOV    BX,CX

    MOV    CX,WORD PTR TexMapWidth  { Assume source Y advances non-negative }
    AND    DX,DX              { Which way does source Y advance?            }
    JNS    @SourceYNonNeg     { Non-negative                                }
    NEG    CX                 { Negative                                    }
    CMP    AX,0               { Is the whole step exactly an integer?       }
    JZ     @SourceYNonNeg     { Yes                                         }
    INC    DX                 { No, truncate to integer in the direction of }
                              {  0, because otherwise we'll end up with a   }
                              {  whole step of 1-too-large magnitude        }
    INC    BX
@SourceYNonNeg:
    MOV    WORD PTR LYAdvanceByOne,CX { Amount to add to source pointer to  }
                                      {  move by one in Y                   }
    MOV    AX,WORD PTR TexMapWidth   { Minimum distance skipped in source   }
    IMUL   DX                        {  image bitmap when Y steps (ignoring }
    MOV    WORD PTR LYBaseAdvance,AX {  carry from the fractional part      }
    MOV    AX,WORD PTR TexMapWidth
    IMUL   BX
    MOV    WORD PTR _LYBaseAdvance,AX

    { Advance 1/2 step in the stepping direction, to space scanned pixels   }
    { evenly between the left and right edges.  (There's a slight inaccuracy}
    { in dividing negative numbers by 2 by shifting rather than dividing,   }
    { but the inaccuracy is in the least significant bit, and we'll just    }
    { live with it.                                                         }

    MOV    AX,WORD PTR LSourceStepX
    MOV    DX,WORD PTR LSourceStepX + 2
    SAR    DX,1
    RCR    AX,1
    ADD    WORD PTR LSourceX,AX
    ADC    WORD PTR LSourceX + 2,DX

    MOV    AX,WORD PTR LSourceStepY
    MOV    DX,WORD PTR LSourceStepY + 2
    SAR    DX,1
    RCR    AX,1
    ADD    WORD PTR LSourceY,AX
    ADC    WORD PTR LSourceY + 2,DX

    { Clip the right edge if necessary. }

    MOV    SI,WORD PTR RightEdge.DestX
    CMP    SI,WORD PTR ClipMaxX
    JL     @RightEdgeClipped
    MOV    SI,WORD PTR ClipMaxX
@RightEdgeClipped:
    { Clip the left edge if necessary. }

    MOV    DI,WORD PTR LeftEdge.DestX
    CMP    DI,WORD PTR ClipMinX
    JGE    @LeftEdgeClipped

    { Left clipping is necessary; advance the source accordingly }

    NEG    DI
    ADD    DI,WORD PTR ClipMinX { ClipMinX - DestX                          }
                                { First, advance the source in X            }
    PUSH   DI                   { Push ClipMinX - DestX, in fixedpoint form }
    SUB    AX,AX
    PUSH   AX               { Push 0 as fractional part of ClipMinX - DestX }
    PUSH   WORD PTR LSourceStepX + 2
    PUSH   WORD PTR LSourceStepX
    CALL   FAR PTR _FixedMul  { Total source X stepping in clipped area     }
    ADD    SP,8               { Clear parameters from stack                 }
    ADD    WORD PTR LSourceX,AX  { Step the source X past clipping          }
    ADC    WORD PTR LSourceX + 2,DX

    { now advance the source in Y }

    PUSH   DI                 { Push ClipMinX - DestX, in fixedpoint form   }
    SUB    AX,AX
    PUSH   AX               { Push 0 as fractional part of ClipMinX - DestX }
    PUSH   WORD PTR LSourceStepY + 2
    PUSH   WORD PTR LSourceStepY
    CALL   FAR PTR _FixedMul  { Total source Y stepping in clipped area     }
    ADD    SP,8               { Clear parameters from stack                 }
    ADD    WORD PTR LSourceY,AX  { Step the source X past clipping          }
    ADC    WORD PTR LSourceY + 2,DX
    MOV    DI,WORD PTR ClipMinX { Start X coordinate in dest after clipping }
@LeftEdgeClipped:

    { Calculate actual clipped destination drawing width. }

    SUB    SI,DI

    { Scan across the destination scan line, updating the source image      }
    { position accordingly.  Point to the initial source image pixel, adding}
    { 0.5 to both X and Y so that we can truncate to integers from now on   }
    { but effectively get rounding.                                         }

    ADD    WORD PTR LSourceY,8000h   { Add 0.5                              }
    MOV    AX,WORD PTR LSourceY + 2
    ADC    AX,0
    MUL    WORD PTR TexMapWidth { Initial scan line in source image         }
    ADD    WORD PTR LSourceX,8000h   { Add 0.5                              }
    MOV    BX,WORD PTR LSourceX + 2  { Offset into source scan line         }
    ADC    BX,AX              { Initial source offset in source image       }
    ADD    BX,WORD PTR TexMapBits { DS:BX points to the initial image pixel }
    PUSH   DS
    MOV    AX,WORD PTR TexMapBits + 2
    MOV    DS,AX

    { Point to initial destination pixel }

    MOV    AX,SCREEN_SEG
    MOV    ES,AX
{    MOV    AX,_SCREEN_WIDTH}
    MOV    AX,WORD PTR DestY  { Hard-coded multiply by 80 }
    MOV    CX,0204h
    SHL    AX,CL
    MOV    DX,AX
    XCHG   CH,CL
    SHL    DX,CL
    ADD    AX,DX
{    MUL    WORD PTR DestY}     { Offset of initial dest scan line            }
    MOV    CX,DI              { Initial destination X                       }
    SHR    DI,1
    SHR    DI,1               { X/4 = offset of pixel in scan line          }
    ADD    DI,AX              { Offset of pixel in page                     }
    ADD    DI,WORD PTR CurrentPageBase { Offset of pixel in display memory  }
                              { ES:DI now points to the first destination   }
                              {  pixel                                      }

    AND    CL,011b            { CL = pixel's plane                          }
    MOV    AL,MAP_MASK
    MOV    DX,SC_INDEX
    OUT    DX,AL              { Point the SC Index register to the Map Mask }
    MOV    AL,11h             { One plane bit in each nibble, so we'll get  }
                              {  carry automatically when going from plane 3}
                              {  to plane 0                                 }
    SHL    AL,CL              { Set the bit for the first pixel's plane to 1}

    { If the source X step is negative, change over to working with non-    }
    { negative values.                                                      }

    CMP    WORD PTR LXAdvanceByOne,0
    JGE    @SXStepSet
    NEG    WORD PTR LSourceStepX
    NEG    WORD PTR _LSourceStepX
    NOT    WORD PTR LSourceX
@SXStepSet:

    { If the source Y step is negative, change over to working with non-    }
    { negative values.                                                      }

    CMP    WORD PTR LYAdvanceByOne,0
    JGE    @SYStepSet
    NEG    WORD PTR LSourceStepY
    NEG    WORD PTR _LSourceStepY
    NOT    WORD PTR LSourceY
@SYStepSet:

    { At this point:                                                        }
    {      AL = initial pixel's plane mask                                  }
    {      BX = pointer to initial image pixel                              }
    {      SI = # of pixels to fill                                         }
    {      DI = pointer to initial destination pixel                        }

    MOV    DX,SC_INDEX + 1    { Point to SC Data; Index points to Map Mask  }
    MOV    CX,WORD PTR LXBaseAdvance
    ADD    CX,WORD PTR LYBaseAdvance
    MOV    WORD PTR LXYBaseAdvance,CX
    MOV    CX,WORD PTR _LXBaseAdvance
    ADD    CX,WORD PTR _LYBaseAdvance
    MOV    WORD PTR _LXYBaseAdvance,CX
    CMP    WORD PTR LXAdvanceByOne,0
    JGE    @TexScanLoopPos
    NEG    CX
@TexScanLoopPos:
    MOV    WORD PTR CS:[@Rep1  - 2],CX  { Self-modifying code; it's faster  }
    MOV    WORD PTR CS:[@Rep5  - 2],CX  {  to use immediates than to read   }
    MOV    WORD PTR CS:[@Rep9  - 2],CX  {  from memory                      }
    MOV    WORD PTR CS:[@Rep13 - 2],CX
    MOV    WORD PTR CS:[@Rep17 - 2],CX
    MOV    WORD PTR CS:[@Rep21 - 2],CX
    MOV    WORD PTR CS:[@Rep25 - 2],CX
    MOV    WORD PTR CS:[@Rep29 - 2],CX
    MOV    CX,WORD PTR _LSourceStepX
    MOV    WORD PTR CS:[@Rep2  - 2],CX
    MOV    WORD PTR CS:[@Rep6  - 2],CX
    MOV    WORD PTR CS:[@Rep10 - 2],CX
    MOV    WORD PTR CS:[@Rep14 - 2],CX
    MOV    WORD PTR CS:[@Rep18 - 2],CX
    MOV    WORD PTR CS:[@Rep22 - 2],CX
    MOV    WORD PTR CS:[@Rep26 - 2],CX
    MOV    WORD PTR CS:[@Rep30 - 2],CX
    MOV    CX,WORD PTR _LSourceStepY
    MOV    WORD PTR CS:[@Rep3  - 2],CX
    MOV    WORD PTR CS:[@Rep7  - 2],CX
    MOV    WORD PTR CS:[@Rep11 - 2],CX
    MOV    WORD PTR CS:[@Rep15 - 2],CX
    MOV    WORD PTR CS:[@Rep19 - 2],CX
    MOV    WORD PTR CS:[@Rep23 - 2],CX
    MOV    WORD PTR CS:[@Rep27 - 2],CX
    MOV    WORD PTR CS:[@Rep31 - 2],CX
    MOV    CX,WORD PTR LYAdvanceByOne
    MOV    WORD PTR CS:[@Rep4  - 2],CX
    MOV    WORD PTR CS:[@Rep8  - 2],CX
    MOV    WORD PTR CS:[@Rep12 - 2],CX
    MOV    WORD PTR CS:[@Rep16 - 2],CX
    MOV    WORD PTR CS:[@Rep20 - 2],CX
    MOV    WORD PTR CS:[@Rep24 - 2],CX
    MOV    WORD PTR CS:[@Rep28 - 2],CX
    MOV    WORD PTR CS:[@Rep32 - 2],CX

    OUT    DX,AL
    PUSH   SI
    ADD    SI,3
    SHR    SI,1
    SHR    SI,1
    JZ     @ExitScan
    PUSH   BX
    PUSH   DI
    MOV    CX,WORD PTR LSourceX
    MOV    WORD PTR SaveSourceX,CX
    MOV    DX,CX
    MOV    CX,WORD PTR LSourceY
    MOV    WORD PTR SaveSourceY,CX
    CMP    WORD PTR LXAdvanceByOne,0
    JL     @TexScanLoopNeg
@TexScanLoop:

    { Set the Map Mask for this pixel's plane, then draw the pixel.         }

    MOV    AH,[BX]            { Get image pixel                             }
    MOV    ES:[DI],AH         { Set image pixel                             }

    { Point to the next source pixel.                                       }

    ADD    DX,2211h                   { _LSourceStepX }
@Rep2:
    ADC    BX,2211h                   { _LXYBaseAdvance }
@Rep1:
    ADD    CX,2211h                   { _LSourceStepY }
@Rep3:
    JNC    @NoExtraYAdvance           { Didn't turn over; no extra advance  }
    ADD    BX,2211h                   { LYAdvanceByOne }
@Rep4:
@NoExtraYAdvance:

    INC    DI

    { Continue if there are any more dest pixels to draw.                   }

    DEC    SI
    JNZ    @TexScanLoop

    MOV    CX,WORD PTR SaveSourceX
    MOV    WORD PTR LSourceX,CX
    MOV    CX,WORD PTR SaveSourceY
    MOV    WORD PTR LSourceY,CX

    POP    DI
    POP    BX
    POP    SI
    PUSH   SI
    ADD    SI,2
    SHR    SI,1
    SHR    SI,1
    JZ     @ExitScan
    ROL    AL,1
    ADC    DI,0
    MOV    DX,SC_INDEX + 1    { Point to SC Data; Index points to Map Mask  }
    OUT    DX,AL
    ADD    BX,WORD PTR LXYBaseAdvance
    MOV    CX,WORD PTR LSourceStepX
    ADD    WORD PTR LSourceX,CX       { Step the source X fractional part   }
    ADC    BX,0
    MOV    CX,WORD PTR LSourceStepY
    ADD    WORD PTR LSourceY,CX       { Step the source Y fractional part   }
    JNC    @NoExtraYAdvance1          { Didn't turn over; no extra advance  }
    ADD    BX,WORD PTR LYAdvanceByOne { Did turn over; advance Y one extra  }
@NoExtraYAdvance1:
    PUSH   BX
    PUSH   DI
    MOV    CX,WORD PTR LSourceX
    MOV    WORD PTR SaveSourceX,CX
    MOV    DX,CX
    MOV    CX,WORD PTR LSourceY
    MOV    WORD PTR SaveSourceY,CX
@TexScanLoop1:

    { Set the Map Mask for this pixel's plane, then draw the pixel.         }

    MOV    AH,[BX]            { Get image pixel                             }
    MOV    ES:[DI],AH         { Set image pixel                             }

    { Point to the next source pixel.                                       }

    ADD    DX,2211h                   { _LSourceStepX }
@Rep6:
    ADC    BX,2211h                   { _LXYBaseAdvance }
@Rep5:
    ADD    CX,2211h                   { _LSourceStepY }
@Rep7:
    JNC    @NoExtraYAdvance2          { Didn't turn over; no extra advance  }
    ADD    BX,2211h                   { LYAdvanceByOne }
@Rep8:
@NoExtraYAdvance2:

    INC    DI

    { Continue if there are any more dest pixels to draw.                   }

    DEC    SI
    JNZ    @TexScanLoop1

    MOV    CX,WORD PTR SaveSourceX
    MOV    WORD PTR LSourceX,CX
    MOV    CX,WORD PTR SaveSourceY
    MOV    WORD PTR LSourceY,CX

    POP    DI
    POP    BX
    POP    SI
    PUSH   SI
    INC    SI
    SHR    SI,1
    SHR    SI,1
    JZ     @ExitScan
    ROL    AL,1
    ADC    DI,0
    MOV    DX,SC_INDEX + 1    { Point to SC Data; Index points to Map Mask  }
    OUT    DX,AL
    ADD    BX,WORD PTR LXYBaseAdvance
    MOV    CX,WORD PTR LSourceStepX
    ADD    WORD PTR LSourceX,CX       { Step the source X fractional part   }
    ADC    BX,0
    MOV    CX,WORD PTR LSourceStepY
    ADD    WORD PTR LSourceY,CX       { Step the source Y fractional part   }
    JNC    @NoExtraYAdvance3          { Didn't turn over; no extra advance  }
    ADD    BX,WORD PTR LYAdvanceByOne { Did turn over; advance Y one extra  }
@NoExtraYAdvance3:
    PUSH   BX
    PUSH   DI
    MOV    CX,WORD PTR LSourceX
    MOV    WORD PTR SaveSourceX,CX
    MOV    DX,CX
    MOV    CX,WORD PTR LSourceY
    MOV    WORD PTR SaveSourceY,CX
@TexScanLoop2:

    { Set the Map Mask for this pixel's plane, then draw the pixel.         }

    MOV    AH,[BX]            { Get image pixel                             }
    MOV    ES:[DI],AH         { Set image pixel                             }

    { Point to the next source pixel.                                       }

    ADD    DX,2211h                   { _LSourceStepX }
@Rep10:
    ADC    BX,2211h                   { _LXYBaseAdvance }
@Rep9:
    ADD    CX,2211h                   { _LSourceStepY }
@Rep11:
    JNC    @NoExtraYAdvance4          { Didn't turn over; no extra advance  }
    ADD    BX,2211h                   { LYAdvanceByOne }
@Rep12:
@NoExtraYAdvance4:

    INC    DI

    { Continue if there are any more dest pixels to draw.                   }

    DEC    SI
    JNZ    @TexScanLoop2

    MOV    CX,WORD PTR SaveSourceX
    MOV    WORD PTR LSourceX,CX
    MOV    CX,WORD PTR SaveSourceY
    MOV    WORD PTR LSourceY,CX

    POP    DI
    POP    BX
    POP    SI
    PUSH   SI
    SHR    SI,1
    SHR    SI,1
    JZ     @ExitScan
    ROL    AL,1
    ADC    DI,0
    MOV    DX,SC_INDEX + 1    { Point to SC Data; Index points to Map Mask  }
    OUT    DX,AL
    ADD    BX,WORD PTR LXYBaseAdvance
    MOV    CX,WORD PTR LSourceStepX
    ADD    WORD PTR LSourceX,CX       { Step the source X fractional part   }
    ADC    BX,0
    MOV    CX,WORD PTR LSourceStepY
    ADD    WORD PTR LSourceY,CX       { Step the source Y fractional part   }
    JNC    @NoExtraYAdvance5          { Didn't turn over; no extra advance  }
    ADD    BX,WORD PTR LYAdvanceByOne { Did turn over; advance Y one extra  }
@NoExtraYAdvance5:
    MOV    CX,WORD PTR LSourceX
    MOV    DX,CX
    MOV    CX,WORD PTR LSourceY
@TexScanLoop3:

    { Set the Map Mask for this pixel's plane, then draw the pixel.         }

    MOV    AH,[BX]            { Get image pixel                             }
    MOV    ES:[DI],AH         { Set image pixel                             }

    { Point to the next source pixel.                                       }

    ADD    DX,2211h                   { _LSourceStepX }
@Rep14:
    ADC    BX,2211h                   { _LXYBaseAdvance }
@Rep13:
    ADD    CX,2211h                   { _LSourceStepY }
@Rep15:
    JNC    @NoExtraYAdvance6          { Didn't turn over; no extra advance  }
    ADD    BX,2211h                   { LYAdvanceByOne }
@Rep16:
@NoExtraYAdvance6:

    INC    DI

    { Continue if there are any more dest pixels to draw.                   }

    DEC    SI
    JNZ    @TexScanLoop3
    JMP    @ExitScan

@TexScanLoopNeg:

    { Set the Map Mask for this pixel's plane, then draw the pixel.         }

    MOV    AH,[BX]            { Get image pixel                             }
    MOV    ES:[DI],AH         { Set image pixel                             }

    { Point to the next source pixel.                                       }

    ADD    DX,2211h                   { _LSourceStepX }
@Rep18:
    SBB    BX,2211h                   { _LXYBaseAdvance }
@Rep17:
    ADD    CX,2211h                   { _LSourceStepY }
@Rep19:
    JNC    @NoExtraYAdvanceNeg        { Didn't turn over; no extra advance  }
    ADD    BX,2211h                   { LYAdvanceByOne }
@Rep20:
@NoExtraYAdvanceNeg:

    INC    DI

    { Continue if there are any more dest pixels to draw.                   }

    DEC    SI
    JNZ    @TexScanLoopNeg

    MOV    CX,WORD PTR SaveSourceX
    MOV    WORD PTR LSourceX,CX
    MOV    CX,WORD PTR SaveSourceY
    MOV    WORD PTR LSourceY,CX

    POP    DI
    POP    BX
    POP    SI
    PUSH   SI
    ADD    SI,2
    SHR    SI,1
    SHR    SI,1
    JZ     @ExitScan
    ROL    AL,1
    ADC    DI,0
    MOV    DX,SC_INDEX + 1    { Point to SC Data; Index points to Map Mask  }
    OUT    DX,AL
    ADD    BX,WORD PTR LXYBaseAdvance
    MOV    CX,WORD PTR LSourceStepX
    ADD    WORD PTR LSourceX,CX       { Step the source X fractional part   }
    SBB    BX,0
    MOV    CX,WORD PTR LSourceStepY
    ADD    WORD PTR LSourceY,CX       { Step the source Y fractional part   }
    JNC    @NoExtraYAdvanceNeg1       { Didn't turn over; no extra advance  }
    ADD    BX,WORD PTR LYAdvanceByOne { Did turn over; advance Y one extra  }
@NoExtraYAdvanceNeg1:
    PUSH   BX
    PUSH   DI
    MOV    CX,WORD PTR LSourceX
    MOV    WORD PTR SaveSourceX,CX
    MOV    DX,CX
    MOV    CX,WORD PTR LSourceY
    MOV    WORD PTR SaveSourceY,CX
@TexScanLoopNeg1:

    { Set the Map Mask for this pixel's plane, then draw the pixel.         }

    MOV    AH,[BX]            { Get image pixel                             }
    MOV    ES:[DI],AH         { Set image pixel                             }

    { Point to the next source pixel.                                       }

    ADD    DX,2211h                   { _LSourceStepX }
@Rep22:
    SBB    BX,2211h                   { _LXYBaseAdvance }
@Rep21:
    ADD    CX,2211h                   { _LSourceStepY }
@Rep23:
    JNC    @NoExtraYAdvanceNeg2       { Didn't turn over; no extra advance  }
    ADD    BX,2211h                   { LYAdvanceByOne }
@Rep24:
@NoExtraYAdvanceNeg2:

    INC    DI

    { Continue if there are any more dest pixels to draw.                   }

    DEC    SI
    JNZ    @TexScanLoopNeg1

    MOV    CX,WORD PTR SaveSourceX
    MOV    WORD PTR LSourceX,CX
    MOV    CX,WORD PTR SaveSourceY
    MOV    WORD PTR LSourceY,CX

    POP    DI
    POP    BX
    POP    SI
    PUSH   SI
    INC    SI
    SHR    SI,1
    SHR    SI,1
    JZ     @ExitScan
    ROL    AL,1
    ADC    DI,0
    MOV    DX,SC_INDEX + 1    { Point to SC Data; Index points to Map Mask  }
    OUT    DX,AL
    ADD    BX,WORD PTR LXYBaseAdvance
    MOV    CX,WORD PTR LSourceStepX
    ADD    WORD PTR LSourceX,CX       { Step the source X fractional part   }
    SBB    BX,0
    MOV    CX,WORD PTR LSourceStepY
    ADD    WORD PTR LSourceY,CX       { Step the source Y fractional part   }
    JNC    @NoExtraYAdvanceNeg3       { Didn't turn over; no extra advance  }
    ADD    BX,WORD PTR LYAdvanceByOne { Did turn over; advance Y one extra  }
@NoExtraYAdvanceNeg3:
    PUSH   BX
    PUSH   DI
    MOV    CX,WORD PTR LSourceX
    MOV    WORD PTR SaveSourceX,CX
    MOV    DX,CX
    MOV    CX,WORD PTR LSourceY
    MOV    WORD PTR SaveSourceY,CX
@TexScanLoopNeg2:

    { Set the Map Mask for this pixel's plane, then draw the pixel.         }

    MOV    AH,[BX]            { Get image pixel                             }
    MOV    ES:[DI],AH         { Set image pixel                             }

    { Point to the next source pixel.                                       }

    ADD    DX,2211h                   { _LSourceStepX }
@Rep26:
    SBB    BX,2211h                   { _LXYBaseAdvance }
@Rep25:
    ADD    CX,2211h                   { _LSourceStepY }
@Rep27:
    JNC    @NoExtraYAdvanceNeg4       { Didn't turn over; no extra advance  }
    ADD    BX,2211h                   { LYAdvanceByOne }
@Rep28:
@NoExtraYAdvanceNeg4:

    INC    DI

    { Continue if there are any more dest pixels to draw.                   }

    DEC    SI
    JNZ    @TexScanLoopNeg2

    MOV    CX,WORD PTR SaveSourceX
    MOV    WORD PTR LSourceX,CX
    MOV    CX,WORD PTR SaveSourceY
    MOV    WORD PTR LSourceY,CX

    POP    DI
    POP    BX
    POP    SI
    PUSH   SI
    SHR    SI,1
    SHR    SI,1
    JZ     @ExitScan
    ROL    AL,1
    ADC    DI,0
    MOV    DX,SC_INDEX + 1    { Point to SC Data; Index points to Map Mask  }
    OUT    DX,AL
    ADD    BX,WORD PTR LXYBaseAdvance
    MOV    CX,WORD PTR LSourceStepX
    ADD    WORD PTR LSourceX,CX       { Step the source X fractional part   }
    SBB    BX,0
    MOV    CX,WORD PTR LSourceStepY
    ADD    WORD PTR LSourceY,CX       { Step the source Y fractional part   }
    JNC    @NoExtraYAdvanceNeg5       { Didn't turn over; no extra advance  }
    ADD    BX,WORD PTR LYAdvanceByOne { Did turn over; advance Y one extra  }
@NoExtraYAdvanceNeg5:
    MOV    CX,WORD PTR LSourceX
    MOV    DX,CX
    MOV    CX,WORD PTR LSourceY
@TexScanLoopNeg3:

    { Set the Map Mask for this pixel's plane, then draw the pixel.         }

    MOV    AH,[BX]            { Get image pixel                             }
    MOV    ES:[DI],AH         { Set image pixel                             }

    { Point to the next source pixel.                                       }

    ADD    DX,2211h                   { _LSourceStepX }
@Rep30:
    SBB    BX,2211h                   { _LXYBaseAdvance }
@Rep29:
    ADD    CX,2211h                   { _LSourceStepY }
@Rep31:
    JNC    @NoExtraYAdvanceNeg6       { Didn't turn over; no extra advance  }
    ADD    BX,2211h                   { LYAdvanceByOne }
@Rep32:
@NoExtraYAdvanceNeg6:

    INC    DI

    { Continue if there are any more dest pixels to draw.                   }

    DEC    SI
    JNZ    @TexScanLoopNeg3

@ExitScan:

    POP    SI
    POP    DS
  End; { Asm }
ScanDone:
End; { ScanOutLine }


Procedure ScanOutLineVert(LeftEdge,RightEdge: EdgeScan; TexMapWidth,
 DestX,CurrentPageBase,ClipMinY,ClipMaxY: Word; TexMapBits: Pointer);
{ ------------------------------------------------------------------------- }
{ Draws all pixels in the specified scan line, with the pixel colors taken  }
{ from the specified texture map.  Uses approach of pre-stepping 1/2 pixel  }
{ into the source image and rounding to the nearest source pixel at each    }
{ step, so that texture maps will appear reasonably similar at all angles.  }
{ This routine is specific to 320-pixel-wide planar (non-Chain4) 256-color  }
{ modes, such as mode X, which is a planar (non-Chain4) 256-color mode with }
{ a resolution of 320x240.                                                  }
{ ------------------------------------------------------------------------- }
Var
  LSourceX       : LongInt; { Current X coordinate in source image          }
  LSourceY       : LongInt; { Current Y coordinate in source image          }
  LSourceStepX   : LongInt; { X step in source image for X dest step of 1   }
  LSourceStepY   : LongInt; { Y step in source image for X dest step of 1   }
  LXAdvanceByOne : Word;    { Used to step source pointer 1 pixel           }
                            {  incrementally in X                           }
  LXBaseAdvance  : Word;    { Use to step source pointer minimum number of  }
                            {  pixels incrementally in X                    }
  LYAdvanceByOne : Word;    { Used to step source pointer 1 pixel           }
                            {  incrementally in Y                           }
  LYBaseAdvance  : Word;    { Use to step source pointer minimum number of  }
                            {  pixels incrementally in Y                    }
  LXYBaseAdvance : Word;
  Save_SP        : Word;

Label ScanDone;
Begin
  Asm
    JMP    @Start
@ToScanDone:
    JMP    ScanDone
@Start:

    { Nothing to do if destination is fully Y clipped. }

    MOV    SI,WORD PTR RightEdge.DestX
    CMP    SI,WORD PTR ClipMinY
    JLE    @ToScanDone        { Right edge is to left of clip rect, so done }
    MOV    DX,WORD PTR LeftEdge.DestX
    CMP    DX,WORD PTR ClipMaxY
    JGE    @ToScanDone        { Left edge is to right of clip rect, so done }
    SUB    SI,DX
    JLE    @ToScanDone        { Null or negative full width, so done        }

    MOV    AX,WORD PTR LeftEdge.SourceX       { Initial source X coordinate }
    MOV    WORD PTR LSourceX,AX
    MOV    AX,WORD PTR LeftEdge.SourceX + 2
    MOV    WORD PTR LSourceX + 2,AX
    MOV    AX,WORD PTR LeftEdge.SourceY       { Initial source Y coordinate }
    MOV    WORD PTR LSourceY,AX
    MOV    AX,WORD PTR LeftEdge.SourceY + 2
    MOV    WORD PTR LSourceY + 2,AX

    { Calculate source steps that correspond to each 1-pixel destination X  }
    { step (across the destination scan line).                              }

    PUSH   SI                 { Push dest X width, in fixedpoint form       }
    SUB    AX,AX
    PUSH   AX                 { Push 0 as fractional part of dest X width   }
    MOV    AX,WORD PTR RightEdge.SourceX
    SUB    AX,WORD PTR LSourceX               { Low word of source X width  }
    MOV    DX,WORD PTR RightEdge.SourceX + 2
    SBB    DX,WORD PTR LSourceX + 2           { High word of source X width }
    PUSH   DX                 { Push source X width, in fixedpoint form     }
    PUSH   AX
    CALL   FAR PTR _FixedDiv  { Scale source X width to dest X width        }
    ADD    SP,8               { Clear parameters from stack                 }
    MOV    WORD PTR LSourceStepX,AX           { Remember source X step for  }
    MOV    WORD PTR LSourceStepX + 2,DX       {  1-pixel destination X step }
    MOV    CX,1               { Assume source X advances non-negative       }
    AND    DX,DX              { Which way does source X advance?            }
    JNS    @SourceXNonNeg     { Non-negative                                }
    NEG    CX                 { Negative                                    }
    CMP    AX,0               { Is the whole step exactly an integer?       }
    JZ     @SourceXNonNeg     { Yes                                         }
    INC    DX                 { No, truncate to integer in the direction of }
                              {  0, because otherwise we'll end up with a   }
                              {  whole step of 1-too-large magnitude        }
@SourceXNonNeg:
    MOV    WORD PTR LXAdvanceByOne,CX { Amount to add to source pointer to  }
                                      {  move by one in X                   }
    MOV    WORD PTR LXBaseAdvance,DX  { Minimum amount to add to source     }
                                      {  pointer to advance in X each time  }
                                      {  the dest advances one in X         }
    PUSH   SI                 { Push dest Y height, in fixedpoint form      }
    SUB    AX,AX
    PUSH   AX                 { Push 0 as fractional part of dest Y height  }
    MOV    AX,WORD PTR RightEdge.SourceY
    SUB    AX,WORD PTR LSourceY              { Low word of source Y height  }
    MOV    DX,WORD PTR RightEdge.SourceY + 2
    SBB    DX,WORD PTR LSourceY + 2          { High word of source Y height }
    PUSH   DX                 { Push source Y height, in fixedpoint form    }
    PUSH   AX
    CALL   FAR PTR _FixedDiv  { Scale source Y height to dest X width       }
    ADD    SP,8               { Clear parameters from stack                 }
    MOV    WORD PTR LSourceStepY,AX           { Remember source Y step for  }
    MOV    WORD PTR LSourceStepY + 2,DX       {  1-pixel destination X step }
    MOV    CX,WORD PTR TexMapWidth  { Assume source Y advances non-negative }
    AND    DX,DX              { Which way does source Y advance?            }
    JNS    @SourceYNonNeg     { Non-negative                                }
    NEG    CX                 { Negative                                    }
    CMP    AX,0               { Is the whole step exactly an integer?       }
    JZ     @SourceYNonNeg     { Yes                                         }
    INC    DX                 { No, truncate to integer in the direction of }
                              {  0, because otherwise we'll end up with a   }
                              {  whole step of 1-too-large magnitude        }
@SourceYNonNeg:
    MOV    WORD PTR LYAdvanceByOne,CX { Amount to add to source pointer to  }
                                      {  move by one in Y                   }
    MOV    AX,WORD PTR TexMapWidth   { Minimum distance skipped in source   }
    IMUL   DX                        {  image bitmap when Y steps (ignoring }
    MOV    WORD PTR LYBaseAdvance,AX {  carry from the fractional part      }

    { Advance 1/2 step in the stepping direction, to space scanned pixels   }
    { evenly between the left and right edges.  (There's a slight inaccuracy}
    { in dividing negative numbers by 2 by shifting rather than dividing,   }
    { but the inaccuracy is in the least significant bit, and we'll just    }
    { live with it.                                                         }

    MOV    AX,WORD PTR LSourceStepX
    MOV    DX,WORD PTR LSourceStepX + 2
    SAR    DX,1
    RCR    AX,1
    ADD    WORD PTR LSourceX,AX
    ADC    WORD PTR LSourceX + 2,DX

    MOV    AX,WORD PTR LSourceStepY
    MOV    DX,WORD PTR LSourceStepY + 2
    SAR    DX,1
    RCR    AX,1
    ADD    WORD PTR LSourceY,AX
    ADC    WORD PTR LSourceY + 2,DX

    { Clip the right edge if necessary. }

    MOV    SI,WORD PTR RightEdge.DestX
    CMP    SI,WORD PTR ClipMaxY
    JL     @RightEdgeClipped
    MOV    SI,WORD PTR ClipMaxY
@RightEdgeClipped:
    { Clip the left edge if necessary. }

    MOV    DI,WORD PTR LeftEdge.DestX
    CMP    DI,WORD PTR ClipMinY
    JGE    @LeftEdgeClipped

    { Left clipping is necessary; advance the source accordingly }

    NEG    DI
    ADD    DI,WORD PTR ClipMinY { ClipMinX - DestX                          }
                                { First, advance the source in X            }
    PUSH   DI                   { Push ClipMinX - DestX, in fixedpoint form }
    SUB    AX,AX
    PUSH   AX               { Push 0 as fractional part of ClipMinX - DestX }
    PUSH   WORD PTR LSourceStepX + 2
    PUSH   WORD PTR LSourceStepX
    CALL   FAR PTR _FixedMul  { Total source X stepping in clipped area     }
    ADD    SP,8               { Clear parameters from stack                 }
    ADD    WORD PTR LSourceX,AX  { Step the source X past clipping          }
    ADC    WORD PTR LSourceX + 2,DX

    { now advance the source in Y }

    PUSH   DI                 { Push ClipMinX - DestX, in fixedpoint form   }
    SUB    AX,AX
    PUSH   AX               { Push 0 as fractional part of ClipMinX - DestX }
    PUSH   WORD PTR LSourceStepY + 2
    PUSH   WORD PTR LSourceStepY
    CALL   FAR PTR _FixedMul  { Total source Y stepping in clipped area     }
    ADD    SP,8               { Clear parameters from stack                 }
    ADD    WORD PTR LSourceY,AX  { Step the source X past clipping          }
    ADC    WORD PTR LSourceY + 2,DX
    MOV    DI,WORD PTR ClipMinY { Start X coordinate in dest after clipping }
@LeftEdgeClipped:

    { Calculate actual clipped destination drawing width. }

    SUB    SI,DI

    { Scan across the destination scan line, updating the source image      }
    { position accordingly.  Point to the initial source image pixel, adding}
    { 0.5 to both X and Y so that we can truncate to integers from now on   }
    { but effectively get rounding.                                         }

    ADD    WORD PTR LSourceY,8000h   { Add 0.5                              }
    MOV    AX,WORD PTR LSourceY + 2
    ADC    AX,0
    MUL    WORD PTR TexMapWidth { Initial scan line in source image         }
    ADD    WORD PTR LSourceX,8000h   { Add 0.5                              }
    MOV    BX,WORD PTR LSourceX + 2  { Offset into source scan line         }
    ADC    BX,AX              { Initial source offset in source image       }
    ADD    BX,WORD PTR TexMapBits { DS:BX points to the initial image pixel }
    PUSH   DS
    MOV    AX,WORD PTR TexMapBits + 2
    MOV    DS,AX

    { Point to initial destination pixel }

    MOV    AX,SCREEN_SEG
    MOV    ES,AX
{    MOV    AX,_SCREEN_WIDTH}
    MOV    AX,DI              { Hard-coded multiply by 80 }
    MOV    CX,0204h
    SHL    AX,CL
    MOV    DX,AX
    XCHG   CH,CL
    SHL    DX,CL
    ADD    AX,DX
{    MUL    WORD PTR DestY}    { Offset of initial dest scan line            }
    MOV    CX,WORD PTR DestX  { Initial destination X                       }
    MOV    DI,CX
    SHR    DI,1
    SHR    DI,1               { X/4 = offset of pixel in scan line          }
    ADD    DI,AX              { Offset of pixel in page                     }
    ADD    DI,WORD PTR CurrentPageBase { Offset of pixel in display memory  }
                              { ES:DI now points to the first destination   }
                              {  pixel                                      }

    AND    CL,011b            { CL = pixel's plane                          }
    MOV    AL,MAP_MASK
    MOV    DX,SC_INDEX
    OUT    DX,AL              { Point the SC Index register to the Map Mask }
    MOV    AL,11h             { One plane bit in each nibble, so we'll get  }
                              {  carry automatically when going from plane 3}
                              {  to plane 0                                 }
    SHL    AL,CL              { Set the bit for the first pixel's plane to 1}

    { If the source X step is negative, change over to working with non-    }
    { negative values.                                                      }

    CMP    WORD PTR LXAdvanceByOne,0
    JGE    @SXStepSet
    NEG    WORD PTR LSourceStepX
    NOT    WORD PTR LSourceX
@SXStepSet:

    { If the source Y step is negative, change over to working with non-    }
    { negative values.                                                      }

    CMP    WORD PTR LYAdvanceByOne,0
    JGE    @SYStepSet
    NEG    WORD PTR LSourceStepY
    NOT    WORD PTR LSourceY
@SYStepSet:

    { At this point:                                                        }
    {      AL = initial pixel's plane mask                                  }
    {      BX = pointer to initial image pixel                              }
    {      SI = # of pixels to fill                                         }
    {      DI = pointer to initial destination pixel                        }

    MOV    DX,SC_INDEX + 1    { Point to SC Data; Index points to Map Mask  }
    OUT    DX,AL
    MOV    CX,WORD PTR LXBaseAdvance
    ADD    CX,WORD PTR LYBaseAdvance
    MOV    WORD PTR LXYBaseAdvance,CX
    CMP    WORD PTR LXAdvanceByOne,0
    JGE    @TexScanLoopPos
    NEG    CX
@TexScanLoopPos:
    MOV    WORD PTR CS:[@Rep1 - 2],CX  { Self-modifying code; it's faster   }
    MOV    WORD PTR CS:[@Rep5 - 2],CX  {  to use immediates than to read    }
    MOV    CX,WORD PTR LSourceStepX    {  from memory                       }
    MOV    WORD PTR CS:[@Rep2 - 2],CX
    MOV    WORD PTR CS:[@Rep6 - 2],CX
    MOV    CX,WORD PTR LSourceStepY
    MOV    WORD PTR CS:[@Rep3 - 2],CX
    MOV    WORD PTR CS:[@Rep7 - 2],CX
    MOV    CX,WORD PTR LYAdvanceByOne
    MOV    WORD PTR CS:[@Rep4 - 2],CX
    MOV    WORD PTR CS:[@Rep8 - 2],CX

    MOV    DX,WORD PTR LSourceX
    MOV    CX,WORD PTR LSourceY
    CMP    WORD PTR LXAdvanceByOne,0
    JL     @TexScanLoop1
@TexScanLoop:                 { Positive source X increments                }

    { Set the Map Mask for this pixel's plane, then draw the pixel.         }

    MOV    AH,[BX]            { Get image pixel                             }
    MOV    ES:[DI],AH         { Set image pixel                             }

    { Point to the next source pixel.                                       }

    ADD    DX,2211h                   { LSourceStepX }
@Rep2:
    ADC    BX,2211h                   { LXYBaseAdvance}
@Rep1:
    ADD    CX,2211h                   { LsourceStepY }
@Rep3:
    JNC    @NoExtraYAdvance           { Didn't turn over; no extra advance  }
    ADD    BX,2211h                   { LYAdvanceByOne }
@Rep4:
@NoExtraYAdvance:

    ADD    DI,_SCREEN_WIDTH

    { Continue if there are any more dest pixels to draw.                   }

    DEC    SI
    JNZ    @TexScanLoop
    JMP    @EndScan
@TexScanLoop1:                { Negative source X increments                }

    { Set the Map Mask for this pixel's plane, then draw the pixel.         }

    MOV    AH,[BX]            { Get image pixel                             }
    MOV    ES:[DI],AH         { Set image pixel                             }

    { Point to the next source pixel.                                       }

    ADD    DX,2211h                   { LSourceStepX }
@Rep6:
    SBB    BX,2211h                   { LXYBaseAdvance }
@Rep5:
    ADD    CX,2211h                   { LSourceStepY }
@Rep7:
    JNC    @NoExtraYAdvance1          { Didn't turn over; no extra advance  }
    ADD    BX,2211h                   { LYAdvanceByOne }
@Rep8:
@NoExtraYAdvance1:

    ADD    DI,_SCREEN_WIDTH

    { Continue if there are any more dest pixels to draw.                   }

    DEC    SI
    JNZ    @TexScanLoop1
@EndScan:
    POP    DS
  End; { Asm }
ScanDone:
End; { ScanOutLineVert }


Procedure ScanOutLineBuf(LeftEdge,RightEdge: EdgeScan; TexMapWidth,
 DestX,BufSeg,ClipMinX,ClipMaxX: Word; TexMapBits: Pointer);
{ ------------------------------------------------------------------------- }
{ Draws all pixels in the specified scan line, with the pixel colors taken  }
{ from the specified texture map.  Uses approach of pre-stepping 1/2 pixel  }
{ into the source image and rounding to the nearest source pixel at each    }
{ step, so that texture maps will appear reasonably similar at all angles.  }
{ This routine is specific to 320-pixel-wide planar (non-Chain4) 256-color  }
{ modes, such as mode X, which is a planar (non-Chain4) 256-color mode with }
{ a resolution of 320x240.                                                  }
{ ------------------------------------------------------------------------- }
Var
  LSourceX       : LongInt; { Current X coordinate in source image          }
  LSourceY       : LongInt; { Current Y coordinate in source image          }
  LSourceStepX   : LongInt; { X step in source image for X dest step of 1   }
  LSourceStepY   : LongInt; { Y step in source image for X dest step of 1   }
  LXAdvanceByOne : Word;    { Used to step source pointer 1 pixel           }
                            {  incrementally in X                           }
  LXBaseAdvance  : Word;    { Use to step source pointer minimum number of  }
                            {  pixels incrementally in X                    }
  LYAdvanceByOne : Word;    { Used to step source pointer 1 pixel           }
                            {  incrementally in Y                           }
  LYBaseAdvance  : Word;    { Use to step source pointer minimum number of  }
                            {  pixels incrementally in Y                    }
  Save_SP        : Word;
  StartX         : Word;

Label ScanDone;
Begin
  Asm
    CLI
    CLD
    JMP    @Start
@ToScanDone:
    JMP    ScanDone
@Start:

    { Nothing to do if destination is fully X clipped. }

    MOV    SI,WORD PTR RightEdge.DestX
    CMP    SI,WORD PTR ClipMinX
    JLE    @ToScanDone        { Right edge is to left of clip rect, so done }
    MOV    DX,WORD PTR LeftEdge.DestX
    CMP    DX,WORD PTR ClipMaxX
    JGE    @ToScanDone        { Left edge is to right of clip rect, so done }
    SUB    SI,DX
    JLE    @ToScanDone        { Null or negative full width, so done        }

    DB     _32BIT
    MOV    AX,WORD PTR LeftEdge.SourceX       { Initial source X coordinate }
    DB     _32BIT
    MOV    WORD PTR LSourceX,AX

    DB     _32BIT
    MOV    AX,WORD PTR LeftEdge.SourceY       { Initial source Y coordinate }
    DB     _32BIT
    MOV    WORD PTR LSourceY,AX

    { Calculate source steps that correspond to each 1-pixel destination X  }
    { step (across the destination scan line).                              }

    SUB    DX,DX
    DB     _32BIT
    MOV    AX,WORD PTR RightEdge.SourceX      { Get source X width          }
    DB     _32BIT
    SUB    AX,WORD PTR LSourceX
    DB     _32BIT
    OR     AX,AX
    JZ     @SkipDiv
    PUSH   SI                 { Push dest X width, in fixedpoint form       }
    PUSH   WORD PTR 0         { Push 0 as fractional part of dest X width   }
    DB     _32BIT             { Push source X width, in fixedpoint form     }
    PUSH   AX

    CALL   FAR PTR _FixedDiv  { Scale source X width to dest X width        }

    ADD    SP,8               { Clear parameters from stack                 }
@SkipDiv:
    MOV    WORD PTR LSourceStepX,AX           { Remember source X step for  }
    MOV    WORD PTR LSourceStepX + 2,DX       {  1-pixel destination X step }
    MOV    CX,1               { Assume source X advances non-negative       }
    AND    DX,DX              { Which way does source X advance?            }
    JNS    @SourceXNonNeg     { Non-negative                                }
    NEG    CX                 { Negative                                    }
    CMP    AX,0               { Is the whole step exactly an integer?       }
    JZ     @SourceXNonNeg     { Yes                                         }
    INC    DX                 { No, truncate to integer in the direction of }
                              {  0, because otherwise we'll end up with a   }
                              {  whole step of 1-too-large magnitude        }
@SourceXNonNeg:
    MOV    WORD PTR LXAdvanceByOne,CX { Amount to add to source pointer to  }
                                      {  move by one in X                   }
    MOV    WORD PTR LXBaseAdvance,DX  { Minimum amount to add to source     }
                                      {  pointer to advance in X each time  }
                                      {  the dest advances one in X         }
    SUB    DX,DX
    DB     _32BIT
    MOV    AX,WORD PTR RightEdge.SourceY      { Get source Y height         }
    DB     _32BIT
    SUB    AX,WORD PTR LSourceY
    DB     _32BIT
    OR     AX,AX
    JZ     @SkipDiv1
    PUSH   SI                 { Push dest Y height, in fixedpoint form      }
    PUSH   WORD PTR 0         { Push 0 as fractional part of dest Y height  }
    DB     _32BIT             { Push source Y height, in fixedpoint form    }
    PUSH   AX

    CALL   FAR PTR _FixedDiv  { Scale source Y height to dest X width       }

    ADD    SP,8               { Clear parameters from stack                 }
@SkipDiv1:
    MOV    WORD PTR LSourceStepY,AX           { Remember source Y step for  }
    MOV    WORD PTR LSourceStepY + 2,DX       {  1-pixel destination X step }
    MOV    CX,WORD PTR TexMapWidth  { Assume source Y advances non-negative }
    AND    DX,DX              { Which way does source Y advance?            }
    JNS    @SourceYNonNeg     { Non-negative                                }
    NEG    CX                 { Negative                                    }
    CMP    AX,0               { Is the whole step exactly an integer?       }
    JZ     @SourceYNonNeg     { Yes                                         }
    INC    DX                 { No, truncate to integer in the direction of }
                              {  0, because otherwise we'll end up with a   }
                              {  whole step of 1-too-large magnitude        }
@SourceYNonNeg:
    MOV    WORD PTR LYAdvanceByOne,CX { Amount to add to source pointer to  }
                                      {  move by one in Y                   }
    MOV    AX,WORD PTR TexMapWidth   { Minimum distance skipped in source   }
    OR     DX,DX
    JNZ    @DoMul
    MOV    AX,DX
    JMP    @SkipMul
@DoMul:
    IMUL   DX                        {  image bitmap when Y steps (ignoring }
@SkipMul:
    MOV    WORD PTR LYBaseAdvance,AX {  carry from the fractional part      }

    { Advance 1/2 step in the stepping direction, to space scanned pixels   }
    { evenly between the left and right edges.  (There's a slight inaccuracy}
    { in dividing negative numbers by 2 by shifting rather than dividing,   }
    { but the inaccuracy is in the least significant bit, and we'll just    }
    { live with it.                                                         }

    DB     _32BIT
    MOV    AX,WORD PTR LSourceStepX      { MOV   EAX,DWORD PTR LSourceStepX }
    DB     _32BIT
    SAR    AX,1                          { SAR   EAX,1                      }
    DB     _32BIT
    ADD    WORD PTR LSourceX,AX          { ADD   DWORD PTR LSourceX,EAX     }

    DB     _32BIT
    MOV    AX,WORD PTR LSourceStepY      { MOV   EAX,DWORD PTR LSourceStepY }
    DB     _32BIT
    SAR    AX,1                          { SAR   EAX,1                      }
    DB     _32BIT
    ADD    WORD PTR LSourceY,AX          { ADD   DWORD PTR LSourceY,EAX     }

    { Clip the right edge if necessary. }

    MOV    SI,WORD PTR RightEdge.DestX
    CMP    SI,WORD PTR ClipMaxX
    JL     @RightEdgeClipped
    MOV    SI,WORD PTR ClipMaxX
@RightEdgeClipped:

    { Clip the left edge if necessary. }

    MOV    DI,WORD PTR LeftEdge.DestX
    MOV    WORD PTR StartX,DI
    CMP    DI,WORD PTR ClipMinX
    JGE    @LeftEdgeClipped

    { Left clipping is necessary; advance the source accordingly }

    MOV    AX,WORD PTR ClipMinX
    MOV    WORD PTR StartX,AX
    NEG    DI
    ADD    DI,WORD PTR ClipMinX { ClipMinX - DestX                          }
                                { First, advance the source in X            }
    PUSH   DI                   { Push ClipMinX - DestX, in fixedpoint form }
    PUSH   WORD PTR 0           { Push 0 as fractional part of ClipMinX - DestX }
    DB     _32BIT
    PUSH   WORD PTR LSourceStepX
    CALL   FAR PTR _FixedMul  { Total source X stepping in clipped area     }
    ADD    SP,8               { Clear parameters from stack                 }
    ADD    WORD PTR LSourceX,AX  { Step the source X past clipping          }
    ADC    WORD PTR LSourceX + 2,DX

    { now advance the source in Y }

    PUSH   DI                 { Push ClipMinX - DestX, in fixedpoint form   }
    PUSH   WORD PTR 0         { Push 0 as fractional part of ClipMinX - DestX }
    DB     _32BIT
    PUSH   WORD PTR LSourceStepY
    CALL   FAR PTR _FixedMul  { Total source Y stepping in clipped area     }
    ADD    SP,8               { Clear parameters from stack                 }
    ADD    WORD PTR LSourceY,AX  { Step the source X past clipping          }
    ADC    WORD PTR LSourceY + 2,DX
    MOV    DI,WORD PTR ClipMinX { Start X coordinate in dest after clipping }
@LeftEdgeClipped:

    { Calculate actual clipped destination drawing width. }

    SUB    SI,DI

    { Scan across the destination scan line, updating the source image      }
    { position accordingly.  Point to the initial source image pixel, adding}
    { 0.5 to both X and Y so that we can truncate to integers from now on   }
    { but effectively get rounding.                                         }

    DB     _32BIT
    ADD    WORD PTR LSourceY,8000h   { Add 0.5                              }
    DB     0,0
    MOV    AX,WORD PTR LSourceY + 2
    MUL    WORD PTR TexMapWidth { Initial scan line in source image         }
    DB     _32BIT
    ADD    WORD PTR LSourceX,8000h   { Add 0.5                              }
    DB     0,0
    MOV    BX,WORD PTR LSourceX + 2  { Offset into source scan line         }
    ADD    BX,AX
    ADD    BX,WORD PTR TexMapBits { DS:BX points to the initial image pixel }
    PUSH   DS
    MOV    AX,WORD PTR TexMapBits + 2
    MOV    DS,AX

    { Point to initial destination pixel }

    MOV    AX,BufSeg
    MOV    ES,AX

    MOV    AX,WORD PTR DestX                  { Hard-coded multiply by 320 }
    MOV    DH,AL
    SUB    DL,DL
    SHL    AX,6
    ADD    AX,DX
    MOV    DI,WORD PTR StartX { Initial destination X                       }
    ADD    DI,AX              { Offset of pixel in page                     }
                              { ES:DI now points to the first destination   }
                              {  pixel                                      }

    { If the source X step is negative, change over to working with non-    }
    { negative values.                                                      }

    CMP    WORD PTR LXAdvanceByOne,0
    JGE    @SXStepSet
    NEG    WORD PTR LSourceStepX
    NOT    WORD PTR LSourceX
@SXStepSet:

    { If the source Y step is negative, change over to working with non-    }
    { negative values.                                                      }

    CMP    WORD PTR LYAdvanceByOne,0
    JGE    @SYStepSet
    NEG    WORD PTR LSourceStepY
    NOT    WORD PTR LSourceY
@SYStepSet:

    { At this point:                                                        }
    {      BX = pointer to initial image pixel                              }
    {      SI = # of pixels to fill                                         }
    {      DI = pointer to initial destination pixel                        }

    MOV    CX,WORD PTR LXBaseAdvance
    ADD    CX,WORD PTR LYBaseAdvance   { LXYBaseAdvance }
    DEC    CX
    XCHG   SI,BX

    CMP    WORD PTR LSourceStepX,0
    JE     @NoSourceStepX
    CMP    WORD PTR LSourceStepY,0
    JE     @NoSourceStepY

{ ------------------------------------------------------ }
{ General case - LSourceStepX <> 0 and LSourceStepY <> 0 }
{ ------------------------------------------------------ }

    CMP    WORD PTR LXAdvanceByOne,0
    JL     @TexScanLoop1A
    MOV    DX,WORD PTR LSourceX
    MOV    WORD PTR CS:[@Rep1 - 2],CX  { Self-modifying code; it's faster   }
    ADD    CX,WORD PTR LYAdvanceByOne  {  to use immediates than to read    }
    MOV    WORD PTR CS:[@Rep4 - 2],CX  {  from memory                       }
    MOV    CX,WORD PTR LSourceStepX
    MOV    WORD PTR CS:[@Rep2 - 2],CX
    MOV    WORD PTR CS:[@Rep2A - 2],CX
    MOV    CX,WORD PTR LSourceStepY
    MOV    WORD PTR CS:[@Rep3 - 2],CX
    MOV    CX,WORD PTR LSourceY
{ --------------------------------------------- }
@TexScanLoop:                 { Positive source X increments                }

    { Draw the pixel. }

    MOVSB                     { Copy image pixel                            }

    { Point to the next source pixel.                                       }

    ADD    CX,2211h                   { LSourceStepY }
@Rep3:
    JNC    @NoExtraYAdvance           { Didn't turn over; no extra advance  }
    ADD    DX,2211h                   { LSourceStepX }
@Rep2:
    ADC    SI,2211h                   { LXYBaseAdvance+LYAdvanceByOne }
@Rep4:

    { Continue if there are any more dest pixels to draw.                   }

    DEC    BX
    JNZ    @TexScanLoop
    JMP    @EndScan
@NoExtraYAdvance:
    ADD    DX,2211h                   { LSourceStepX }
@Rep2A:
    ADC    SI,2211h                   { LXYBaseAdvance }
@Rep1:

    { Continue if there are any more dest pixels to draw.                   }

    DEC    BX
    JNZ    @TexScanLoop
    JMP    @EndScan
{ --------------------------------------------- }
@TexScanLoop1A:
    NEG    CX
    MOV    WORD PTR CS:[@Rep5 - 2],CX  { Self-modifying code; it's faster   }
    SUB    CX,WORD PTR LYAdvanceByOne
    MOV    WORD PTR CS:[@Rep8 - 2],CX
    MOV    CX,WORD PTR LSourceStepX    {  to use immediates than to read    }
    MOV    WORD PTR CS:[@Rep6 - 2],CX  {  from memory                       }
    MOV    WORD PTR CS:[@Rep6A - 2],CX
    MOV    CX,WORD PTR LSourceStepY
    MOV    WORD PTR CS:[@Rep7 - 2],CX
    MOV    CX,WORD PTR LSourceY
@TexScanLoop1:                { Negative source X increments                }

    { Draw the pixel.}

    MOVSB                     { Copy image pixel                            }

    { Point to the next source pixel.                                       }

    ADD    CX,2211h                   { LSourceStepY }
@Rep7:
    JNC    @NoExtraYAdvance1          { Didn't turn over; no extra advance  }
    ADD    DX,2211h                   { LSourceStepX }
@Rep6:
    SBB    SI,2211h                   { -LXYBaseAdvance-LyAdvanceByOne }
@Rep8:

    { Continue if there are any more dest pixels to draw.                   }

    DEC    BX
    JNZ    @TexScanLoop1
    JMP    @EndScan
@NoExtraYAdvance1:
    ADD    DX,2211h                   { LSourceStepX }
@Rep6A:
    SBB    SI,2211h                   { LXYBaseAdvance }
@Rep5:

    { Continue if there are any more dest pixels to draw.                   }

    DEC    BX
    JNZ    @TexScanLoop1
    JMP    @EndScan

{ ------------------------------- }
{ Special case - LSourceStepX = 0 }
{ ------------------------------- }

@NoSourceStepX:
    MOV    WORD PTR CS:[@Rep9 - 2],CX  { Self-modifying code; it's faster   }
    ADD    CX,WORD PTR LYAdvanceByOne  {  to use immediates than to read    }
    MOV    WORD PTR CS:[@Rep11 - 2],CX {  from memory                       }
    MOV    CX,WORD PTR LSourceStepY
    MOV    WORD PTR CS:[@Rep10 - 2],CX
    MOV    CX,WORD PTR LSourceY
@TexScanLoop2:                { Positive source X increments                }

    { Draw the pixel. }

    MOVSB                     { Copy image pixel                            }

    { Point to the next source pixel.                                       }

    ADD    CX,2211h                   { LSourceStepY }
@Rep10:
    JNC    @NoExtraYAdvance2          { Didn't turn over; no extra advance  }
    ADD    SI,2211h                   { LYAdvanceByOne+LXYBaseAdvance }
@Rep11:

    { Continue if there are any more dest pixels to draw.                   }

    DEC    BX
    JNZ    @TexScanLoop2
    JMP    @EndScan
@NoExtraYAdvance2:
    ADD    SI,2211h                   { LXYBaseAdvance }
@Rep9:

    { Continue if there are any more dest pixels to draw.                   }

    DEC    BX
    JNZ    @TexScanLoop2
    JMP    @EndScan

{ ------------------------------- }
{ Special case - LSourceStepY = 0 }
{ ------------------------------- }

@NoSourceStepY:
    MOV    DX,WORD PTR LSourceX
    CMP    WORD PTR LXAdvanceByOne,0
    JL     @TexScanLoop4A
    MOV    WORD PTR CS:[@Rep12 - 2],CX { Self-modifying code; it's faster   }
    MOV    CX,WORD PTR LSourceStepX    {  to use immediates than to read    }
    MOV    WORD PTR CS:[@Rep13 - 2],CX {  from memory                       }
{ --------------------------------------------- }
@TexScanLoop3:                { Positive source X increments                }

    { Draw the pixel. }

    MOVSB                     { Copy image pixel                            }

    { Point to the next source pixel.                                       }

    ADD    DX,2211h                   { LSourceStepX }
@Rep13:
    ADC    SI,2211h                   { LXYBaseAdvance }
@Rep12:

    { Continue if there are any more dest pixels to draw.                   }

    DEC    BX
    JNZ    @TexScanLoop3
    JMP    @EndScan
{ --------------------------------------------- }
@TexScanLoop4A:
    NEG    CX
    MOV    WORD PTR CS:[@Rep14 - 2],CX { Self-modifying code; it's faster   }
    MOV    CX,WORD PTR LSourceStepX    {  to use immediates than to read    }
    MOV    WORD PTR CS:[@Rep15 - 2],CX {  from memory                       }
@TexScanLoop4:                { Negative source X increments                }

    { Draw the pixel. }

    MOVSB                     { Copy image pixel                            }

    { Point to the next source pixel.                                       }

    ADD    DX,2211h                   { LSourceStepX }
@Rep15:
    SBB    SI,2211h                   { LXYBaseAdvance }
@Rep14:

    { Continue if there are any more dest pixels to draw.                   }

    DEC    BX
    JNZ    @TexScanLoop4

@EndScan:
    POP    DS
  End; { Asm }
ScanDone:
  Asm
    STI
  End; { Asm }
End; { ScanOutLineBuf }


Procedure ScanOutLineBufVert(LeftEdge,RightEdge: EdgeScan; TexMapWidth,
 DestX,BufSeg,ClipMinY,ClipMaxY: Word; TexMapBits: Pointer);
{ ------------------------------------------------------------------------- }
{ Draws all pixels in the specified scan line, with the pixel colors taken  }
{ from the specified texture map.  Uses approach of pre-stepping 1/2 pixel  }
{ into the source image and rounding to the nearest source pixel at each    }
{ step, so that texture maps will appear reasonably similar at all angles.  }
{ This routine is specific to 320-pixel-wide planar (non-Chain4) 256-color  }
{ modes, such as mode X, which is a planar (non-Chain4) 256-color mode with }
{ a resolution of 320x240.                                                  }
{ ------------------------------------------------------------------------- }
Var
  LSourceX       : LongInt; { Current X coordinate in source image          }
  LSourceY       : LongInt; { Current Y coordinate in source image          }
  LSourceStepX   : LongInt; { X step in source image for X dest step of 1   }
  LSourceStepY   : LongInt; { Y step in source image for X dest step of 1   }
  LXAdvanceByOne : Word;    { Used to step source pointer 1 pixel           }
                            {  incrementally in X                           }
  LXBaseAdvance  : Word;    { Use to step source pointer minimum number of  }
                            {  pixels incrementally in X                    }
  LYAdvanceByOne : Word;    { Used to step source pointer 1 pixel           }
                            {  incrementally in Y                           }
  LYBaseAdvance  : Word;    { Use to step source pointer minimum number of  }
                            {  pixels incrementally in Y                    }
  Save_SP        : Word;

Label ScanDone;
Begin
  Asm
    CLI
    CLD
    JMP    @Start
@ToScanDone:
    JMP    ScanDone
@Start:

    { Nothing to do if destination is fully Y clipped. }

    MOV    SI,WORD PTR RightEdge.DestX
    CMP    SI,WORD PTR ClipMinY
    JLE    @ToScanDone        { Right edge is to left of clip rect, so done }
    MOV    DX,WORD PTR LeftEdge.DestX
    CMP    DX,WORD PTR ClipMaxY
    JGE    @ToScanDone        { Left edge is to right of clip rect, so done }
    SUB    SI,DX
    JLE    @ToScanDone        { Null or negative full width, so done        }

    DB     _32BIT
    MOV    AX,WORD PTR LeftEdge.SourceX       { Initial source X coordinate }
    DB     _32BIT
    MOV    WORD PTR LSourceX,AX

    DB     _32BIT
    MOV    AX,WORD PTR LeftEdge.SourceY       { Initial source Y coordinate }
    DB     _32BIT
    MOV    WORD PTR LSourceY,AX

    { Calculate source steps that correspond to each 1-pixel destination X  }
    { step (across the destination scan line).                              }

    SUB    DX,DX
    DB     _32BIT
    MOV    AX,WORD PTR RightEdge.SourceX      { Get source X width          }
    DB     _32BIT
    SUB    AX,WORD PTR LSourceX
    DB     _32BIT
    OR     AX,AX
    JZ     @SkipDiv
    PUSH   SI                 { Push dest X width, in fixedpoint form       }
    PUSH   WORD PTR 0         { Push 0 as fractional part of dest X width   }
    DB     _32BIT             { Push source X width, in fixedpoint form     }
    PUSH   AX

    CALL   FAR PTR _FixedDiv  { Scale source X width to dest X width        }

    ADD    SP,8               { Clear parameters from stack                 }
@SkipDiv:

    MOV    WORD PTR LSourceStepX,AX           { Remember source X step for  }
    MOV    WORD PTR LSourceStepX + 2,DX       {  1-pixel destination X step }

    MOV    CX,1               { Assume source X advances non-negative       }
    AND    DX,DX              { Which way does source X advance?            }
    JNS    @SourceXNonNeg     { Non-negative                                }
    NEG    CX                 { Negative                                    }
    CMP    AX,0               { Is the whole step exactly an integer?       }
    JZ     @SourceXNonNeg     { Yes                                         }
    INC    DX                 { No, truncate to integer in the direction of }
                              {  0, because otherwise we'll end up with a   }
                              {  whole step of 1-too-large magnitude        }
@SourceXNonNeg:
    MOV    WORD PTR LXAdvanceByOne,CX { Amount to add to source pointer to  }
                                      {  move by one in X                   }
    MOV    WORD PTR LXBaseAdvance,DX  { Minimum amount to add to source     }
                                      {  pointer to advance in X each time  }
                                      {  the dest advances one in X         }

    SUB    DX,DX
    DB     _32BIT
    MOV    AX,WORD PTR RightEdge.SourceY      { Get source Y height         }
    DB     _32BIT
    SUB    AX,WORD PTR LSourceY
    DB     _32BIT
    OR     AX,AX
    JZ     @SkipDiv1
    PUSH   SI                 { Push dest Y height, in fixedpoint form      }
    PUSH   WORD PTR 0         { Push 0 as fractional part of dest Y height  }
    DB     _32BIT             { Push source Y height, in fixedpoint form    }
    PUSH   AX

    CALL   FAR PTR _FixedDiv  { Scale source Y height to dest X width       }

    ADD    SP,8               { Clear parameters from stack                 }
@SkipDiv1:

    MOV    WORD PTR LSourceStepY,AX           { Remember source Y step for  }
    MOV    WORD PTR LSourceStepY + 2,DX       {  1-pixel destination X step }

    MOV    CX,WORD PTR TexMapWidth  { Assume source Y advances non-negative }
    AND    DX,DX              { Which way does source Y advance?            }
    JNS    @SourceYNonNeg     { Non-negative                                }
    NEG    CX                 { Negative                                    }
    CMP    AX,0               { Is the whole step exactly an integer?       }
    JZ     @SourceYNonNeg     { Yes                                         }
    INC    DX                 { No, truncate to integer in the direction of }
                              {  0, because otherwise we'll end up with a   }
                              {  whole step of 1-too-large magnitude        }
@SourceYNonNeg:
    MOV    WORD PTR LYAdvanceByOne,CX { Amount to add to source pointer to  }
                                      {  move by one in Y                   }
    MOV    AX,WORD PTR TexMapWidth   { Minimum distance skipped in source   }
    OR     DX,DX
    JNZ    @DoMul
    MOV    AX,DX
    JMP    @SkipMul
@DoMul:
    IMUL   DX                        {  image bitmap when Y steps (ignoring }
@SkipMul:
    MOV    WORD PTR LYBaseAdvance,AX {  carry from the fractional part      }

    { Advance 1/2 step in the stepping direction, to space scanned pixels   }
    { evenly between the left and right edges.  (There's a slight inaccuracy}
    { in dividing negative numbers by 2 by shifting rather than dividing,   }
    { but the inaccuracy is in the least significant bit, and we'll just    }
    { live with it.                                                         }

    DB     _32BIT
    MOV    AX,WORD PTR LSourceStepX      { MOV   EAX,DWORD PTR LSourceStepX }
    DB     _32BIT
    SAR    AX,1                          { SAR   EAX,1                      }
    DB     _32BIT
    ADD    WORD PTR LSourceX,AX          { ADD   DWORD PTR LSourceX,EAX     }

    DB     _32BIT
    MOV    AX,WORD PTR LSourceStepY      { MOV   EAX,DWORD PTR LSourceStepY }
    DB     _32BIT
    SAR    AX,1                          { SAR   EAX,1                      }
    DB     _32BIT
    ADD    WORD PTR LSourceY,AX          { ADD   DWORD PTR LSourceY,EAX     }

    { Clip the right edge if necessary. }

    MOV    SI,WORD PTR RightEdge.DestX
    CMP    SI,WORD PTR ClipMaxY
    JL     @RightEdgeClipped
    MOV    SI,WORD PTR ClipMaxY
@RightEdgeClipped:

    { Clip the left edge if necessary. }

    MOV    DI,WORD PTR LeftEdge.DestX
    CMP    DI,WORD PTR ClipMinY
    JGE    @LeftEdgeClipped

    { Left clipping is necessary; advance the source accordingly }

    NEG    DI
    ADD    DI,WORD PTR ClipMinY { ClipMinX - DestX                          }

    { First, advance the source in X            }

    PUSH   DI                   { Push ClipMinX - DestX, in fixedpoint form }
    PUSH   WORD PTR 0           { Push 0 as fractional part of ClipMinX - DestX }
    DB     _32BIT
    PUSH   WORD PTR LSourceStepX
    CALL   FAR PTR _FixedMul  { Total source X stepping in clipped area     }
    ADD    SP,8               { Clear parameters from stack                 }
    ADD    WORD PTR LSourceX,AX  { Step the source X past clipping          }
    ADC    WORD PTR LSourceX + 2,DX

    { now advance the source in Y }

    PUSH   DI                 { Push ClipMinX - DestX, in fixedpoint form   }
    PUSH   WORD PTR 0         { Push 0 as fractional part of ClipMinX - DestX }
    DB     _32BIT
    PUSH   WORD PTR LSourceStepY
    CALL   FAR PTR _FixedMul  { Total source Y stepping in clipped area     }
    ADD    SP,8               { Clear parameters from stack                 }
    ADD    WORD PTR LSourceY,AX  { Step the source X past clipping          }
    ADC    WORD PTR LSourceY + 2,DX
    MOV    DI,WORD PTR ClipMinY { Start X coordinate in dest after clipping }
@LeftEdgeClipped:

    { Calculate actual clipped destination drawing width. }

    SUB    SI,DI

    { Scan across the destination scan line, updating the source image      }
    { position accordingly.  Point to the initial source image pixel, adding}
    { 0.5 to both X and Y so that we can truncate to integers from now on   }
    { but effectively get rounding.                                         }

    DB     _32BIT
    ADD    WORD PTR LSourceY,8000h   { Add 0.5                              }
    DB     0,0
    MOV    AX,WORD PTR LSourceY + 2
{    ADC    AX,0}
    MUL    WORD PTR TexMapWidth      { Initial scan line in source image    }
    DB     _32BIT
    ADD    WORD PTR LSourceX,8000h   { Add 0.5                              }
    DB     0,0
    MOV    BX,WORD PTR LSourceX + 2  { Offset into source scan line         }
{    ADC    BX,AX }                    { Initial source offset in source image}
    ADD    BX,AX
    ADD    BX,WORD PTR TexMapBits { DS:BX points to the initial image pixel }
    PUSH   DS
    MOV    AX,WORD PTR TexMapBits + 2
    MOV    DS,AX

    { Point to initial destination pixel }

    MOV    AX,BufSeg
    MOV    ES,AX

    MOV    AX,DI              { Hard-coded multiply by 320 }
    MOV    DH,AL
    SUB    DL,DL
    SHL    AX,6
    ADD    AX,DX
    MOV    DI,WORD PTR DestX  { Initial destination X                       }
    ADD    DI,AX              { Offset of pixel in page                     }
                              { ES:DI now points to the first destination   }
                              {  pixel                                      }

    { If the source X step is negative, change over to working with non-    }
    { negative values.                                                      }

    CMP    WORD PTR LXAdvanceByOne,0
    JGE    @SXStepSet
    NEG    WORD PTR LSourceStepX
    NOT    WORD PTR LSourceX
@SXStepSet:

    { If the source Y step is negative, change over to working with non-    }
    { negative values.                                                      }

    CMP    WORD PTR LYAdvanceByOne,0
    JGE    @SYStepSet
    NEG    WORD PTR LSourceStepY
    NOT    WORD PTR LSourceY
@SYStepSet:

    { At this point:                                                        }
    {      BX = pointer to initial image pixel                              }
    {      SI = # of pixels to fill                                         }
    {      DI = pointer to initial destination pixel                        }

    MOV    CX,WORD PTR LXBaseAdvance
    ADD    CX,WORD PTR LYBaseAdvance   { LXYBaseAdvance }
    DEC    CX

    CMP    WORD PTR LSourceStepX,0
    JE     @NoSourceStepX
    CMP    WORD PTR LSourceStepY,0
    JE     @NoSourceStepY

{ ------------------------------------------------------ }
{ General case - LSourceStepX <> 0 and LSourceStepY <> 0 }
{ ------------------------------------------------------ }

    MOV    AX,SCREEN_WIDTH - 1
    XCHG   SI,BX

    MOV    DX,WORD PTR LSourceX
    CMP    WORD PTR LXAdvanceByOne,0
    JL     @TexScanLoop1B
    MOV    WORD PTR CS:[@Rep1 - 2],CX  { Self-modifying code; it's faster   }
    ADD    CX,WORD PTR LYAdvanceByOne  {  to use immediates than to read    }
    MOV    WORD PTR CS:[@Rep4 - 2],CX  {  from memory                       }
    MOV    CX,WORD PTR LSourceStepX
    MOV    WORD PTR CS:[@Rep2 - 2],CX
    MOV    WORD PTR CS:[@Rep2A - 2],CX
    MOV    CX,WORD PTR LSourceStepY
    MOV    WORD PTR CS:[@Rep3 - 2],CX
    MOV    CX,WORD PTR LSourceY
    JMP    @TexScanLoopA
{ --------------------------------------------- }
@TexScanLoop:                 { Positive source X increments                }
    ADD    DI,AX
@TexScanLoopA:

    { Draw the pixel. }

    MOVSB                     { Copy image pixel                            }

    { Point to the next source pixel.                                       }

    ADD    CX,2211h                   { LSourceStepY }
@Rep3:
    JNC    @NoExtraYAdvance           { Didn't turn over; no extra advance  }
    ADD    DX,2211h                   { LSourceStepX }
@Rep2:
    ADC    SI,2211h                   { LXYBaseAdvance+LYAdvanceByOne }
@Rep4:

    { Continue if there are any more dest pixels to draw.                   }

    DEC    BX
    JNZ    @TexScanLoop
    JMP    @EndScan
@NoExtraYAdvance:
    ADD    DX,2211h                   { LSourceStepX }
@Rep2A:
    ADC    SI,2211h                   { LXYBaseAdvance }
@Rep1:

    { Continue if there are any more dest pixels to draw.                   }

    DEC    BX
    JNZ    @TexScanLoop
    JMP    @EndScan
{ --------------------------------------------- }
@TexScanLoop1B:
    NEG    CX
    MOV    WORD PTR CS:[@Rep5 - 2],CX  { Self-modifying code; it's faster   }
    SUB    CX,WORD PTR LYAdvanceByOne
    MOV    WORD PTR CS:[@Rep8 - 2],CX
    MOV    CX,WORD PTR LSourceStepX    {  to use immediates than to read    }
    MOV    WORD PTR CS:[@Rep6 - 2],CX  {  from memory                       }
    MOV    WORD PTR CS:[@Rep6A - 2],CX
    MOV    CX,WORD PTR LSourceStepY
    MOV    WORD PTR CS:[@Rep7 - 2],CX
    MOV    CX,WORD PTR LSourceY
    JMP    @TexScanLoop1A
@TexScanLoop1:                { Negative source X increments                }
    ADD    DI,AX
@TexScanLoop1A:

    { Draw the pixel. }

    MOVSB                     { Copy image pixel                            }

    { Point to the next source pixel.                                       }

    ADD    CX,2211h                   { LSourceStepY }
@Rep7:
    JNC    @NoExtraYAdvance1          { Didn't turn over; no extra advance  }
    ADD    DX,2211h                   { LSourceStepX }
@Rep6:
    SBB    SI,2211h                   { -LXYBaseAdvance-LyAdvanceByOne }
@Rep8:

    { Continue if there are any more dest pixels to draw.                   }

    DEC    BX
    JNZ    @TexScanLoop1
    JMP    @EndScan
@NoExtraYAdvance1:
    ADD    DX,2211h                   { LSourceStepX }
@Rep6A:
    SBB    SI,2211h                   { LXYBaseAdvance }
@Rep5:

    { Continue if there are any more dest pixels to draw.                   }

    DEC    BX
    JNZ    @TexScanLoop1
    JMP    @EndScan

{ ------------------------------- }
{ Special case - LSourceStepX = 0 }
{ ------------------------------- }

@NoSourceStepX:
    MOV    WORD PTR CS:[@Rep9 - 2],CX  { Self-modifying code; it's faster   }
    ADD    CX,WORD PTR LYAdvanceByOne  {  to use immediates than to read    }
    MOV    WORD PTR CS:[@Rep11 - 2],CX {  from memory                       }
    MOV    CX,WORD PTR LSourceStepY
    MOV    WORD PTR CS:[@Rep10 - 2],CX

    MOV    AX,SCREEN_WIDTH - 1
    XCHG   SI,BX
    MOV    CX,WORD PTR LSourceY
    JMP    @TexScanLoop2A
@TexScanLoop2:
    ADD    DI,AX
@TexScanLoop2A:

    { Draw the pixel. }

    MOVSB                     { Copy image pixel                            }

    { Point to the next source pixel.                                       }

    ADD    CX,2211h                   { LSourceStepY }
@Rep10:
    JNC    @NoExtraYAdvance2          { Didn't turn over; no extra advance  }
    ADD    SI,2211h                   { LYAdvanceByOne+LXYBaseAdvance }
@Rep11:

    { Continue if there are any more dest pixels to draw.                   }

    DEC    BX
    JNZ    @TexScanLoop2
    JMP    @EndScan
@NoExtraYAdvance2:
    ADD    SI,2211h                   { LXYBaseAdvance }
@Rep9:

    { Continue if there are any more dest pixels to draw.                   }

    DEC    BX
    JNZ    @TexScanLoop2
    JMP    @EndScan

{ ------------------------------- }
{ Special case - LSourceStepY = 0 }
{ ------------------------------- }

@NoSourceStepY:
    MOV    AX,SCREEN_WIDTH - 1
    XCHG   SI,BX
    MOV    DX,WORD PTR LSourceX
    CMP    WORD PTR LXAdvanceByOne,0
    JL     @TexScanLoop4B
    MOV    WORD PTR CS:[@Rep12 - 2],CX  { Self-modifying code; it's faster   }
    MOV    CX,WORD PTR LSourceStepX     {  to use immediates than to read    }
    MOV    WORD PTR CS:[@Rep13 - 2],CX  {  from memory                       }
    JMP    @TexScanLoop3A
{ --------------------------------------------- }
@TexScanLoop3:                { Positive source X increments                }
    ADD    DI,AX
@TexScanLoop3A:

    { Draw the pixel. }

    MOVSB                     { Copy image pixel                            }

    { Point to the next source pixel.                                       }

    ADD    DX,2211h                   { LSourceStepX }
@Rep13:
    ADC    SI,2211h                   { LXYBaseAdvance }
@Rep12:

    { Continue if there are any more dest pixels to draw.                   }

    DEC    BX
    JNZ    @TexScanLoop3
    JMP    @EndScan
{ --------------------------------------------- }
@TexScanLoop4B:
    NEG    CX
    MOV    WORD PTR CS:[@Rep14 - 2],CX  { Self-modifying code; it's faster   }
    MOV    CX,WORD PTR LSourceStepX     {  to use immediates than to read    }
    MOV    WORD PTR CS:[@Rep15 - 2],CX  {  from memory                       }
    JMP    @TexScanLoop4A
@TexScanLoop4:                { Negative source X increments                }
    ADD    DI,AX
@TexScanLoop4A:

    { Draw the pixel. }

    MOVSB                     { Copy image pixel                            }

    { Point to the next source pixel.                                       }

    ADD    DX,2211h                   { LSourceStepX }
@Rep15:
    SBB    SI,2211h                   { LXYBaseAdvance }
@Rep14:

    { Continue if there are any more dest pixels to draw.                   }

    DEC    BX
    JNZ    @TexScanLoop4
@EndScan:
    POP    DS
  End; { Asm }
ScanDone:
  Asm
    STI
  End; { Asm }
End; { ScanOutLineBufVert }


Procedure ScanOutLineBufWhole(ClipMinX,ClipMinY,ClipMaxX,ClipMaxY: Word;
 SourceLimits,DestLimits: Pointer; Top,DestY: Word;
 TexMapWidth: Word; TexMapBits: Pointer; BufSeg: Word);
{ ------------------------------------------------------------------------- }
{ Draws all pixels in the specified scan line, with the pixel colors taken  }
{ from the specified texture map.  Uses approach of pre-stepping 1/2 pixel  }
{ into the source image and rounding to the nearest source pixel at each    }
{ step, so that texture maps will appear reasonably similar at all angles.  }
{ This routine is specific to 320-pixel-wide planar (non-Chain4) 256-color  }
{ modes, such as mode X, which is a planar (non-Chain4) 256-color mode with }
{ a resolution of 320x240.                                                  }
{ ------------------------------------------------------------------------- }
Var
  LSourceY       : LongInt; { Current Y coordinate in source image          }
  LSourceStepX   : LongInt; { X step in source image for X dest step of 1   }
  LSourceStepY   : LongInt; { Y step in source image for X dest step of 1   }
  LXAdvanceByOne : Word;    { Used to step source pointer 1 pixel           }
                            {  incrementally in X                           }
  LXBaseAdvance  : Word;    { Use to step source pointer minimum number of  }
                            {  pixels incrementally in X                    }
  LYAdvanceByOne : Word;    { Used to step source pointer 1 pixel           }
                            {  incrementally in Y                           }
  LYBaseAdvance  : Word;    { Use to step source pointer minimum number of  }
                            {  pixels incrementally in Y                    }
  StartX         : Word;

Begin
  Asm
    CLI
    CLD
    JMP    @Start
@ToScanDone:
    JMP    @ScanDone
@Start:
    PUSH   DS

    { Nothing to do if destination is fully X clipped. }

    LES    DI,DWORD PTR SourceLimits
    LDS    SI,DWORD PTR DestLimits
    MOV    BX,WORD PTR Top
    MOV    AX,BX
    SHL    BX,4
    ADD    DI,BX
    SHL    AX,2
    ADD    SI,AX
@NextLine:
    MOV    CX,WORD PTR [SI+2]                 { Dest.X2 }
    CMP    CX,WORD PTR ClipMinX
    JLE    @ToScanDone        { Right edge is to left of clip rect, so done }
    MOV    BX,WORD PTR [SI]                   { Dest.X1 }
    CMP    BX,WORD PTR ClipMaxX
    JGE    @ToScanDone        { Left edge is to right of clip rect, so done }
    SUB    CX,BX
    JLE    @ToScanDone        { Null or negative full width, so done        }

    { Calculate source steps that correspond to each 1-pixel destination X  }
    { step (across the destination scan line).                              }

    DB     _32BIT
    MOV    AX,WORD PTR [ES:DI+4]              { Get source X width          }
    DB     _32BIT
    SUB    AX,WORD PTR [ES:DI]
    DB     _32BIT                             { OR    EAX,EAX               }
    OR     AX,AX
    JZ     @SkipDiv
    DB     _32BIT,99h                         { CDQ                         }
    DB     _32BIT,0Fh,0B7h,0D9h               { MOVZX EBX,CX                }
    DB     _32BIT                             { IDIV  EBX                   }
    IDIV   BX
@SkipDiv:
    DB     _32BIT
    MOV    WORD PTR LSourceStepX,AX           { Remember source X step for  }
                                              {  1-pixel destination X step }
    PUSH   CX
    MOV    CX,1               { Assume source X advances non-negative       }
    DB     _32BIT
    AND    AX,AX              { Which way does source X advance?            }
    JNS    @SourceXNonNeg     { Non-negative                                }
    NEG    CX                 { Negative                                    }
    CMP    AX,0               { Is the whole step exactly an integer?       }
    JZ     @SourceXNonNeg     { Yes                                         }
    DB     _32BIT,5,0,0,1,0   { ADD   EAX,10000h                            }
                              { No, truncate to integer in the direction of }
                              {  0, because otherwise we'll end up with a   }
                              {  whole step of 1-too-large magnitude        }
@SourceXNonNeg:
    MOV    WORD PTR LXAdvanceByOne,CX { Amount to add to source pointer to  }
                                      {  move by one in X                   }
    DB     _32BIT
    SAR    AX,16
    MOV    WORD PTR LXBaseAdvance,AX  { Minimum amount to add to source     }
                                      {  pointer to advance in X each time  }
                                      {  the dest advances one in X         }
    POP    CX
    DB     _32BIT                             { SUB   EDX,EDX               }
    MOV    AX,WORD PTR [ES:DI+12]             { Get source Y height         }
    DB     _32BIT
    SUB    AX,WORD PTR [ES:DI+8]
    DB     _32BIT
    OR     AX,AX
    JZ     @SkipDiv1
    DB     _32BIT,99h                         { CDQ                         }
    DB     _32BIT,0Fh,0B7h,0D9h               { MOVZX EBX,CX                }
    DB     _32BIT                             { IDIV  EBX                   }
    IDIV   BX
@SkipDiv1:
    DB     _32BIT
    MOV    WORD PTR LSourceStepY,AX           { Remember source Y step for  }
                                              {  1-pixel destination X step }
    MOV    CX,WORD PTR TexMapWidth  { Assume source Y advances non-negative }
    DB     _32BIT
    AND    AX,AX              { Which way does source Y advance?            }
    JNS    @SourceYNonNeg     { Non-negative                                }
    NEG    CX                 { Negative                                    }
    CMP    AX,0               { Is the whole step exactly an integer?       }
    JZ     @SourceYNonNeg     { Yes                                         }
    DB     _32BIT,5,0,0,1,0   { ADD   EAX,10000h                            }
                              { No, truncate to integer in the direction of }
                              {  0, because otherwise we'll end up with a   }
                              {  whole step of 1-too-large magnitude        }
@SourceYNonNeg:
    MOV    WORD PTR LYAdvanceByOne,CX { Amount to add to source pointer to  }
                                      {  move by one in Y                   }
    DB     _32BIT,0Fh,0A4h,0C2h,10h           { SHLD  EDX,EAX,16            }

    OR     DX,DX
    JNZ    @DoMul
    MOV    WORD PTR LYBaseAdvance,0  {  carry from the fractional part      }
    JMP    @SkipMul
@DoMul:
    MOV    AX,WORD PTR TexMapWidth   { Minimum distance skipped in source   }
    IMUL   DX                        {  image bitmap when Y steps (ignoring }
    MOV    WORD PTR LYBaseAdvance,AX {  carry from the fractional part      }
@SkipMul:

    { Advance 1/2 step in the stepping direction, to space scanned pixels   }
    { evenly between the left and right edges.  (There's a slight inaccuracy}
    { in dividing negative numbers by 2 by shifting rather than dividing,   }
    { but the inaccuracy is in the least significant bit, and we'll just    }
    { live with it.                                                         }

    DB     _32BIT
    MOV    AX,WORD PTR LSourceStepX      { MOV   EAX,DWORD PTR LSourceStepX }
    DB     _32BIT
    SAR    AX,1                          { SAR   EAX,1                      }
    DB     _32BIT
    ADD    WORD PTR [ES:DI],AX           { ADD   DWORD PTR LSourceX,EAX     }

    DB     _32BIT
    MOV    AX,WORD PTR LSourceStepY      { MOV   EAX,DWORD PTR LSourceStepY }
    DB     _32BIT
    SAR    AX,1                          { SAR   EAX,1                      }
    DB     _32BIT
    ADD    WORD PTR [ES:DI+8],AX         { ADD   DWORD PTR LSourceY,EAX     }

    { Clip the right edge if necessary. }

    MOV    CX,WORD PTR [SI+2]
    CMP    CX,WORD PTR ClipMaxX
    JL     @RightEdgeClipped
    MOV    CX,WORD PTR ClipMaxX
@RightEdgeClipped:
    { Clip the left edge if necessary. }

    MOV    BX,WORD PTR [SI]
    MOV    WORD PTR StartX,BX
    CMP    BX,WORD PTR ClipMinX
    JGE    @LeftEdgeClipped

    { Left clipping is necessary; advance the source accordingly }

    MOV    AX,WORD PTR ClipMinX
    MOV    WORD PTR StartX,AX
    NEG    BX
    ADD    BX,WORD PTR ClipMinX { ClipMinX - DestX                          }
                                { First, advance the source in X            }
    DB     _32BIT,0Fh,0B7h,0C3h { MOVZX EAX,BX                              }
    DB     _32BIT
    IMUL   WORD PTR LSourceStepX
    DB     _32BIT
    ADD    WORD PTR [ES:DI],AX

    { now advance the source in Y }

    DB     _32BIT,0Fh,0B7h,0C3h { MOVZX EAX,BX                              }
    DB     _32BIT
    IMUL   WORD PTR LSourceStepY
    DB     _32BIT
    ADD    WORD PTR [ES:DI+8],AX
    MOV    BX,WORD PTR ClipMinX { Start X coordinate in dest after clipping }
@LeftEdgeClipped:

    { Calculate actual clipped destination drawing width. }

    SUB    CX,BX

    { Scan across the destination scan line, updating the source image      }
    { position accordingly.  Point to the initial source image pixel, adding}
    { 0.5 to both X and Y so that we can truncate to integers from now on   }
    { but effectively get rounding.                                         }

    DB     _32BIT
    ADD    WORD PTR [ES:DI+8],8000h   { Add 0.5 }
    DB     0,0
    DB     _32BIT
    ADD    WORD PTR [ES:DI],8000h     { Add 0.5 }
    DB     0,0
    MOV    AX,WORD PTR [ES:DI+10]
{    ADC    AX,0}
    MUL    WORD PTR TexMapWidth      { Initial scan line in source image    }
    MOV    BX,WORD PTR [ES:DI+2]     { Offset into source scan line         }
{    ADC    BX,AX}            { Initial source offset in source image       }
    ADD    BX,AX
    ADD    BX,WORD PTR TexMapBits { DS:BX points to the initial image pixel }
    PUSH   DS
    MOV    AX,WORD PTR TexMapBits + 2
    MOV    DS,AX

    MOV    AX,WORD PTR [ES:DI+8]
    MOV    WORD PTR LSourceY,AX

    { If the source X step is negative, change over to working with non-    }
    { negative values.                                                      }

    CMP    WORD PTR LXAdvanceByOne,0
    JGE    @SXStepSet
    NEG    WORD PTR LSourceStepX
    NOT    WORD PTR [ES:DI]            {LSourceX}
@SXStepSet:

    { If the source Y step is negative, change over to working with non-    }
    { negative values.                                                      }

    CMP    WORD PTR LYAdvanceByOne,0
    JGE    @SYStepSet
    NEG    WORD PTR LSourceStepY
    NOT    WORD PTR LSourceY
@SYStepSet:

    { Point to initial destination pixel }

    MOV    AX,WORD PTR Top {DI}                { Hard-coded multiply by 320 }
    MOV    DH,AL
    SUB    DL,DL
    SHL    AX,6
    ADD    AX,DX


    MOV    DX,WORD PTR [ES:DI]  { Load LSourceX now }


    PUSH   DI
    MOV    DI,WORD PTR StartX { Initial destination X                       }
    ADD    DI,AX              { Offset of pixel in page                     }
                              { ES:DI now points to the first destination   }
                              {  pixel                                      }
    PUSH   ES
    MOV    AX,WORD PTR BufSeg
    MOV    ES,AX

    { At this point:                                                        }
    {      BX = pointer to initial image pixel                              }
    {      SI = # of pixels to fill                                         }
    {      DI = pointer to initial destination pixel                        }
    {      CX = destination line width                                      }
    {      DX = low part of LSourceX                                        }

    PUSH   SI
    MOV    SI,BX
    MOV    BX,WORD PTR LXBaseAdvance
    ADD    BX,WORD PTR LYBaseAdvance   { LXYBaseAdvance }
    DEC    BX

    CMP    WORD PTR LSourceStepX,0
    JE     @NoSourceStepX
    CMP    WORD PTR LSourceStepY,0
    JE     @NoSourceStepY

{ ------------------------------------------------------ }
{ General case - LSourceStepX <> 0 and LSourceStepY <> 0 }
{ ------------------------------------------------------ }

    CMP    WORD PTR LXAdvanceByOne,0
    JL     @TexScanLoop1A
    MOV    WORD PTR CS:[@Rep1 - 2],BX  { Self-modifying code; it's faster   }
    ADD    BX,WORD PTR LYAdvanceByOne  {  to use immediates than to read    }
    MOV    WORD PTR CS:[@Rep4 - 2],BX  {  from memory                       }
    MOV    BX,WORD PTR LSourceStepX
    MOV    WORD PTR CS:[@Rep2 - 2],BX  
    MOV    WORD PTR CS:[@Rep2A - 2],BX
    MOV    BX,WORD PTR LSourceStepY
    MOV    WORD PTR CS:[@Rep3 - 2],BX
    MOV    BX,WORD PTR LSourceY
@TexScanLoop:                 { Positive source X increments                }

    { Draw the pixel. }

    MOVSB                     { Copy image pixel                            }

    { Point to the next source pixel.                                       }

    ADD    BX,2211h                   { LSourceStepY }
@Rep3:
    JNC    @NoExtraYAdvance
    ADD    DX,2211h                   { LSourceStepX }
@Rep2:
    ADC    SI,2211h                   { LXYBaseAdvance+LYAdvanceByOne }
@Rep4:

    { Continue if there are any more dest pixels to draw.                   }

    DEC    CX
    JNZ    @TexScanLoop
    JMP    @EndScan
@NoExtraYAdvance:
    ADD    DX,2211h                   { LSourceStepX }
@Rep2A:
    ADC    SI,2211h                   { LXYBaseAdvance }
@Rep1:

    { Continue if there are any more dest pixels to draw.                   }

    DEC    CX
    JNZ    @TexScanLoop
    JMP    @EndScan
{ --------------------------------------------- }
@TexScanLoop1A:
    NEG    BX
    MOV    WORD PTR CS:[@Rep5 - 2],BX  { Self-modifying code; it's faster   }
    SUB    BX,WORD PTR LYAdvanceByOne  {  to use immediates than to read    }
    MOV    WORD PTR CS:[@Rep8 - 2],BX  {  from memory                       }
    MOV    BX,WORD PTR LSourceStepX
    MOV    WORD PTR CS:[@Rep6 - 2],BX
    MOV    WORD PTR CS:[@Rep6A - 2],BX
    MOV    BX,WORD PTR LSourceStepY
    MOV    WORD PTR CS:[@Rep7 - 2],BX
    MOV    BX,WORD PTR LSourceY
@TexScanLoop1:                { Negative source X increments                }

    { Draw the pixel. }

    MOVSB                     { Copy image pixel                            }

    { Point to the next source pixel.                                       }

    ADD    BX,2211h                   { LSourceStepY }
@Rep7:
    JNC    @NoExtraYAdvance1
    ADD    DX,2211h                   { LSourceStepX }
@Rep6:
    SBB    SI,2211h                   { -LXYBaseAdvance-LYAdvanceByOne }
@Rep8:

    { Continue if there are any more dest pixels to draw.                   }

    DEC    CX
    JNZ    @TexScanLoop1
    JMP    @EndScan
@NoExtraYAdvance1:
    ADD    DX,2211h                   { LSourceStepX }
@Rep6A:
    SBB    SI,2211h                   { LXYBaseAdvance }
@Rep5:

    { Continue if there are any more dest pixels to draw.                   }

    DEC    CX
    JNZ    @TexScanLoop1
    JMP    @EndScan

{ ------------------------------- }
{ Special case - LSourceStepX = 0 }
{ ------------------------------- }
@NoSourceStepX:
    MOV    WORD PTR CS:[@Rep9 - 2],BX  { Self-modifying code; it's faster   }
    ADD    BX,WORD PTR LYAdvanceByOne  {  to use immediates than to read    }
    MOV    WORD PTR CS:[@Rep11 - 2],BX {  from memory                       }
    MOV    BX,WORD PTR LSourceStepY
    MOV    WORD PTR CS:[@Rep10 - 2],BX
    MOV    BX,WORD PTR LSourceY
@TexScanLoop2:                { Positive source X increments                }

    { Draw the pixel. }

    MOVSB                     { Copy image pixel                            }

    { Point to the next source pixel.                                       }

    ADD    BX,2211h                   { LSourceStepY }
@Rep10:
    JNC    @NoExtraYAdvance2          { Didn't turn over; no extra advance  }
    ADD    SI,2211h                   { LYAdvanceByOne+LXYBaseAdvance }
@Rep11:

    { Continue if there are any more dest pixels to draw.                   }

    DEC    CX
    JNZ    @TexScanLoop2
    JMP    @EndScan
@NoExtraYAdvance2:
    ADD    SI,2211h                   { LXYBaseAdvance }
@Rep9:

    { Continue if there are any more dest pixels to draw.                   }

    DEC    CX
    JNZ    @TexScanLoop2
    JMP    @EndScan

{ ------------------------------- }
{ Special case - LSourceStepY = 0 }
{ ------------------------------- }

@NoSourceStepY:
    CMP    WORD PTR LXAdvanceByOne,0
    JL     @TexScanLoop4A
    MOV    WORD PTR CS:[@Rep12 - 2],BX { Self-modifying code; it's faster   }
    MOV    BX,WORD PTR LSourceStepX    {  to use immediates than to read    }
    MOV    WORD PTR CS:[@Rep13 - 2],BX {  from memory                       }
{ --------------------------------------------- }
@TexScanLoop3:                { Positive source X increments                }

    { Draw the pixel. }

    MOVSB                     { Copy image pixel                            }

    { Point to the next source pixel.                                       }

    ADD    DX,2211h                   { LSourceStepX }
@Rep13:
    ADC    SI,2211h                   { LXYBaseAdvance }
@Rep12:

    { Continue if there are any more dest pixels to draw.                   }

    DEC    CX
    JNZ    @TexScanLoop3
    JMP    @EndScan
{ --------------------------------------------- }
@TexScanLoop4A:
    NEG    BX
    MOV    WORD PTR CS:[@Rep14 - 2],BX { Self-modifying code; it's faster   }
    MOV    BX,WORD PTR LSourceStepX    {  to use immediates than to read    }
    MOV    WORD PTR CS:[@Rep15 - 2],BX {  from memory                       }
@TexScanLoop4:                { Negative source X increments                }

    { Draw the pixel. }

    MOVSB                     { Copy image pixel                            }

    { Point to the next source pixel.                                       }

    ADD    DX,2211h                   { LSourceStepX }
@Rep15:
    SBB    SI,2211h                   { LXYBaseAdvance }
@Rep14:

    { Continue if there are any more dest pixels to draw.                   }

    DEC    CX
    JNZ    @TexScanLoop4

@EndScan:
    POP    SI
    POP    ES
    POP    DI
    POP    DS
@ScanDone:
    INC    WORD PTR Top
    MOV    AX,WORD PTR DestY
    CMP    WORD PTR Top,AX
    JGE    @GetOut
    ADD    SI,4
    ADD    DI,16
    JMP    @NextLine
@GetOut:
    POP    DS
    STI
  End; { Asm }
End; { ScanOutLineBufWhole }


Procedure ScanOutLineBufWholeLight(ClipMinX,ClipMinY,ClipMaxX,ClipMaxY: Word;
 SourceLimits,DestLimits: Pointer; Top,DestY: Word;
 TexMapWidth: Word; TexMapBits: Pointer; BufSeg: Word; LightData: Pointer;
 Light: Byte);
{ ------------------------------------------------------------------------- }
{ Draws all pixels in the specified scan line, with the pixel colors taken  }
{ from the specified texture map.  Uses approach of pre-stepping 1/2 pixel  }
{ into the source image and rounding to the nearest source pixel at each    }
{ step, so that texture maps will appear reasonably similar at all angles.  }
{ This routine is specific to 320-pixel-wide planar (non-Chain4) 256-color  }
{ modes, such as mode X, which is a planar (non-Chain4) 256-color mode with }
{ a resolution of 320x240.                                                  }
{ ------------------------------------------------------------------------- }
Var
  LSourceY       : LongInt; { Current Y coordinate in source image          }
  LSourceStepX   : LongInt; { X step in source image for X dest step of 1   }
  LSourceStepY   : LongInt; { Y step in source image for X dest step of 1   }
  LXAdvanceByOne : Word;    { Used to step source pointer 1 pixel           }
                            {  incrementally in X                           }
  LXBaseAdvance  : Word;    { Use to step source pointer minimum number of  }
                            {  pixels incrementally in X                    }
  LYAdvanceByOne : Word;    { Used to step source pointer 1 pixel           }
                            {  incrementally in Y                           }
  LYBaseAdvance  : Word;    { Use to step source pointer minimum number of  }
                            {  pixels incrementally in Y                    }
  StartX         : Word;

Begin
  Asm
    CLI
    CLD
    JMP    @Start
@ToScanDone:
    JMP    @ScanDone
@Start:
    PUSH   DS
    DB     0Fh,0A0h                           { PUSH FS }

    MOV    AX,WORD PTR LightData[2]
    DB     8Eh,0E0h                           { MOV  FS,AX }

    { Nothing to do if destination is fully X clipped. }

    LES    DI,DWORD PTR SourceLimits
    LDS    SI,DWORD PTR DestLimits
    MOV    BX,WORD PTR Top
    MOV    AX,BX
    SHL    BX,4
    ADD    DI,BX
    SHL    AX,2
    ADD    SI,AX
@NextLine:
    MOV    CX,WORD PTR [SI+2]                 { Dest.X2 }
    CMP    CX,WORD PTR ClipMinX
    JLE    @ToScanDone        { Right edge is to left of clip rect, so done }
    MOV    BX,WORD PTR [SI]                   { Dest.X1 }
    CMP    BX,WORD PTR ClipMaxX
    JGE    @ToScanDone        { Left edge is to right of clip rect, so done }
    SUB    CX,BX
    JLE    @ToScanDone        { Null or negative full width, so done        }

    { Calculate source steps that correspond to each 1-pixel destination X  }
    { step (across the destination scan line).                              }

    DB     _32BIT
    MOV    AX,WORD PTR [ES:DI+4]              { Get source X width          }
    DB     _32BIT
    SUB    AX,WORD PTR [ES:DI]
    DB     _32BIT                             { OR    EAX,EAX               }
    OR     AX,AX
    JZ     @SkipDiv
    DB     _32BIT,99h                         { CDQ                         }
    DB     _32BIT,0Fh,0B7h,0D9h               { MOVZX EBX,CX                }
    DB     _32BIT                             { IDIV  EBX                   }
    IDIV   BX
@SkipDiv:
    DB     _32BIT
    MOV    WORD PTR LSourceStepX,AX           { Remember source X step for  }
                                              {  1-pixel destination X step }
    PUSH   CX
    MOV    CX,1               { Assume source X advances non-negative       }
    DB     _32BIT
    AND    AX,AX              { Which way does source X advance?            }
    JNS    @SourceXNonNeg     { Non-negative                                }
    NEG    CX                 { Negative                                    }
    CMP    AX,0               { Is the whole step exactly an integer?       }
    JZ     @SourceXNonNeg     { Yes                                         }
    DB     _32BIT,5,0,0,1,0   { ADD   EAX,10000h                            }
                              { No, truncate to integer in the direction of }
                              {  0, because otherwise we'll end up with a   }
                              {  whole step of 1-too-large magnitude        }
@SourceXNonNeg:
    MOV    WORD PTR LXAdvanceByOne,CX { Amount to add to source pointer to  }
                                      {  move by one in X                   }
    DB     _32BIT
    SAR    AX,16
    MOV    WORD PTR LXBaseAdvance,AX  { Minimum amount to add to source     }
                                      {  pointer to advance in X each time  }
                                      {  the dest advances one in X         }
    POP    CX
    DB     _32BIT                             { SUB   EDX,EDX               }
    MOV    AX,WORD PTR [ES:DI+12]             { Get source Y height         }
    DB     _32BIT
    SUB    AX,WORD PTR [ES:DI+8]
    DB     _32BIT
    OR     AX,AX
    JZ     @SkipDiv1
    DB     _32BIT,99h                         { CDQ                         }
    DB     _32BIT,0Fh,0B7h,0D9h               { MOVZX EBX,CX                }
    DB     _32BIT                             { IDIV  EBX                   }
    IDIV   BX
@SkipDiv1:
    DB     _32BIT
    MOV    WORD PTR LSourceStepY,AX           { Remember source Y step for  }
                                              {  1-pixel destination X step }
    MOV    CX,WORD PTR TexMapWidth  { Assume source Y advances non-negative }
    DB     _32BIT
    AND    AX,AX              { Which way does source Y advance?            }
    JNS    @SourceYNonNeg     { Non-negative                                }
    NEG    CX                 { Negative                                    }
    CMP    AX,0               { Is the whole step exactly an integer?       }
    JZ     @SourceYNonNeg     { Yes                                         }
    DB     _32BIT,5,0,0,1,0   { ADD   EAX,10000h                            }
                              { No, truncate to integer in the direction of }
                              {  0, because otherwise we'll end up with a   }
                              {  whole step of 1-too-large magnitude        }
@SourceYNonNeg:
    MOV    WORD PTR LYAdvanceByOne,CX { Amount to add to source pointer to  }
                                      {  move by one in Y                   }
    DB     _32BIT,0Fh,0A4h,0C2h,10h           { SHLD  EDX,EAX,16            }

    OR     DX,DX
    JNZ    @DoMul
    MOV    WORD PTR LYBaseAdvance,0  {  carry from the fractional part      }
    JMP    @SkipMul
@DoMul:
    MOV    AX,WORD PTR TexMapWidth   { Minimum distance skipped in source   }
    IMUL   DX                        {  image bitmap when Y steps (ignoring }
    MOV    WORD PTR LYBaseAdvance,AX {  carry from the fractional part      }
@SkipMul:

    { Advance 1/2 step in the stepping direction, to space scanned pixels   }
    { evenly between the left and right edges.  (There's a slight inaccuracy}
    { in dividing negative numbers by 2 by shifting rather than dividing,   }
    { but the inaccuracy is in the least significant bit, and we'll just    }
    { live with it.                                                         }

    DB     _32BIT
    MOV    AX,WORD PTR LSourceStepX      { MOV   EAX,DWORD PTR LSourceStepX }
    DB     _32BIT
    SAR    AX,1                          { SAR   EAX,1                      }
    DB     _32BIT
    ADD    WORD PTR [ES:DI],AX           { ADD   DWORD PTR LSourceX,EAX     }

    DB     _32BIT
    MOV    AX,WORD PTR LSourceStepY      { MOV   EAX,DWORD PTR LSourceStepY }
    DB     _32BIT
    SAR    AX,1                          { SAR   EAX,1                      }
    DB     _32BIT
    ADD    WORD PTR [ES:DI+8],AX         { ADD   DWORD PTR LSourceY,EAX     }

    { Clip the right edge if necessary. }

    MOV    CX,WORD PTR [SI+2]
    CMP    CX,WORD PTR ClipMaxX
    JL     @RightEdgeClipped
    MOV    CX,WORD PTR ClipMaxX
@RightEdgeClipped:
    { Clip the left edge if necessary. }

    MOV    BX,WORD PTR [SI]
    MOV    WORD PTR StartX,BX
    CMP    BX,WORD PTR ClipMinX
    JGE    @LeftEdgeClipped

    { Left clipping is necessary; advance the source accordingly }

    MOV    AX,WORD PTR ClipMinX
    MOV    WORD PTR StartX,AX
    NEG    BX
    ADD    BX,WORD PTR ClipMinX { ClipMinX - DestX                          }
                                { First, advance the source in X            }
    DB     _32BIT,0Fh,0B7h,0C3h { MOVZX EAX,BX                              }
    DB     _32BIT
    IMUL   WORD PTR LSourceStepX
    DB     _32BIT
    ADD    WORD PTR [ES:DI],AX

    { now advance the source in Y }

    DB     _32BIT,0Fh,0B7h,0C3h { MOVZX EAX,BX                              }
    DB     _32BIT
    IMUL   WORD PTR LSourceStepY
    DB     _32BIT
    ADD    WORD PTR [ES:DI+8],AX
    MOV    BX,WORD PTR ClipMinX { Start X coordinate in dest after clipping }
@LeftEdgeClipped:

    { Calculate actual clipped destination drawing width. }

    SUB    CX,BX

    { Scan across the destination scan line, updating the source image      }
    { position accordingly.  Point to the initial source image pixel, adding}
    { 0.5 to both X and Y so that we can truncate to integers from now on   }
    { but effectively get rounding.                                         }

    DB     _32BIT
    ADD    WORD PTR [ES:DI+8],8000h   { Add 0.5 }
    DB     0,0
    DB     _32BIT
    ADD    WORD PTR [ES:DI],8000h     { Add 0.5 }
    DB     0,0
    MOV    AX,WORD PTR [ES:DI+10]
{    ADC    AX,0}
    MUL    WORD PTR TexMapWidth      { Initial scan line in source image    }
    MOV    BX,WORD PTR [ES:DI+2]     { Offset into source scan line         }
{    ADC    BX,AX}            { Initial source offset in source image       }
    ADD    BX,AX
    ADD    BX,WORD PTR TexMapBits { DS:BX points to the initial image pixel }
    PUSH   DS
    MOV    AX,WORD PTR TexMapBits + 2
    MOV    DS,AX

    MOV    AX,WORD PTR [ES:DI+8]
    MOV    WORD PTR LSourceY,AX

    { If the source X step is negative, change over to working with non-    }
    { negative values.                                                      }

    CMP    WORD PTR LXAdvanceByOne,0
    JGE    @SXStepSet
    NEG    WORD PTR LSourceStepX
    NOT    WORD PTR [ES:DI]            {LSourceX}
@SXStepSet:

    { If the source Y step is negative, change over to working with non-    }
    { negative values.                                                      }

    CMP    WORD PTR LYAdvanceByOne,0
    JGE    @SYStepSet
    NEG    WORD PTR LSourceStepY
    NOT    WORD PTR LSourceY
@SYStepSet:

    { Point to initial destination pixel }

    MOV    AX,WORD PTR Top {DI}                { Hard-coded multiply by 320 }
    MOV    DH,AL
    SUB    DL,DL
    SHL    AX,6
    ADD    AX,DX


    MOV    DX,WORD PTR [ES:DI]  { Load LSourceX now }


    PUSH   DI
    MOV    DI,WORD PTR StartX { Initial destination X                       }
    ADD    DI,AX              { Offset of pixel in page                     }
                              { ES:DI now points to the first destination   }
                              {  pixel                                      }
    PUSH   ES
    MOV    AX,WORD PTR BufSeg
    MOV    ES,AX

    { At this point:                                                        }
    {      BX = pointer to initial image pixel                              }
    {      SI = # of pixels to fill                                         }
    {      DI = pointer to initial destination pixel                        }
    {      CX = destination line width                                      }
    {      DX = low part of LSourceX                                        }

    PUSH   SI
    MOV    SI,BX
    MOV    BX,WORD PTR LXBaseAdvance
    ADD    BX,WORD PTR LYBaseAdvance   { LXYBaseAdvance }
    DEC    BX

    CMP    WORD PTR LSourceStepX,0
    JE     @NoSourceStepX
    CMP    WORD PTR LSourceStepY,0
    JE     @NoSourceStepY

{ ------------------------------------------------------ }
{ General case - LSourceStepX <> 0 and LSourceStepY <> 0 }
{ ------------------------------------------------------ }

    CMP    WORD PTR LXAdvanceByOne,0
    JL     @TexScanLoop1A
    MOV    WORD PTR CS:[@Rep1 - 2],BX  { Self-modifying code; it's faster   }
    ADD    BX,WORD PTR LYAdvanceByOne  {  to use immediates than to read    }
    MOV    WORD PTR CS:[@Rep4 - 2],BX  {  from memory                       }
    MOV    BX,WORD PTR LSourceStepX
    MOV    WORD PTR CS:[@Rep2 - 2],BX
    MOV    WORD PTR CS:[@Rep2A - 2],BX
    MOV    BX,WORD PTR LSourceStepY
    MOV    WORD PTR CS:[@Rep3 - 2],BX
    MOV    BX,WORD PTR LSourceY
    DB     _32BIT
    SHL    BX,16
    MOV    BX,DX
    DB     _32BIT
    MOV    DX,BX

    MOV    BH,BYTE PTR Light
    SUB    BL,BL
    ADD    BX,WORD PTR LightData
@TexScanLoop:                 { Positive source X increments                }

    { Draw the pixel. }

{    MOV    AL,[SI]}
    LODSB                     { Copy image pixel                            }
    DB     64h                { XLAT FS: }
    XLAT
    STOSB

    { Point to the next source pixel.                                       }

    DB     _32BIT
    DB     81h,0C2h,0,0,0,0           { ADD  EDX,LSourceStepY Shl 16 }
@Rep3:
    JNC    @NoExtraYAdvance
    ADD    DX,2211h                   { LSourceStepX }
@Rep2:
    ADC    SI,2211h                   { LXYBaseAdvance+LYAdvanceByOne }
@Rep4:

    { Continue if there are any more dest pixels to draw.                   }

    DEC    CX
    JNZ    @TexScanLoop
    JMP    @EndScan
@NoExtraYAdvance:
    ADD    DX,2211h                   { LSourceStepX }
@Rep2A:
    ADC    SI,2211h                   { LXYBaseAdvance }
@Rep1:

    { Continue if there are any more dest pixels to draw.                   }

    DEC    CX
    JNZ    @TexScanLoop
    JMP    @EndScan
{ --------------------------------------------- }
@TexScanLoop1A:
    NEG    BX
    MOV    WORD PTR CS:[@Rep5 - 2],BX  { Self-modifying code; it's faster   }
    SUB    BX,WORD PTR LYAdvanceByOne  {  to use immediates than to read    }
    MOV    WORD PTR CS:[@Rep8 - 2],BX  {  from memory                       }
    MOV    BX,WORD PTR LSourceStepX
    MOV    WORD PTR CS:[@Rep6 - 2],BX
    MOV    WORD PTR CS:[@Rep6A - 2],BX
    MOV    BX,WORD PTR LSourceStepY
    MOV    WORD PTR CS:[@Rep7 - 2],BX
    MOV    BX,WORD PTR LSourceY
    DB     _32BIT
    SHL    BX,16
    MOV    BX,DX
    DB     _32BIT
    MOV    DX,BX

    MOV    BH,BYTE PTR Light
    SUB    BL,BL
    ADD    BX,WORD PTR LightData
@TexScanLoop1:                { Negative source X increments                }

    { Draw the pixel. }

{    MOV    AL,[SI]}
    LODSB                     { Copy image pixel                            }
    DB     64h                { XLAT FS: }
    XLAT
    STOSB

    { Point to the next source pixel.                                       }

    DB     _32BIT
    DB     81h,0C2h,0,0,0,0           { ADD  EDX,LSourceStepY Shl 16 }
@Rep7:
    JNC    @NoExtraYAdvance1
    ADD    DX,2211h                   { LSourceStepX }
@Rep6:
    SBB    SI,2211h                   { -LXYBaseAdvance-LYAdvanceByOne }
@Rep8:

    { Continue if there are any more dest pixels to draw.                   }

    DEC    CX
    JNZ    @TexScanLoop1
    JMP    @EndScan
@NoExtraYAdvance1:
    ADD    DX,2211h                   { LSourceStepX }
@Rep6A:
    SBB    SI,2211h                   { LXYBaseAdvance }
@Rep5:

    { Continue if there are any more dest pixels to draw.                   }

    DEC    CX
    JNZ    @TexScanLoop1
    JMP    @EndScan

{ ------------------------------- }
{ Special case - LSourceStepX = 0 }
{ ------------------------------- }
@NoSourceStepX:
    MOV    WORD PTR CS:[@Rep9 - 2],BX  { Self-modifying code; it's faster   }
    ADD    BX,WORD PTR LYAdvanceByOne  {  to use immediates than to read    }
    MOV    WORD PTR CS:[@Rep11 - 2],BX {  from memory                       }
    MOV    BX,WORD PTR LSourceStepY
    MOV    WORD PTR CS:[@Rep10 - 2],BX
    MOV    BX,WORD PTR LSourceY
    DB     _32BIT
    SHL    BX,16
    MOV    BX,DX
    DB     _32BIT
    MOV    DX,BX

    MOV    BH,BYTE PTR Light
    SUB    BL,BL
    ADD    BX,WORD PTR LightData
@TexScanLoop2:                { Positive source X increments                }

    { Draw the pixel. }

{    MOV    AL,[SI]}
    LODSB                     { Copy image pixel                            }
    DB     64h                { XLAT FS: }
    XLAT
    STOSB

    { Point to the next source pixel.                                       }

    DB     _32BIT
    DB     81h,0C2h,0,0,0,0           { ADD  EDX,LSourceStepY Shl 16 }
@Rep10:
    JNC    @NoExtraYAdvance2          { Didn't turn over; no extra advance  }
    ADD    SI,2211h                   { LYAdvanceByOne+LXYBaseAdvance }
@Rep11:

    { Continue if there are any more dest pixels to draw.                   }

    DEC    CX
    JNZ    @TexScanLoop2
    JMP    @EndScan
@NoExtraYAdvance2:
    ADD    SI,2211h                   { LXYBaseAdvance }
@Rep9:

    { Continue if there are any more dest pixels to draw.                   }

    DEC    CX
    JNZ    @TexScanLoop2
    JMP    @EndScan

{ ------------------------------- }
{ Special case - LSourceStepY = 0 }
{ ------------------------------- }

@NoSourceStepY:
    CMP    WORD PTR LXAdvanceByOne,0
    JL     @TexScanLoop4A
    MOV    WORD PTR CS:[@Rep12 - 2],BX { Self-modifying code; it's faster   }
    MOV    BX,WORD PTR LSourceStepX    {  to use immediates than to read    }
    MOV    WORD PTR CS:[@Rep13 - 2],BX {  from memory                       }

    MOV    BH,BYTE PTR Light
    SUB    BL,BL
    ADD    BX,WORD PTR LightData
{ --------------------------------------------- }
@TexScanLoop3:                { Positive source X increments                }

    { Draw the pixel. }

{    MOV    AL,[SI]}
    LODSB                     { Copy image pixel                            }
    DB     64h                { XLAT FS: }
    XLAT
    STOSB

    { Point to the next source pixel.                                       }

    ADD    DX,2211h                   { LSourceStepX }
@Rep13:
    ADC    SI,2211h                   { LXYBaseAdvance }
@Rep12:

    { Continue if there are any more dest pixels to draw.                   }

    DEC    CX
    JNZ    @TexScanLoop3
    JMP    @EndScan
{ --------------------------------------------- }
@TexScanLoop4A:
    NEG    BX
    MOV    WORD PTR CS:[@Rep14 - 2],BX { Self-modifying code; it's faster   }
    MOV    BX,WORD PTR LSourceStepX    {  to use immediates than to read    }
    MOV    WORD PTR CS:[@Rep15 - 2],BX {  from memory                       }

    MOV    BH,BYTE PTR Light
    SUB    BL,BL
    ADD    BX,WORD PTR LightData
@TexScanLoop4:                { Negative source X increments                }

    { Draw the pixel. }

{    MOV    AL,[SI]}
    LODSB                     { Copy image pixel                            }
    DB     64h                { XLAT FS: }
    XLAT
    STOSB

    { Point to the next source pixel.                                       }

    ADD    DX,2211h                   { LSourceStepX }
@Rep15:
    SBB    SI,2211h                   { LXYBaseAdvance }
@Rep14:

    { Continue if there are any more dest pixels to draw.                   }

    DEC    CX
    JNZ    @TexScanLoop4

@EndScan:
    POP    SI
    POP    ES
    POP    DI
    POP    DS
@ScanDone:
    INC    WORD PTR Top
    MOV    AX,WORD PTR DestY
    CMP    WORD PTR Top,AX
    JGE    @GetOut
    ADD    SI,4
    ADD    DI,16
    JMP    @NextLine
@GetOut:
    DB     0Fh,0A1h                           { POP  FS }
    POP    DS
    STI
  End; { Asm }
End; { ScanOutLineBufWholeLight }


Procedure ScanOutLineBufWholeLightVert(ClipMinX,ClipMinY,ClipMaxX,ClipMaxY: Word;
 SourceLimits,DestLimits: Pointer; Left,DestX: Word;
 TexMapWidth: Word; TexMapBits: Pointer; BufSeg: Word; LightData: Pointer;
 Light: Byte);
{ ------------------------------------------------------------------------- }
{ Draws all pixels in the specified scan line, with the pixel colors taken  }
{ from the specified texture map.  Uses approach of pre-stepping 1/2 pixel  }
{ into the source image and rounding to the nearest source pixel at each    }
{ step, so that texture maps will appear reasonably similar at all angles.  }
{ This routine is specific to 320-pixel-wide planar (non-Chain4) 256-color  }
{ modes, such as mode X, which is a planar (non-Chain4) 256-color mode with }
{ a resolution of 320x240.                                                  }
{ ------------------------------------------------------------------------- }
Var
  LSourceY       : LongInt; { Current Y coordinate in source image          }
  LSourceStepX   : LongInt; { X step in source image for X dest step of 1   }
  LSourceStepY   : LongInt; { Y step in source image for X dest step of 1   }
  LXAdvanceByOne : Word;    { Used to step source pointer 1 pixel           }
                            {  incrementally in X                           }
  LXBaseAdvance  : Word;    { Use to step source pointer minimum number of  }
                            {  pixels incrementally in X                    }
  LYAdvanceByOne : Word;    { Used to step source pointer 1 pixel           }
                            {  incrementally in Y                           }
  LYBaseAdvance  : Word;    { Use to step source pointer minimum number of  }
                            {  pixels incrementally in Y                    }
  StartY         : Word;

Begin
  Asm
    CLI
    CLD
    JMP    @Start
@ToScanDone:
    JMP    @ScanDone
@Start:
    PUSH   DS
    DB     0Fh,0A0h                           { PUSH FS }

    MOV    AX,WORD PTR LightData[2]
    DB     8Eh,0E0h                           { MOV  FS,AX }

    { Nothing to do if destination is fully X clipped. }

    LES    DI,DWORD PTR SourceLimits
    LDS    SI,DWORD PTR DestLimits
    MOV    BX,WORD PTR Left
    MOV    AX,BX
    SHL    BX,4
    ADD    DI,BX
    SHL    AX,2
    ADD    SI,AX
@NextLine:
    MOV    CX,WORD PTR [SI+2]                 { Dest.Y2 }
    CMP    CX,WORD PTR ClipMinY
    JLE    @ToScanDone        { Bottom edge is above clip rect, so done }
    MOV    BX,WORD PTR [SI]                   { Dest.Y1 }
    CMP    BX,WORD PTR ClipMaxY
    JGE    @ToScanDone        { Left edge is below of clip rect, so done }
    SUB    CX,BX
    JLE    @ToScanDone        { Null or negative full width, so done        }

    { Calculate source steps that correspond to each 1-pixel destination Y  }
    { step (across the destination scan line).                              }

    DB     _32BIT
    MOV    AX,WORD PTR [ES:DI+4]              { Get source X width          }
    DB     _32BIT
    SUB    AX,WORD PTR [ES:DI]
    DB     _32BIT                             { OR    EAX,EAX               }
    OR     AX,AX
    JZ     @SkipDiv
    DB     _32BIT,99h                         { CDQ                         }
    DB     _32BIT,0Fh,0B7h,0D9h               { MOVZX EBX,CX                }
    DB     _32BIT                             { IDIV  EBX                   }
    IDIV   BX
@SkipDiv:
    DB     _32BIT
    MOV    WORD PTR LSourceStepX,AX           { Remember source X step for  }
                                              {  1-pixel destination X step }
    PUSH   CX
    MOV    CX,1               { Assume source X advances non-negative       }
    DB     _32BIT
    AND    AX,AX              { Which way does source X advance?            }
    JNS    @SourceXNonNeg     { Non-negative                                }
    NEG    CX                 { Negative                                    }
    CMP    AX,0               { Is the whole step exactly an integer?       }
    JZ     @SourceXNonNeg     { Yes                                         }
    DB     _32BIT,5,0,0,1,0   { ADD   EAX,10000h                            }
                              { No, truncate to integer in the direction of }
                              {  0, because otherwise we'll end up with a   }
                              {  whole step of 1-too-large magnitude        }
@SourceXNonNeg:
    MOV    WORD PTR LXAdvanceByOne,CX { Amount to add to source pointer to  }
                                      {  move by one in X                   }
    DB     _32BIT
    SAR    AX,16
    MOV    WORD PTR LXBaseAdvance,AX  { Minimum amount to add to source     }
                                      {  pointer to advance in X each time  }
                                      {  the dest advances one in X         }
    POP    CX
    DB     _32BIT                             { SUB   EDX,EDX               }
    MOV    AX,WORD PTR [ES:DI+12]             { Get source Y height         }
    DB     _32BIT
    SUB    AX,WORD PTR [ES:DI+8]
    DB     _32BIT
    OR     AX,AX
    JZ     @SkipDiv1
    DB     _32BIT,99h                         { CDQ                         }
    DB     _32BIT,0Fh,0B7h,0D9h               { MOVZX EBX,CX                }
    DB     _32BIT                             { IDIV  EBX                   }
    IDIV   BX
@SkipDiv1:
    DB     _32BIT
    MOV    WORD PTR LSourceStepY,AX           { Remember source Y step for  }
                                              {  1-pixel destination X step }
    MOV    CX,WORD PTR TexMapWidth  { Assume source Y advances non-negative }
    DB     _32BIT
    AND    AX,AX              { Which way does source Y advance?            }
    JNS    @SourceYNonNeg     { Non-negative                                }
    NEG    CX                 { Negative                                    }
    CMP    AX,0               { Is the whole step exactly an integer?       }
    JZ     @SourceYNonNeg     { Yes                                         }
    DB     _32BIT,5,0,0,1,0   { ADD   EAX,10000h                            }
                              { No, truncate to integer in the direction of }
                              {  0, because otherwise we'll end up with a   }
                              {  whole step of 1-too-large magnitude        }
@SourceYNonNeg:
    MOV    WORD PTR LYAdvanceByOne,CX { Amount to add to source pointer to  }
                                      {  move by one in Y                   }
    DB     _32BIT,0Fh,0A4h,0C2h,10h           { SHLD  EDX,EAX,16            }

    OR     DX,DX
    JNZ    @DoMul
    MOV    WORD PTR LYBaseAdvance,0  {  carry from the fractional part      }
    JMP    @SkipMul
@DoMul:
    MOV    AX,WORD PTR TexMapWidth   { Minimum distance skipped in source   }
    IMUL   DX                        {  image bitmap when Y steps (ignoring }
    MOV    WORD PTR LYBaseAdvance,AX {  carry from the fractional part      }
@SkipMul:

    { Advance 1/2 step in the stepping direction, to space scanned pixels   }
    { evenly between the left and right edges.  (There's a slight inaccuracy}
    { in dividing negative numbers by 2 by shifting rather than dividing,   }
    { but the inaccuracy is in the least significant bit, and we'll just    }
    { live with it.                                                         }

    DB     _32BIT
    MOV    AX,WORD PTR LSourceStepX      { MOV   EAX,DWORD PTR LSourceStepX }
    DB     _32BIT
    SAR    AX,1                          { SAR   EAX,1                      }
    DB     _32BIT
    ADD    WORD PTR [ES:DI],AX           { ADD   DWORD PTR LSourceX,EAX     }

    DB     _32BIT
    MOV    AX,WORD PTR LSourceStepY      { MOV   EAX,DWORD PTR LSourceStepY }
    DB     _32BIT
    SAR    AX,1                          { SAR   EAX,1                      }
    DB     _32BIT
    ADD    WORD PTR [ES:DI+8],AX         { ADD   DWORD PTR LSourceY,EAX     }

    { Clip the bottom edge if necessary. }

    MOV    CX,WORD PTR [SI+2]
    CMP    CX,WORD PTR ClipMaxY
    JL     @BottomEdgeClipped
    MOV    CX,WORD PTR ClipMaxY
@BottomEdgeClipped:

    { Clip the top edge if necessary. }

    MOV    BX,WORD PTR [SI]
    MOV    WORD PTR StartY,BX
    CMP    BX,WORD PTR ClipMinY
    JGE    @TopEdgeClipped

    { Top clipping is necessary; advance the source accordingly }

    MOV    AX,WORD PTR ClipMinY
    MOV    WORD PTR StartY,AX
    NEG    BX
    ADD    BX,WORD PTR ClipMinY { ClipMinY - DestY                          }
                                { First, advance the source in X            }
    DB     _32BIT,0Fh,0B7h,0C3h { MOVZX EAX,BX                              }
    DB     _32BIT
    IMUL   WORD PTR LSourceStepX
    DB     _32BIT
    ADD    WORD PTR [ES:DI],AX

    { now advance the source in Y }

    DB     _32BIT,0Fh,0B7h,0C3h { MOVZX EAX,BX                              }
    DB     _32BIT
    IMUL   WORD PTR LSourceStepY
    DB     _32BIT
    ADD    WORD PTR [ES:DI+8],AX
    MOV    BX,WORD PTR ClipMinY { Start Y coordinate in dest after clipping }
@TopEdgeClipped:

    { Calculate actual clipped destination drawing width. }

    SUB    CX,BX

    { Scan across the destination scan line, updating the source image      }
    { position accordingly.  Point to the initial source image pixel, adding}
    { 0.5 to both X and Y so that we can truncate to integers from now on   }
    { but effectively get rounding.                                         }

    DB     _32BIT
    ADD    WORD PTR [ES:DI+8],8000h   { Add 0.5 }
    DB     0,0
    DB     _32BIT
    ADD    WORD PTR [ES:DI],8000h     { Add 0.5 }
    DB     0,0
    MOV    AX,WORD PTR [ES:DI+10]
{    ADC    AX,0}
    MUL    WORD PTR TexMapWidth      { Initial scan line in source image    }
    MOV    BX,WORD PTR [ES:DI+2]     { Offset into source scan line         }
{    ADC    BX,AX}            { Initial source offset in source image       }
    ADD    BX,AX
    ADD    BX,WORD PTR TexMapBits { DS:BX points to the initial image pixel }
    PUSH   DS
    MOV    AX,WORD PTR TexMapBits + 2
    MOV    DS,AX

    MOV    AX,WORD PTR [ES:DI+8]
    MOV    WORD PTR LSourceY,AX

    { If the source X step is negative, change over to working with non-    }
    { negative values.                                                      }

    CMP    WORD PTR LXAdvanceByOne,0
    JGE    @SXStepSet
    NEG    WORD PTR LSourceStepX
    NOT    WORD PTR [ES:DI]            {LSourceX}
@SXStepSet:

    { If the source Y step is negative, change over to working with non-    }
    { negative values.                                                      }

    CMP    WORD PTR LYAdvanceByOne,0
    JGE    @SYStepSet
    NEG    WORD PTR LSourceStepY
    NOT    WORD PTR LSourceY
@SYStepSet:

    { Point to initial destination pixel }

    MOV    AX,WORD PTR StartY {DI}    { Hard-coded multiply by 320 }
    MOV    DH,AL
    SUB    DL,DL
    SHL    AX,6
    ADD    AX,DX


    MOV    DX,WORD PTR [ES:DI]  { Load LSourceX now }


    PUSH   DI
    MOV    DI,WORD PTR Left   { Initial destination X                       }
    ADD    DI,AX              { Offset of pixel in page                     }
                              { ES:DI now points to the first destination   }
                              {  pixel                                      }
    PUSH   ES
    MOV    AX,WORD PTR BufSeg
    MOV    ES,AX

    { At this point:                                                        }
    {      BX = pointer to initial image pixel                              }
    {      SI = # of pixels to fill                                         }
    {      DI = pointer to initial destination pixel                        }
    {      CX = destination line width                                      }
    {      DX = low part of LSourceX                                        }

    PUSH   SI
    MOV    SI,BX
    MOV    BX,WORD PTR LXBaseAdvance
    ADD    BX,WORD PTR LYBaseAdvance   { LXYBaseAdvance }

    CMP    WORD PTR LSourceStepX,0
    JE     @NoSourceStepX
    CMP    WORD PTR LSourceStepY,0
    JE     @NoSourceStepY

{ ------------------------------------------------------ }
{ General case - LSourceStepX <> 0 and LSourceStepY <> 0 }
{ ------------------------------------------------------ }

    CMP    WORD PTR LXAdvanceByOne,0
    JL     @TexScanLoop1A
    MOV    WORD PTR CS:[@Rep1 - 2],BX  { Self-modifying code; it's faster   }
    ADD    BX,WORD PTR LYAdvanceByOne  {  to use immediates than to read    }
    MOV    WORD PTR CS:[@Rep4 - 2],BX  {  from memory                       }
    MOV    BX,WORD PTR LSourceStepX
    MOV    WORD PTR CS:[@Rep2 - 2],BX
    MOV    WORD PTR CS:[@Rep2A - 2],BX
    MOV    BX,WORD PTR LSourceStepY
    MOV    WORD PTR CS:[@Rep3 - 2],BX
    MOV    BX,WORD PTR LSourceY
    DB     _32BIT
    SHL    BX,16
    MOV    BX,DX
    DB     _32BIT
    MOV    DX,BX

    MOV    BH,BYTE PTR Light
    SUB    BL,BL
    ADD    BX,WORD PTR LightData
@TexScanLoop:                 { Positive source X increments                }

    { Draw the pixel. }


    MOV    AL,[SI]
    DB     64h                { XLAT FS: }
    XLAT
    MOV    [ES:DI],AL
    ADD    DI,SCREEN_WIDTH

    { Point to the next source pixel.                                       }

    DB     _32BIT
    DB     81h,0C2h,0,0,0,0           { ADD  EDX,LSourceStepY Shl 16 }
@Rep3:
    JNC    @NoExtraYAdvance
    ADD    DX,2211h                   { LSourceStepX }
@Rep2:
    ADC    SI,2211h                   { LXYBaseAdvance+LYAdvanceByOne }
@Rep4:

    { Continue if there are any more dest pixels to draw.                   }

    DEC    CX
    JNZ    @TexScanLoop
    JMP    @EndScan
@NoExtraYAdvance:
    ADD    DX,2211h                   { LSourceStepX }
@Rep2A:
    ADC    SI,2211h                   { LXYBaseAdvance }
@Rep1:

    { Continue if there are any more dest pixels to draw.                   }

    DEC    CX
    JNZ    @TexScanLoop
    JMP    @EndScan
{ --------------------------------------------- }
@TexScanLoop1A:
    NEG    BX
    MOV    WORD PTR CS:[@Rep5 - 2],BX  { Self-modifying code; it's faster   }
    SUB    BX,WORD PTR LYAdvanceByOne  {  to use immediates than to read    }
    MOV    WORD PTR CS:[@Rep8 - 2],BX  {  from memory                       }
    MOV    BX,WORD PTR LSourceStepX
    MOV    WORD PTR CS:[@Rep6 - 2],BX
    MOV    WORD PTR CS:[@Rep6A - 2],BX
    MOV    BX,WORD PTR LSourceStepY
    MOV    WORD PTR CS:[@Rep7 - 2],BX
    MOV    BX,WORD PTR LSourceY
    DB     _32BIT
    SHL    BX,16
    MOV    BX,DX
    DB     _32BIT
    MOV    DX,BX

    MOV    BH,BYTE PTR Light
    SUB    BL,BL
    ADD    BX,WORD PTR LightData
@TexScanLoop1:                { Negative source X increments                }

    { Draw the pixel. }

    MOV    AL,[SI]
    DB     64h                { XLAT FS: }
    XLAT
    MOV    [ES:DI],AL
    ADD    DI,SCREEN_WIDTH

    { Point to the next source pixel.                                       }

    DB     _32BIT
    DB     81h,0C2h,0,0,0,0           { ADD  EDX,LSourceStepY Shl 16 }
@Rep7:
    JNC    @NoExtraYAdvance1
    ADD    DX,2211h                   { LSourceStepX }
@Rep6:
    SBB    SI,2211h                   { -LXYBaseAdvance-LYAdvanceByOne }
@Rep8:

    { Continue if there are any more dest pixels to draw.                   }

    DEC    CX
    JNZ    @TexScanLoop1
    JMP    @EndScan
@NoExtraYAdvance1:
    ADD    DX,2211h                   { LSourceStepX }
@Rep6A:
    SBB    SI,2211h                   { LXYBaseAdvance }
@Rep5:

    { Continue if there are any more dest pixels to draw.                   }
             DEC    CX
    JNZ    @TexScanLoop1
    JMP    @EndScan

{ ------------------------------- }
{ Special case - LSourceStepX = 0 }
{ ------------------------------- }
@NoSourceStepX:
    MOV    WORD PTR CS:[@Rep9 - 2],BX  { Self-modifying code; it's faster   }
    ADD    BX,WORD PTR LYAdvanceByOne  {  to use immediates than to read    }
    MOV    WORD PTR CS:[@Rep11 - 2],BX {  from memory                       }
    MOV    BX,WORD PTR LSourceStepY
    MOV    WORD PTR CS:[@Rep10 - 2],BX
    MOV    BX,WORD PTR LSourceY
    DB     _32BIT
    SHL    BX,16
    MOV    BX,DX
    DB     _32BIT
    MOV    DX,BX

    MOV    BH,BYTE PTR Light
    SUB    BL,BL
    ADD    BX,WORD PTR LightData
@TexScanLoop2:                { Positive source X increments                }

    { Draw the pixel. }

    MOV    AL,[SI]
    DB     64h                { XLAT FS: }
    XLAT
    MOV    [ES:DI],AL
    ADD    DI,SCREEN_WIDTH

    { Point to the next source pixel.                                       }

    DB     _32BIT
    DB     81h,0C2h,0,0,0,0           { ADD  EDX,LSourceStepY Shl 16 }
@Rep10:
    JNC    @NoExtraYAdvance2          { Didn't turn over; no extra advance  }
    ADD    SI,2211h                   { LYAdvanceByOne+LXYBaseAdvance }
@Rep11:

    { Continue if there are any more dest pixels to draw.                   }

    DEC    CX
    JNZ    @TexScanLoop2
    JMP    @EndScan
@NoExtraYAdvance2:
    ADD    SI,2211h                   { LXYBaseAdvance }
@Rep9:

    { Continue if there are any more dest pixels to draw.                   }

    DEC    CX
    JNZ    @TexScanLoop2
    JMP    @EndScan

{ ------------------------------- }
{ Special case - LSourceStepY = 0 }
{ ------------------------------- }

@NoSourceStepY:
    CMP    WORD PTR LXAdvanceByOne,0
    JL     @TexScanLoop4A
    MOV    WORD PTR CS:[@Rep12 - 2],BX { Self-modifying code; it's faster   }
    MOV    BX,WORD PTR LSourceStepX    {  to use immediates than to read    }
    MOV    WORD PTR CS:[@Rep13 - 2],BX {  from memory                       }

    MOV    BH,BYTE PTR Light
    SUB    BL,BL
    ADD    BX,WORD PTR LightData
{ --------------------------------------------- }
@TexScanLoop3:                { Positive source X increments                }

    { Draw the pixel. }

    MOV    AL,[SI]
    DB     64h                { XLAT FS: }
    XLAT
    MOV    [ES:DI],AL
    ADD    DI,SCREEN_WIDTH

    { Point to the next source pixel.                                       }

    ADD    DX,2211h                   { LSourceStepX }
@Rep13:
    ADC    SI,2211h                   { LXYBaseAdvance }
@Rep12:

    { Continue if there are any more dest pixels to draw.                   }

    DEC    CX
    JNZ    @TexScanLoop3
    JMP    @EndScan
{ --------------------------------------------- }
@TexScanLoop4A:
    NEG    BX
    MOV    WORD PTR CS:[@Rep14 - 2],BX { Self-modifying code; it's faster   }
    MOV    BX,WORD PTR LSourceStepX    {  to use immediates than to read    }
    MOV    WORD PTR CS:[@Rep15 - 2],BX {  from memory                       }

    MOV    BH,BYTE PTR Light
    SUB    BL,BL
    ADD    BX,WORD PTR LightData
@TexScanLoop4:                { Negative source X increments                }

    { Draw the pixel. }

    MOV    AL,[SI]
    DB     64h                { XLAT FS: }
    XLAT
    MOV    [ES:DI],AL
    ADD    DI,SCREEN_WIDTH

    { Point to the next source pixel.                                       }

    ADD    DX,2211h                   { LSourceStepX }
@Rep15:
    SBB    SI,2211h                   { LXYBaseAdvance }
@Rep14:

    { Continue if there are any more dest pixels to draw.                   }

    DEC    CX
    JNZ    @TexScanLoop4

@EndScan:
    POP    SI
    POP    ES
    POP    DI
    POP    DS
@ScanDone:
    INC    WORD PTR Left
    MOV    AX,WORD PTR DestX
    CMP    WORD PTR Left,AX
    JGE    @GetOut
    ADD    SI,4
    ADD    DI,16
    JMP    @NextLine
@GetOut:
    DB     0Fh,0A1h                           { POP  FS }
    POP    DS
    STI
  End; { Asm }
End; { ScanOutLineBufWholeLightVert }

Begin

  { Initialize tables for rotation }

  For I := 0 To 359 Do
  Begin
    R := I * (Pi / 180);
    _Cos[I] := Cos(R);
    _Sin[I] := Sin(R);
    Case I Of
         0..45: S := I / 45;
        46..90: S := (90 - I) / 45;
       91..135: S := (I - 90) / 45;
      136..180: S := (180 - I) / 45;
      181..225: S := (I - 180) / 45;
      226..270: S := (270 - I) / 45;
      271..315: S := (I - 270) / 45;
      316..359: S := (360 - I) / 45;
    End; { Case }
    If (I <= 45) Or ((I >= 135) And (I <= 225)) Or (I >= 315) Then
    Begin
      _AddValX[I] := Round(512 * Abs(Sin(R) / Cos(R)));
      _AddValY[I] := Round(_AddValX[I] / (1 + Abs(Sin(R) / Cos(R))));
      _XMult[I]   := 1 / Sqrt(1 + S * S);
      _YMult[I] := Sqrt(1 + Abs(Sin(2 * R)));
    End
    Else
    Begin
      _AddValX[I] := Round(512 * Abs(Cos(R) / Sin(R)));
      _AddValY[I] := Round(_AddValX[I] / (1 + Abs(Cos(R) / Sin(R))));
      _XMult[I]   := 1 / Sqrt(1 + S * S);
      _YMult[I] := Sqrt(1 + Abs(Sin(2 * R)));
    End;
  End; { For I }
End.
