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 getting cats to march in a parade; 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 (and also an assembler version of DoRLE, called ENRLE) may be found 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$ = 'ram1_'
19 tdr$ = 'ram1_32_'
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 rem     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:             rem (re-use this LOCal)
80 IF spu: RETurn -19:                     rem Only pattern supported
81 IF (ct% && 64) = 64: RETurn -8:         rem Already compressed
82 IF ct% <>  0:      RETurn -19:          rem Only single, simple sprite allowed
83 :
84 rem Get colour mode and work out compression code
85 SELect ON md%
86  = 528:      md% = 1:                   rem GD2-16
87  = 544, 545: md% = 2:                   rem GD2-32/33
88  = 576:      md% = 4:                   rem GD2-64
89  = REMAINDER : RETurn -19:              rem Unsupported mode
90 END SELect
91 :
92 rem     Create suitable buffer and load sprite
93 spu = ALCHP(fl + fl)
94 spc = spu + fl
95 LBYTES fnm$, spu
96 :
97 rem     Copy sprite header and patch for compressed
98 POKE$ spc, PEEK$(spu, 24)
99 POKE spc + 3, PEEK(spc + 3) + 64:       rem 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 | V0.02, pjw, 2020 Dec 11, bug fix: same loop didnt terminate properly     |
133 rem + ------------------------------------------------------------------------ +
134 :
135 DEFine FuNction DoRLE(fad, tad, osz, z%)
136 LOCal lp, dl, sl, er, fbe, tbe, fpt, tpt
137 LOCal c%, adr, cit$(4), nit$(4)
138 :
139 rem     Set header
140 POKE$ tad, 'RLE'
141 POKE tad + 3, z% + 48
142 POKE_L tad + 4, osz
143 :
144 rem     Init
145 fbe = fad + osz:                        rem From Buffer End
146 tbe = tad + osz:                        rem To Buffer End
147 fpt = fad:                              rem From running PoinTer
148 tpt = tad + 8:                          rem To running PoinTer
149 :
150 rem     RUN
151 cit$ = PEEK$(fpt, z%):                  rem Current ITem
152 REPeat lp
153  er = diff: IF er < 0: EXIT lp:         rem Different Loop
154  er = same: IF er < 0: EXIT lp:         rem Same Loop
155 END REPeat lp
156 IF er = -5: RETurn er:                  rem Buffer overrun
157 RETurn tpt - tad:                       rem Return compressed data size
158 END DEFine DoRLE
159 :
160 DEFine FuNction diff
161 adr = tpt: tpt = tpt + 1: c% = -1
162 REPeat dl
163  fpt = fpt + z%: IF fpt > fbe: POKE adr, c%: RETurn -10
164  nit$ = PEEK$(fpt, z%)
165  IF nit$ = cit$ THEN
166   rem End of differences
167   IF c% > -1: POKE adr, c%: ELSE : tpt = adr
168   EXIT dl
169  ELSE
170   IF tpt > tbe: RETurn -5
171   POKE$ tpt, cit$
172   tpt = tpt + z%: c% = c% + 1
173   cit$ = nit$
174   IF c% = 127 THEN
175    rem Reset counter
176    POKE adr, 127
177    adr = tpt: tpt = tpt + 1: c% = -1
178   END IF
179  END IF
180 END REPeat dl
181 RETurn 0
182 END DEFine diff
183 :
184 DEFine FuNction same
185 c% = 2
186 REPeat sl
187  fpt = fpt + z%
188  IF fpt > fbe THEN
189   POKE tpt, 257 - c%: tpt = tpt + 1: POKE$ tpt, cit$
190   RETurn -10
191  END IF
192  nit$ = PEEK$(fpt, z%)
193  IF nit$ <> cit$ THEN
194   rem End of same run
195   IF c% > 1 THEN
196    POKE tpt, 257 - c%: tpt = tpt + 1: POKE$ tpt, cit$
197    tpt = tpt + z%: IF tpt > tbe: RETurn -5
198   ELSE
199    IF c% = 1: fpt = fpt - z%
200   END IF
201   cit$ = nit$
202   EXIT sl
203  ELSE
204   c% = c% + 1
205   IF c% = 128 THEN
206    rem Reset counter
207    POKE tpt, 129: tpt = tpt + 1: POKE$ tpt, cit$
208    tpt = tpt + z%: IF tpt > tbe: RETurn -5
209    c% = 0
210   END IF
211  END IF
212 END REPeat sl
213 RETurn 0
214 END DEFine same
215 :
216 :

  
Generated with sb2htm on 2020 Dec 14
©pjwitte March 2oi9