view src/com/aurellem/capture/MicrosoftRLEEncoder.java @ 6:0634e72bad3e

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