;*DDK*************************************************************************/
;
; COPYRIGHT    Copyright (C) 1995 IBM Corporation
;
;    The following IBM OS/2 WARP source code is provided to you solely for
;    the purpose of assisting you in your development of OS/2 WARP device
;    drivers. You may use this code in accordance with the IBM License
;    Agreement provided in the IBM Device Driver Source Kit for OS/2. This
;    Copyright statement may not be removed.;
;*****************************************************************************/
ifdef   DCAF
        PAGE     55,132
        TITLE    Compress
        SUBTITLE Header

;/*****************************************************************************
;*
;* SOURCE FILE NAME = COMPRESS.ASM
;*
;* DESCRIPTIVE NAME = DCAF compression functions.
;*
;*
;* VERSION      V2.0
;*
;* DATE         06/01/92
;*
;* DESCRIPTION
;*
;*
;* FUNCTIONS   CompressScreenBits
;*             compress_rect
;*             compress_row_4pl_4pl
;*             compress_row_4pl_4
;*
;* NOTES
;*
;*    Data is passed between the drivers in run-length encoded form.
;*
;*    The algorithm is a modification of Golomb run-length encoding
;*    (Golomb, "SW Run-length encoding", IEEE Transactions on
;*    Information Theory, IT-12 1966, pp 399-401).
;*
;*
;*    The format of the compressed data is:
;*
;*    PACKET HEADER
;*
;*    RECTANGLE 1 HEADER
;*    RECTANGLE 1 DATA
;*
;*    RECTANGLE 2 HEADER
;*    RECTANGLE 2 DATA
;*    .    .       .
;*    .    .       .
;*    RECTANGLE n HEADER
;*    RECTANGLE n DATA
;*
;*
;*    Definitions of the data structures are:
;*
;*    1) PACKET HEADER
;*
;*    dd   total_data_packet_length (including header)
;*    dw   data_format
;*
;*
;*    2) RECTANGLE HEADER
;*
;*    dw   xLeft
;*    dw   yBottom
;*    dw   xRight
;*    dw   yTop
;*
;*    3) RECTANGLE DATA
;*
;*    The rectangle data is split into individual rows.
;*    Each row is split into run-length encoded blocks("cells"), each of
;*    which comprises a length field followed by one or more data fields.
;*
;*    If the length field contains a positive value then the following
;*    single data field is repeated (length) times.
;*
;*    If the length field contains a negative value (most significant bit set)
;*    then (length - m.s. bit) fields of non-repeating data follow.
;*
;*    If the length field is zero and the following field is non-zero, the
;*    non-zero field is a count of the number of times that the single
;*    previous row is repeated. This will only appear as the first cell in
;*    a row, and only after there has been at least one row of data.
;*
;*    If the length field is zero and the following field is zero,
;*    the next (i.e. third) field is a count of the number of times that
;*    the previous pair of rows are repeated. This will only appear as the
;*    first cell in a row, and only after there have been at least two
;*    rows of data.
;*
;*
;*    The size of both the length and data fields varies according to
;*    the format of the data being transmitted (as specified by the
;*    data_format field in the packet header):
;*
;*    - 4bpp:        field size is one byte  (8 bits)
;*    - 8bpp, 16bpp: field size is two bytes (16 bits)
;*
;*
;*    A picture paints a thousand words but takes a thousand times longer
;*    to write!
;*
;* STRUCTURES   NONE
;*
;* EXTERNAL REFERENCES
;*
;*              NONE
;*
;* EXTERNAL FUNCTIONS
;*
;*              NONE
;*
;* CHANGE ACTIVITY =
;*   DATE      FLAG        APAR   CHANGE DESCRIPTION
;*   --------  ----------  -----  --------------------------------------
;*   mm/dd/yy  @Vr.mpppxx  xxxxx  xxxxxxx
;*   06/01/92                     Created for DCAF 1.3
;*   08/01/92                     Converted to 32-bit for DCAF 2.0
;*
;*****************************************************************************/

        .xlist
        .386p

REGION_TYPE             equ     0200h   ;@GPL - define it here to save
                                        ;       requiring pmgre component
INCL_ERRORS             equ     1
INCL_DDICOMFLAGS        equ     1
INCL_GPIREGIONS         equ     1
INCL_GRE_CLIP           equ     1
INCL_GRE_DCAF           equ     1
INCL_GRE_REGIONS        equ     1
INCL_GRE_SCREEN         equ     1
DINCL_ENABLE            equ     1
DINCL_BITMAP            equ     1

        include pmgre.inc
        include driver.inc
        include extern.inc
        include assert.mac
        include protos.inc
        include egafam.inc

;/*
;** Include DCAF macros
;*/
        include dcafmac.inc
        include compmac.inc

        .list



        .MODEL  FLAT
        ASSUME  CS:FLAT, SS:FLAT, DS:FLAT, ES:FLAT
        .DATA

