

; ---------------------------------------------------------------------
;
; QTMACROS.INC - QuickTime for Windows Helper Macros
;
;                Version 1.1
;
;                (c) 1988-1993 Apple Computer, Inc. All Rights Reserved.
;
; ---------------------------------------------------------------------


COMMENT @
Video dispatch codes
@
VDSP_SETBANK     EQU  1
VDSP_SLIDEWINDOW EQU  2
VDSP_SAVECONTEXT EQU  3
VDSP_RESTCONTEXT EQU  4
VDSP_SETTARGET   EQU  5
VDSP_IDENTIFY    EQU 21
VDSP_VERSION     EQU 22
VDSP_BANKTABLE   EQU 23
VDSP_BITBLTTYPE  EQU 24
VDSP_SCANWIDTH   EQU 25
VDSP_TERMINATE   EQU 86

COMMENT @
BitBlt types for hardware support
@
BBL_NONE         EQU  0           ; unknown BitBlt type
BBL_MOVSD        EQU  1           ; use MOVSD
BBL_DRVR         EQU  2           ; use the driver's BitBlt
BBL_MOVPL16      EQU  3           ; 16-bit planar

COMMENT @
BMP types

These should match the BMP_TYPE enumerated values in QTCODEC.H
@
BMP_NONE         EQU  0           ; unknown type
BMP_DIB          EQU  1           ; DIB
BMP_MONO         EQU  2           ; monochrome
BMP_PACKED_4     EQU  3           ; packed 4 bit, e.g., Fahrenheit
BMP_PLANAR_4     EQU  4           ; VGA or SVGA
BMP_INDEX_8      EQU  5           ; palettized driver
BMP_5_5_5        EQU  6           ; 32,768 colors
BMP_5_6_5        EQU  7           ; XGA, Intel order
BMP_PLANAR_16    EQU  8           ; two planes of one byte each
BMP_8_8_8_RGB    EQU  9           ; true color RGB
BMP_MEMERR       EQU 10           ; insufficient memory for buffers
BMP_8_8_8_BGR    EQU 11           ; true color BGR
BMP_5_6_5_M      EQU 12           ; XGA, Motorola order

COMMENT @
Macros AlignBP and AlignBPRet:
Align BP on a DWORD boundary

On 386DX and 486 CPUs, applications get better performance by accessing
DWORD operands on DWORD boundaries.  Unaligned operands require an extra
bus cycle.  Our tests on various QuickTime assembler functions shows
unaligned operands typically add a 10% peformance penalty to the entire
function.  By being careful, we can ensure that WORD arguments and WORD
local variables occur in pairs, but we cannot guarantee the alignment of
BP, which is used to access these variables.  The alignment of BP is
determined at run time.  MSC 7.0 aligns the stack only on WORD
boundaries.

This macro ensures that BP is aligned on a DWORD boundary.  It is
intended only for use with NEAR PROCs that use the PASCAL calling
sequence.

For a NEAR PROC the function arguments and local variables are separated
by two WORDs, the return address and the caller's BP.  Thus, an aligned
BP optimizes accesses to both.  For a FAR PROC the function arguments
and local variables are separated by three WORDs, since the return
address is two WORDs.  In that case, we must define a dummy WORD as the
first local variable and misalign BP, so that the memory references will
be aligned.

When BP is not aligned, the macro aligns it, then moves the function
arguments, return address and saved BP accordingly.  The PASCAL
restriction allows the macro to find the byte size of the arguments in
the RET instruction.  The application must insert label "AlignBPRet"
immediately after one of its RET instructions.  The application must
also define a dummy WORD as the last local variable.

The macro assumes the direction indicator is 0 (forward), that the
application already saved SI and DI (via USES SI DI on the PROC
statement), and that ES and CX can be destroyed.

Upon exit from the function, we must adjust the stack pointer if BP was
changed at entry.  We accomplish this by storing in the extra word made
available at the top of the stack the caller's return address.  At the
location in the stack that normally holds the caller's return address,
we place an address in this program.  At the doctored return address, we
execute a near return.

