;=========================================================================
; SCALE1.ASM By John A. Slagel, jas37876@uxa.cso.uiuc.edu.
; This is some code to do bitmap scaling on an 256 color bitmap.  This can
; be used to write to VGA memory or a virtual page in system memory by
; simply changing the ScreenPtr and BytesPerLine variables. It can scale a
; bitmap of any size down to 2 pixels wide, or up to thousands of pixels
; wide.  It performs complete clipping, with only a small constant amount
; of time to clip, no matter how huge the image is.  The inner scanline
; loop has been optimized for no memory accesses, except to read or
; write a pixel.  This uses MASM 5.1 features, and can be compiled in any
; memory model by changing the .MODEL line, but make sure that you always
; pass a far pointer to the bitmap data, regardless of memory model.
; C-callable as:
;   void SCALE1( int X, int Y, int DW, int DY,
;                     int SW, int SH, void far * Bitmap );
; X,Y   are the upper left-hand coordinates of where to draw the bitmap.
; DW,DH are the width and height of the SCALEed bitmap
; SW,SH are the width and height of the source bitmap.
; Bitmap is a pointer to the bitmap bits.
;
;==========================================================================
.MODEL LARGE, C
.386

.DATA
; Make these externs if you want your program to change them.
ClipLt          DW  20                  ; Left clipping boundry
ClipRt          DW  300                 ; Right clipping boundry
ClipTp          DW  20                  ; Top clipping boundry
ClipBt          DW  190                 ; Bottom clipping boundry
ScreenPtr       DD  0A0000000h          ; Where to draw in memory.
BytesPerLine    DW  320                 ; Bytes per scanline

.CODE

SCALE1  PROC FAR USES DS DI SI, DestX:WORD, DestY:WORD, DestWidth:WORD, DestHeight:WORD, SourceWidth:WORD, SourceHeight:WORD, Bitmap:FAR PTR
LOCAL   DecisionX:WORD, DecisionY:WORD, ClippedWidth:WORD, ClippedHeight:WORD, SourceWidth2:WORD

        mov     ax, @DATA
        mov     ds, ax

        cmp     DestWidth, 2        ; If destination width is less than 2
        jl      Done                ;     then don't draw it.

        cmp     DestHeight, 2       ; If destination height is less than 2
        jl      Done                ;     then don't draw it.

        mov     ax, DestY           ; If it is completely below the
        cmp     ax, ClipBt          ; lower clip bondry,
        jg      Done                ;     then don't draw it.

        add     ax, DestHeight      ; If it is above clip boundries
        dec     ax                  ;     then don't draw it.
        cmp     ax, ClipTp
        jl      Done

        mov     ax, DestX           ; If it is to the right of the
        mov     cx, ClipRt          ; right clip boundry
        cmp     ax, ClipRt          ;     then don't draw it.
        jg      Done

        add     ax, DestWidth       ; If it is completely to the left
        dec     ax                  ; of the left clip boundry,
        cmp     ax, ClipLt          ;     then don't draw it.
        jl      Done

        lfs     si, Bitmap          ; Make FS:SI point to bitmap data

        mov     ax, DestWidth       ; ClippedWidth is initially set to
        mov     ClippedWidth, ax    ; the requested dest width.

        shl     ax,1                ; Initialize the X decision var
        neg     ax                  ; to be -2*DestWidth
        mov     DecisionX, ax       ;

        mov     ax, DestHeight      ; ClippedHeight is initially set to
        mov     ClippedHeight, ax   ; the requested dest size.

        shl     ax,1                ; Initialize the Y decision var
        neg     ax                  ; to be -2*DestHeight
        mov     DecisionY, ax       ;

        movsx   eax, ClipTp         ; If Y is below the top
        mov     edx, eax            ; clipping boundry, then we don't
        sub     dx, DestY           ; need to clip the top, so we can
        js      NoTopClip           ; jump over the clipping stuff.

        mov     DestY, ax           ; This block performs clipping on the
        sub     ClippedHeight, dx   ; top of the bitmap.  I have heavily
        movsx   ecx, SourceHeight   ; optimized this block to use only 4
        imul    ecx, edx            ; 32-bit registers, so I'm not even
        mov     eax, ecx            ; gonna try to explain what it's doing.
        mov     edx, 0              ; But I can tell you what results from
        movsx   ebx, DestHeight     ; this:  The DecisionY var is updated
        idiv    ebx                 ; to start at the right clipped row.
        movsx   edx, SourceWidth    ; Y is moved to the top clip
        imul    edx, eax            ; boundry. ClippedHeight is lowered since
        add     si, dx              ; we won't be drawing all the requested
        imul    eax, ebx            ; rows.  SI is changed to point over
        sub     ecx, eax            ; the bitmap data that is clipped off.
        sub     ecx, ebx            ;
        shl     ecx, 1              ;
        mov     DecisionY, cx       ; <end of top clipping block >

