Shrink92 - Documentation

SHRINK92

(89/92+ version by Jean Canazzi)
Copyright(c) David Kuehling 1998/99

This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.

Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:

1. The origin of this software must not be misrepresented; you must not
   claim that you wrote the original software. If you use this software
   in a product, an acknowledgment in the product documentation would be
   appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
   misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.


Table of Contents


Introduction

Shrink92 is a compression tool that is designed to reduce the size of Fargo programs by making it possible to compress binary data on your PC and uncompressing them on a TI-92 by the Fargo library shrnklib. It may also be used by other PC programs for text compressions and similar purposes. When you've allready used other compression libraries such as hufflib, lzsslib or pk92lib you won't have any problems with that library. In fact Shrink92 is superior to all those compression algorithms. It took me over a week to design the Shrink compression. It is a combination of an optimized LZSS, RLE and Huffman compression. As you will see compression is quite slow. But therefore uncompression runs very fast and compression ratios are sometimes almost Winzip - like. In case you already worked with Packer92, please don't judge my programing skills by that program. Packer92 was the first thing I programed for Fargo, it's more than a year ago, I was quite an amateur in these times. To convince you of Shrink92's power, here is a comparison of the most common algorithms, used by TI-92 programmers: (ratio specifies the difference of the original file to the compressed file, thus a greater ratio represents a better compression)

Filename Size (bytes) Huffman LZSS Packer92 Shrink92
traps.txt 5403 3390 (ratio: 37.26%) 2665 (ratio: 50.68%) 2201 (ratio: 59.26%) 2141 (ratio: 60.37%)
fargo.txt 47152 29220 (ratio: 38.03%) 21410 (ratio: 54.59%) 18080 (ratio: 61.66%) 16286 (ratio: 65.46%)
fpl.92p 2035 1902 (ratio: 6.54%) 1759 (ratio: 13.56%) 1859 (ratio: 8.65%) 1774 (ratio: 12.83%)
db92.92p 12882 10232 (ratio: 20.57%) 9534 (ratio: 25.99%) 8882 (ratio: 31.05%) 8408 (ratio: 34.73%)
bd.92p 14773 10736 (ratio: 27.33%) 9636 (ratio: 34.77%) 8465 (ratio: 42.70%) 8157 (ratio: 44.78%)
some 3 plane picture 11520 6812 (ratio: 40.87%) 7440 (ratio: 35.42%) 6944 (ratio: 39.72%) 6370 (ratio: 44.70%)

As you can see, small files (about 2K) aren't compressed very efficent. This can be compensated by putting many small files into one archive. The structure of Shrink92 archives allows single files to be uncompressed without scanning through the rest of the archive, although different files in one archive partially use same compression data, thus optimize the total compression ratio. Shrink92 is also capable of creating an include file that will help you to access the archive.


How to Use Shrink92

Shrink92 allways creates one archive. Archives may consist of one or more sections. Normally each section represents one input file (except you're using the -m option). Since archive sections partially share compression tables the compression ratio will (in most cases) increase when you create one archive with many sections instead of many archives with one section only. Actually Shrink92's syntax is quite simple:

SHRINK92 file1 [file2] ... [-iinclude-file] [-ooutput-file] [-v] [-m]

fileX
Name of an input file. May contain wildcards. You may specify up to 256 input files. Each input file will be stored into one archive section, except you're using the -m option.
-ooutput-file
Specify the name of the output archive. Note that you mustn't type spaces between -o and output-file.
-iinclude-file
Make Shrink92 to create an include file named include-file. This file will contain some equates that may help you to access the archive, especially when it consists of many sections or when you're using the -m option.
-m
Merge all input files and put them to one archive section. This method is similar to RAR's Solid archives. The resulting file will in most cases be smaller than the non-merged version. However, to access single files of the archive you will have to extract the hole archive. For this reason -m is only helpful when you're working with many small files. When you're using -m, the type of the include file (-i option) will be different. Shrink92 will define one EQUate for each file. This EQUate contains the relative offset of that file within archive section #0, where all the files are stored. Example: You compressed picture1.raw and picture2.raw with the -m option and created an include file. This include file contains the two EQUates PICTURE1.RAW and PICTURE2.RAW. After you extracted archive section #0 that contains these two files, A0 pointes to the memory region where the two extracted pictures are. You may now access each single picture by PICTURE1.RAW(a0) and PICTURE2.RAW(a0) (of course you will have to INCLUDE the include file that you've created).
-v
This option was originally programed for debugging purposes. I kept it in Shrink92 for all those people who are afraid of bugs. -v will make Shrink92 to verify the archive's contents. This is done by extracting each section of the archive and comparing it with the source. In case that an error is reported during verification please contact me. (Although it is very unlikely that there are still bugs...).


