;-----------------------------------------------------------------------
; MODULE XRLETOOL
;
; Hardware detection module
;
; Compile with Tasm.
; C callable.
;
;
; ****** XLIB - Mode X graphics library                ****************
; ******                                               ****************
; ****** Written By Themie Gouthas                     ****************
;
; egg@dstos3.dsto.gov.au
; teg@bart.dsto.gov.au
;-----------------------------------------------------------------------

COMMENT $

Firstly, please note that this module has been built from the ground up
in a rush so although I'm confident all the functions work, I have'nt
extensively checked them. If any should surface please let me know.


This module implements a number of functions comprising an RLE encoding
decoding system.

RLE stands for RUN LENGTH ENCODING. It is a quick simple data compression
scheme which is commonly used for image data compression or compression
of any data. Although not the most efficient system, it is fast, which is
why it is used in image storage systems like PCX. This implementation is
more efficient than the one used in PCX files because it uses 1 bit to
identify a Run Length byte as opposed to two in PCX files, but more on this
later.

This set of functions can be used to implement your own compressed image
file format or for example compress game mapse for various levels etc.
The uses are limited by your imagination.

I opted for trading off PCX RLE compatibility for the improved compression
efficiency.

Here is how the data is un-compressed to give an idea of its structure.


STEP 1 read a byte from the RLE compressed source buffer.

STEP 2 if has its high bit then the lower 7 bits represent the number of
       times the next byte is to be repeated in the destination buffer.
       if the count (lower 7 bits) is zero then
	  we have finished decoding goto STEP 5
       else goto STEP 4

STEP 3 Read a data from the source buffer and copy it directly to the
       destination buffer.
       goto STEP 1

STEP 4 Read a data from the source buffer and copy it to the destination
       buffer the number of times specified by step 2.
       goto STEP 1

STEP 5 Stop, decoding done.

If the byte does not have the high bit set then the byte itself is transfered
 to the destination buffer.

Data bytes that have the high bit already set and are unique in the input
 stream are represented as a Run Length of 1 (ie 81 which includes high bit)
 followed by the data byte.

If your original uncompressed data contains few consecutive bytes and most
have high bit set (ie have values > 127) then your so called
compressed data would require up to 2x the space of the uncompressed data,
so be aware that the compression ratio is extremely variable depending on the
type of data being compressed.

Apologies for this poor attempt at a description, but you can look up
RLE in any good text. Alternatively, any text that describes the PCX file
structure in any depth should have a section on RLE compression.



$

LOCALS
.286

include model.inc
include xrletool.inc

	.data

_RLE_last_buff_offs dw (0)
RLEbuff db 2 dup (?)

	.code

;****************************************************************
;*
;* NAME: x_buff_RLEncode
;*
;*
;* RLE Compresses a source buffer to a destination buffer and returns
;* the size of the resultant compressed data.
;*
;* C PROTOTYPE:
;*
;*  extern unsigned int x_buff_RLEncode(char far * source_buff,
;*      	 char far * dest_buff,unsigned int count);
;*
;* source_buff   - The buffer to compress
;* dest_buff     - The destination buffer
;* count         - The size of the source data in bytes
;*
;* WARNING: buffers must be pre allocated.
;*
proc _x_buff_RLEncode
ARG   src:dword,dest:dword,count:word
	push bp
	mov  bp,sp
	push ds
	push si
	push di

	lds  si,[src]
	les  di,[dest]
	mov  dx,[count]

	push di

	lodsb              ; Load first byte into BL
	mov  bl,al
	xor  cx,cx         ; Set number characters packed to zero
	cld                ; All moves are forward

@@RepeatByte:
	lodsb		   ; Get byte into AL
	inc  cx            ; Increment compressed byte count
	sub  dx,1          ; Decrement bytes left
	je   @@LastByte    ; Finished when dx = 1
	cmp  cx,7fh        ; Filled block yet
	jne  @@NotFilled   ; Nope!

	or   cl,80h        ; Set bit to indicate value is repeat count
	mov  es:[di],cl    ; store it
	inc  di
	xor  cx,cx         ; clear compressed byte count
	mov  es:[di],bl    ; store byte to be repeated
	inc  di

@@NotFilled:
	cmp  al,bl         ; hase there been a byte transition ?
	je   @@RepeatByte  ; No!

	cmp  cl,1          ; do we have a unique byte ?
	jne  @@NotUnique   ; No

	test bl,80h        ; Can this byte be mistaken for repeat count
	jz   @@Unambiguous ; No ! Dont bother with repeat count