;/*
;** The following table defines the formats that can be requested.
;** This depends upon the internal bits per pel and data format
;** (packed/planar).
;**
;** Each entry in the table consists of a pair of bytes. The first (low)
;** byte represents the internal (screen) data format. The second (high)
;** byte represents the external (requested) data format.
;**
;** When GetScreenBits is called, a word consisting of the internal and
;** external formats is formed and the formats_table is scanned for
;** a match.
;**
;** If no match is found then the requested format is invalid.
;** If a match is found then the requested format is valid, and the index
;** into the table tells us which conversion routine should be used.
;**
;** The order of this table MUST MATCH THE GSB_ROW_CONV_TABLE below.
;*/
public  gsb_formats_table
gsb_formats_table   label   byte

;/*
;** The GSB_FORMAT_FLAGS must all fit in a byte for the following table
;** to be valid.
;*/
        .errnz  GSB_FORMAT_FLAGS gt 0FFh

;/*
;**                Internal (screen) format         Requested format
;*/
        db      GSB_OPT_4BPP or GSB_OPT_PLANAR,  GSB_OPT_4BPP or GSB_OPT_LINEAR
        db      GSB_OPT_4BPP or GSB_OPT_PLANAR,  GSB_OPT_4BPP or GSB_OPT_PLANAR

FORMATS_TABLE_LENGTH equ        $-gsb_formats_table


;/*
;** gsb_row_conv_table contains the addresses of the routines to perform
;** the conversion/compression for each combination of internal formats.
;** The order of this table MUST MATCH THE GSB_FORMATS_TABLE above.
;*/
public  gsb_row_conv_table
gsb_row_conv_table      label   byte
        dd      compress_row_4pl_4
        dd      compress_row_4pl_4pl

ROW_CONV_TABLE_LENGTH   equ     $-gsb_row_conv_table

;/*
;** Check that the two tables have the same number of entries
;*/
        .erre   (ROW_CONV_TABLE_LENGTH/2) eq (FORMATS_TABLE_LENGTH)


EXTERNDEF       pelbuffer:BYTE

;/*
;** Declare the control block used by DCAF.
;*/

public  gsbdev
gsbdev  GSB_DEV_STRUCT  < >


;/*
;** Declare external procedures.
;*/

EXTERNDEF  GreEntry5  :DWORD



        .CODE

;/*************************************************************************
;** Function:
;** CompressScreenBits
;**
;** Overview:
;** Compresses the requested area of the screen (PRECTL or HRGN) into
;** the buffer supplied.
;**
;** Assumes device specific error checking / semaphore access etc has been
;** performed by calling routine.
;** Assumes buffer length is <= 64K
;**
;** Parameters:
;**
;**
;** Entry:
;**   None
;** Returns:
;**   EAX = 1  - buffer not full
;**         2  - buffer full
;**         3  - buffer empty (NULL region passed in)
;** Error Returns:
;**   EAX = FALSE (0)
;**   Error logged
;** Registers Preserved:
;**   32 bit  ESI,EDI,EBX,EBP,DS
;** Registers Destroyed:
;**   32 bit  EAX,ECX,EDX,FLAGS
;** History:
;**
;** Wed 14-May-1992       -by-    Andy Rogers
;** Wrote it.
;**
;**********************************************************************/
ALIGN 4
CompressScreenBits PROC SYSCALL USES edi esi ebx,
                hdc :DWORD,         ; Direct DC handle
                hrgnApp :DWORD,     ; Handle of region, or pointer to rectl
                pDest :DWORD,       ; Pointer to destination buffer
                ulBufferLen :DWORD, ; Buffer length
                flCmd :DWORD,       ; Flags (GSB_... region/rect,format of data)
                hddc  :DWORD

NRECTS  equ     10                  ; Number of rectls in frame variable

LOCAL   rclBound :RECTL,
        arclBuffer[NRECTS] :RECTL,
        Control :RGNRECT,
        pNextFreeDestByte :PBYTE,
        fOutputBufferFull :DWORD,
        usFormatCode :DWORD,
        cRemainingRectsInBuffer :DWORD,
        cCompressedRectsInBuffer :DWORD,
        prclCurrentRect :DWORD,
        fMoreEngineRects :DWORD,
        pfnCompressFunction :DWORD


;/*
;** Check that the format is valid.
;** This is done by creating a word containing the internal
;** (screen) format and the external (requested) format.
;** The gsb_formats_table is then scanned for a match.
;** If matched, the format is valid. Otherwise it is invalid.
;*/
        mov    eax, flCmd

;/*
;** eax (actually al) contains the requested format flags.
;** Mask out the format flags (as opposed to option flags).
;** Move these to ah.
;*/
        and     al, GSB_FORMAT_FLAGS
        mov     ah,al

;/*
;** Now merge in the internal format
;*/
        mov     al,gsbdev.gsb_fbScreenFormatFlags

;/*
;** eax now contains the combined internal/external format word.
;** Save this code on the stack
;*/
        mov     usFormatCode, eax

;/*
;** Search for this word in the formats table.
;*/

;/*
;** We need edi->start of table
;*/
        lea     edi,gsb_formats_table

;/*
;** Load ecx with the table length in words
;*/
        mov     ecx,FORMATS_TABLE_LENGTH/2

;/*
;** Get format code back
;*/
        mov     eax, usFormatCode

;/*
;** Search the table
;*/
        repne   scasw

;/*
;** Jump to error handler if format not found
;*/
        jne     csb_error_invalid_option

