rlm@3
|
1 /*
|
rlm@3
|
2 * @(#)AppleRLEEncoder.java 1.1.1 2011-01-17
|
rlm@3
|
3 *
|
rlm@3
|
4 * Copyright © 2011 Werner Randelshofer, Immensee, Switzerland.
|
rlm@3
|
5 * All rights reserved.
|
rlm@3
|
6 *
|
rlm@3
|
7 * You may not use, copy or modify this file, except in compliance with the
|
rlm@3
|
8 * license agreement you entered into with Werner Randelshofer.
|
rlm@3
|
9 * For details see accompanying license terms.
|
rlm@3
|
10 */
|
rlm@3
|
11 package com.aurellem.capture;
|
rlm@3
|
12
|
rlm@3
|
13 import java.io.ByteArrayOutputStream;
|
rlm@3
|
14 import java.io.IOException;
|
rlm@3
|
15 import java.io.OutputStream;
|
rlm@3
|
16 import java.util.Arrays;
|
rlm@3
|
17
|
rlm@3
|
18 /**
|
rlm@3
|
19 * Implements the run length encoding of the Microsoft RLE format.
|
rlm@3
|
20 * <p>
|
rlm@3
|
21 * Each line of a frame is compressed individually. A line consists of two-byte
|
rlm@3
|
22 * op-codes optionally followed by data. The end of the line is marked with
|
rlm@3
|
23 * the EOL op-code.
|
rlm@3
|
24 * <p>
|
rlm@3
|
25 * The following op-codes are supported:
|
rlm@3
|
26 * <ul>
|
rlm@3
|
27 * <li>{@code 0x00 0x00}
|
rlm@3
|
28 * <br>Marks the end of a line.</li>
|
rlm@3
|
29 *
|
rlm@3
|
30 * <li>{@code 0x00 0x01}
|
rlm@3
|
31 * <br>Marks the end of the bitmap.</li>
|
rlm@3
|
32 *
|
rlm@3
|
33 * <li>{@code 0x00 0x02 x y}
|
rlm@3
|
34 * <br> Marks a delta (skip). {@code x} and {@code y}
|
rlm@3
|
35 * indicate the horizontal and vertical offset from the current position.
|
rlm@3
|
36 * {@code x} and {@code y} are unsigned 8-bit values.</li>
|
rlm@3
|
37 *
|
rlm@3
|
38 * <li>{@code 0x00 n data{n} 0x00?}
|
rlm@3
|
39 * <br> Marks a literal run. {@code n}
|
rlm@3
|
40 * gives the number of data bytes that follow. {@code n} must be between 3 and
|
rlm@3
|
41 * 255. If n is odd, a pad byte with the value 0x00 must be added.
|
rlm@3
|
42 * </li>
|
rlm@3
|
43 * <li>{@code n data}
|
rlm@3
|
44 * <br> Marks a repetition. {@code n}
|
rlm@3
|
45 * gives the number of times the data byte is repeated. {@code n} must be
|
rlm@3
|
46 * between 1 and 255.
|
rlm@3
|
47 * </li>
|
rlm@3
|
48 * </ul>
|
rlm@3
|
49 * Example:
|
rlm@3
|
50 * <pre>
|
rlm@3
|
51 * Compressed data Expanded data
|
rlm@3
|
52 *
|
rlm@3
|
53 * 03 04 04 04 04
|
rlm@3
|
54 * 05 06 06 06 06 06 06
|
rlm@3
|
55 * 00 03 45 56 67 00 45 56 67
|
rlm@3
|
56 * 02 78 78 78
|
rlm@3
|
57 * 00 02 05 01 Move 5 right and 1 down
|
rlm@3
|
58 * 02 78 78 78
|
rlm@3
|
59 * 00 00 End of line
|
rlm@3
|
60 * 09 1E 1E 1E 1E 1E 1E 1E 1E 1E 1E
|
rlm@3
|
61 * 00 01 End of RLE bitmap
|
rlm@3
|
62 * </pre>
|
rlm@3
|
63 *
|
rlm@3
|
64 * References:<br/>
|
rlm@3
|
65 * <a href="http://wiki.multimedia.cx/index.php?title=Microsoft_RLE">http://wiki.multimedia.cx/index.php?title=Microsoft_RLE</a><br>
|
rlm@3
|
66 *
|
rlm@3
|
67 * @author Werner Randelshofer
|
rlm@3
|
68 * @version 1.1.1 2011-01-17 Removes unused imports.
|
rlm@3
|
69 * <br>1.1 2011-01-07 Improves performance.
|
rlm@3
|
70 * <br>1.0 2011-01-05 Created.
|
rlm@3
|
71 */
|
rlm@3
|
72 public class MicrosoftRLEEncoder {
|
rlm@3
|
73
|
rlm@3
|
74 private SeekableByteArrayOutputStream tempSeek=new SeekableByteArrayOutputStream();
|
rlm@3
|
75 private DataChunkOutputStream temp=new DataChunkOutputStream(tempSeek);
|
rlm@3
|
76
|
rlm@3
|
77 /** Encodes a 8-bit key frame.
|
rlm@3
|
78 *
|
rlm@3
|
79 * @param temp The output stream. Must be set to Big-Endian.
|
rlm@3
|
80 * @param data The image data.
|
rlm@3
|
81 * @param offset The offset to the first pixel in the data array.
|
rlm@3
|
82 * @param length The width of the image in data elements.
|
rlm@3
|
83 * @param step The number to add to offset to get to the next scanline.
|
rlm@3
|
84 */
|
rlm@3
|
85 public void writeKey8(OutputStream out, byte[] data, int offset, int length, int step, int height)
|
rlm@3
|
86 throws IOException {
|
rlm@3
|
87 tempSeek.reset();
|
rlm@3
|
88 int ymax = offset + height * step;
|
rlm@3
|
89 int upsideDown = ymax-step+offset;
|
rlm@3
|
90
|
rlm@3
|
91 // Encode each scanline separately
|
rlm@3
|
92 for (int y = offset; y < ymax; y += step) {
|
rlm@3
|
93 int xy = upsideDown-y;
|
rlm@3
|
94 int xymax = xy + length;
|
rlm@3
|
95
|
rlm@3
|
96 int literalCount = 0;
|
rlm@3
|
97 int repeatCount = 0;
|
rlm@3
|
98 for (; xy < xymax; ++xy) {
|
rlm@3
|
99 // determine repeat count
|
rlm@3
|
100 byte v = data[xy];
|
rlm@3
|
101 for (repeatCount = 0; xy < xymax && repeatCount < 255; ++xy, ++repeatCount) {
|
rlm@3
|
102 if (data[xy] != v) {
|
rlm@3
|
103 break;
|
rlm@3
|
104 }
|
rlm@3
|
105 }
|
rlm@3
|
106 xy -= repeatCount;
|
rlm@3
|
107 if (repeatCount < 3) {
|
rlm@3
|
108 literalCount++;
|
rlm@3
|
109 if (literalCount == 254) {
|
rlm@3
|
110 temp.write(0);temp.write(literalCount); // Literal OP-code
|
rlm@3
|
111 temp.write(data, xy - literalCount + 1, literalCount);
|
rlm@3
|
112 literalCount = 0;
|
rlm@3
|
113 }
|
rlm@3
|
114 } else {
|
rlm@3
|
115 if (literalCount > 0) {
|
rlm@3
|
116 if (literalCount < 3) {
|
rlm@3
|
117 for (; literalCount > 0; --literalCount) {
|
rlm@3
|
118 temp.write(1); // Repeat OP-code
|
rlm@3
|
119 temp.write(data[xy - literalCount]);
|
rlm@3
|
120 }
|
rlm@3
|
121 } else {
|
rlm@3
|
122 temp.write(0);temp.write(literalCount); // Literal OP-code
|
rlm@3
|
123 temp.write(data, xy - literalCount, literalCount);
|
rlm@3
|
124 if (literalCount % 2 == 1) {
|
rlm@3
|
125 temp.write(0); // pad byte
|
rlm@3
|
126 }
|
rlm@3
|
127 literalCount = 0;
|
rlm@3
|
128 }
|
rlm@3
|
129 }
|
rlm@3
|
130 temp.write(repeatCount); // Repeat OP-code
|
rlm@3
|
131 temp.write(v);
|
rlm@3
|
132 xy += repeatCount - 1;
|
rlm@3
|
133 }
|
rlm@3
|
134 }
|
rlm@3
|
135
|
rlm@3
|
136 // flush literal run
|
rlm@3
|
137 if (literalCount > 0) {
|
rlm@3
|
138 if (literalCount < 3) {
|
rlm@3
|
139 for (; literalCount > 0; --literalCount) {
|
rlm@3
|
140 temp.write(1); // Repeat OP-code
|
rlm@3
|
141 temp.write(data[xy - literalCount]);
|
rlm@3
|
142 }
|
rlm@3
|
143 } else {
|
rlm@3
|
144 temp.write(0);temp.write(literalCount);
|
rlm@3
|
145 temp.write(data, xy - literalCount, literalCount);
|
rlm@3
|
146 if (literalCount % 2 == 1) {
|
rlm@3
|
147 temp.write(0); // pad byte
|
rlm@3
|
148 }
|
rlm@3
|
149 }
|
rlm@3
|
150 literalCount = 0;
|
rlm@3
|
151 }
|
rlm@3
|
152
|
rlm@3
|
153 temp.write(0);temp.write(0x0000);// End of line
|
rlm@3
|
154 }
|
rlm@3
|
155 temp.write(0);temp.write(0x0001);// End of bitmap
|
rlm@3
|
156 tempSeek.toOutputStream(out);
|
rlm@3
|
157 }
|
rlm@3
|
158
|
rlm@3
|
159 /** Encodes a 8-bit delta frame.
|
rlm@3
|
160 *
|
rlm@3
|
161 * @param temp The output stream. Must be set to Big-Endian.
|
rlm@3
|
162 * @param data The image data.
|
rlm@3
|
163 * @param prev The image data of the previous frame.
|
rlm@3
|
164 * @param offset The offset to the first pixel in the data array.
|
rlm@3
|
165 * @param length The width of the image in data elements.
|
rlm@3
|
166 * @param step The number to add to offset to get to the next scanline.
|
rlm@3
|
167 */
|
rlm@3
|
168 public void writeDelta8(OutputStream out, byte[] data, byte[] prev, int offset, int length, int step, int height)
|
rlm@3
|
169 throws IOException {
|
rlm@3
|
170
|
rlm@3
|
171 tempSeek.reset();
|
rlm@3
|
172 // Determine whether we can skip lines at the beginning
|
rlm@3
|
173 int ymin;
|
rlm@3
|
174 int ymax = offset + height * step;
|
rlm@3
|
175 int upsideDown = ymax-step+offset;
|
rlm@3
|
176 scanline:
|
rlm@3
|
177 for (ymin = offset; ymin < ymax; ymin += step) {
|
rlm@3
|
178 int xy = upsideDown-ymin;
|
rlm@3
|
179 int xymax = xy + length;
|
rlm@3
|
180 for (; xy < xymax; ++xy) {
|
rlm@3
|
181 if (data[xy] != prev[xy]) {
|
rlm@3
|
182 break scanline;
|
rlm@3
|
183 }
|
rlm@3
|
184 }
|
rlm@3
|
185 }
|
rlm@3
|
186
|
rlm@3
|
187 if (ymin == ymax) {
|
rlm@3
|
188 // => Frame is identical to previous one
|
rlm@3
|
189 temp.write(0);temp.write(0x0001); // end of bitmap
|
rlm@3
|
190 return;
|
rlm@3
|
191 }
|
rlm@3
|
192
|
rlm@3
|
193 if (ymin > offset) {
|
rlm@3
|
194 int verticalOffset = ymin / step;
|
rlm@3
|
195 while (verticalOffset > 255) {
|
rlm@3
|
196 temp.write(0);temp.write(0x0002); // Skip OP-code
|
rlm@3
|
197 temp.write(0); // horizontal offset
|
rlm@3
|
198 temp.write(255); // vertical offset
|
rlm@3
|
199 verticalOffset -= 255;
|
rlm@3
|
200 }
|
rlm@3
|
201 if (verticalOffset == 1) {
|
rlm@3
|
202 temp.write(0);temp.write(0x0000); // End of line OP-code
|
rlm@3
|
203 } else {
|
rlm@3
|
204 temp.write(0);temp.write(0x0002); // Skip OP-code
|
rlm@3
|
205 temp.write(0); // horizontal offset
|
rlm@3
|
206 temp.write(verticalOffset); // vertical offset
|
rlm@3
|
207 }
|
rlm@3
|
208 }
|
rlm@3
|
209
|
rlm@3
|
210
|
rlm@3
|
211 // Determine whether we can skip lines at the end
|
rlm@3
|
212 scanline:
|
rlm@3
|
213 for (; ymax > ymin; ymax -= step) {
|
rlm@3
|
214 int xy = upsideDown-ymax+step;
|
rlm@3
|
215 int xymax = xy + length;
|
rlm@3
|
216 for (; xy < xymax; ++xy) {
|
rlm@3
|
217 if (data[xy] != prev[xy]) {
|
rlm@3
|
218 break scanline;
|
rlm@3
|
219 }
|
rlm@3
|
220 }
|
rlm@3
|
221 }
|
rlm@3
|
222 //System.out.println("MicrosoftRLEEncoder ymin:" + ymin / step + " ymax" + ymax / step);
|
rlm@3
|
223
|
rlm@3
|
224
|
rlm@3
|
225 // Encode each scanline
|
rlm@3
|
226 int verticalOffset = 0;
|
rlm@3
|
227 for (int y = ymin; y < ymax; y += step) {
|
rlm@3
|
228 int xy = upsideDown-y;
|
rlm@3
|
229 int xymax = xy + length;
|
rlm@3
|
230
|
rlm@3
|
231 // determine skip count
|
rlm@3
|
232 int skipCount = 0;
|
rlm@3
|
233 for (; xy < xymax; ++xy, ++skipCount) {
|
rlm@3
|
234 if (data[xy] != prev[xy]) {
|
rlm@3
|
235 break;
|
rlm@3
|
236 }
|
rlm@3
|
237 }
|
rlm@3
|
238 if (skipCount == length) {
|
rlm@3
|
239 // => the entire line can be skipped
|
rlm@3
|
240 ++verticalOffset;
|
rlm@3
|
241 if (verticalOffset == 255) {
|
rlm@3
|
242 temp.write(0);temp.write(0x0002); // Skip OP-code
|
rlm@3
|
243 temp.write(0); // horizontal offset
|
rlm@3
|
244 temp.write(255); // vertical offset
|
rlm@3
|
245 verticalOffset = 0;
|
rlm@3
|
246 }
|
rlm@3
|
247 continue;
|
rlm@3
|
248 }
|
rlm@3
|
249
|
rlm@3
|
250 if (verticalOffset > 0 || skipCount > 0) {
|
rlm@3
|
251 if (verticalOffset == 1 && skipCount == 0) {
|
rlm@3
|
252 temp.write(0);temp.write(0x0000); // End of line OP-code
|
rlm@3
|
253 } else {
|
rlm@3
|
254 temp.write(0);temp.write(0x0002); // Skip OP-code
|
rlm@3
|
255 temp.write(Math.min(255, skipCount)); // horizontal offset
|
rlm@3
|
256 skipCount -= 255;
|
rlm@3
|
257 temp.write(verticalOffset); // vertical offset
|
rlm@3
|
258 }
|
rlm@3
|
259 verticalOffset = 0;
|
rlm@3
|
260 }
|
rlm@3
|
261 while (skipCount > 0) {
|
rlm@3
|
262 temp.write(0);temp.write(0x0002); // Skip OP-code
|
rlm@3
|
263 temp.write(Math.min(255, skipCount)); // horizontal offset
|
rlm@3
|
264 temp.write(0); // vertical offset
|
rlm@3
|
265 skipCount -= 255;
|
rlm@3
|
266 }
|
rlm@3
|
267
|
rlm@3
|
268 int literalCount = 0;
|
rlm@3
|
269 int repeatCount = 0;
|
rlm@3
|
270 for (; xy < xymax; ++xy) {
|
rlm@3
|
271 // determine skip count
|
rlm@3
|
272 for (skipCount = 0; xy < xymax; ++xy, ++skipCount) {
|
rlm@3
|
273 if (data[xy] != prev[xy]) {
|
rlm@3
|
274 break;
|
rlm@3
|
275 }
|
rlm@3
|
276 }
|
rlm@3
|
277 xy -= skipCount;
|
rlm@3
|
278
|
rlm@3
|
279 // determine repeat count
|
rlm@3
|
280 byte v = data[xy];
|
rlm@3
|
281 for (repeatCount = 0; xy < xymax && repeatCount < 255; ++xy, ++repeatCount) {
|
rlm@3
|
282 if (data[xy] != v) {
|
rlm@3
|
283 break;
|
rlm@3
|
284 }
|
rlm@3
|
285 }
|
rlm@3
|
286 xy -= repeatCount;
|
rlm@3
|
287
|
rlm@3
|
288 if (skipCount < 4 && xy + skipCount < xymax && repeatCount < 3) {
|
rlm@3
|
289 literalCount++;
|
rlm@3
|
290 if (literalCount == 254) {
|
rlm@3
|
291 temp.write(0);temp.write(literalCount); // Literal OP-code
|
rlm@3
|
292 temp.write(data, xy - literalCount + 1, literalCount);
|
rlm@3
|
293 literalCount = 0;
|
rlm@3
|
294 }
|
rlm@3
|
295 } else {
|
rlm@3
|
296 if (literalCount > 0) {
|
rlm@3
|
297 if (literalCount < 3) {
|
rlm@3
|
298 for (; literalCount > 0; --literalCount) {
|
rlm@3
|
299 temp.write(1); // Repeat OP-code
|
rlm@3
|
300 temp.write(data[xy - literalCount]);
|
rlm@3
|
301 }
|
rlm@3
|
302 } else {
|
rlm@3
|
303 temp.write(0);temp.write(literalCount);
|
rlm@3
|
304 temp.write(data, xy - literalCount, literalCount);
|
rlm@3
|
305 if (literalCount % 2 == 1) {
|
rlm@3
|
306 temp.write(0); // pad byte
|
rlm@3
|
307 }
|
rlm@3
|
308 }
|
rlm@3
|
309 literalCount = 0;
|
rlm@3
|
310 }
|
rlm@3
|
311 if (xy + skipCount == xymax) {
|
rlm@3
|
312 // => we can skip until the end of the line without
|
rlm@3
|
313 // having to write an op-code
|
rlm@3
|
314 xy += skipCount - 1;
|
rlm@3
|
315 } else if (skipCount >= repeatCount) {
|
rlm@3
|
316 while (skipCount > 255) {
|
rlm@3
|
317 temp.write(0);temp.write(0x0002); // Skip OP-code
|
rlm@3
|
318 temp.write(255);
|
rlm@3
|
319 temp.write(0);
|
rlm@3
|
320 xy += 255;
|
rlm@3
|
321 skipCount -= 255;
|
rlm@3
|
322 }
|
rlm@3
|
323 temp.write(0);temp.write(0x0002); // Skip OP-code
|
rlm@3
|
324 temp.write(skipCount);
|
rlm@3
|
325 temp.write(0);
|
rlm@3
|
326 xy += skipCount - 1;
|
rlm@3
|
327 } else {
|
rlm@3
|
328 temp.write(repeatCount); // Repeat OP-code
|
rlm@3
|
329 temp.write(v);
|
rlm@3
|
330 xy += repeatCount - 1;
|
rlm@3
|
331 }
|
rlm@3
|
332 }
|
rlm@3
|
333 }
|
rlm@3
|
334
|
rlm@3
|
335 // flush literal run
|
rlm@3
|
336 if (literalCount > 0) {
|
rlm@3
|
337 if (literalCount < 3) {
|
rlm@3
|
338 for (; literalCount > 0; --literalCount) {
|
rlm@3
|
339 temp.write(1); // Repeat OP-code
|
rlm@3
|
340 temp.write(data[xy - literalCount]);
|
rlm@3
|
341 }
|
rlm@3
|
342 } else {
|
rlm@3
|
343 temp.write(0);temp.write(literalCount);
|
rlm@3
|
344 temp.write(data, xy - literalCount, literalCount);
|
rlm@3
|
345 if (literalCount % 2 == 1) {
|
rlm@3
|
346 temp.write(0); // pad byte
|
rlm@3
|
347 }
|
rlm@3
|
348 }
|
rlm@3
|
349 }
|
rlm@3
|
350
|
rlm@3
|
351 temp.write(0);temp.write(0x0000); // End of line OP-code
|
rlm@3
|
352 }
|
rlm@3
|
353
|
rlm@3
|
354 temp.write(0);temp.write(0x0001);// End of bitmap
|
rlm@3
|
355 tempSeek.toOutputStream(out);
|
rlm@3
|
356 }
|
rlm@3
|
357
|
rlm@3
|
358 public static void main(String[] args) {
|
rlm@3
|
359 byte[] data = {//
|
rlm@3
|
360 8, 2, 3, 4, 4, 3,7,7,7, 8,//
|
rlm@3
|
361 8, 1, 1, 1, 1, 2,7,7,7, 8,//
|
rlm@3
|
362 8, 0, 2, 0, 0, 0,7,7,7, 8,//
|
rlm@3
|
363 8, 2, 2, 3, 4, 4,7,7,7, 8,//
|
rlm@3
|
364 8, 1, 4, 4, 4, 5,7,7,7, 8};
|
rlm@3
|
365
|
rlm@3
|
366
|
rlm@3
|
367 byte[] prev = {//
|
rlm@3
|
368 8, 3, 3, 3, 3, 3,7,7,7, 8,//
|
rlm@3
|
369 8, 1, 1, 1, 1, 1,7,7,7, 8, //
|
rlm@3
|
370 8, 5, 5, 5, 5, 0,7,7,7, 8,//
|
rlm@3
|
371 8, 2, 2, 0, 0, 0,7,7,7, 8,//
|
rlm@3
|
372 8, 2, 0, 0, 0, 5,7,7,7, 8};
|
rlm@3
|
373 ByteArrayOutputStream buf = new ByteArrayOutputStream();
|
rlm@3
|
374 DataChunkOutputStream out = new DataChunkOutputStream(buf);
|
rlm@3
|
375 MicrosoftRLEEncoder enc = new MicrosoftRLEEncoder();
|
rlm@3
|
376
|
rlm@3
|
377 try {
|
rlm@3
|
378 enc.writeDelta8(out, data, prev, 1, 8, 10, 5);
|
rlm@3
|
379 //enc.writeKey8(out, data, 1, 8, 10,5);
|
rlm@3
|
380 out.close();
|
rlm@3
|
381
|
rlm@3
|
382 byte[] result = buf.toByteArray();
|
rlm@3
|
383 System.out.println("size:" + result.length);
|
rlm@3
|
384 System.out.println(Arrays.toString(result));
|
rlm@3
|
385 System.out.print("0x [");
|
rlm@3
|
386
|
rlm@3
|
387 for (int i = 0; i < result.length; i++) {
|
rlm@3
|
388 if (i != 0) {
|
rlm@3
|
389 System.out.print(',');
|
rlm@3
|
390 }
|
rlm@3
|
391 String hex = "00" + Integer.toHexString(result[i]);
|
rlm@3
|
392 System.out.print(hex.substring(hex.length() - 2));
|
rlm@3
|
393 }
|
rlm@3
|
394 System.out.println(']');
|
rlm@3
|
395
|
rlm@3
|
396 } catch (IOException ex) {
|
rlm@3
|
397 ex.printStackTrace();
|
rlm@3
|
398 }
|
rlm@3
|
399 }
|
rlm@3
|
400 }
|