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