;/*
;** Adjust ecx so that it contains the index of the found entry
;*/
        inc     ecx     ; ecx is always decremented, so inc to get match
        sub     ecx,FORMATS_TABLE_LENGTH/2
        neg     ecx

;/*
;** Check that ecx is within range
;*/
        assert  ecx,L,FORMATS_TABLE_LENGTH/2

;/*
;** Get a pointer to the compression function
;*/
        mov     ebx, ecx
        mov     ebx, dword ptr gsb_row_conv_table[ebx*4]   ; get 32 bit addr

        mov     pfnCompressFunction, ebx

;/*
;** Initialize variables.
;**
;** Initialize the output buffer full flag to FALSE.
;*/
        xor     eax,eax
        mov     fOutputBufferFull,eax

;/*
;** Initialize our destination pointer to the start of the
;** supplied buffer.
;*/
        mov     eax, pDest
        mov     pNextFreeDestByte, eax

;/*
;** Now branch according to whether we have been passed a rectangle
;** or a region.
;*/
        test    flCmd,GSB_OPT_HRGN
        jnz     csb_region_parm         ;Its a region, let pmgre check handle

;/*
;** Rectl: Copy the rectangle from the caller's data area into rclBound,
;** making exclusive while doing so.
;**
;** Load up destination pointer
;*/
        lea     edi,rclBound

;/*
;** Load up source pointer
;*/
        mov     esi,hrgnApp             ; We need esi->supplied rectl
        mov     ecx,(size RECTL)/4      ; Load length (dwords) into ecx
        rep     movsd                   ; Move those dwords!

;/*
;** Make the rectangle exclusive
;*/
        inc     rclBound.rcl_xRight
        inc     rclBound.rcl_yTop

;/*
;** Now copy the same rect into the rect buffer
;*/
        sub     esi,(size RECTL)        ; Restore source pointer
        lea     edi,arclBuffer          ; es:di->rect buffer
        mov     ecx,(size RECTL)/4
        rep     movsd

;/*
;** Make the rectangle exclusive
;*/
        inc     arclBuffer.rcl_xRight
        inc     arclBuffer.rcl_yTop

;/*
;** Check that the rectangle is well-ordered
;** i.e. that (xLeft < xRight), (yBottom < yTop)
;** If rectangle is NULL then skip to exit.
;*/
        mov     eax,rclBound.rcl_xLeft
        cmp     eax,rclBound.rcl_xRight
        jg      csb_error_too_big
        je      null_region_passed_in

        mov     eax,rclBound.rcl_yBottom
        cmp     eax,rclBound.rcl_yTop
        jg      csb_error_too_big
        je      null_region_passed_in

;/*
;** Update the count of rectangles in our buffer
;*/
        mov     ecx, 1
        mov     cRemainingRectsInBuffer,ecx

;/*
;** Clear flag to indicate no rects in engine
;*/
        mov     fMoreEngineRects,FALSE

;/*
;** Initialise prclCurrentRect to the start of our rect buffer
;*/
        lea     ebx,arclBuffer
        mov     prclCurrentRect,ebx

;/*
;** Skip forwards over region code
;*/
        jmp     csb_check_device_limits

;/*
;** Hrgn: Get the bounding box for the app's region into rclBound
;**
;** public csb_region_parm
;*/
csb_region_parm:

;/*
;** Call back to the engine to get the region bounding box
;*/
        INVOKE  PFNDefGetRegionBox PTR pfnDefGetRegionBox,
                hdc,
                hrgnApp,
                ADDR rclBound,
                hddc,
                NGreGetRegionBox

;/*
;** Check whether an error occured (ax zero)
;*/
        or      eax,eax                 ; Set flags
        jz      csb_exit_ax             ; Jump if zero

;/*
;** Prime our rect buffer with the first set of rectangles
;*/
        fill_rect_buffer  null_region_passed_in

;/*
;** rclBound contains the exclusive bounding box of the screen area to
;** be saved. Ensure it does not exceed device limits.
;*/
csb_check_device_limits:

        mov     eax,gsbdev.gsb_cxScreenWidth  ; Get screen width
        cmp     rclBound.rcl_xRight,eax       ; Check right hand side
        ja      csb_error_too_big
        mov     eax,gsbdev.gsb_cyScreenHeight ; Get screen height
        cmp     rclBound.rcl_yTop,eax         ; Check top
        ja      csb_error_too_big

        jmp     short csb_write_format

;/*
;** Put error handlers here for accessibility.
;*/

csb_error_too_big:
        mov     eax,PMERR_INV_IMAGE_DIMENSION
        jmp     short csb_log_error_in_ax

csb_error_invalid_option:
        mov     eax,PMERR_INV_FORMAT_CONTROL
        jmp     short csb_log_error_in_ax

csb_log_error_in_ax:
        save_error_code

csb_exit_error_logged:
        xor     eax,eax
        jmp     csb_exit_ax

;/*
;** Return of the main code path!
;*/