Macro AlignBPExit uses a machine instruction for the near RET to prevent
the assembler from generating epilog code.
@
AlignBP  MACRO
         LOCAL   AlreadyAligned
         TEST    BP, 2            ;; Already on DWORD boundary?
         JE      AlreadyAligned   ;; Skip if already aligned
         CMP     BYTE PTR AlignBPRet - 3, 0C2H ;; Expected RET instruction?
         JNE     AlreadyAligned   ;; Don't adjust if environment unknown
         MOV     CX, WORD PTR [AlignBPRet - 2];; Get byte size of parameter list
         SHR     CX, 1            ;; Convert byte count to word count
         ADD     CX, 2            ;; Account for return address and saved BP
         SUB     BP, 2            ;; Perform the alignment ourselves
                                  ;; Requires a word of slop after all local
                                  ;; variables
         PUSH    SS               ;; Copy SS to ES
         POP     ES               ;;
         MOV     DI, BP           ;; Set target pointer
         LEA     SI, [DI+2]       ;; Set source pointer
         REP     MOVSW [DI],SS:[SI] ;; Move the arguments down
         MOV     AX, [BP+2]       ;; Get caller's return address
         MOV     ES:[DI], AX      ;; Store at top of stack
         MOV     [BP+2], OFFSET AlignFixSP ;; Substitute our return address
AlreadyAligned:
         ENDM

AlignBPExit MACRO
AlignBPRet:                       ;; Immediately follow the RETN immed
         ALIGN   16               ;; Align for better performance
AlignFixSP:
         RETN                     ;; Return to caller without epilog
         ENDM

COMMENT @
Macro BSWAPX:
Exchange the bytes in a DWORD register.

On a 486, the BSWAP instruction uses 1 clock instead of the 6 or 7
clocks required for this macro. Unfortunately, there is no cheap
inline way at run time to distinguish between a 386 and 486.
@
BSWAPX   MACRO   reg:REQ
Root     SUBSTR  <reg>, 2, 1
Xchg8    CATSTR  Root, <X>
         ROL     Xchg8, 8
         ROL     reg, 16
         ROL     Xchg8, 8
         ENDM

COMMENT @
Macro to initialize dirty list, initialize bank bounds table, set GS:0
to bank bounds table, set ES:EDI to initial hardware target address,
set the bank for banked adapters.
@
SetHdwTarget MACRO
         LGS     DI, BankBds      ;; Point to bank bounds table
         MOV     DI, GS:[DI]      ;; Get residual block count of first bad row
         MOV     NextBadBlock, DI ;; Save the residual block count to match

         MOV     AX, WORD PTR OffScreen ;; Point to dirty list
         REPEAT  BPPT * Mag
         ADD     AX, BWIDTH       ;; Allocate room for offscreen pixels
         ENDM                     ;; of REPEAT  BPPT * Mag
         IF      Mag EQ 2
         ADD     AX, WdBytes      ;; Allocate room for second scan line in pair
         ENDIF
         INC     AX               ;; Round up to even offset for efficiency
         AND     AL, 0FEH         ;;
         ADD     AX, 4            ;; Leave room for post-processing
         MOV     DirtyListFirst, AX ;;

         MOV     AX, DESTY        ;; Get starting row
         MUL     WdBytes          ;; Multiply by width of adapter scan line
         REPEAT  BPPT             ;; Bytes per target pixel
         ADD     AX, DESTX        ;; Add in destination column
         ADC     DX, 0            ;; Bump bank number if carry
         ENDM                     ;; of REPEAT BPPT
         PUSH    DX               ;; Push high order word
         PUSH    AX               ;; Push low order word
         PUSH    VDSP_SETTARGET   ;; Dispatch code for SetTarget
         CALL    [Hardware]       ;; Set the initial bank, also ES:DI
         ADD     SP, 6            ;; Remove arguments from the stack
         MOV     Bank, AX         ;; Save the returned bank
         MOV     SelVRAM, ES      ;; Save VRAM selector
         ENDM                     ;; of SetHdwTarget MACRO

COMMENT @
Bank change macros:
Used by functions that write directly to video adapter hardware.