NoTopClip:
        mov     ax, DestY           ; If the bitmap doesn't extend over the
        add     ax, ClippedHeight   ; bottom clipping boundry, then we
        dec     ax                  ; don't need to clip the bottom, so we
        cmp     ax, ClipBt          ; can jump over the bottom clip code.
        jle     NoBottomClip        ;

        mov     ax, ClipBt          ; Clip off the bottom by reducing the
        sub     ax, DestY           ; ClippedHeight so that the bitmap won't
        inc     ax                  ; extend over the lower clipping
        mov     ClippedHeight, ax   ; boundry.

NoBottomClip:
        movsx   eax, ClipLt         ; If X is to the left of the
        mov     edx, eax            ; top clipping boundry, then we don't
        sub     dx, DestX           ; need to clip the left, so we can
        js      NoLeftClip          ; jump over the clipping stuff.

        mov     DestX, ax           ; This block performs clipping on the
        sub     ClippedWidth, dx    ; left of the bitmap.  I have heavily
        movsx   ecx, SourceWidth    ; optimized this block to use only 4
        imul    ecx, edx            ; 32-bit registers, so I'm not even
        mov     eax, ecx            ; gonna try to explain what it's doing.
        mov     edx, 0              ; But I can tell you what results from
        movsx   ebx, DestWidth      ; this:  The DecisionX var is updated
        idiv    ebx                 ; to start at the right clipped column.
        add     si, ax              ; X is moved to the left clip
        imul    eax, ebx            ; boundry. ClippedWidth is reduced since
        sub     ecx, eax            ; we won't be drawing all the requested
        sub     ecx, ebx            ; cols.  SI is changed to point over
        shl     ecx, 1              ; the bitmap data that is clipped off.
        mov     DecisionX, cx       ; <end of left clipping block >

NoLeftClip:
        mov     ax, DestX           ; If the bitmap doesn't extend over the
        add     ax, ClippedWidth    ; right clipping boundry, then we
        dec     ax                  ; don't need to clip the right, so we
        cmp     ax, ClipRt          ; can jump over the right clip code.
        jle     NoClipRight         ;

        mov     ax, ClipRt          ; Clip off the right by reducing the
        sub     ax, DestX           ; ClippedWidth so that the bitmap won't
        inc     ax                  ; extend over the right clipping
        mov     ClippedWidth, ax    ; boundry.

        ;Calculate starting video address
NoClipRight:
        les     di, ScreenPtr       ; Make ES:DI point to the dest memory.
        mov     ax, DestY           ; Calculated as:
        imul    ax, BytesPerLine ;
        add     di, ax              ; DI=DI+Y*BytesPerScanLine+X
        add     di, DestX           ; ES:DI -> Video memory

        ; Multiply all the widths, etc by 2 to reduce inner loop calcs.
        shl     DestWidth,1         ; Dest width *= 2
        shl     SourceHeight,1      ; SourceHeight *= 2
        shl     DestHeight,1        ; DestHeight *= 2
        mov     ax, SourceWidth
        shl     ax, 1
        mov     SourceWidth2, ax    ; Source Width *= 2

        ALIGN   4                   ; Since this point gets jumped to a lot,
                                    ; make sure that it is DWORD aligned.
RowLoop:
        push    si                  ; Save the starting source index
        push    di                  ; Save the starting dest index
        push    bp                  ; Save the current base pointer

        mov     bx, DecisionX       ; Use BX for X decision variable
        mov     cx, ClippedWidth    ; Use CX for column counter
        mov     dx, SourceWidth2    ; Use DX for source width * 2
        mov     bp, DestWidth       ; Use GS for dest width * 2
        mov     al, fs:[si]         ; Get the first source pixel

        ; From this point to DoneWithScanLine is the critical inner-loop.
        ALIGN   4                   ; Common jump point... align for speed.
ScanLineLoop:
        stosb                       ; Draw a pixel
        dec     cx                  ; Decrement width counter
        jz      DoneWithScanLine    ; See if we're done with this scan line
        add     bx, dx              ; Increment the decision variable
        js      ScanLineLoop        ; Do we increment the source pixel?

IncSourceCol:
        inc     si                  ; Go on to next source pixel
        sub     bx, bp              ; Decrement the decision variable
        jns     IncSourceCol        ; See if we need to skip another source pixel
        mov     al, fs:[si]         ; Get the next source pixel
        jmp     ScanLineLoop        ; Start drawing this pixel

DoneWithScanLine:
        pop     bp                  ; Restore BP to access variables
        pop     di                  ; Restore DI to top row of screen
        pop     si                  ; Restore SI to top row of source bits

        add     di, BytesPerLine
        mov     bx, DecisionY       ; Use BX for the Y decision variable
        add     bx, SourceHeight    ; Increment the Y decision variable
        js      NextRow             ; Jump if we're in the same source row.

IncSourceRow:
        add     si, SourceWidth     ; Move to next source row
        sub     bx, DestHeight      ; Decrement Y decision variable
        jns     IncSourceRow        ; See if we skip another source row

NextRow:
        mov     DecisionY, bx       ; Free up BX for RowLoop
        dec     ClippedHeight       ; If we're not at last row
        jnz     RowLoop             ;    then do another row
Done:
        ret                         ; We're done!

SCALE1  ENDP

        END


