blob: 4e9969c7247a972a64e895d8e24dab48a54a4c8c [file] [log] [blame]
Adam Tkacf53e62a2009-03-13 13:20:26 +00001package com.tigervnc.decoder;
enikeyf7160bd2008-12-19 07:54:40 +00002
Adam Tkacf53e62a2009-03-13 13:20:26 +00003import com.tigervnc.decoder.common.Repaintable;
4import com.tigervnc.vncviewer.RfbInputStream;
enikeyf7160bd2008-12-19 07:54:40 +00005import java.awt.Graphics;
6import java.awt.Color;
7import java.awt.Image;
8import java.awt.Rectangle;
9import java.awt.Toolkit;
10import java.awt.image.ImageObserver;
enikey98232912008-12-25 09:51:03 +000011import java.io.IOException;
12import java.util.zip.Deflater;
enikeyf7160bd2008-12-19 07:54:40 +000013import java.util.zip.Inflater;
14
15//
16// Class that used for decoding Tight encoded data.
17//
18
enikey1893c9a2008-12-19 08:16:45 +000019public class TightDecoder extends RawDecoder implements ImageObserver {
enikeyf7160bd2008-12-19 07:54:40 +000020
enikey6558a302008-12-24 05:14:30 +000021 final static int EncodingTight = 7;
22
enikeyf7160bd2008-12-19 07:54:40 +000023 //
24 // Tight decoder constants
25 //
26
27 final static int TightExplicitFilter = 0x04;
28 final static int TightFill = 0x08;
29 final static int TightJpeg = 0x09;
30 final static int TightMaxSubencoding = 0x09;
31 final static int TightFilterCopy = 0x00;
32 final static int TightFilterPalette = 0x01;
33 final static int TightFilterGradient = 0x02;
34 final static int TightMinToCompress = 12;
35
36 // Tight encoder's data.
37 final static int tightZlibBufferSize = 512;
38
39 public TightDecoder(Graphics g, RfbInputStream is) {
40 super(g, is);
41 tightInflaters = new Inflater[4];
42 }
43
44 public TightDecoder(Graphics g, RfbInputStream is, int frameBufferW,
45 int frameBufferH) {
46 super(g, is, frameBufferW, frameBufferH);
47 tightInflaters = new Inflater[4];
48 }
49
50 //
51 // Set and get methods for private TightDecoder
52 //
53
54 public void setRepainableControl(Repaintable r) {
55 repainatableControl = r;
56 }
57
58 //
59 // JPEG processing statistic methods
60 //
61
enikey191695b2008-12-24 09:09:31 +000062 public long getNumJPEGRects() {
enikeyf7160bd2008-12-19 07:54:40 +000063 return statNumRectsTightJPEG;
64 }
65
66 public void setNumJPEGRects(int v) {
67 statNumRectsTightJPEG = v;
68 }
69
70 //
enikey1214ab12008-12-24 09:01:19 +000071 // Tight processing statistic methods
72 //
73
enikey191695b2008-12-24 09:09:31 +000074 public long getNumTightRects() {
enikey1214ab12008-12-24 09:01:19 +000075 return statNumRectsTight;
76 }
77
78 public void setNumTightRects(int v) {
79 statNumRectsTight = v;
80 }
81
82 //
enikey1893c9a2008-12-19 08:16:45 +000083 // Handle a Tight-encoded rectangle.
84 //
85
86 public void handleRect(int x, int y, int w, int h) throws Exception {
87
enikey2f0294e2008-12-24 08:18:54 +000088 //
89 // Write encoding ID to record output stream
90 //
91
92 if (dos != null) {
93 dos.writeInt(TightDecoder.EncodingTight);
94 }
95
enikey1893c9a2008-12-19 08:16:45 +000096 int comp_ctl = rfbis.readU8();
enikeyfd22cd62008-12-25 09:42:32 +000097
98 if (dos != null) {
99 // Tell the decoder to flush each of the four zlib streams.
100 dos.writeByte(comp_ctl | 0x0F);
enikey1893c9a2008-12-19 08:16:45 +0000101 }
102
103 // Flush zlib streams if we are told by the server to do so.
104 for (int stream_id = 0; stream_id < 4; stream_id++) {
105 if ((comp_ctl & 1) != 0 && tightInflaters[stream_id] != null) {
106 tightInflaters[stream_id] = null;
107 }
108 comp_ctl >>= 1;
109 }
110
111 // Check correctness of subencoding value.
112 if (comp_ctl > TightDecoder.TightMaxSubencoding) {
113 throw new Exception("Incorrect tight subencoding: " + comp_ctl);
114 }
115
116 // Handle solid-color rectangles.
117 if (comp_ctl == TightDecoder.TightFill) {
118
119 if (bytesPerPixel == 1) {
120 int idx = rfbis.readU8();
121 graphics.setColor(getColor256()[idx]);
enikeyfd22cd62008-12-25 09:42:32 +0000122 if (dos != null) {
123 dos.writeByte(idx);
enikey1893c9a2008-12-19 08:16:45 +0000124 }
125 } else {
126 byte[] buf = new byte[3];
127 rfbis.readFully(buf);
enikeyfd22cd62008-12-25 09:42:32 +0000128 if (dos != null) {
129 dos.write(buf);
enikey1893c9a2008-12-19 08:16:45 +0000130 }
131 Color bg = new Color(0xFF000000 | (buf[0] & 0xFF) << 16 |
132 (buf[1] & 0xFF) << 8 | (buf[2] & 0xFF));
133 graphics.setColor(bg);
134 }
135 graphics.fillRect(x, y, w, h);
136 repainatableControl.scheduleRepaint(x, y, w, h);
137 return;
138
139 }
140
141 if (comp_ctl == TightDecoder.TightJpeg) {
142
143 statNumRectsTightJPEG++;
144
145 // Read JPEG data.
146 byte[] jpegData = new byte[rfbis.readCompactLen()];
147 rfbis.readFully(jpegData);
enikeyfd22cd62008-12-25 09:42:32 +0000148 if (dos != null) {
enikey98232912008-12-25 09:51:03 +0000149 recordCompactLen(jpegData.length);
enikeyfd22cd62008-12-25 09:42:32 +0000150 dos.write(jpegData);
enikey1893c9a2008-12-19 08:16:45 +0000151 }
152
153 // Create an Image object from the JPEG data.
154 Image jpegImage = Toolkit.getDefaultToolkit().createImage(jpegData);
155
156 // Remember the rectangle where the image should be drawn.
157 jpegRect = new Rectangle(x, y, w, h);
158
159 // Let the imageUpdate() method do the actual drawing, here just
160 // wait until the image is fully loaded and drawn.
161 synchronized(jpegRect) {
162 Toolkit.getDefaultToolkit().prepareImage(jpegImage, -1, -1, this);
163 try {
164 // Wait no longer than three seconds.
165 jpegRect.wait(3000);
166 } catch (InterruptedException e) {
167 throw new Exception("Interrupted while decoding JPEG image");
168 }
169 }
170
171 // Done, jpegRect is not needed any more.
172 jpegRect = null;
173 return;
174
enikey1214ab12008-12-24 09:01:19 +0000175 } else {
176 statNumRectsTight++;
enikey1893c9a2008-12-19 08:16:45 +0000177 }
178
179 // Read filter id and parameters.
180 int numColors = 0, rowSize = w;
181 byte[] palette8 = new byte[2];
182 int[] palette24 = new int[256];
183 boolean useGradient = false;
184 if ((comp_ctl & TightDecoder.TightExplicitFilter) != 0) {
185 int filter_id = rfbis.readU8();
enikeyfd22cd62008-12-25 09:42:32 +0000186 if (dos != null) {
187 dos.writeByte(filter_id);
enikey1893c9a2008-12-19 08:16:45 +0000188 }
189 if (filter_id == TightDecoder.TightFilterPalette) {
190 numColors = rfbis.readU8() + 1;
enikeyfd22cd62008-12-25 09:42:32 +0000191 if (dos != null) {
192 dos.writeByte((numColors - 1));
enikey1893c9a2008-12-19 08:16:45 +0000193 }
194 if (bytesPerPixel == 1) {
195 if (numColors != 2) {
196 throw new Exception("Incorrect tight palette size: " + numColors);
197 }
198 rfbis.readFully(palette8);
enikeyfd22cd62008-12-25 09:42:32 +0000199 if (dos != null) {
200 dos.write(palette8);
enikey1893c9a2008-12-19 08:16:45 +0000201 }
202 } else {
203 byte[] buf = new byte[numColors * 3];
204 rfbis.readFully(buf);
enikeyfd22cd62008-12-25 09:42:32 +0000205 if (dos != null) {
206 dos.write(buf);
enikey1893c9a2008-12-19 08:16:45 +0000207 }
208 for (int i = 0; i < numColors; i++) {
209 palette24[i] = ((buf[i * 3] & 0xFF) << 16 |
210 (buf[i * 3 + 1] & 0xFF) << 8 |
211 (buf[i * 3 + 2] & 0xFF));
212 }
213 }
214 if (numColors == 2) {
215 rowSize = (w + 7) / 8;
216 }
217 } else if (filter_id == TightDecoder.TightFilterGradient) {
218 useGradient = true;
219 } else if (filter_id != TightDecoder.TightFilterCopy) {
220 throw new Exception("Incorrect tight filter id: " + filter_id);
221 }
222 }
223 if (numColors == 0 && bytesPerPixel == 4)
224 rowSize *= 3;
225
226 // Read, optionally uncompress and decode data.
227 int dataSize = h * rowSize;
228 if (dataSize < TightDecoder.TightMinToCompress) {
229 // Data size is small - not compressed with zlib.
230 if (numColors != 0) {
231 // Indexed colors.
232 byte[] indexedData = new byte[dataSize];
233 rfbis.readFully(indexedData);
enikeyfd22cd62008-12-25 09:42:32 +0000234 if (dos != null) {
235 dos.write(indexedData);
enikey1893c9a2008-12-19 08:16:45 +0000236 }
237 if (numColors == 2) {
238 // Two colors.
239 if (bytesPerPixel == 1) {
240 decodeMonoData(x, y, w, h, indexedData, palette8);
241 } else {
242 decodeMonoData(x, y, w, h, indexedData, palette24);
243 }
244 } else {
245 // 3..255 colors (assuming bytesPixel == 4).
246 int i = 0;
247 for (int dy = y; dy < y + h; dy++) {
248 for (int dx = x; dx < x + w; dx++) {
249 pixels24[dy * framebufferWidth + dx] =
250 palette24[indexedData[i++] & 0xFF];
251 }
252 }
253 }
254 } else if (useGradient) {
255 // "Gradient"-processed data
256 byte[] buf = new byte[w * h * 3];
257 rfbis.readFully(buf);
enikeyfd22cd62008-12-25 09:42:32 +0000258 if (dos != null) {
259 dos.write(buf);
enikey1893c9a2008-12-19 08:16:45 +0000260 }
261 decodeGradientData(x, y, w, h, buf);
262 } else {
263 // Raw truecolor data.
264 if (bytesPerPixel == 1) {
265 for (int dy = y; dy < y + h; dy++) {
266 rfbis.readFully(pixels8, dy * framebufferWidth + x, w);
enikeyfd22cd62008-12-25 09:42:32 +0000267 if (dos != null) {
268 dos.write(pixels8, dy * framebufferWidth + x, w);
enikey1893c9a2008-12-19 08:16:45 +0000269 }
270 }
271 } else {
272 byte[] buf = new byte[w * 3];
273 int i, offset;
274 for (int dy = y; dy < y + h; dy++) {
275 rfbis.readFully(buf);
enikeyfd22cd62008-12-25 09:42:32 +0000276 if (dos != null) {
277 dos.write(buf);
enikey1893c9a2008-12-19 08:16:45 +0000278 }
279 offset = dy * framebufferWidth + x;
280 for (i = 0; i < w; i++) {
281 pixels24[offset + i] =
282 (buf[i * 3] & 0xFF) << 16 |
283 (buf[i * 3 + 1] & 0xFF) << 8 |
284 (buf[i * 3 + 2] & 0xFF);
285 }
286 }
287 }
288 }
289 } else {
290 // Data was compressed with zlib.
291 int zlibDataLen = rfbis.readCompactLen();
292 byte[] zlibData = new byte[zlibDataLen];
293 rfbis.readFully(zlibData);
enikey1893c9a2008-12-19 08:16:45 +0000294 int stream_id = comp_ctl & 0x03;
295 if (tightInflaters[stream_id] == null) {
296 tightInflaters[stream_id] = new Inflater();
297 }
298 Inflater myInflater = tightInflaters[stream_id];
299 myInflater.setInput(zlibData);
300 byte[] buf = new byte[dataSize];
301 myInflater.inflate(buf);
enikeyfd22cd62008-12-25 09:42:32 +0000302 if (dos != null) {
enikey98232912008-12-25 09:51:03 +0000303 recordCompressedData(buf);
enikey1893c9a2008-12-19 08:16:45 +0000304 }
305
306 if (numColors != 0) {
307 // Indexed colors.
308 if (numColors == 2) {
309 // Two colors.
310 if (bytesPerPixel == 1) {
311 decodeMonoData(x, y, w, h, buf, palette8);
312 } else {
313 decodeMonoData(x, y, w, h, buf, palette24);
314 }
315 } else {
316 // More than two colors (assuming bytesPixel == 4).
317 int i = 0;
318 for (int dy = y; dy < y + h; dy++) {
319 for (int dx = x; dx < x + w; dx++) {
320 pixels24[dy * framebufferWidth + dx] =
321 palette24[buf[i++] & 0xFF];
322 }
323 }
324 }
325 } else if (useGradient) {
326 // Compressed "Gradient"-filtered data (assuming bytesPixel == 4).
327 decodeGradientData(x, y, w, h, buf);
328 } else {
329 // Compressed truecolor data.
330 if (bytesPerPixel == 1) {
331 int destOffset = y * framebufferWidth + x;
332 for (int dy = 0; dy < h; dy++) {
333 System.arraycopy(buf, dy * w, pixels8, destOffset, w);
334 destOffset += framebufferWidth;
335 }
336 } else {
337 int srcOffset = 0;
338 int destOffset, i;
339 for (int dy = 0; dy < h; dy++) {
340 myInflater.inflate(buf);
341 destOffset = (y + dy) * framebufferWidth + x;
342 for (i = 0; i < w; i++) {
343 RawDecoder.pixels24[destOffset + i] =
344 (buf[srcOffset] & 0xFF) << 16 |
345 (buf[srcOffset + 1] & 0xFF) << 8 |
346 (buf[srcOffset + 2] & 0xFF);
347 srcOffset += 3;
348 }
349 }
350 }
351 }
352 }
353 handleUpdatedPixels(x, y, w, h);
354 }
355
356 //
enikeya909a902008-12-19 08:04:11 +0000357 // Decode 1bpp-encoded bi-color rectangle (8-bit and 24-bit versions).
358 //
359
360 private void decodeMonoData(int x, int y, int w, int h, byte[] src, byte[] palette) {
361
362 int dx, dy, n;
363 int i = y * framebufferWidth + x;
364 int rowBytes = (w + 7) / 8;
365 byte b;
366
367 for (dy = 0; dy < h; dy++) {
368 for (dx = 0; dx < w / 8; dx++) {
369 b = src[dy*rowBytes+dx];
370 for (n = 7; n >= 0; n--)
371 pixels8[i++] = palette[b >> n & 1];
372 }
373 for (n = 7; n >= 8 - w % 8; n--) {
374 pixels8[i++] = palette[src[dy*rowBytes+dx] >> n & 1];
375 }
376 i += (framebufferWidth - w);
377 }
378 }
379
380 private void decodeMonoData(int x, int y, int w, int h, byte[] src, int[] palette) {
381
382 int dx, dy, n;
383 int i = y * framebufferWidth + x;
384 int rowBytes = (w + 7) / 8;
385 byte b;
386
387 for (dy = 0; dy < h; dy++) {
388 for (dx = 0; dx < w / 8; dx++) {
389 b = src[dy*rowBytes+dx];
390 for (n = 7; n >= 0; n--)
391 pixels24[i++] = palette[b >> n & 1];
392 }
393 for (n = 7; n >= 8 - w % 8; n--) {
394 pixels24[i++] = palette[src[dy*rowBytes+dx] >> n & 1];
395 }
396 i += (framebufferWidth - w);
397 }
398 }
399
400 //
401 // Decode data processed with the "Gradient" filter.
402 //
403
404 private void decodeGradientData (int x, int y, int w, int h, byte[] buf) {
405
406 int dx, dy, c;
407 byte[] prevRow = new byte[w * 3];
408 byte[] thisRow = new byte[w * 3];
409 byte[] pix = new byte[3];
410 int[] est = new int[3];
411
412 int offset = y * framebufferWidth + x;
413
414 for (dy = 0; dy < h; dy++) {
415
416 /* First pixel in a row */
417 for (c = 0; c < 3; c++) {
418 pix[c] = (byte)(prevRow[c] + buf[dy * w * 3 + c]);
419 thisRow[c] = pix[c];
420 }
421 pixels24[offset++] =
422 (pix[0] & 0xFF) << 16 | (pix[1] & 0xFF) << 8 | (pix[2] & 0xFF);
423
424 /* Remaining pixels of a row */
425 for (dx = 1; dx < w; dx++) {
426 for (c = 0; c < 3; c++) {
427 est[c] = ((prevRow[dx * 3 + c] & 0xFF) + (pix[c] & 0xFF) -
428 (prevRow[(dx-1) * 3 + c] & 0xFF));
429 if (est[c] > 0xFF) {
430 est[c] = 0xFF;
431 } else if (est[c] < 0x00) {
432 est[c] = 0x00;
433 }
434 pix[c] = (byte)(est[c] + buf[(dy * w + dx) * 3 + c]);
435 thisRow[dx * 3 + c] = pix[c];
436 }
437 pixels24[offset++] =
438 (pix[0] & 0xFF) << 16 | (pix[1] & 0xFF) << 8 | (pix[2] & 0xFF);
439 }
440
441 System.arraycopy(thisRow, 0, prevRow, 0, w * 3);
442 offset += (framebufferWidth - w);
443 }
444 }
445
446 //
enikey1893c9a2008-12-19 08:16:45 +0000447 // Override the ImageObserver interface method to handle drawing of
448 // JPEG-encoded data.
449 //
450
451 public boolean imageUpdate(Image img, int infoflags,
452 int x, int y, int width, int height) {
453 if ((infoflags & (ALLBITS | ABORT)) == 0) {
454 return true; // We need more image data.
455 } else {
456 // If the whole image is available, draw it now.
457 if ((infoflags & ALLBITS) != 0) {
458 if (jpegRect != null) {
459 synchronized(jpegRect) {
460 graphics.drawImage(img, jpegRect.x, jpegRect.y, null);
461 repainatableControl.scheduleRepaint(jpegRect.x, jpegRect.y,
462 jpegRect.width, jpegRect.height);
463 jpegRect.notify();
464 }
465 }
466 }
467 return false; // All image data was processed.
468 }
469 }
470
471 //
enikey98232912008-12-25 09:51:03 +0000472 // Write an integer in compact representation (1..3 bytes) into the
473 // recorded session file.
474 //
475
476 void recordCompactLen(int len) throws IOException {
477 byte[] buf = new byte[3];
478 int bytes = 0;
479 buf[bytes++] = (byte)(len & 0x7F);
480 if (len > 0x7F) {
481 buf[bytes-1] |= 0x80;
482 buf[bytes++] = (byte)(len >> 7 & 0x7F);
483 if (len > 0x3FFF) {
484 buf[bytes-1] |= 0x80;
485 buf[bytes++] = (byte)(len >> 14 & 0xFF);
486 }
487 }
488 if (dos != null) dos.write(buf, 0, bytes);
489 }
490
491 //
492 // Compress and write the data into the recorded session file.
493 //
494
495 void recordCompressedData(byte[] data, int off, int len) throws IOException {
496 Deflater deflater = new Deflater();
497 deflater.setInput(data, off, len);
498 int bufSize = len + len / 100 + 12;
499 byte[] buf = new byte[bufSize];
500 deflater.finish();
501 int compressedSize = deflater.deflate(buf);
502 recordCompactLen(compressedSize);
503 if (dos != null) dos.write(buf, 0, compressedSize);
504 }
505
506 void recordCompressedData(byte[] data) throws IOException {
507 recordCompressedData(data, 0, data.length);
508 }
509
510 //
enikeyf7160bd2008-12-19 07:54:40 +0000511 // Private members
512 //
513
514 private Inflater[] tightInflaters;
515 // Since JPEG images are loaded asynchronously, we have to remember
516 // their position in the framebuffer. Also, this jpegRect object is
517 // used for synchronization between the rfbThread and a JVM's thread
518 // which decodes and loads JPEG images.
519 private Rectangle jpegRect;
520 private Repaintable repainatableControl = null;
521 // Jpeg decoding statistics
enikey191695b2008-12-24 09:09:31 +0000522 private long statNumRectsTightJPEG = 0;
enikey1214ab12008-12-24 09:01:19 +0000523 // Tight decoding statistics
enikey191695b2008-12-24 09:09:31 +0000524 private long statNumRectsTight = 0;
enikeyf7160bd2008-12-19 07:54:40 +0000525}