The bank change macros assume that all bank changes are of size 1.  This
is so, since the program moves at most 4 rows at a time and each bank holds
at least 40 rows.
@
DoBankUp MACRO
         INC     Bank             ;; Go to next bank
         PUSH    Bank             ;; Pass argument to function
         PUSH    VDSP_SETBANK     ;; Dispatch code for SetBank
         CALL    [Hardware]       ;; Set the new bank
         ADD     SP, 4            ;; Remove arguments from the stack
         ENDM

CheckBankUp MACRO  nbr:REQ        ;; Check bank, jumping only when the bank
         JC      NewBankUp&nbr&   ;;  changes
NewBankUp&nbr&Ret:
         ENDM

SetBankUp MACRO  nbr:REQ          ;; Effect a bank change
         ALIGN   16
NewBankUp&nbr&:
         DoBankUp
         JMP     NewBankUp&nbr&Ret;; Return to main code
         ENDM

DoBankDn MACRO
         DEC     Bank             ;; Go to previous bank
         PUSH    Bank             ;; Pass argument to function
         PUSH    VDSP_SETBANK     ;; Dispatch code for SetBank
         CALL    [Hardware]       ;; Set the new bank
         ADD     SP, 4            ;; Remove arguments from the stack
         ENDM

CheckBankDn MACRO  nbr:REQ        ;; Check bank, jumping only when the bank
         JC      NewBankDn&nbr&   ;;  changes
NewBankDn&nbr&Ret:
         ENDM

SetBankDn MACRO  nbr:REQ          ;; Effect a bank change
         ALIGN   16
NewBankDn&nbr&:
         DoBankDn
         JMP     NewBankDn&nbr&Ret;; Return to main code
         ENDM

SlideWindow MACRO
         PUSH    Bank             ;; SlideWindow may adjust this
         PUSH    VDSP_SLIDEWINDOW ;; Dispatch code for SlideWindow
         CALL    [Hardware]       ;; Call the function
         ADD     SP, 4            ;; Remove arguments from the stack
                                  ;; AX = bank, ES:DI may be changed
         MOV     Bank, AX         ;; Bank number may have changed
         ENDM

COMMENT @
Macros used with downward dithers from 24 bit sources

Get554 is used with 8 bit targets.  The result is placed in BX, where it
can then be used as an index into a dither table.

Get555 is used with 15 bit (5-5-5) targets.

Get565 and Get565M are used with 16 bit (5-6-5) targets.
@
Get554   MACRO   offst:REQ             ;;
         MOV     BH, [ESI+offst]       ;; Get R
         SHR     BH, 3                 ;; Save 5 bits of R
         MOV     BL, [ESI+offst+1]     ;; Get G
         SHL     EBX, 5                ;; Save 5 bits of G
         MOV     BL, [ESI+offst+2]     ;; Get B
         SHR     EBX, 4                ;; Get 5-5-4
         ENDM

Get555   MACRO   offst:REQ
         MOV     AH, [ESI+offst]       ;; Get R
         SHR     AH, 3                 ;; Save 5 bits of R
         MOV     AL, [ESI+offst+1]     ;; Get G
         SHL     EAX, 5                ;; Save 5 bits of G
         MOV     AL, [ESI+offst+2]     ;; Get B
         SHR     EAX, 3                ;; Get 5-5-5
         ENDM

Get565   MACRO   offst:REQ
         MOV     AH, [ESI+offst]       ;; Get R
         SHR     AH, 3                 ;; Save 5 bits of R
         MOV     AL, [ESI+offst+1]     ;; Get G
         SHL     EAX, 6                ;; Save 6 bits of G
         MOV     AL, [ESI+offst+2]     ;; Get B
         SHR     EAX, 3                ;; Get 5-6-5
         ENDM

Get565M  MACRO   offst:REQ
         Get565  offst                 ;; Get 5-6-5 in Intel order
         ROL     AX, 8                 ;; Put bytes in Motorola order
         ENDM

COMMENT @
Utility macros

The macro assumes CX bytes will be moved from DS:SI to ES:DI, and that
DX is available.
@
@Equals  MACRO   p1:REQ, p2:REQ
         EXITM   %( @InStr(,p1,p2) * @InStr(,p2,p1))
         ENDM                     ;; of @Equals MACRO