;public  csb_write_format
csb_write_format:
;/*
;** Skip over the length of data word and write the format word.
;**
;** Check that there is room for the header!
;*/
        cmp     ulBufferLen,size PACKETHDR
        jb      csb_interrupted_exit

        mov     edi,pNextFreeDestByte ; This currently points to start of buffer
        add     edi,PACKETHDR.phd_format; Move to the Format field
        mov     eax,flCmd               ; Read requested flags
        and     eax,GSB_FORMAT_FLAGS    ; Extract just the format flags
        stosw                           ; Store format and update dest pointer

;/*
;** Save destination pointer
;*/
        mov     pNextFreeDestByte,edi

;/*
;** Update the free bytes in the output buffer
;** This is the only update to ulBufferLen that uses DWORD rather than
;** WORD values. ulBufferLen <= 64k so after this subtraction the
;** length will always fit in a single word.
;*/
        sub     ulBufferLen, size PACKETHDR

;/*
;** Exclude the pointer from the rectangle specified in rclBound
;*/
        exclude_pointer rclBound

;/*
;** Start the main loop.
;*/
csb_main_loop:
        cmp     cRemainingRectsInBuffer,0
        jne     have_rects_left

;/*
;** There are no more rectangles in our buffer.
;** If there are more in the engine then we will refresh it,
;** otherwise we are all done!
;*/
        cmp     fMoreEngineRects,TRUE
        jne     csb_all_rects_done

;/*
;** We need to remove the rectangles that have been compressed from
;** the region
;*/
        remove_compressed_rects

;/*
;** Now fill the rect buffer with fresh rectangles
;*/
        fill_rect_buffer   csb_all_rects_done

have_rects_left:
        ; Make [ebx] -> current rect
        mov     ebx,prclCurrentRect
        ASSUME  EBX:PTR RECTL

;/*
;** Adjust the current rectangle coordinates if necessary.
;**
;** if the source or destination format is planar
;** : adjust xLeft and xRight coords to be multiples of 8
;** else if the source or destination format is 4bpp or 8bpp
;** : adjust xLeft and xRight coords to be multiples of 2
;** endif
;**
;** This is so we do not have to worry about the masking associated
;** with compressing/decompressing partial bytes.
;** 8bpp is aligned on even boundaries because we transmit pels
;** in 16-bit data fields i.e. two pels per data field.
;*/
        test    usFormatCode,(GSB_OPT_PLANAR SHL 8) OR GSB_OPT_PLANAR
        jz      srcdst_not_planar

;/*
;** Src or dest is planar - adjust coords to be multiples of 8.
;*/
        and     [ebx].rcl_xLeft,0FFF8h
        add     [ebx].rcl_xRight,7
        and     [ebx].rcl_xRight,0FFF8h
        jmp     short coords_adjusted

srcdst_not_planar:
        test    usFormatCode,(GSB_OPT_4BPP shl 8) or GSB_OPT_4BPP or \
                             (GSB_OPT_8BPP shl 8) or GSB_OPT_8BPP
        jz      coords_adjusted

;/*
;** Src or dest is 4bpp or 8bpp - adjust coords to be multiples of 2.
;*/
        and     [ebx].rcl_xLeft,0FFFEh
        inc     [ebx].rcl_xRight
        and     [ebx].rcl_xRight,0FFFEh

;public coords_adjusted
coords_adjusted:

ifdef FIREWALLS
        ; Check for null rectangles.
        mov     ecx,[ebx].rcl_yTop
        cmp     ecx,[ebx].rcl_yBottom
        jg      @F
        rip     text,<NULL rectangle in Y>
        int     3
@@:
        mov     ecx,[ebx].rcl_xRight
        cmp     ecx,[ebx].rcl_xLeft
        jg      @F
        rip     text,<NULL rectangle in X>
        int     3
@@:
endif

;/*
;** Compress the current rectangle!
;** Load up the destination pointer, buffer length and compression
;** function
;*/
        mov     edi,pNextFreeDestByte
        mov     ecx,ulBufferLen

        INVOKE  compress_rect, prclCurrentRect, pfnCompressFunction

;/*
;** Return code tells us whether the output buffer filled up.
;*/
        mov     fOutputBufferFull,eax

;/*
;** Update other variables.
;*/
        mov     pNextFreeDestByte,edi
        mov     ulBufferLen,ecx

;/*
;** Update counters.
;*/
        inc     cCompressedRectsInBuffer
        dec     cRemainingRectsInBuffer
        add     prclCurrentRect,SIZE RECTL

;/*
;** Check whether the output buffer is full.
;** If not, loop back to process next rect
;*/
        or      eax,eax
        jz      csb_main_loop

csb_all_rects_done:
;/*
;** If region supplied (rather than rectangle) then update it
;** to reflect the compressed area.
;*/
        test    flCmd,GSB_OPT_HRGN
        jz      rect_supplied

;/*
;** See if we terminated because the output buffer was full.
;*/
        cmp     fOutputBufferFull,TRUE
        jne     output_buffer_not_full

;/*
;** The output buffer is full. Remove the rects that we compressed
;** from the app region.
;*/
        remove_compressed_rects

        ; Exit.
        jmp     short   csb_interrupted_exit

output_buffer_not_full:
;/*
;** The output buffer is not full, which means that we must have
;** successfully returned all of the requested region. We therefore
;** just have to make the apps region NULL.
;*/
        return_null_region
        jmp     short   csb_uninterrupted_exit