@@NotUnique:
	or   cl,80h        ; Set bit to indicate value is repeat count
	mov  es:[di],cl    ; store it
	inc  di
@@Unambiguous:
	xor  cx,cx         ; clear compressed byte count
	mov  es:[di],bl    ; store byte to be repeated
	inc  di
	mov  bl,al         ; move latest byte into bl
	jmp  short @@RepeatByte

@@LastByte:
	cmp  cl,1          ; Is this a unique byte
	jne  @@FinalCount  ; No

	test bl,80h        ; Can this byte be mistaken for repeat count
	jz   @@FinalByte   ; No, so dont bother with the repeat count

@@FinalCount:              ; Output the repeat count
	or   cl,80h
	mov  al,cl
	stosb

@@FinalByte:
	mov  al,bl
	stosb

	mov  al,80h       ; store terminating null length
	stosb

        ; Calculate encoded length of buffer

	mov  ax,di
	pop  di
	sub  ax,di

	pop  di
	pop  si
	pop  ds
	pop  bp
	ret
_x_buff_RLEncode endp



;****************************************************************
;*
;* NAME: x_buff_RLE_size
;*
;*
;* Returns the size the input data would compress to.
;*
;* C PROTOTYPE:
;*
;*  extern unsigned int x_buff_RLE_size(char far * source_buff,
;*      	 unsigned int count);
;*
;* source_buff   - The uncompressed data buffer
;* count         - The size of the source data in bytes
;*
;*
proc _x_buff_RLE_size
ARG   src:dword,count:word
	push bp
	mov  bp,sp
	push ds
	push si
	push di

	lds  si,[src]
	mov  dx,[count]

	xor  di,di

	lodsb              ; Load first byte into BL
	mov  bl,al
	xor  cx,cx         ; Set number characters packed to zero
	cld                ; All moves are forward

@@RepeatByte:
	lodsb		   ; Get byte into AL
	inc  cx            ; Increment compressed byte count
	sub  dx,1          ; Decrement bytes left
	je   @@LastByte    ; Finished when dx = 1
	cmp  cx,7fh        ; Filled block yet
	jne  @@NotFilled   ; Nope!

	add  di,2          ; RL/BYTE pair stub

@@NotFilled:
	cmp  al,bl         ; hase there been a byte transition ?
	je   @@RepeatByte  ; No!

	cmp  cl,1          ; do we have a unique byte ?
	jne  @@NotUnique   ; No

	test bl,80h        ; Can this byte be mistaken for repeat count
	jz   @@Unambiguous ; No ! Dont bother with repeat count

@@NotUnique:
	inc  di            ; RL stub

@@Unambiguous:
	xor  cx,cx         ; clear compressed byte count
	inc  di            ; BYTE stub
	mov  bl,al         ; move latest byte into bl
	jmp  short @@RepeatByte

@@LastByte:
	cmp  cl,1          ; Is this a unique byte
	jne  @@FinalCount  ; No

	test bl,80h        ; Can this byte be mistaken for repeat count
	jz   @@FinalByte   ; No, so dont bother with the repeat count

@@FinalCount:              ; Output the repeat count
	inc  di            ; RL stub

@@FinalByte:
	inc  di            ; BYTE stub
	inc  di            ; RL stub - Account for termiating null
	mov  ax,di

	pop  di
	pop  si
	pop  ds
	pop  bp
	ret
_x_buff_RLE_size endp

;****************************************************************
;*
;* NAME: x_buff_RLDecode
;*
;*
;* Expands an RLE compresses source buffer to a destination buffer.
;* returns the size of the resultant uncompressed data.
;*
;* C PROTOTYPE:
;*
;*  extern unsigned int x_buff_RLDecode(char far * source_buff,
;*      	 char far * dest_buff);
;*
;* source_buff   - The buffer to compress
;* dest_buff     - The destination buffer
;*
;* WARNING: buffers must be pre allocated.
;*
proc _x_buff_RLDecode
ARG   src:dword,dest:dword
LOCAL si_ini:word=LocalStk
	push bp
	mov  bp,sp
        sub  sp,LocalStk
	push ds
	push si
	push di

	mov  dx,-1        ; zero output data buffer size - 1 (compensate for
			  ; terminating null RL)
	xor  cx,cx        ; clear CX
	cld               ; Move forward

	lds  si,[src]     ; point ds:si -> RLE source
	les  di,[dest]    ; point es:di -> uncompressed buffer
        mov  [si_ini],si