CopyBytes MACRO
         MOV     DX, CX           ;; Save byte count
         SHR     CX, 2            ;; Byte count to DWORD count
         REP     MOVSD            ;; Copy most of the bytes
         MOV     CX, DX           ;; Restore byte count
         AND     CX, 3            ;; 0-3 bytes left
         SHR     CX, 1            ;; Check for 2 or more bytes left
         REP     MOVSW            ;; Copy stray WORD, if any
         ADC     CX, CX           ;; 0-1 bytes left
         REP     MOVSB            ;; Copy stray byte, if any
         ENDM                     ;; of CopyBytes MACRO

COMMENT @
Macros used with decompressors that write directly to video adapter
hardware.
@
GoOnScreenX MACRO
         ALIGN   16
GoOnScreen:
         PUSH    CX               ;; Save register
         MOV     CX, DirtyListNext     ;; Offset of next dirty list entry
         SUB     CX, DirtyListFirst    ;; 4 * number of dirty list entries
         JLE     NotDirty         ;; Skip if dirty list is empty

         PUSH    BX               ;; Save registers
         PUSH    DX               ;;
         PUSH    SI               ;;
         PUSH    DI               ;;
         PUSH    DS               ;;
         PUSH    ES               ;;

         SHR     CX, 2            ;; Number of dirty list entries
         MOV     DX, BadStart     ;; Onscreen offset of line start
         NEG     DX               ;; Offset in offscreen buffer of bank split
                                  ;; Merge adjacent ranges
                                  ;; Split a range that splits a bank
         MOV     DS, WORD PTR OffScreen +2  ;; Offscreen buffer is the source
         MOV     SI, DirtyListFirst         ;; Point to first dirty list entry
         LEA     DI, [SI-4]                 ;; Allow room for one split entry
LoopNewRange:
         MOV     AX, [SI]         ;; Get start of range
         MOV     [DI], AX         ;; Save start of range
LoopSameRange:
         MOV     BX, [SI+2]       ;; Get end of range
         ADD     SI, 4            ;; Point to next source slot
         DEC     CX               ;; One fewer source slot
         JLE     RangeDone        ;; Skip if no more source slots
         CMP     BX, [SI]         ;; Is the next range adjacent to this?
         JB      RangeDone        ;; Skip if ranges are not adjacent
         JMP     LoopSameRange    ;; Keep combining adjacent ranges
RangeDone:
         CMP     AX, DX           ;; Might split occur in this range?
         JGE     NoSplit          ;; Skip if split does not occur in this range
         CMP     BX, DX           ;; Does split occur in this range?
         JLE     NoSplit          ;; Skip if split does not occur in this range
         MOV     [DI+2], DX       ;;
         ADD     DI, 4            ;; Point to next target slot
         MOV     [DI], DX         ;; Start of new range
NoSplit:
         MOV     [DI+2], BX       ;; Set right edge of range
         ADD     DI, 4            ;; Point to next target slot
         TEST    CX, CX           ;; Any source slots left?
         JG      LoopNewRange     ;; Loop while source slots remain
         OR      WORD PTR [DI], -1;; Terminate the list
                                  ;; Make offsets relative to prior entries
                                  ;; Convert ending offsets to byte sizes
         LEA     CX, [DI+4]       ;; Compute number of list entries
         SUB     CX, DirtyListFirst    ;; 4 * number of WORDs in the list
         LEA     BX, [DI-2]       ;; Point to last WORD in list
         SHR     CX, 1            ;; 2 * number of WORDs in list
         DEC     CX               ;; Don't process first WORD
LoopDiff:
         MOV     AX, [BX-2]       ;; Get previous WORD in list
         SUB     [BX], AX         ;; Compute difference from previous WORD
         SUB     BX, 2            ;; Back up one WORD
         DEC     CX               ;; One fewer WORD
         JG      LoopDiff         ;; Loop once for each WORD in list
                                  ;; Copy the first line of the pair
         MOV     ES, SelVRAM      ;; VRAM is the target
         MOV     DI, BadStart     ;;
         MOV     SI, WORD PTR OffScreen     ;; Offscreen buffer is the source