rect_supplied:
;/*
;** We need to update the supplied rectangle to reflect the
;** data compressed.
;** Only the rcl_yTop needs to be returned - all other edges will
;** have remained the same.
;** If the entire area was compressed we need to return a NULL
;** rectangle
;*/
        cmp     fOutputBufferFull,TRUE
        je      adjust_rectangle

;/*
;** Return a NULL rectangle - set yBottom = yTop + 1
;*/
        mov     edi,hrgnApp             ; [edi]-> app's rect
        ASSUME  EDI:PTR RECTL
        mov     eax, [edi].rcl_yTop
        inc     eax
        mov     [edi].rcl_yBottom,eax
        jmp     short csb_uninterrupted_exit

adjust_rectangle:
;/*
;** The rectangle stored in arclBuffer has been updated to contain
;** the area compressed. (only yBottom will have changed)
;** We need to update the rectangle passed in to contain the
;** area NOT compressed
;*/
        mov     edi,hrgnApp                     ; [edi]-> app's rect
        ASSUME  EDI:PTR RECTL
        mov     eax,arclBuffer.rcl_yBottom      ; Fetch yBottom
        dec     eax
        mov     [edi].rcl_yTop,eax    ; Update the passed in rectangle

;/*
;** Now jump to the appropriate exit point.
;*/
        jmp     short   csb_interrupted_exit

csb_bad_exit:
        xor     eax,eax                 ; Error return code.
        jmp     short csb_exit_ax

null_region_passed_in:
;/*
;** A null region was passed in - nothing is in the buffer
;*/
        mov     eax, 3
        jmp     short  csb_exit_ax

csb_interrupted_exit:
        mov     eax,2                   ; Good but uncompleted return code.
        jmp     short @F

csb_uninterrupted_exit:
        mov     eax,1                   ; Good and completed return code.
@@:
;/*
;** Before we exit, write out the length of data stored in the buffer.
;** Calculate this by subtracting the buffer start from the current
;** pointer.
;*/
        mov     ebx,pNextFreeDestByte
        sub     ebx,pDest

;/*
;** Write the total length into the first dword of the compressed
;** data.
;*/
        mov     edi,pDest     ; Fetch pointer to buffer.
        ASSUME  EDI:PTR DWORD
        mov     [edi],ebx     ; Write out length.

csb_exit_ax:
;/*
;** Unexclude the pointer (without losing the return code in ax).
;*/
        push    eax
        unexclude_pointer
        pop     eax
        ret

CompressScreenBits ENDP


;/**********************************************************************
;**
;** Function:
;**   compress_rect
;**
;** Overview:
;**   Compresses the supplied rectangle into the destination buffer.
;**
;** Entry:
;**   edi      Pointer to next free byte in output buffer (dest)
;**   ecx      Count of bytes available in output buffer
;**
;** Exit:
;**   edi      Updated ptr to next free byte in output buffer (dest)
;**   ecx      Updated count of bytes available in output buffer
;**
;** Updated:
;**   *prclCurrentRect Updated to contain the rectangle area that was
;**                    compressed. In practice this will only be modified
;**                    if the output buffer fills up and a partial rectangle
;**                    is returned.
;**
;** Returns:
;**   eax = 0 indicates output buffer not filled i.e. whole rect returned
;**   eax = 1 indicates output buffer full i.e. only partial rect returned
;**
;**********************************************************************/
ALIGN   4
compress_rect           PROC SYSCALL,
        prclCurrentRect :DWORD,
        pfnCompressRow  :DWORD

    LOCAL   cbBytesPerSrcRow:DWORD,
        cbBytesPerSrcPlane:DWORD,
        cbWorstCaseBytesPerDstRow:DWORD,
        pRectHeader:DWORD,
        yCurrentRow:DWORD,
        yStartRow:DWORD,
        yEndRow:DWORD,
        cbBytesPerScanline:DWORD,
        cbFreeBytesInDestBuffer:DWORD,
        fFirstTime:DWORD,
        fFirstTimePair:DWORD,
        pCurrentSrcPos:DWORD,
        pNextFreeDstPos:DWORD,
        pDestAtStartOfLine:DWORD,
        fOutputBufferFull:DWORD,
        cPlane:DWORD


;/*
;** Store the number of bytes available in the output buffer
;*/
        mov     cbFreeBytesInDestBuffer, ecx

;/*
;** Initialise yCurrentRow to yTop of the supplied rectangle. Note that
;** the rectangle is exclusive, so this value will be one greater than
;** the first row  we want to compress.
;*/


;/*
;** Calculate the flipped yCurrentRow (i.e. relative to top of screen).
;** and the flipped yStartRow and yEndRow
;** Note that the top is exclusive, but the flipping operation
;** will give us the number of rows from the top of the screen.
;*/
        lea     ebx,gsbdev
        ASSUME  EBX:PTR GSB_DEV_STRUCT
        mov     eax,[ebx].gsb_cyScreenHeight
        mov     edx,eax