@@UnpackLoop:
	lodsb             ; load a byte into AL
	cmp  al,80h       ; is it terminating null RL code
	je   @@done       ; if so jump

	test al,80h       ; is AL a RL code (is high bit set ?)
	jz   @@NoRepeats  ; if not the no RL encoding for this byte, jump

	mov  cl,al        ; set CL to RL (run length) taking care
	xor  cl,80h       ; to remove the bit identifying it as a RL
        add  dx,cx        ; increment buffer size

	lodsb             ; get the next byte which should be a data byte

	shr  cx,1         ; divide RL by 2 to use word stos
	jcxz @@NoRepeats  ; result is zero, jump

	mov  ah,al        ; copy data byte to AH since going to use stosw
	rep  stosw        ; copy AX to outbut buffer RL times
	jnb  @@UnpackLoop ; when we shifted the RL if we had a carry =>
			  ; we had an odd number of repeats so store the
			  ; last BYTE if carry was set otherwise jump
        stosb             ; store AL in destination buffer
        jmp  short @@UnpackLoop

@@NoRepeats:
	inc  dx           ; increment buffer size
	stosb             ; store AL in destination buffer
        jmp  short @@UnpackLoop

@@done:

        mov  bx,si
        sub  bx,[si_ini]
	mov  ax,dx
	pop  di
	pop  si
	pop  ds
        mov  [_RLE_last_buff_offs],bx
        mov  sp,bp
	pop  bp
	ret
_x_buff_RLDecode endp

;==========================================================================
;==========================================================================
; RLEncode to file / RLDecode from file
; WARNING the following functions are *MUCH* slower than the above
; Its best to use the above functions with intermediate buffers where
; disk i/o is concearned... See demo 4
;==========================================================================
;==========================================================================

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
; put_cx
;
;   Local utility proc for x_file_RLEncode - write cx to file
;
; Entry:
;       es:dx -> output buffer
;       cx = word to write
;
;
put_cx proc near
	push ds             ; preserve critical registers
	push ax
	push bx
	mov  ax,ds          ; set up DS to output buffers segment
	mov  ds,ax
	mov  word ptr [RLEbuff],cx ; copy CX to output buffer
	mov  ah,40h         ; select "write to file or device" DOS service
	mov  bx,[handle]    ; select handle of file to write
	mov  cx,2           ; sending 2 bytes
	int  21h	    ; call DOS service
	pop  bx             ; recover registers
	pop  ax
	pop  ds
	ret
put_cx endp

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
; put_cx
;
;   Local utility proc for x_file_RLEncode - write cx to file
;
; Entry:
;       es:dx -> output buffer
;       cx = word to write
;
;
put_cl proc near
	push ds             ; preserve critical registers
	push ax
	push bx
	mov  ax,ds          ; set up DS to output buffers segment
	mov  ds,ax
	mov  byte ptr [RLEbuff],cl
	mov  ah,40h         ; select "write to file or device" DOS service
	mov  bx,[handle]    ; select handle of file to write
	mov  cx,1           ; sending 1 byte
	int  21h	    ; call DOS service
	pop  bx             ; recover registers
	pop  ax
	pop  ds
	ret
put_cl endp


;****************************************************************
;*
;* NAME: x_file_RLEncode
;*
;*
;* RLE Compresses a source buffer to an output file returning
;* the size of the resultant compressed data or 0 if it fails.
;*
;* C PROTOTYPE:
;*
;*  extern unsigned int x_file_RLEncode(int handle,
;*      	 char far * source_buff,unsigned int count);
;*
;* source_buff   - The buffer to compress
;* handle        - The file handler
;* count         - The size of the source data in bytes
;*
;*
proc _x_file_RLEncode
ARG   handle:word,src:dword,count:word
LOCAL filesize:word=LocalStk
	push bp
	mov  bp,sp
	sub  sp,LocalStk
	push ds
	push si
	push di

	mov  [filesize],0
	mov  dx,offset [RLEbuff]
	mov  ax,ds
	mov  es,ax
	lds  si,[src]
	mov  di,[count]

	lodsb              ; Load first byte into BL
	mov  bl,al
	xor  cx,cx         ; Set number characters packed to zero
	cld                ; All moves are forward

@@RepeatByte:
	lodsb		   ; Get byte into AL
	inc  cx            ; Increment compressed byte count
	sub  di,1          ; Decrement bytes left
	je   @@LastByte    ; Finished when di = 1
	cmp  cx,7fh        ; Filled block yet
	jne  @@NotFilled   ; Nope!

	or   cl,80h        ; Set bit to indicate value is repeat count
	mov  ch,bl
	add  [filesize],2
	call put_cx
	jb   @@FileError   ; if carry set then file I/O error
	xor  cx,cx         ; clear compressed byte count