LoopCopy:
         MOV     AX, [BX]         ;; Get offset of range start
         TEST    AX, AX           ;; End of the list?
         JL      DirtyDone        ;; Skip if end of the list
         MOV     CX, [BX+2]       ;; Get number of bytes in the range
         ADD     BX, 4            ;; Point to next list entry
         ADD     SI, AX           ;; Locate data in offscreen buffer
         ADD     DI, AX           ;; Position pointer in onscreen buffer
         CheckBankUp 01E          ;; Change bank if necessary
         CopyBytes                ;; Copy the bytes
         TEST    DI, DI           ;; Did range end on a bank boundary?
         JZ      NewBankUp02E     ;; Skip if range ended on a bank boundary
NewBankUp02ERet:
         JMP     LoopCopy         ;; Loop once for each range

         ALIGN   16
DirtyDone:
         TEST    DI, DI           ;; Did we cross the bank boundary?
         JGE     DirtyCrossed     ;; Skip if we already crossed
         DoBankUp                 ;; Cross the bank boundary
DirtyCrossed:
         POP     ES               ;; Restore registers
         POP     DS               ;;
         POP     DI               ;;
         POP     SI               ;;
         POP     DX               ;;
         POP     BX               ;;
         POP     CX               ;;
         RETN                     ;; Return to caller without epilog

         ALIGN   16
NotDirty:
         POP     CX               ;; Restore register
         DoBankUp                 ;; Go to next bank
         RETN                     ;; Return to caller without epilog
         ENDM                     ;; of GoOnScreenX

GoOnScreen2x MACRO
         ALIGN   16
GoOnScreen:
         PUSH    CX               ;; Save register
         MOV     CX, DirtyListNext     ;; Offset of next dirty list entry
         SUB     CX, DirtyListFirst    ;; 4 * number of dirty list entries
         JLE     NotDirty         ;; Skip if dirty list is empty

         PUSH    BX               ;; Save registers
         PUSH    DX               ;;
         PUSH    SI               ;;
         PUSH    DI               ;;
         PUSH    DS               ;;
         PUSH    ES               ;;

         SHR     CX, 2            ;; Number of dirty list entries
         MOV     BX, BadStart     ;; Onscreen offset of line start
         MOV     DX, BX           ;; Copy onscreen offset of line start
         NEG     DX               ;; Offset in offscreen buffer of bank split?
         ADD     BX, WdBytes      ;; Onscreen offset of second line in pair
         JC      FirstLineSplit   ;; Skip if first line in pair has bank split
         MOV     DX, BX           ;; Copy onscreen offset of line start
         NEG     DX               ;; Offset in offscreen buffer of bank split
FirstLineSplit:
                                  ;; Merge adjacent ranges
                                  ;; Split a range that splits a bank
         MOV     DS, WORD PTR OffScreen +2  ;; Offscreen buffer is the source
         MOV     SI, DirtyListFirst         ;; Point to first dirty list entry
         LEA     DI, [SI-4]                 ;; Allow room for one split entry
LoopNewRange:
         MOV     AX, [SI]         ;; Get start of range
         MOV     [DI], AX         ;; Save start of range
LoopSameRange:
         MOV     BX, [SI+2]       ;; Get end of range
         ADD     SI, 4            ;; Point to next source slot
         DEC     CX               ;; One fewer source slot
         JLE     RangeDone        ;; Skip if no more source slots
         CMP     BX, [SI]         ;; Is the next range adjacent to this?
         JB      RangeDone        ;; Skip if ranges are not adjacent
         JMP     LoopSameRange    ;; Keep combining adjacent ranges
RangeDone:
         CMP     AX, DX           ;; Might split occur in this range?
         JGE     NoSplit          ;; Skip if split does not occur in this range
         CMP     BX, DX           ;; Does split occur in this range?
         JLE     NoSplit          ;; Skip if split does not occur in this range
         MOV     [DI+2], DX       ;;
         ADD     DI, 4            ;; Point to next target slot
         MOV     [DI], DX         ;; Start of new range