;/*
;** Get pointer to the rectangle. The rectangle will always be on
;** the stack, which is why we only need a near pointer.
;*/
        mov     ebx,prclCurrentRect
        ASSUME  EBX:PTR RECTL

        sub     eax,[ebx].rcl_yBottom
        mov     yEndRow,eax
        mov     eax,edx
        sub     eax,[ebx].rcl_yTop
        mov     yCurrentRow,eax
        mov     yStartRow,eax


;/*
;** Calculate the width of a source row (in pels).
;**
;** Fetch the rectangle right-hand coordinate.
;*/
        mov     eax,[ebx].rcl_xRight

;/*
;** Subtract the rectangle left-hand coordinate to give us the
;** rectangle width in pels.
;*/
        sub     eax,[ebx].rcl_xLeft

;/*
;** Convert the source row width from pels into bytes.
;**
;** Bpp is 4 - byte count is half the pel count.
;*/
        shr     eax,1

;/*
;** Store the bytes per source row.
;*/
        mov     cbBytesPerSrcRow,eax

;/*
;** Calculate the bytes per src plane (rounding up)
;*/
        shr     eax, 2
        mov     cbBytesPerSrcPlane, eax

;/*
;** Store the bytes per scan line.
;** Constant for device.
;*/
        lea     ebx,gsbdev
        ASSUME  EBX:PTR GSB_DEV_STRUCT
        mov     eax, [ebx].gsb_cbScanLineDelta
        mov     cbBytesPerScanline,eax

;/*
;** Calculate the worst-case bytes per destination row.
;*/
        mov     eax, cbBytesPerSrcRow
        add     eax,WORST_CASE_ROW_EXPANSION
        mov     cbWorstCaseBytesPerDstRow,eax

;/*
;** Do an initial check to see whether it is worth starting to
;** compress the rectangle.
;**
;** Calculate the size needed for the rect header, plus a single
;** row. ax already contains the worst case row length.
;*/
        add     eax,size RECTS

;/*
;** Compare the size calculated with the free bytes available
;** in the destination buffer (currently in cx).
;*/
        cmp     eax,ecx
        jbe     buffer_size_ok

;/*
;** We can't pass back a single row of this rect!
;** Set fOutputBufferFull to TRUE.
;*/
        mov     fOutputBufferFull,TRUE

;/*
;** Update the supplied rectangle to NULL and exit.
;** This is a special case exit as the rectangle is not in the
;** buffer and can't be updated.
;*/
        mov     ebx,prclCurrentRect
        ASSUME  EBX:PTR RECTL
        mov     eax,[ebx].rcl_yTop
        mov     [ebx].rcl_yBottom, eax

        jmp     compress_rect_no_rects_exit

buffer_size_ok:
        ; Initialise fOutputBufferFull.
        mov     fOutputBufferFull,FALSE

;/*
;** Calculate the start address of the rectangle on the screen
;**
;** Start Address = Screen Start + (YStartRow * BytesPerScan) + (LeftX / 8)
;*/

        lea     ebx,gsbdev
        ASSUME  EBX:PTR GSB_DEV_STRUCT
        mov     ebx, [ebx].gsb_pScreenStart
        mov     eax, yStartRow
        mov     ecx, cbBytesPerScanline
        mul     ecx
        add     eax, ebx
        mov     ebx,prclCurrentRect
        ASSUME  EBX:PTR RECTL
        mov     ecx, [ebx].rcl_xLeft
        shr     ecx, 3
        add     eax, ecx
        mov     pCurrentSrcPos, eax

;/*
;** Save the current pointer to the destination buffer.
;** This is where we will fill in the rectangle header at the
;** end of the routine.
;*/
        mov     pRectHeader,edi

;/*
;** Move the destination pointer over the rectangle header.
;*/
        add     edi,size RECTS

;/*
;** Update the number of free bytes remaining in the dest buffer.
;*/
        sub     cbFreeBytesInDestBuffer, size RECTS

;/*
;** Load up the current start of line
;*/
        mov     esi, pCurrentSrcPos

;/*
;** Begin the main loop that processes each row of the rectangle.
;**
;** Here:  esi is the current source position
;**        edi is the next free destination position
;**
;*/
main_row_loop:
;/*
;** Set first time counters to true
;*/
        mov     eax,TRUE
        mov     fFirstTime,eax
        mov     fFirstTimePair,eax

;/*
;** Check whether the remaining space in the destination
;** buffer is enough for a worst-case row.
;*/
        mov     ecx,cbFreeBytesInDestBuffer
        cmp     ecx,cbWorstCaseBytesPerDstRow
        ja      row_will_fit

;/*
;** Indicate that the output buffer is full and exit.
;*/
        mov     fOutputBufferFull,TRUE
        jmp     compress_rect_exit

row_will_fit:
;/*
;** See if we are on the first line - if so we can't do duplicate
;** scanline (or scanline_pair) checking
;*/
        mov     eax,yCurrentRow
        sub     eax,yStartRow
        cmp     eax,1
        jl      no_scanline_pair

;/*
;** Check for duplicate scanlines.
;** Compare the current row with the previous row to see if they match.
;*/
dup_scanline_loop:
;/*
;** We need esi->current row
;** edi->current row-1
;*/

;/*
;** Save current positions
;*/
        mov     pCurrentSrcPos, esi
        mov     pNextFreeDstPos, edi

