enrle

This should have been a really easy thing to do! It is in effect a simple two-state machine with hardly any complicated conditions or special cases. Yet I found it something akin to herding a mischief of mice; each one running off in its own direction! Change one little thing, and something else breaks.. Even now I cant be 100% sure that Ive caught them all, although Im reasonably sure that it produces a legal representation of the uncompressed data.

Originally written in assembler, I re-wrote it from scratch in SBASIC to get a better understanding of whats going on. Afterall, in most cases the compression of a sprite only has to be performed once, so speed is not as critical as for decompression. (DERLE, the corresponding de-compression routine, may be found as an assembler toolkit here).

There are many ways to peel an apple. Equally, there are many ways to do RLE compression. Below is one way.

I wrapped the routine in a harness to more easily test it against a variety of targets during development. The top level simply goes through some directory, taking each sprite file found. This brings it to the next level, which takes a suitable sprite and performs RLE compression on its pattern data. The program will only compress a solid sprite, ie a sprite without a bitmask or alpha channel. It doesnt do a detailed analysis of a sprite, so unusual or malformed sprites will produce nonsense or nothing. I leave it as an excercise for the interested programmer to devise a program to compress a complete (complex) GD2 sprite!

The same compressor, DoRLE, can also be used to compress the mask data. Bitmasks are usually compressed with the same item size as the pattern, while alpha data is best compressed with an item size of one.

To invoke the program use either LRUN it in a classic three window job #0, or EW/EX it.