NoSplit:
         MOV     [DI+2], BX       ;; Set right edge of range
         ADD     DI, 4            ;; Point to next target slot
         TEST    CX, CX           ;; Any source slots left?
         JG      LoopNewRange     ;; Loop while source slots remain
                                  ;;
         OR      WORD PTR [DI], -1;; Terminate the list
                                  ;; Make offsets relative to prior entries
                                  ;; Convert ending offsets to byte sizes
         LEA     CX, [DI+4]       ;; Compute number of list entries
         SUB     CX, DirtyListFirst    ;; 4 * number of WORDs in the list
         LEA     BX, [DI-2]       ;; Point to last WORD in list
         SHR     CX, 1            ;; 2 * number of WORDs in list
         DEC     CX               ;; Don't process first WORD
LoopDiff:
         MOV     AX, [BX-2]       ;; Get previous WORD in list
         SUB     [BX], AX         ;; Compute difference from previous WORD
         SUB     BX, 2            ;; Back up one WORD
         DEC     CX               ;; One fewer WORD
         JG      LoopDiff         ;; Loop once for each WORD in list

                                  ;; Copy the first line of the pair
         MOV     ES, SelVRAM      ;; VRAM is the target
         MOV     DI, BadStart     ;;
         MOV     SI, WORD PTR OffScreen     ;; Offscreen buffer is the source
LoopCopy1:
         MOV     AX, [BX]         ;; Get offset of range start
         TEST    AX, AX           ;; End of the list?
         JL      DirtyDone1       ;; Skip if end of the list
         MOV     CX, [BX+2]       ;; Get number of bytes in the range
         ADD     BX, 4            ;; Point to next list entry
         ADD     SI, AX           ;; Locate data in offscreen buffer
         ADD     DI, AX           ;; Position pointer in onscreen buffer
         CheckBankUp 01E          ;; Change bank if necessary
         CopyBytes                ;; Copy the bytes
         TEST    DI, DI           ;; Did range end on a bank boundary?
         JZ      NewBankUp02E     ;; Skip if range ended on a bank boundary
NewBankUp02ERet:
         JMP     LoopCopy1        ;; Loop once for each range

         ALIGN   16
                                  ;; Copy the second line of the pair
DirtyDone1:
         MOV     AX, BadStart     ;; Onscreen offset of first line in pair
         ADD     AX, WdBytes      ;; Onscreen offset of second line in pair
         SUB     AX, DI           ;; Offset from current target pointer
         ADD     DI, AX           ;; Add the offset back in
         CheckBankUp 03E          ;; Change bank if necessary
         MOV     SI, WORD PTR OffScreen     ;; Offscreen buffer is the source
         MOV     BX, DirtyListFirst         ;; Point to first dirty list entry
         SUB     BX, 4                      ;; Post-processed first entry
LoopCopy2:
         MOV     AX, [BX]         ;; Get offset of range start
         TEST    AX, AX           ;; End of the list?
         JL      DirtyDone2       ;; Skip if end of the list
         MOV     CX, [BX+2]       ;; Get number of bytes in the range
         ADD     BX, 4            ;; Point to next list entry
         ADD     SI, AX           ;; Locate data in offscreen buffer
         ADD     DI, AX           ;; Position pointer in onscreen buffer
         CheckBankUp 04E          ;; Change bank if necessary
         CopyBytes                ;; Copy the bytes
         TEST    DI, DI           ;; Did range end on a bank boundary?
         JZ      NewBankUp05E     ;; Skip if range ended on a bank boundary
NewBankUp05ERet:
         JMP     LoopCopy2        ;; Loop once for each range

         ALIGN   16
DirtyDone2:
         TEST    DI, DI           ;; Did we cross the bank boundary?
         JGE     DirtyCrossed2    ;; Skip if we already crossed
         DoBankUp                 ;; Cross the bank boundary
DirtyCrossed2:
         POP     ES               ;; Restore registers
         POP     DS               ;;
         POP     DI               ;;
         POP     SI               ;;
         POP     DX               ;;
         POP     BX               ;;
         POP     CX               ;;
         RETN                     ;; Return to caller without epilog

         ALIGN   16