;/*
;** Copy the source to the destination
;*/
        mov     edi,esi

;/*
;** Now adjust edi to point to the previous row.
;*/
        mov     ebx,cbBytesPerScanline
        sub     edi,ebx

;/*
;** Set up the count
;*/
        mov     ecx,cbBytesPerSrcPlane

;/*
;** Do the compare
;*/
        compare_rows

;/*
;** Restore the current positions (source segment will not have changed)
;*/
        mov     esi, pCurrentSrcPos
        mov     edi, pNextFreeDstPos

;/*
;** Jump forward if rows are different
;*/
        jne     adjacent_scanlines_differ

;/*
;** We have a duplicate scanline!
;**
;** If this is the first time through then write a new cell
;** to the destination, else increment the count in the cell
;** that has previously been written.
;*/
        cmp     fFirstTime,TRUE
        jne     inc_scanline_count

;/*
;** Reset fFirstTime flag
;*/
        mov     fFirstTime,FALSE

;/*
;** Write out a duplicate scanline cell to the destination buffer.
;*/
        append_data_size <write_dup_scanline_cell >

        jmp     processed_dup_scanline

inc_scanline_count:
;/*
;** Increment the count in the cell that we have already written.
;*/
        append_data_size<inc_cell_count dup_scan, >

processed_dup_scanline:
;/*
;** Move to next source row. This will jump to the end of the loop
;** if the last row of the rectangle is passed.
;*/
        inc_source_row compress_rect_exit

;/*
;** Loop back to check for more duplicate scanlines.
;*/
        jmp     dup_scanline_loop


adjacent_scanlines_differ:
;/*
;** Check for duplicate scanline pairs.
;**
;**                  (row-2)    ababababababa
;**                  (row-1)    cdcdcdcdcdcdc
;** current row ---->(row)      ababababababa
;**                  (row+1)    cdcdcdcdcdcdc
;**
;** (row) must match (row-2), and (row+1) must match (row-1).
;**
;** As we are testing 2 rows behind and 1 row ahead of the current row,
;** we will only compare the rows if we are:
;**   - at least 2 rows into the rectangle
;**   - at least 1 row from the end of the rectangle.
;*/

;/*
;** Check that we are at least 2 rows into the rectangle.
;*/
        mov     eax,yCurrentRow
        sub     eax,yStartRow
        cmp     eax,2
        jl      no_scanline_pair

dup_scanline_pair_loop:
;/*
;** Check that we are not on the last row of the rectangle.
;*/
        mov     eax, yEndRow
        dec     eax
        cmp     eax, yCurrentRow
        je      no_scanline_pair

;/*
;** We can now proceed and check for a matching pair.
;** First compare (row) with (row-2).
;**
;** Save current positions
;*/
        mov     pCurrentSrcPos, esi
        mov     pNextFreeDstPos, edi

;/*
;** Copy the source to the destination
;*/
        mov     edi,esi

;/*
;** We need to move edi back by 2 scanlines.
;*/
        mov     ebx,cbBytesPerScanline
        shl     ebx,1
        sub     edi,ebx

;/*
;** Set up the count
;*/
        mov     ecx,cbBytesPerSrcPlane

;/*
;** Compare the rows.
;*/
        compare_rows

;/*
;** Restore the current positions
;** Restore the current positions (source segment will not have changed)
;*/
        mov     esi, pCurrentSrcPos
        mov    edi, pNextFreeDstPos

;/*
;** Jump forward if rows are different.
;*/
        jne     no_scanline_pair

;/*
;** If we get here, (row) matches (row-2).
;** Now check whether (row+1) matched (row-1).
;*/

;/*
;** Copy the source to the destination
;*/
        mov     edi,esi

;/*
;** Adjust Source destination back one row, source forward one
;*/
        mov     ebx,cbBytesPerScanline
        sub     edi,ebx
        add     esi,ebx

;/*
;** Set up the count
;*/
        mov     ecx,cbBytesPerSrcPlane

;/*
;** Compare the rows.
;*/
        compare_rows

;/*
;** Restore the current positions (source segment will not have changed)
;*/
        mov     esi, pCurrentSrcPos
        mov     edi, pNextFreeDstPos

;/*
;** Jump forward if rows are different.
;*/
        jne     no_scanline_pair

;/*
;** We have a duplicate scanline pair!
;**
;** If this is the first time through then write a new cell
;** to the destination, else increment the count in the cell
;** that has previously been written.
;*/
        cmp     fFirstTimePair,TRUE
        jne     inc_pair_count

;/*
;** Write out the cell.
;*/
        append_data_size<write_dup_scanline_pair_cell >

;/*
;** Reset the first time flag.
;*/
        mov     fFirstTimePair,FALSE

;/*
;** Jump forward to update the current source position.
;*/
        jmp     processed_dup_pair

inc_pair_count:
;/*
;** Increment the cell count.
;*/
        append_data_size<inc_cell_count dup_scan_pair,>

processed_dup_pair:
;/*
;** Skip to the next unprocessed row (the row after next).
;** Note that this will automatically exit if we reach the end
;** of the rectangle.
;*/
        inc_source_row_twice compress_rect_exit

