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