NotDirty:
         POP     CX               ;; Restore register
         DoBankUp                 ;; Go to next bank
         RETN                     ;; Return to caller without epilog
         ENDM                     ;; of GoOnScreen2x

COMMENT @
Macro used to move pixels from onscreen buffer to offscreen buffer.
@
GoOffScreenX MACRO
         LOCAL   NewBank
         LOCAL   AllFits
         LOCAL   ExitPath
         ALIGN   16
GoOffScreen:
         PUSH    DS               ;; Save register
         PUSH    CX               ;; Save register
         PUSH    DX               ;; Save register
         PUSH    SI               ;; Save register
         PUSH    DI               ;; Save register
         MOV     CX, AX           ;; Get byte count
         MOV     DS, SelVRAM      ;; Point to onscreen buffer
         MOV     SI, DI           ;; Copy target offset
         SUB     SI, WORD PTR OffScreen ;; Compute offset from start of buffer
         ADD     SI, BadStart     ;; Index into onscreen buffer
         JC      NewBank          ;; Skip if we crossed a bank boundary
         ADD     SI, CX           ;; Check ending source offset
         JNC     AllFits          ;; Skip if no bank change required
         JZ      AllFits          ;; Skip if no bank change required
         PUSH    SI               ;; Save byte count for new bank
         SUB     SI, CX           ;; Restore source pointer
         MOV     CX, SI           ;; Compute byte count for old bank
         NEG     CX               ;;
         CopyBytes                ;; Copy the bytes
         POP     CX               ;; Get byte count for new bank
NewBank:
         DoBankUp                 ;; Go to new bank
         CopyBytes                ;; Copy the bytes
         DoBankDn                 ;; Back to old bank
ExitPath:
         POP     DI               ;; Restore register
         POP     SI               ;; Restore register
         POP     DX               ;; Restore register
         POP     CX               ;; Restore register
         POP     DS               ;; Restore register
         RETN                     ;; Return to caller without epilog

         ALIGN   16
AllFits:                          ;; Source does not cross a bank boundary
         SUB     SI, CX           ;; Restore source pointer
         CopyBytes                ;; Copy the bytes
         JMP     ExitPath         ;; Go to common exit
         ENDM

COMMENT @
Macro to compute number of source bytes processed, used for BMP banding
@
BytesUsed MACRO
         XOR     EAX, EAX           ;; Zero out high order word
         MOV     AX, WORD PTR Inbuf ;; Get offset of source pointer
         SUB     ESI, EAX           ;; Compute number of source bytes processed
         SHLD    EDX, ESI, 16       ;; Put high order word in DX
         MOV     AX, SI             ;; Put low order word in AX
         ENDM

COMMENT @
Macros for moving pixels to and from overscan area

Note that the 2X versions of the macros should not be used with 8-bit
targets since dithering will produce different target values for the
same source.

We generate error messages only in the SavePixels macro, figuring they
would be superfluous in RestPixels.
@
SaveOneRow MACRO
j        =       0
         REPEAT  Limit
         MOV     EAX, ES:[DI+j]   ;; Get 4 bytes
         MOV     OverScan[i], EAX ;; Save the bytes
i        =       i + 4
j        =       j + 4
         ENDM                     ;; of REPEAT Limit
         ENDM

RestOneRow MACRO Width:REQ
j        =       0
         WHILE   j  LT  Width - Width MOD 4
         MOV     EAX, OverScan[i] ;; Get 4 bytes
         MOV     ES:[DI+j], EAX   ;; Restore the bytes
i        =       i + 4
j        =       j + 4
         ENDM                     ;; of WHILE  j  LT  Width - Width MOD 4
         IF      j  LT  Width - Width MOD 2
         MOV     AX, WORD PTR OverScan[i]   ;; Get 2 bytes
         MOV     ES:[DI+j], AX              ;; Restore the bytes
i        =       i + 2
j        =       j + 2
         ENDIF                    ;; of IF      j  LT  Width - Width MOD 2
         IF      j  LT  Width
         MOV     AL, BYTE PTR OverScan[i]   ;; Get 1 byte
         MOV     ES:[DI+j], AL              ;; Restore the byte