;/*
;** Jump back to look for another matching pair.
;*/
        jmp     dup_scanline_pair_loop

no_scanline_pair:
;/*
;** If we reach this point we need to compress the whole source row.
;** We call the appropriate function using a table of function addresses.
;**
;** Save the current destination pointer as the start of the line
;*/
        mov     pDestAtStartOfLine,edi

;/*
;** Load up the row width(in bytes) into cx.
;*/
        mov     ecx,cbBytesPerSrcRow

;/*
;** Call the function to compress this row
;*/
        mov     ebx, pfnCompressRow
        call    ebx

;/*
;** Calculate the number of bytes stored
;*/
        mov     eax, edi
        sub     eax, pDestAtStartOfLine

        assert  eax,be,cbFreeBytesInDestBuffer
;/*
;** Update the free bytes remaining in the destination buffer.
;*/
        sub     cbFreeBytesInDestBuffer,eax

;/*
;** Move the source pointer back to the beginning of this row
;*/
        mov     esi, pCurrentSrcPos

;/*
;** Move to the next source row.
;** This will automatically exit if we reach the end of the
;** rectangle.
;*/

        inc_source_row compress_rect_exit

;/*
;** Loop back to the start of the main loop.
;*/
        jmp     main_row_loop


compress_rect_exit:
;/*
;** Update the rectangle to the area compressed
;*/
        mov     eax, fOutputBufferFull
        or      eax, eax
        jz      rectangle_ok

;/*
;** The entire rectangle was not saved, adjust the yBottom
;** value to indicate how much of the rectangle was saved
;*/
        mov     ebx,prclCurrentRect
        ASSUME  EBX:PTR RECTL
        mov     eax,[ebx].rcl_yBottom
        add     eax, yEndRow
        sub     eax, yCurrentRow
        mov     [ebx].rcl_yBottom, eax
rectangle_ok:

;/*
;** Update the rectangle in the buffer
;**
;** Save the current destination pointer
;*/
        pushem edi

;/*
;** Retrieve the position of the rectangle in the buffer
;*/
        mov     edi, pRectHeader

;/*
;** Copy the rectangle (RECTL -> RECTS)
;*/
        mov     ebx,prclCurrentRect
        ASSUME  EBX:PTR RECTL

        mov     eax,[ebx].rcl_xLeft
        stosw
        mov     eax,[ebx].rcl_yBottom
        stosw
        mov     eax,[ebx].rcl_xRight
        stosw
        mov     eax,[ebx].rcl_yTop
        stosw

        popem edi

compress_rect_no_rects_exit:
;/*
;** Return whether the buffer is full or not
;** and how many bytes are remaining
;*/
        mov     eax, fOutputBufferFull
        mov     ecx, cbFreeBytesInDestBuffer
        ret

compress_rect ENDP



;/**********************************************************************
;** Function: compress_row_4pl_4pl
;**
;** Input values:
;**   esi - Points to source row (data to be compressed)
;**   edi - Points to destination buffer (to receive compressed data)
;**   ecx     - Length of row in bytes (note: row NOT plane)
;**
;** On Exit:
;**   edi - Current place in destination buffer
;**********************************************************************/
ALIGN   4
compress_row_4pl_4pl    PROC SYSCALL
LOCAL   pRowStartAddress            :DWORD,
        cbBytesToCompress           :DWORD,
        cPlane                      :DWORD,
        cBytesInRow                 :DWORD


;/*
;** Save the row start address and the calculate the plane length (rounding up)
;*/
        mov     pRowStartAddress, esi
        shr     ecx, 2
        mov     cBytesInRow, ecx

;/*
;** Initialise the plane selection
;*/
        plane_select_initialise

;/*
;** Select the next plane
;*/
next_plane:
        plane_select_next

;/*
;** RESTORE pRowStartAddress and row length
;*/
        mov     esi, pRowStartAddress
        mov     ecx, cBytesInRow

compress_row_in_4pl_4pl:
        compress_row    8

;/*
;** Loop back for next plane
;*/
        plane_select_repeat next_plane

        ret
compress_row_4pl_4pl    ENDP

;/**********************************************************************
;** Function: compress_row_4pl_4
;**
;** Input values:
;**   esi - Points to source row (data to be compressed)
;**   ecx - Length of row in bytes
;**
;** On Exit:
;**   edi - Current place in destination buffer
;**********************************************************************/
ALIGN   4
compress_row_4pl_4    PROC SYSCALL

LOCAL   pRowStartAddress  :DWORD,
        cbBytesToCompress :DWORD

        add     ecx,ecx         ; change bytes to pels (mult by 2)
        push    ecx             ; save the pels per row count

;/*
;** First pack the data direct from the screen into the
;** temporary buffer
;*/
        lea     eax, pelbuffer

        INVOKE  PackBuffer, esi, eax, ecx, 0050h

        pop     ecx             ; restore the pels per row count
        shr     ecx,1           ; change pels to packed bytes (2 per byte)

        push    esi

        lea     esi, pelbuffer

;/*
;** Now compress the packed data to the destination buffer
;*/
        compress_row    8

        pop     esi
        ret

compress_row_4pl_4    ENDP



endif ; DCAF

end