10 rem This harness compresses all solid, mode
11 rem 16..64 sprites in a directory
12 :
13 rem Alter from_dir and to_dir as appropriate.
14 rem No serious error checking: You must use
15 rem correct parameters!
16 rem Overwrites same name in target!
17 :
18 fdr$ = 'dos3_QL_spr_tmp_'
19 tdr$ = 'win3_spr_tmp_'
20 :
21 IF JOBID = 0 THEN
22  cw = 1
23 ELSE
24  cw = FOPEN("con"): BORDER#cw; 1, 5
25 END IF
26 CLS#cw
27 :
28 fdv$ = fdr$(1 TO 5)
29 IF LEN(fdr$) > 5: dr$  = fdr$(6 TO): ELSE : dr$ = ''
30 dl% = LEN(dr$) + 1
31 ch = FOP_DIR(fdr$)
32 fl = FLEN(#ch)
33 pos = -64
34 :
35 REPeat lp
36  pos = pos + 64: IF pos >= fl: EXIT lp
37  BGET#ch\ pos + 5, t%: IF t% = 255: NEXT lp
38  GET#ch\ pos + 14, nm$
39  nl% = LEN(nm$)
40  IF nl% = 0: NEXT lp
41  IF NOT ('spr' INSTR nm$) = (nl% - 2): NEXT lp
42  fnm$ = fdv$ & nm$
43  tnm$ = tdr$ & nm$(dl% TO)
44  PRINT#cw; fnm$,
45  PRINT#cw; SprComp(fnm$, tnm$)
46 END REPeat lp
47 :
48 CLOSE#ch
49 BEEP 2000, 2: PRINT#cw; 'Done!'
50 IF JOBID: BGET#cw; k%
51 :
52 :
53 rem + ------------------------------------------------------------------------ +
54 rem |<                                 SprComp                                >|
55 rem + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +
56 rem |                     RLE-compress a solid GD2 sprite                      |
57 rem |                                                                          |
58 rem | Given the filename of a suitable sprite in fnm$, this routine            |
59 rem | compresses it and writes the sprite to tnm$ (overwrite!) fnm$ can        |
60 rem | equal tnm$, if you like.                                                 |
61 rem |                                                                          |
62 rem | Returns the compressed size (+ve) or an error:                           |
63 rem | -7  => File not found                                                    |
64 rem | -8  => Already compressed                                                |
65 rem | -19 => Wrong mode or not a solid sprite, etc                             |
66 rem | -5  => The sprite did not compress                                       |
67 rem | If tnm$ is in use, the program just crashes!                             |
68 rem + ------------------------------------------------------------------------ +
69 rem | V0.01, pjw, 2019 Mar 20                                                  |
70 rem + ------------------------------------------------------------------------ +
71 :
72 DEFine FuNction SprComp(fnm$, tnm$)
73 LOCal ch, fl, sz, spu, spc, md%, ct%
74 :
75 REMark     Check for valid sprite
76 ch  = FOP_IN(fnm$): IF ch < 0: RETurn ch
77 fl  = FLEN(#ch)
78 WGET#ch; md%, ct%
79 LGET#ch\ 16, spu: CLOSE#ch:             REMark (re-use this LOCal)
80 IF spu: RETurn -19:                     REMark Only pattern supported
81 IF (ct% && 64) = 64: RETurn -8:         REMark Already compressed
82 IF ct% <>  0:      RETurn -19:          REMark Only single, simple sprite allowed
83 :
84 REMark Get colour mode and work out compression code
85 SELect ON md%
86  = 528:      md% = 1:                   REMark GD2-16
87  = 544, 545: md% = 2:                   REMark GD2-32/33
88  = 576:      md% = 4:                   REMark GD2-64
89  = REMAINDER : RETurn -19:              REMark Unsupported mode
90 END SELect
91 :
92 REMark     Create suitable buffer and load sprite
93 spu = ALCHP(fl + fl)
94 spc = spu + fl
95 LBYTES fnm$, spu
96 :
97 REMark     Copy sprite header and patch for compressed
98 POKE$ spc, PEEK$(spu, 24)
99 POKE spc + 3, PEEK(spc + 3) + 64:       REMark Flag RLE pattern
100 :
101 sz = DoRLE(spu + 24, spc + 24, fl - 24, md%)
102 IF sz > 0: SBYTES_O tnm$, spc, sz + 24
103 RECHP spu
104 RETurn sz
105 END DEFine SprComp
106 :
107 :
108 :
109 rem + ------------------------------------------------------------------------ +
110 rem |<                                 Do RLE                                 >|
111 rem + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +
112 rem |                     Standard sprite RLE compression                      |
113 rem |                                                                          |
114 rem | The compressed data always consists of one byte and one or more items.   |
115 rem | If the leading byte x is in the range 0..127 then x + 1 uncompressed     |
116 rem | items follow. Otherwise only one item follows, which represents 257 - x  |
117 rem | times that item in the uncompressed data.                                |
118 rem |                                                                          |
119 rem | Given the item size z% = 1, 2, or 4, this routine compresses the data    |
120 rem | at address fad to the buffer at address tad, according to the            |
121 rem | description above. The compressed data is flagged with the literal       |
122 rem | "RLE<item size>" followed by a longword giving the uncompressed data     |
123 rem | size, osz, followed by the compressed data.                              |
124 rem |                                                                          |
125 rem | Returns: The size of the compressed data (+ 8b for the header).          |
126 rem | NB The target buffer is assumed to be of the same size as the            |
127 rem | uncompressed data! In the event the compressed data would exceed the     |
128 rem | buffer, the error Buffer Full (-5) is returned instead. The contents of  |
129 rem | the buffer is then undefined.                                            |
130 rem + ------------------------------------------------------------------------ +
131 rem | V0.01, pjw, 2019 Mar 20                                                  |
132 rem + ------------------------------------------------------------------------ +
133 :
134 DEFine FuNction DoRLE(fad, tad, osz, z%)
135 LOCal lp, dl, sl, er, fbe, tbe, fpt, tpt
136 LOCal c%, adr, cit$(4), nit$(4)
137 :
138 REMark     Set header
139 POKE$ tad, 'RLE'
140 POKE tad + 3, z% + 48
141 POKE_L tad + 4, osz
142 :
143 REMark     Init
144 fbe = fad + osz:                        REMark From Buffer End
145 tbe = tad + osz:                        REMark To Buffer End
146 fpt = fad:                              REMark From running PoinTer
147 tpt = tad + 8:                          REMark To running PoinTer
148 :
149 REMark     RUN
150 cit$ = PEEK$(fpt, z%):                  REMark Current ITem
151 REPeat lp
152  er = diff: IF er < 0: EXIT lp:         REMark Different Loop
153  er = same: IF er < 0: EXIT lp:         REMark Same Loop
154 END REPeat lp
155 IF er = -5: RETurn er:                  REMark Buffer overrun
156 RETurn tpt - tad:                       REMark Return compressed data size
157 END DEFine DoRLE
158 :
159 DEFine FuNction diff
160 adr = tpt: tpt = tpt + 1: c% = -1
161 REPeat dl
162  fpt = fpt + z%: IF fpt > fbe: POKE adr, c%: RETurn -10
163  nit$ = PEEK$(fpt, z%)
164  IF nit$ = cit$ THEN
165   REMark End of differences
166   IF c% > -1: POKE adr, c%: ELSE : tpt = adr
167   EXIT dl
168  ELSE
169   IF tpt > tbe: RETurn -5
170   POKE$ tpt, cit$
171   tpt = tpt + z%: c% = c% + 1
172   cit$ = nit$
173   IF c% = 127 THEN
174    REMark Reset counter
175    POKE adr, 127
176    adr = tpt: tpt = tpt + 1: c% = -1
177   END IF
178  END IF
179 END REPeat dl
180 RETurn 0
181 END DEFine diff
182 :
183 DEFine FuNction same
184 c% = 2
185 REPeat sl
186  fpt = fpt + z%: IF fpt > fbe: RETurn -10
187  nit$ = PEEK$(fpt, z%)
188  IF nit$ <> cit$ THEN
189   REMark End of same run
190   IF c% > 1 THEN
191    POKE tpt, 257 - c%: tpt = tpt + 1: POKE$ tpt, cit$
192    tpt = tpt + z%: IF tpt > tbe: RETurn -5
193   ELSE
194    IF c% = 1: fpt = fpt - z%
195   END IF
196   cit$ = nit$
197   EXIT sl
198  ELSE
199   c% = c% + 1
200   IF c% = 128 THEN
201    REMark Reset counter
202    POKE tpt, 129: tpt = tpt + 1: POKE$ tpt, cit$
203    tpt = tpt + z%: IF tpt > tbe: RETurn -5
204    c% = 0
205   END IF
206  END IF
207 END REPeat sl
208 RETurn 0
209 END DEFine same
210 :
211 :
  
Generated with sb2htm on 2019 Mar 21
©pjwitte March 2oi9