i        =       i + 1
j        =       j + 1
         ENDIF                    ;; of IF      j  LT  Width
i        =       (i + 3) AND 0FFFCH    ;; Round up to multiple of 4
         ENDM                     ;; of RestOneRow MACRO

SavePixels MACRO Width:REQ, Height:REQ, Increment:REQ
i        =       0
Limit    =       (((Width) + 3) AND 0FFFCH) / 4
ReqdSize TEXTEQU %(Limit * Height)
         IFNDEF  OverScan
%       .ERR     <Define OverScan[ReqdSize]:DWORD>
         EXITM
         ENDIF
         IF      ( TYPE OverScan  NE  4)  OR  (ReqdSize  NE  LENGTHOF OverScan)
%       .ERR     <Define OverScan[ReqdSize]:DWORD>
         EXITM
         ENDIF
         REPEAT  Height
         ADD     DI, Increment
         SaveOneRow
         ENDM                     ;; of REPEAT Height
         ENDM                     ;; of MACRO

RestPixels MACRO Width:REQ, Height:REQ, Increment:REQ
i        =       0
Limit    =       (((Width) + 3) AND 0FFFCH) / 4
ReqdSize TEXTEQU %(Limit * Height)
         IFNDEF  OverScan         ;; If variable not defined
         EXITM                    ;; Avoid further error messages
         ENDIF
         IF      ( TYPE OverScan  NE  4)  OR  (ReqdSize  NE  LENGTHOF OverScan)
         EXITM
         ENDIF
         REPEAT  Height
         ADD     DI, Increment
         RestOneRow (Width)
         ENDM                     ;; of REPEAT Height
         ENDM                     ;; of MACRO

SavePix2X MACRO Width:REQ, Height:REQ, Increment:REQ
i        =       0
Limit    =       (((Width) * 2 + 3) AND 0FFFCH) / 4
ReqdSize TEXTEQU %(Limit * Height)
         IFNDEF  OverScan
%       .ERR     <Define OverScan[ReqdSize]:DWORD>
         EXITM
         ENDIF
         IF      ( TYPE OverScan  NE  4)  OR  (ReqdSize  NE  LENGTHOF OverScan)
%       .ERR     <Define OverScan[ReqdSize]:DWORD>
         EXITM
         ENDIF
         REPEAT  Height
         ADD     DI, Increment
         ADD     DI, Increment
         SaveOneRow
         ENDM                     ;; of REPEAT Height
         ENDM                     ;; of MACRO

RestPix2X MACRO Width:REQ, Height:REQ, Increment:REQ
i        =       0
Limit    =       (((Width) * 2 + 3) AND 0FFFCH) / 4
ReqdSize TEXTEQU %(Limit * Height)
         IFNDEF  OverScan         ;; If variable not defined
         EXITM                    ;; Avoid further error messages
         ENDIF
         IF      ( TYPE OverScan  NE  4)  OR  (ReqdSize  NE  LENGTHOF OverScan)
         EXITM
         ENDIF
         PUSH    BX               ;; Save register
         MOV     BX, Increment    ;; Initialize index register
j        =       0
         REPEAT  Limit            ;; Restore row #2 from row #1
         MOV     EAX, ES:[DI+j]   ;; Get 4 uncorrupted bytes
         MOV     ES:[DI+BX+j], EAX;; Restore the bytes
j        =       j + 4
         ENDM                     ;; of REPEAT Limit
         REPEAT  Height - 1
         ADD     DI, BX
         ADD     DI, BX
j        =       0
         REPEAT  Limit
         MOV     EAX, OverScan[i] ;; Get 4 saved bytes
         MOV     ES:[DI+j], EAX   ;; Restore the bytes to row #1
         MOV     ES:[DI+BX+j], EAX;; Restore the bytes to row #2
i        =       i + 4
j        =       j + 4
         ENDM                     ;; of REPEAT Limit
         ENDM                     ;; of REPEAT Height
         ADD     DI, BX           ;; Point to last row
         ADD     DI, BX
         RestOneRow ((Width) * 2)
         POP     BX               ;; Restore register
         ENDM                     ;; of MACRO