How to Use shrnklib for Extraction

The shrnklib library contains only 3 routines:
shrnklib::OpenArchive
Opens the archive pointed by A0 for access, returns an archive descriptor handle in D0. That archive descriptor contains some compression data that only need to be extracted once - it would be inefficent to extract them for each file again. The archive descriptor also contains the pointer to the archive data. For this reason you only need D0 for specifying the archive you want to access. A0 isn't required any more.
shrnklib::CloseArchive
Closes the archive, specified by the archive descriptor handle D0. This will free all memory that is used by the archive descriptor. The handle becomes invalid.
shrnklib::Extract
Extracts the section, specified by D1, of the archive whose descriptor handle is given by D0. You may specify the destination in memory where to extract the data to by A0. If you set A0 to zero shrnklib::Extract will automatically allocate sufficient memory and return the handle of the allocated block in D2 and it's address in A0.
Further information are contained in shrnklib.h.


Trouble Shooting

Here is a list of mistakes you could do when using Shrink92: If you can't find the mistake, and you start thinking about bugs in Shrink92 or shrinklib, use the -v option when compressing the files. This will make Shrink92 to verify the archive's contents. In case that the created archive is somehow wrong (the uncompressed archive doesn't match the source) you'll get an error message. Please contact me if this happens.


An Example of Using Shrink92 and shrnklib

In this example you want to compress a 3 plane picture and display it on your calculator by the gray7lib library. The 3 planes are given separated in the files picture0.1pl, picture1.1pl and picture2.1pl. To display the picture these planes have to be put to the equivalent planes of gray7lib. I gained that pictures by converting a .TGA file by UGPConv and separating the planes by a hex editor, however this is irrelevant for that example. Note that I didn't optimized the example programs very much, since I wanted to make them as overviewing as possible. First of all I want to show you how the program would look like if you didn't compress the pictures:


	INCLUDE "flib.h"
	INCLUDE "gray7lib.h"
	
	XDEF	_main
	XDEF	_comment

;-----------------------------------------------------------------------------
; Copy one plane (3840 bytes) from (a1) to (a2)
;-----------------------------------------------------------------------------
CopyPlane:
	move.w	#3840/4-1,d7		; d7 is our counter
\Loop:	move.l	(a1)+,(a2)+
	dbra	d7,\Loop		; loop back until d7 reaches -1
	rts

;-----------------------------------------------------------------------------
; MAIN
;-----------------------------------------------------------------------------
_main:	jsr	gray7lib::on		; switch 7 shade grayscale on
	tst.l	d0			; if d0.l = zero: failure
	beq	\Error

	; copy plane #2 to gray7lib::plane2
	lea	Plane2(PC),a1		; load address of source plane 
	movea.l	gray7lib::plane2,a2	; load destination address
	bsr	CopyPlane		; copy the plane from (a1) to (a2)

	; copy plane #1 to gray7lib::plane1
	lea	Plane1(PC),a1		; load address of source plane 
	movea.l	gray7lib::plane1,a2	; load destination address
	bsr	CopyPlane		; copy the plane from (a1) to (a2)

	; copy plane #0 to gray7lib::plane0
	lea	Plane0(PC),a1		; load address of source plane 
	movea.l	gray7lib::plane0,a2	; load destination address
	bsr	CopyPlane		; copy the plane from (a1) to (a2)

	jsr	flib::idle_loop		; wait for keypress

	jsr	gray7lib::off		; disable 7 shade grayscale

\Error:	rts				; exit program
	

; here are the 3 planes, added binary to the program
	EVEN
Plane0:	INCBIN "picture0.1pl"
Plane1:	INCBIN "picture1.1pl"
Plane2:	INCBIN "picture2.1pl"

_comment:	dc.b	"Displays an uncompressed pic",0
	END

Now we move on to the first possiblity to compress the data: Creating an archive with 3 sections, each section containing one plane. Section #0 will contain plane #0, section #1 will contain plane #1 and so on. To do the compression, type the following from the DOS command line:

shrink92 picture?.1pl -oarchive1.shk -iarchive1.inc

This will create an archive named archive1.shk, containing the planes. Note: if you're using wildcards like the ? in the above example the files will be loaded in alphabetical order. The include file archive1.inc that is also created will consist of EQUates that contain the number of the section each file is stored in, and each section's length:

archive1.inc

;=============================================================================
; Include file automatically created by SHRINK92   Fri Jan  8 09:09:35 1999
; Purpose: access of archive `archive.shk'
; That archive consists of 3 files in 3 sections.
;=============================================================================

PICTURE0.1PL       EQU       0  ; index of archive section
PICTURE0.1PL_LEN   EQU    3840  ; length of file
PICTURE1.1PL       EQU       1  ; index of archive section
PICTURE1.1PL_LEN   EQU    3840  ; length of file
PICTURE2.1PL       EQU       2  ; index of archive section
PICTURE2.1PL_LEN   EQU    3840  ; length of file

Displaying the pictures is now very simple: Each section has to be extracted to the corresponding gray plane of gray7lib. The section, whose index is given by the EQUate PICTURE0.1PL has to be uncompressed to gray7lib::plane0, the section, specified by PICTURE1.1PL to gray7lib::plane1 and so on. The resulting program will look like:

examp1e1.asm

	INCLUDE "flib.h"
	INCLUDE "gray7lib.h"
	INCLUDE "shrnklib.h"

	; include file that was automatically created by Shrink92
	INCLUDE "archive1.inc"

	XDEF	_main
	XDEF	_comment

_main:	jsr	gray7lib::on		; switch 7 shade grayscale on
	tst.l	d0			; if d0.l = zero: failure
	beq	\Error

	; open the archive
	lea	Archive(PC),a0		; load addresss of archive into a0
	jsr	shrnklib::OpenArchive	; return: d0.w = archive descriptor handle

	; extract plane #2 of picture
	moveq	#PICTURE2.1PL,d1	; d1 = index of archive section to extract
	movea.l	gray7lib::plane2,a0	; extraction destination is plane #2
	jsr	shrnklib::Extract	; do the extraction

	; extract plane #1 of picture
	moveq	#PICTURE1.1PL,d1	; d1 = index of archive section to extract
	movea.l	gray7lib::plane1,a0	; extraction destination is plane #1
	jsr	shrnklib::Extract	; do the extraction

	; extract plane #0 of picture
	moveq	#PICTURE0.1PL,d1	; d1 = index of archive section to extract
	movea.l	gray7lib::plane0,a0	; extraction destination is plane #0
	jsr	shrnklib::Extract	; do the extraction

	jsr	shrnklib::CloseArchive	; close the archive whose handle is given
					; by d0 (d0 is still the descriptor handle)

	jsr	flib::idle_loop		; wait for keypress

	jsr	gray7lib::off		; disable 7 shade grayscale

\Error:	rts				; exit program
	

	; shrink archive containing the 3 planes in 3 archive sections
Archive:	INCBIN "archive1.shk"

_comment:	dc.b "Shrink92 Example #1",0

	END

After the archive has been opened by shrnklib::OpenArchive, d0 will be the archive descriptor handle. During the extraction the value of d0 isn't modified any more, since it is required as input for the shrnklib::Extract and the shrnklib::CloseArchive routine (look shrnklib.h). The rest of the program should be self explaining. This method of compressing each plane of a picture into a separated archive section needs very few memory. 7680 bytes for 7 shade grayscale, about 1.2 K temporarily during the shrnklib::OpenArchive routine, and 1-2K are used by the archive descriptor handle.

When I said that this was the first possiblity then there's of course a second one. This one needs a little more memory but the archive is in most cases smaller. In this case we put all planes into one archive section. So we have one great archive section instead of 3 small ones. That's why in most cases the compression ration is better. Unfortunately I chose the example pictures picture0.1pl - picture2.1pl very badly - they became a little greater when using that method :-(. But remember that this is only an example, normaly files will become smaller. To put all files into one section you'll have to use the -m option. This will make Shrink92 to merge the input files before creating the archive. Type the following from the command line:

shrink92 picture?.1pl -oarchive2.shk -iarchive2.inc -m

When you're using the -m option Shrink92 will create a different kind of include file. Instead of creating one EQUate for each file, that contains the index of the section the file is stored in, Shrink92 will create one EQUate for each file that contains the offset of that file within archive section #0, where all files are contained in. In the current example the include file looks like:

archive2.inc

;=============================================================================
; Include file automatically created by SHRINK92   Fri Jan  8 09:43:48 1999
; Purpose: access of archive `archive2.shk'
; That archive consists of 3 files in 1 section.
;=============================================================================

PICTURE0.1PL       EQU       0  ; offset of file in archive section #0
PICTURE0.1PL_LEN   EQU    3840  ; length of file
PICTURE1.1PL       EQU    3840  ; offset of file in archive section #0
PICTURE1.1PL_LEN   EQU    3840  ; length of file
PICTURE2.1PL       EQU    7680  ; offset of file in archive section #0
PICTURE2.1PL_LEN   EQU    3840  ; length of file

This means that after we have extracted section #0, and a0 points to the extracted data, we may access the planes separated by PICTURE0.1PL(a0) up to PICTURE2.1PL(a0). To uncompress section #0 we need a memory block of 11520 bytes (size of one plane * 3). If we don't want to allocate it ourselves we can make shrnklib::Extract to do it for us, by setting the destination pointer a0 to zero. In that case shrnklib::Extract will return the handle of the allocated memory block in d2 and the pointer in a0. Here is the program's source:

examp1e1.asm

	INCLUDE "tios.h"
	INCLUDE "flib.h"
	INCLUDE "gray7lib.h"
	INCLUDE "shrnklib.h"

	; include file that was automatically created by Shrink92
	INCLUDE "archive2.inc"

	XDEF	_main
	XDEF	_comment

;-----------------------------------------------------------------------------
; Copy one plane (3840 bytes) from (a1) to (a2)
;-----------------------------------------------------------------------------
CopyPlane:
	move.w	#3840/4-1,d7		; d7 is our counter
\Loop:	move.l	(a1)+,(a2)+
	dbra	d7,\Loop		; loop back until d7 reaches -1
	rts

;-----------------------------------------------------------------------------
; MAIN
;-----------------------------------------------------------------------------
_main:	jsr	gray7lib::on		; switch 7 shade grayscale on
	tst.l	d0			; if d0.l = zero: failure
	beq	\Error

	; open the archive
	lea	Archive(PC),a0		; load addresss of archive into a0
	jsr	shrnklib::OpenArchive	; return: d0.w = archive descriptor handle

	; extract section #0 that contains all planes
	moveq	#0,d1			; d1 = index of archive section to extract
	movea.l	#0,a0			; a0 = 0: 'Extract' has to allocate memory
	jsr	shrnklib::Extract	; do the extraction
					; ->a0.l points to allocated extraction memory
					; ->d2.l is the handle of that memory block

	jsr	shrnklib::CloseArchive	; close the archive whose handle is given
					; by d0 (d0 is still the descriptor handle)

	; copy plane #2 to gray7lib::plane2
	lea	PICTURE2.1PL(a0),a1	; load address of plane within extracted block
	movea.l	gray7lib::plane2,a2	; load destination address
	bsr	CopyPlane		; copy the plane from (a1) to (a2)

	; copy plane #1 to gray7lib::plane1
	lea	PICTURE1.1PL(a0),a1	; load address of plane within extracted block
	movea.l	gray7lib::plane1,a2	; load destination address
	bsr	CopyPlane		; copy the plane from (a1) to (a2)

	; copy plane #0 to gray7lib::plane0
	lea	PICTURE0.1PL(a0),a1	; load address of plane within extracted block
	movea.l	gray7lib::plane0,a2	; load destination address
	bsr	CopyPlane		; copy the plane from (a1) to (a2)

  	; free the memory that was allocated for extracting section #0
	move.w	d2,-(a7)		; d2 is still the handle (look shrnklib::Extract)
	jsr	tios::HeapFree
	addq.l	#2,a7

	jsr	flib::idle_loop		; wait for keypress

	jsr	gray7lib::off		; disable 7 shade grayscale

\Error:	rts				; exit program
	

	; shrink archive containing the 3 planes in 1 archive section
Archive:	INCBIN "archive2.shk"

_comment:	dc.b "Shrink92 Example #2",0

	END

Ok, that's it. I hope you understood everything. Just a little note on the examples: You'll perhaps have to specify absolute include paths for INCLUDE and INCBIN. If you want to recompile the example files, included with Shrink92, you'll need to change the include paths in those files.


The Programs' Sourcecodes

All the sourcecodes included with Shrink92 are licensed under an open source license, see the respective files and the file COPYING. The C-sourcecode of Shrink92 consists of:
SHRINK.C
The compression algorithm.
SHRINK92.C
The command line interface to the compression routines of SHRINK.C.
SHRINK.H
The header file that contains prototypes and descriptions of all important routines of SHRINK.C.
Since I used the DJGPP GNU C compiler, you should be able to compile Shrink92 for Linux. If you're doing so, don't forgot to switch optimisations on. The -o2 option of my C compiler incrased Shrink92's speed by factor 2.

Contacting me

If you have question, suggestions, comments or bug reports don't hesitate to mail me! My email is dvdkhlng TA gmx TOD de When you succeed in making a program that uses shrnklib, please send me a copy of it, no matter if it seems to be good!