blob: 96042cb87b6f92271e0ab7de0eae4ea1ff393292 [file] [log] [blame]
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +00001/* Copyright (C) 2000-2003 Constantin Kaplinsky. All Rights Reserved.
Peter Åstrandd69bcc42011-09-28 12:52:53 +00002 * Copyright 2004-2005 Cendio AB.
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +00003 *
4 * This is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This software is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this software; if not, write to the Free Software
16 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
17 * USA.
18 */
19
20//
21// Tight decoding functions.
22//
23// This file is #included after having set the following macros:
24// BPP - 8, 16 or 32
25// EXTRA_ARGS - optional extra arguments
26// FILL_RECT - fill a rectangle with a single color
27// IMAGE_RECT - draw a rectangle of pixel data from a buffer
28
29#include <rdr/InStream.h>
30#include <rdr/ZlibInStream.h>
31#include <rfb/Exception.h>
32#include <assert.h>
33
34namespace rfb {
35
36// CONCAT2E concatenates its arguments, expanding them if they are macros
37
38#ifndef CONCAT2E
39#define CONCAT2(a,b) a##b
40#define CONCAT2E(a,b) CONCAT2(a,b)
41#endif
42
43#define PIXEL_T rdr::CONCAT2E(U,BPP)
44#define READ_PIXEL CONCAT2E(readOpaque,BPP)
45#define TIGHT_DECODE CONCAT2E(tightDecode,BPP)
46
47#define TIGHT_MIN_TO_COMPRESS 12
48static bool DecompressJpegRect(const Rect& r, rdr::InStream* is,
49 PIXEL_T* buf, CMsgHandler* handler);
50static void FilterGradient(const Rect& r, rdr::InStream* is, int dataSize,
51 PIXEL_T* buf, CMsgHandler* handler);
52#if BPP == 32
53static void FilterGradient24(const Rect& r, rdr::InStream* is, int dataSize,
54 PIXEL_T* buf, CMsgHandler* handler);
55#endif
56
57// Main function implementing Tight decoder
58
59void TIGHT_DECODE (const Rect& r, rdr::InStream* is,
60 rdr::ZlibInStream zis[], PIXEL_T* buf
61#ifdef EXTRA_ARGS
62 , EXTRA_ARGS
63#endif
64 )
65{
66 rdr::U8 *bytebuf = (rdr::U8*) buf;
67 bool cutZeros = false;
68 const rfb::PixelFormat& myFormat = handler->cp.pf();
69#if BPP == 32
Pierre Ossman67b2b2f2009-03-06 10:12:55 +000070 if (myFormat.is888()) {
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +000071 cutZeros = true;
72 }
73#endif
74
75 rdr::U8 comp_ctl = is->readU8();
76
77 // Flush zlib streams if we are told by the server to do so.
78 for (int i = 0; i < 4; i++) {
79 if (comp_ctl & 1) {
80 zis[i].reset();
81 }
82 comp_ctl >>= 1;
83 }
84
85 // "Fill" compression type.
86 if (comp_ctl == rfbTightFill) {
87 PIXEL_T pix;
88 if (cutZeros) {
89 is->readBytes(bytebuf, 3);
Pierre Ossman7c4efd72010-09-30 11:30:20 +000090 myFormat.bufferFromRGB((rdr::U8*)&pix, bytebuf, 1, NULL);
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +000091 } else {
92 pix = is->READ_PIXEL();
93 }
94 FILL_RECT(r, pix);
95 return;
96 }
97
98 // "JPEG" compression type.
99 if (comp_ctl == rfbTightJpeg) {
100 DecompressJpegRect(r, is, buf, handler);
101 return;
102 }
103
104 // Quit on unsupported compression type.
105 if (comp_ctl > rfbTightMaxSubencoding) {
106 throw Exception("TightDecoder: bad subencoding value received");
107 return;
108 }
109
110 // "Basic" compression type.
111 int palSize = 0;
112 static PIXEL_T palette[256];
113 bool useGradient = false;
114
115 if ((comp_ctl & rfbTightExplicitFilter) != 0) {
116 rdr::U8 filterId = is->readU8();
117
118 switch (filterId) {
119 case rfbTightFilterPalette:
120 palSize = is->readU8() + 1;
121 if (cutZeros) {
Pierre Ossman7c4efd72010-09-30 11:30:20 +0000122 rdr::U8 elem[3];
123 for (int i = 0;i < palSize;i++) {
124 is->readBytes(elem, 3);
125 myFormat.bufferFromRGB((rdr::U8*)&palette[i], elem, 1, NULL);
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000126 }
127 } else {
128 for (int i = 0; i < palSize; i++)
129 palette[i] = is->READ_PIXEL();
130 }
131 break;
132 case rfbTightFilterGradient:
133 useGradient = true;
134 break;
135 case rfbTightFilterCopy:
136 break;
137 default:
138 throw Exception("TightDecoder: unknown filter code received");
139 return;
140 }
141 }
142
143 int bppp = BPP;
144 if (palSize != 0) {
145 bppp = (palSize <= 2) ? 1 : 8;
146 } else if (cutZeros) {
147 bppp = 24;
148 }
149
150 // Determine if the data should be decompressed or just copied.
151 int rowSize = (r.width() * bppp + 7) / 8;
152 int dataSize = r.height() * rowSize;
153 int streamId = -1;
154 rdr::InStream *input;
155 if (dataSize < TIGHT_MIN_TO_COMPRESS) {
156 input = is;
157 } else {
158 int length = is->readCompactLength();
159 streamId = comp_ctl & 0x03;
160 zis[streamId].setUnderlying(is, length);
161 input = &zis[streamId];
162 }
163
164 if (palSize == 0) {
165 // Truecolor data
166 if (useGradient) {
167#if BPP == 32
168 if (cutZeros) {
169 FilterGradient24(r, input, dataSize, buf, handler);
170 } else
171#endif
172 {
173 FilterGradient(r, input, dataSize, buf, handler);
174 }
175 } else {
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000176 if (cutZeros) {
Pierre Ossman7c4efd72010-09-30 11:30:20 +0000177 rdr::U8 elem[3];
178 for (int i = 0;i < r.area();i++) {
179 input->readBytes(elem, 3);
180 myFormat.bufferFromRGB((rdr::U8*)&buf[i], elem, 1, NULL);
181 }
182 } else {
183 input->readBytes(buf, dataSize);
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000184 }
185 }
186 } else {
Adam Tkacbc9bb152011-02-21 11:58:06 +0000187 int x, y, b;
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000188 PIXEL_T *ptr = buf;
189 rdr::U8 bits;
190 if (palSize <= 2) {
191 // 2-color palette
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000192 for (y = 0; y < r.height(); y++) {
193 for (x = 0; x < r.width() / 8; x++) {
194 bits = input->readU8();
195 for (b = 7; b >= 0; b--) {
196 *ptr++ = palette[bits >> b & 1];
197 }
198 }
199 if (r.width() % 8 != 0) {
200 bits = input->readU8();
201 for (b = 7; b >= 8 - r.width() % 8; b--) {
202 *ptr++ = palette[bits >> b & 1];
203 }
204 }
205 }
206 } else {
207 // 256-color palette
208 for (y = 0; y < r.height(); y++) {
209 for (x = 0; x < r.width(); x++) {
210 *ptr++ = palette[input->readU8()];
211 }
212 }
213 }
214 }
215
216 IMAGE_RECT(r, buf);
217
218 if (streamId != -1) {
219 zis[streamId].reset();
220 }
221}
222
223static bool
224DecompressJpegRect(const Rect& r, rdr::InStream* is,
225 PIXEL_T* buf, CMsgHandler* handler)
226{
227 struct jpeg_decompress_struct cinfo;
228 struct jpeg_error_mgr jerr;
DRCe420dcb2009-04-06 07:20:34 +0000229 int w = r.width();
230 int h = r.height();
Pierre Ossman6f0ce582009-04-30 11:41:03 +0000231 int pixelsize;
DRCe420dcb2009-04-06 07:20:34 +0000232 rdr::U8 *dstBuf = NULL;
233 bool dstBufIsTemp = false;
234 const rfb::PixelFormat& pf = handler->cp.pf();
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000235
236 // Read length
237 int compressedLen = is->readCompactLength();
238 if (compressedLen <= 0) {
239 throw Exception("Incorrect data received from the server.\n");
240 }
241
242 // Allocate netbuf and read in data
243 rdr::U8* netbuf = new rdr::U8[compressedLen];
244 if (!netbuf) {
245 throw Exception("rfb::tightDecode unable to allocate buffer");
246 }
247 is->readBytes(netbuf, compressedLen);
248
249 // Set up JPEG decompression
250 cinfo.err = jpeg_std_error(&jerr);
251 jpeg_create_decompress(&cinfo);
252 JpegSetSrcManager(&cinfo, (char*)netbuf, compressedLen);
253 jpeg_read_header(&cinfo, TRUE);
DRCe420dcb2009-04-06 07:20:34 +0000254
Pierre Ossman6f0ce582009-04-30 11:41:03 +0000255 cinfo.out_color_space = JCS_RGB;
256 pixelsize = 3;
257
258#ifdef JCS_EXTENSIONS
259 // Try to have libjpeg output directly to our native format
260 if (pf.is888()) {
DRCe420dcb2009-04-06 07:20:34 +0000261 int redShift, greenShift, blueShift;
Pierre Ossman6f0ce582009-04-30 11:41:03 +0000262
DRCe420dcb2009-04-06 07:20:34 +0000263 if(pf.bigEndian) {
264 redShift = 24 - pf.redShift;
265 greenShift = 24 - pf.greenShift;
266 blueShift = 24 - pf.blueShift;
Pierre Ossman6f0ce582009-04-30 11:41:03 +0000267 } else {
DRCe420dcb2009-04-06 07:20:34 +0000268 redShift = pf.redShift;
269 greenShift = pf.greenShift;
270 blueShift = pf.blueShift;
271 }
Pierre Ossman6f0ce582009-04-30 11:41:03 +0000272
273 // libjpeg can only handle some "standard" formats
274 if(redShift == 0 && greenShift == 8 && blueShift == 16)
DRCe420dcb2009-04-06 07:20:34 +0000275 cinfo.out_color_space = JCS_EXT_RGBX;
Pierre Ossman6f0ce582009-04-30 11:41:03 +0000276 if(redShift == 16 && greenShift == 8 && blueShift == 0)
DRCe420dcb2009-04-06 07:20:34 +0000277 cinfo.out_color_space = JCS_EXT_BGRX;
Pierre Ossman6f0ce582009-04-30 11:41:03 +0000278 if(redShift == 24 && greenShift == 16 && blueShift == 8)
DRCe420dcb2009-04-06 07:20:34 +0000279 cinfo.out_color_space = JCS_EXT_XBGR;
Pierre Ossman6f0ce582009-04-30 11:41:03 +0000280 if(redShift == 8 && greenShift == 16 && blueShift == 24)
DRCe420dcb2009-04-06 07:20:34 +0000281 cinfo.out_color_space = JCS_EXT_XRGB;
Pierre Ossman6f0ce582009-04-30 11:41:03 +0000282
283 if (cinfo.out_color_space != JCS_RGB) {
DRCe420dcb2009-04-06 07:20:34 +0000284 dstBuf = (rdr::U8 *)buf;
Pierre Ossman6f0ce582009-04-30 11:41:03 +0000285 pixelsize = 4;
286 }
DRCe420dcb2009-04-06 07:20:34 +0000287 }
Pierre Ossman6f0ce582009-04-30 11:41:03 +0000288#endif
289
290 if (cinfo.out_color_space == JCS_RGB) {
DRCe420dcb2009-04-06 07:20:34 +0000291 dstBuf = new rdr::U8[w * h * pixelsize];
292 dstBufIsTemp = true;
DRCe420dcb2009-04-06 07:20:34 +0000293 }
294
295 JSAMPROW *rowPointer = new JSAMPROW[h];
296 for (int dy = 0; dy < h; dy++)
297 rowPointer[dy] = (JSAMPROW)(&dstBuf[dy * w * pixelsize]);
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000298
299 jpeg_start_decompress(&cinfo);
300 if (cinfo.output_width != (unsigned)r.width() || cinfo.output_height != (unsigned)r.height() ||
DRCe420dcb2009-04-06 07:20:34 +0000301 cinfo.output_components != pixelsize) {
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000302 jpeg_destroy_decompress(&cinfo);
303 throw Exception("Tight Encoding: Wrong JPEG data received.\n");
304 }
305
306 // Decompress
307 const rfb::PixelFormat& myFormat = handler->cp.pf();
308 while (cinfo.output_scanline < cinfo.output_height) {
DRCe420dcb2009-04-06 07:20:34 +0000309 jpeg_read_scanlines(&cinfo, &rowPointer[cinfo.output_scanline],
310 cinfo.output_height - cinfo.output_scanline);
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000311 if (jpegError) {
312 break;
313 }
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000314 }
315
Adam Tkac43d68232010-01-27 12:37:38 +0000316 delete [] rowPointer;
317
DRCe420dcb2009-04-06 07:20:34 +0000318 if (cinfo.out_color_space == JCS_RGB)
319 myFormat.bufferFromRGB((rdr::U8*)buf, dstBuf, w * h);
320
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000321 IMAGE_RECT(r, buf);
322
323 if (!jpegError) {
324 jpeg_finish_decompress(&cinfo);
325 }
326
327 jpeg_destroy_decompress(&cinfo);
328
DRCe420dcb2009-04-06 07:20:34 +0000329 if (dstBufIsTemp) delete [] dstBuf;
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000330 delete [] netbuf;
331
332 return !jpegError;
333}
334
335#if BPP == 32
336
337static void
338FilterGradient24(const Rect& r, rdr::InStream* is, int dataSize,
339 PIXEL_T* buf, CMsgHandler* handler)
340{
341 int x, y, c;
342 static rdr::U8 prevRow[TIGHT_MAX_WIDTH*3];
343 static rdr::U8 thisRow[TIGHT_MAX_WIDTH*3];
344 rdr::U8 pix[3];
345 int est[3];
346
347 memset(prevRow, 0, sizeof(prevRow));
348
349 // Allocate netbuf and read in data
350 rdr::U8 *netbuf = new rdr::U8[dataSize];
351 if (!netbuf) {
352 throw Exception("rfb::tightDecode unable to allocate buffer");
353 }
354 is->readBytes(netbuf, dataSize);
355
356 // Set up shortcut variables
357 const rfb::PixelFormat& myFormat = handler->cp.pf();
358 int rectHeight = r.height();
359 int rectWidth = r.width();
360
361 for (y = 0; y < rectHeight; y++) {
362 /* First pixel in a row */
363 for (c = 0; c < 3; c++) {
364 pix[c] = netbuf[y*rectWidth*3+c] + prevRow[c];
365 thisRow[c] = pix[c];
366 }
Pierre Ossman7c4efd72010-09-30 11:30:20 +0000367 myFormat.bufferFromRGB((rdr::U8*)&buf[y*rectWidth], pix, 1, NULL);
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000368
369 /* Remaining pixels of a row */
370 for (x = 1; x < rectWidth; x++) {
371 for (c = 0; c < 3; c++) {
372 est[c] = prevRow[x*3+c] + pix[c] - prevRow[(x-1)*3+c];
373 if (est[c] > 0xff) {
374 est[c] = 0xff;
375 } else if (est[c] < 0) {
376 est[c] = 0;
377 }
378 pix[c] = netbuf[(y*rectWidth+x)*3+c] + est[c];
379 thisRow[x*3+c] = pix[c];
380 }
Pierre Ossman7c4efd72010-09-30 11:30:20 +0000381 myFormat.bufferFromRGB((rdr::U8*)&buf[y*rectWidth+x], pix, 1, NULL);
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000382 }
383
384 memcpy(prevRow, thisRow, sizeof(prevRow));
385 }
386
387 delete [] netbuf;
388}
389
390#endif
391
392static void
393FilterGradient(const Rect& r, rdr::InStream* is, int dataSize,
394 PIXEL_T* buf, CMsgHandler* handler)
395{
396 int x, y, c;
397 static rdr::U8 prevRow[TIGHT_MAX_WIDTH*sizeof(PIXEL_T)];
398 static rdr::U8 thisRow[TIGHT_MAX_WIDTH*sizeof(PIXEL_T)];
Pierre Ossman67b2b2f2009-03-06 10:12:55 +0000399 rdr::U8 pix[3];
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000400 int est[3];
401
402 memset(prevRow, 0, sizeof(prevRow));
403
404 // Allocate netbuf and read in data
405 PIXEL_T *netbuf = (PIXEL_T*)new rdr::U8[dataSize];
406 if (!netbuf) {
407 throw Exception("rfb::tightDecode unable to allocate buffer");
408 }
409 is->readBytes(netbuf, dataSize);
410
411 // Set up shortcut variables
412 const rfb::PixelFormat& myFormat = handler->cp.pf();
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000413 int rectHeight = r.height();
414 int rectWidth = r.width();
415
416 for (y = 0; y < rectHeight; y++) {
417 /* First pixel in a row */
Pierre Ossman7c4efd72010-09-30 11:30:20 +0000418 myFormat.rgbFromBuffer(pix, (rdr::U8*)&netbuf[y*rectWidth], 1, NULL);
Pierre Ossman67b2b2f2009-03-06 10:12:55 +0000419 for (c = 0; c < 3; c++)
420 pix[c] += prevRow[c];
421
422 memcpy(thisRow, pix, sizeof(pix));
423
Pierre Ossman7c4efd72010-09-30 11:30:20 +0000424 myFormat.bufferFromRGB((rdr::U8*)&buf[y*rectWidth], pix, 1, NULL);
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000425
426 /* Remaining pixels of a row */
427 for (x = 1; x < rectWidth; x++) {
428 for (c = 0; c < 3; c++) {
Pierre Ossman67b2b2f2009-03-06 10:12:55 +0000429 est[c] = prevRow[x*3+c] + pix[c] - prevRow[(x-1)*3+c];
430 if (est[c] > 255) {
431 est[c] = 255;
432 } else if (est[c] < 0) {
433 est[c] = 0;
434 }
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000435 }
Pierre Ossman67b2b2f2009-03-06 10:12:55 +0000436
Pierre Ossman7c4efd72010-09-30 11:30:20 +0000437 myFormat.rgbFromBuffer(pix, (rdr::U8*)&netbuf[y*rectWidth+x], 1, NULL);
Pierre Ossman67b2b2f2009-03-06 10:12:55 +0000438 for (c = 0; c < 3; c++)
439 pix[c] += est[c];
440
441 memcpy(&thisRow[x*3], pix, sizeof(pix));
442
Pierre Ossman7c4efd72010-09-30 11:30:20 +0000443 myFormat.bufferFromRGB((rdr::U8*)&buf[y*rectWidth+x], pix, 1, NULL);
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000444 }
445
446 memcpy(prevRow, thisRow, sizeof(prevRow));
447 }
448
449 delete [] netbuf;
450}
451
452#undef TIGHT_MIN_TO_COMPRESS
453#undef TIGHT_DECODE
454#undef READ_PIXEL
455#undef PIXEL_T
456}