blob: d0f7b68a60998f861076a81c83de79b4ea3aa67d [file] [log] [blame]
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +00001/* Copyright (C) 2000-2003 Constantin Kaplinsky. All Rights Reserved.
2 * Copyright (C) 2004-2005 Cendio AB. All rights reserved.
3 *
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 {
187 int x, y, b, w;
188 PIXEL_T *ptr = buf;
189 rdr::U8 bits;
190 if (palSize <= 2) {
191 // 2-color palette
192 w = (r.width() + 7) / 8;
193 for (y = 0; y < r.height(); y++) {
194 for (x = 0; x < r.width() / 8; x++) {
195 bits = input->readU8();
196 for (b = 7; b >= 0; b--) {
197 *ptr++ = palette[bits >> b & 1];
198 }
199 }
200 if (r.width() % 8 != 0) {
201 bits = input->readU8();
202 for (b = 7; b >= 8 - r.width() % 8; b--) {
203 *ptr++ = palette[bits >> b & 1];
204 }
205 }
206 }
207 } else {
208 // 256-color palette
209 for (y = 0; y < r.height(); y++) {
210 for (x = 0; x < r.width(); x++) {
211 *ptr++ = palette[input->readU8()];
212 }
213 }
214 }
215 }
216
217 IMAGE_RECT(r, buf);
218
219 if (streamId != -1) {
220 zis[streamId].reset();
221 }
222}
223
224static bool
225DecompressJpegRect(const Rect& r, rdr::InStream* is,
226 PIXEL_T* buf, CMsgHandler* handler)
227{
228 struct jpeg_decompress_struct cinfo;
229 struct jpeg_error_mgr jerr;
DRCe420dcb2009-04-06 07:20:34 +0000230 int w = r.width();
231 int h = r.height();
Pierre Ossman6f0ce582009-04-30 11:41:03 +0000232 int pixelsize;
DRCe420dcb2009-04-06 07:20:34 +0000233 rdr::U8 *dstBuf = NULL;
234 bool dstBufIsTemp = false;
235 const rfb::PixelFormat& pf = handler->cp.pf();
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000236
237 // Read length
238 int compressedLen = is->readCompactLength();
239 if (compressedLen <= 0) {
240 throw Exception("Incorrect data received from the server.\n");
241 }
242
243 // Allocate netbuf and read in data
244 rdr::U8* netbuf = new rdr::U8[compressedLen];
245 if (!netbuf) {
246 throw Exception("rfb::tightDecode unable to allocate buffer");
247 }
248 is->readBytes(netbuf, compressedLen);
249
250 // Set up JPEG decompression
251 cinfo.err = jpeg_std_error(&jerr);
252 jpeg_create_decompress(&cinfo);
253 JpegSetSrcManager(&cinfo, (char*)netbuf, compressedLen);
254 jpeg_read_header(&cinfo, TRUE);
DRCe420dcb2009-04-06 07:20:34 +0000255
Pierre Ossman6f0ce582009-04-30 11:41:03 +0000256 cinfo.out_color_space = JCS_RGB;
257 pixelsize = 3;
258
259#ifdef JCS_EXTENSIONS
260 // Try to have libjpeg output directly to our native format
261 if (pf.is888()) {
DRCe420dcb2009-04-06 07:20:34 +0000262 int redShift, greenShift, blueShift;
Pierre Ossman6f0ce582009-04-30 11:41:03 +0000263
DRCe420dcb2009-04-06 07:20:34 +0000264 if(pf.bigEndian) {
265 redShift = 24 - pf.redShift;
266 greenShift = 24 - pf.greenShift;
267 blueShift = 24 - pf.blueShift;
Pierre Ossman6f0ce582009-04-30 11:41:03 +0000268 } else {
DRCe420dcb2009-04-06 07:20:34 +0000269 redShift = pf.redShift;
270 greenShift = pf.greenShift;
271 blueShift = pf.blueShift;
272 }
Pierre Ossman6f0ce582009-04-30 11:41:03 +0000273
274 // libjpeg can only handle some "standard" formats
275 if(redShift == 0 && greenShift == 8 && blueShift == 16)
DRCe420dcb2009-04-06 07:20:34 +0000276 cinfo.out_color_space = JCS_EXT_RGBX;
Pierre Ossman6f0ce582009-04-30 11:41:03 +0000277 if(redShift == 16 && greenShift == 8 && blueShift == 0)
DRCe420dcb2009-04-06 07:20:34 +0000278 cinfo.out_color_space = JCS_EXT_BGRX;
Pierre Ossman6f0ce582009-04-30 11:41:03 +0000279 if(redShift == 24 && greenShift == 16 && blueShift == 8)
DRCe420dcb2009-04-06 07:20:34 +0000280 cinfo.out_color_space = JCS_EXT_XBGR;
Pierre Ossman6f0ce582009-04-30 11:41:03 +0000281 if(redShift == 8 && greenShift == 16 && blueShift == 24)
DRCe420dcb2009-04-06 07:20:34 +0000282 cinfo.out_color_space = JCS_EXT_XRGB;
Pierre Ossman6f0ce582009-04-30 11:41:03 +0000283
284 if (cinfo.out_color_space != JCS_RGB) {
DRCe420dcb2009-04-06 07:20:34 +0000285 dstBuf = (rdr::U8 *)buf;
Pierre Ossman6f0ce582009-04-30 11:41:03 +0000286 pixelsize = 4;
287 }
DRCe420dcb2009-04-06 07:20:34 +0000288 }
Pierre Ossman6f0ce582009-04-30 11:41:03 +0000289#endif
290
291 if (cinfo.out_color_space == JCS_RGB) {
DRCe420dcb2009-04-06 07:20:34 +0000292 dstBuf = new rdr::U8[w * h * pixelsize];
293 dstBufIsTemp = true;
DRCe420dcb2009-04-06 07:20:34 +0000294 }
295
296 JSAMPROW *rowPointer = new JSAMPROW[h];
297 for (int dy = 0; dy < h; dy++)
298 rowPointer[dy] = (JSAMPROW)(&dstBuf[dy * w * pixelsize]);
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000299
300 jpeg_start_decompress(&cinfo);
301 if (cinfo.output_width != (unsigned)r.width() || cinfo.output_height != (unsigned)r.height() ||
DRCe420dcb2009-04-06 07:20:34 +0000302 cinfo.output_components != pixelsize) {
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000303 jpeg_destroy_decompress(&cinfo);
304 throw Exception("Tight Encoding: Wrong JPEG data received.\n");
305 }
306
307 // Decompress
308 const rfb::PixelFormat& myFormat = handler->cp.pf();
309 while (cinfo.output_scanline < cinfo.output_height) {
DRCe420dcb2009-04-06 07:20:34 +0000310 jpeg_read_scanlines(&cinfo, &rowPointer[cinfo.output_scanline],
311 cinfo.output_height - cinfo.output_scanline);
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000312 if (jpegError) {
313 break;
314 }
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000315 }
316
Adam Tkac43d68232010-01-27 12:37:38 +0000317 delete [] rowPointer;
318
DRCe420dcb2009-04-06 07:20:34 +0000319 if (cinfo.out_color_space == JCS_RGB)
320 myFormat.bufferFromRGB((rdr::U8*)buf, dstBuf, w * h);
321
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000322 IMAGE_RECT(r, buf);
323
324 if (!jpegError) {
325 jpeg_finish_decompress(&cinfo);
326 }
327
328 jpeg_destroy_decompress(&cinfo);
329
DRCe420dcb2009-04-06 07:20:34 +0000330 if (dstBufIsTemp) delete [] dstBuf;
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000331 delete [] netbuf;
332
333 return !jpegError;
334}
335
336#if BPP == 32
337
338static void
339FilterGradient24(const Rect& r, rdr::InStream* is, int dataSize,
340 PIXEL_T* buf, CMsgHandler* handler)
341{
342 int x, y, c;
343 static rdr::U8 prevRow[TIGHT_MAX_WIDTH*3];
344 static rdr::U8 thisRow[TIGHT_MAX_WIDTH*3];
345 rdr::U8 pix[3];
346 int est[3];
347
348 memset(prevRow, 0, sizeof(prevRow));
349
350 // Allocate netbuf and read in data
351 rdr::U8 *netbuf = new rdr::U8[dataSize];
352 if (!netbuf) {
353 throw Exception("rfb::tightDecode unable to allocate buffer");
354 }
355 is->readBytes(netbuf, dataSize);
356
357 // Set up shortcut variables
358 const rfb::PixelFormat& myFormat = handler->cp.pf();
359 int rectHeight = r.height();
360 int rectWidth = r.width();
361
362 for (y = 0; y < rectHeight; y++) {
363 /* First pixel in a row */
364 for (c = 0; c < 3; c++) {
365 pix[c] = netbuf[y*rectWidth*3+c] + prevRow[c];
366 thisRow[c] = pix[c];
367 }
Pierre Ossman7c4efd72010-09-30 11:30:20 +0000368 myFormat.bufferFromRGB((rdr::U8*)&buf[y*rectWidth], pix, 1, NULL);
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000369
370 /* Remaining pixels of a row */
371 for (x = 1; x < rectWidth; x++) {
372 for (c = 0; c < 3; c++) {
373 est[c] = prevRow[x*3+c] + pix[c] - prevRow[(x-1)*3+c];
374 if (est[c] > 0xff) {
375 est[c] = 0xff;
376 } else if (est[c] < 0) {
377 est[c] = 0;
378 }
379 pix[c] = netbuf[(y*rectWidth+x)*3+c] + est[c];
380 thisRow[x*3+c] = pix[c];
381 }
Pierre Ossman7c4efd72010-09-30 11:30:20 +0000382 myFormat.bufferFromRGB((rdr::U8*)&buf[y*rectWidth+x], pix, 1, NULL);
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000383 }
384
385 memcpy(prevRow, thisRow, sizeof(prevRow));
386 }
387
388 delete [] netbuf;
389}
390
391#endif
392
393static void
394FilterGradient(const Rect& r, rdr::InStream* is, int dataSize,
395 PIXEL_T* buf, CMsgHandler* handler)
396{
397 int x, y, c;
398 static rdr::U8 prevRow[TIGHT_MAX_WIDTH*sizeof(PIXEL_T)];
399 static rdr::U8 thisRow[TIGHT_MAX_WIDTH*sizeof(PIXEL_T)];
Pierre Ossman67b2b2f2009-03-06 10:12:55 +0000400 rdr::U8 pix[3];
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000401 int est[3];
402
403 memset(prevRow, 0, sizeof(prevRow));
404
405 // Allocate netbuf and read in data
406 PIXEL_T *netbuf = (PIXEL_T*)new rdr::U8[dataSize];
407 if (!netbuf) {
408 throw Exception("rfb::tightDecode unable to allocate buffer");
409 }
410 is->readBytes(netbuf, dataSize);
411
412 // Set up shortcut variables
413 const rfb::PixelFormat& myFormat = handler->cp.pf();
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000414 int rectHeight = r.height();
415 int rectWidth = r.width();
416
417 for (y = 0; y < rectHeight; y++) {
418 /* First pixel in a row */
Pierre Ossman7c4efd72010-09-30 11:30:20 +0000419 myFormat.rgbFromBuffer(pix, (rdr::U8*)&netbuf[y*rectWidth], 1, NULL);
Pierre Ossman67b2b2f2009-03-06 10:12:55 +0000420 for (c = 0; c < 3; c++)
421 pix[c] += prevRow[c];
422
423 memcpy(thisRow, pix, sizeof(pix));
424
Pierre Ossman7c4efd72010-09-30 11:30:20 +0000425 myFormat.bufferFromRGB((rdr::U8*)&buf[y*rectWidth], pix, 1, NULL);
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000426
427 /* Remaining pixels of a row */
428 for (x = 1; x < rectWidth; x++) {
429 for (c = 0; c < 3; c++) {
Pierre Ossman67b2b2f2009-03-06 10:12:55 +0000430 est[c] = prevRow[x*3+c] + pix[c] - prevRow[(x-1)*3+c];
431 if (est[c] > 255) {
432 est[c] = 255;
433 } else if (est[c] < 0) {
434 est[c] = 0;
435 }
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000436 }
Pierre Ossman67b2b2f2009-03-06 10:12:55 +0000437
Pierre Ossman7c4efd72010-09-30 11:30:20 +0000438 myFormat.rgbFromBuffer(pix, (rdr::U8*)&netbuf[y*rectWidth+x], 1, NULL);
Pierre Ossman67b2b2f2009-03-06 10:12:55 +0000439 for (c = 0; c < 3; c++)
440 pix[c] += est[c];
441
442 memcpy(&thisRow[x*3], pix, sizeof(pix));
443
Pierre Ossman7c4efd72010-09-30 11:30:20 +0000444 myFormat.bufferFromRGB((rdr::U8*)&buf[y*rectWidth+x], pix, 1, NULL);
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000445 }
446
447 memcpy(prevRow, thisRow, sizeof(prevRow));
448 }
449
450 delete [] netbuf;
451}
452
453#undef TIGHT_MIN_TO_COMPRESS
454#undef TIGHT_DECODE
455#undef READ_PIXEL
456#undef PIXEL_T
457}