@@NotFilled:
	cmp  al,bl         ; hase there been a byte transition ?
	je   @@RepeatByte  ; No!

	cmp  cl,1          ; do we have a unique byte ?
	jne  @@NotUnique   ; No

	test bl,80h        ; Can this byte be mistaken for repeat count
	jz   @@Unambiguous ; No ! Dont bother with repeat count

@@NotUnique:
	or   cl,80h        ; Set bit to indicate value is repeat count
	inc  [filesize]
	call put_cl        ; store it
        jb   @@FileError   ; if carry set then file I/O error
@@Unambiguous:

	mov  cl,bl         ; store byte to be repeated
        inc  [filesize]
	call put_cl
        jb   @@FileError   ; if carry set then file I/O error
	mov  bl,al         ; move latest byte into bl
        xor  cx,cx         ; clear compressed byte count
	jmp  short @@RepeatByte

@@FileError:
	mov  ax,0
	jmp  short @@exit

@@LastByte:
	cmp  cl,1          ; Is this a unique byte
	jne  @@FinalCount  ; No

	test bl,80h        ; Can this byte be mistaken for repeat count
	jz   @@FinalByte   ; No, so dont bother with the repeat count

@@FinalCount:              ; Output the repeat count
	or   cl,80h
        inc  [filesize]
	call put_cl
	jb   @@FileError   ; if carry set then file I/O error

@@FinalByte:
	mov  cl,bl
	mov  ch,80h
	add  [filesize],2
	call put_cx        ; store terminating null length
	jb   @@FileError   ; if carry set then file I/O error

	mov  ax,[filesize]
	jmp  short @@exit

@@exit:
	pop  di
	pop  si
	pop  ds
	mov  sp,bp
	pop  bp
	ret
_x_file_RLEncode endp




;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
; GET_BYTE
;
;   macro to read a byte from the input file into al
;
GET_BYTE macro
	push bx
	mov  ah,3fh         ; select "read from file or device" DOS service
	mov  bx,[handle]    ; Select handle of file to close
	mov  cx,1           ; Want to read 1 byte
	int  21h            ; call DOS service
	pop  bx
	jb   @@FileError    ; failed if carry flag set
	mov  al,[RLEbuff]
	endm


;****************************************************************
;*
;* NAME: x_file_RLDecode
;*
;*
;* Expands an RLE compresses file to a destination RAM buffer.
;* returns the size of the resultant uncompressed data.
;*
;* C PROTOTYPE:
;*
;*  extern unsigned int x_buff_RLDecode(int handle,
;*      	 char far * dest_buff);
;*
;* handle        - Input file handle
;* dest_buff     - The destination buffer
;*
;*
proc _x_file_RLDecode
ARG   handle:word,dest:dword
	push bp
	mov  bp,sp
	push si
	push di


	mov  bx,-1        ; zero output data buffer size - 1 (compensate for
			  ; terminating null RL)
	mov  dx,offset [RLEbuff] ; setup DS:DX -> RLEBuffer
        xor  cx,cx        ; clear CX
	cld               ; Move forward

	les  di,[dest]    ; point es:di -> uncompressed buffer

@@UnpackLoop:

	GET_BYTE	  ; Load a byte from file into AL

	cmp  al,80h       ; is it terminating null RL code
	je   @@done       ; if so jump

	test al,80h       ; is AL a RL code (is high bit set ?)
	jz   @@NoRepeats  ; if not the no RL encoding for this byte, jump

	mov  cl,al        ; set CL to RL (run length) taking care
	xor  cl,80h       ; to remove the bit identifying it as a RL
	add  bx,cx        ; increment buffer size
	mov  si,cx        ; save the CX value
	GET_BYTE          ; Load a byte from file into AL
	mov  cx,si        ; restore CX value
        shr  cx,1         ; divide RL by 2 to use word stos
	jcxz @@NoRepeats  ; result is zero, jump

	mov  ah,al        ; copy data byte to AH since going to use stosw
	rep  stosw        ; copy AX to outbut buffer RL times
	jnb  @@UnpackLoop ; when we shifted the RL if we had a carry =>
			  ; we had an odd number of repeats so store the
			  ; last BYTE if carry was set otherwise jump
        stosb             ; store AL in destination buffer
        jmp  short @@UnpackLoop

@@NoRepeats:
	inc  bx
	stosb             ; store AL in destination buffer
        jmp  short @@UnpackLoop

@@FileError:
	mov  ax,0
	jmp  short @@exit

@@done:
	mov  ax,bx
@@exit:
	pop  di
	pop  si
	pop  bp
	ret
_x_file_RLDecode endp

	end

