blob: e9568f8cf0a4967377602e34ae51a1f016fbaeba [file] [log] [blame]
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +00001/* Copyright (C) 2000-2003 Constantin Kaplinsky. All Rights Reserved.
DRCcd2c5d42011-08-11 11:18:34 +00002 * Copyright (C) 2011 D. R. Commander. All Rights Reserved.
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#include <rdr/OutStream.h>
Pierre Ossman456b2c22014-01-15 13:22:03 +010020#include <rfb/TransImageGetter.h>
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +000021#include <rfb/encodings.h>
22#include <rfb/ConnParams.h>
23#include <rfb/SMsgWriter.h>
24#include <rfb/TightEncoder.h>
25
26using namespace rfb;
27
28// Minimum amount of data to be compressed. This value should not be
29// changed, doing so will break compatibility with existing clients.
30#define TIGHT_MIN_TO_COMPRESS 12
31
32// Adjustable parameters.
33// FIXME: Get rid of #defines
DRCcd2c5d42011-08-11 11:18:34 +000034#define TIGHT_MAX_SPLIT_TILE_SIZE 16
35#define TIGHT_MIN_SPLIT_RECT_SIZE 4096
36#define TIGHT_MIN_SOLID_SUBRECT_SIZE 2048
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +000037
38//
39// Compression level stuff. The following array contains various
40// encoder parameters for each of 10 compression levels (0..9).
41// Last three parameters correspond to JPEG quality levels (0..9).
42//
DRCcd2c5d42011-08-11 11:18:34 +000043// NOTE: The parameters used in this encoder are the result of painstaking
44// research by The VirtualGL Project using RFB session captures from a variety
45// of both 2D and 3D applications. See http://www.VirtualGL.org for the full
46// reports.
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +000047
DRC773cf3c2009-03-12 19:26:44 +000048// NOTE: The JPEG quality and subsampling levels below were obtained
49// experimentally by the VirtualGL Project. They represent the approximate
50// average compression ratios listed below, as measured across the set of
51// every 10th frame in the SPECviewperf 9 benchmark suite.
52//
53// 9 = JPEG quality 100, no subsampling (ratio ~= 10:1)
54// [this should be lossless, except for round-off error]
55// 8 = JPEG quality 92, no subsampling (ratio ~= 20:1)
56// [this should be perceptually lossless, based on current research]
57// 7 = JPEG quality 86, no subsampling (ratio ~= 25:1)
58// 6 = JPEG quality 79, no subsampling (ratio ~= 30:1)
59// 5 = JPEG quality 77, 4:2:2 subsampling (ratio ~= 40:1)
60// 4 = JPEG quality 62, 4:2:2 subsampling (ratio ~= 50:1)
61// 3 = JPEG quality 42, 4:2:2 subsampling (ratio ~= 60:1)
62// 2 = JPEG quality 41, 4:2:0 subsampling (ratio ~= 70:1)
63// 1 = JPEG quality 29, 4:2:0 subsampling (ratio ~= 80:1)
64// 0 = JPEG quality 15, 4:2:0 subsampling (ratio ~= 100:1)
65
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +000066const TIGHT_CONF TightEncoder::conf[10] = {
DRCcd2c5d42011-08-11 11:18:34 +000067 { 65536, 2048, 6, 0, 0, 0, 4, 24, 15, SUBSAMP_420 }, // 0
68 { 65536, 2048, 6, 1, 1, 1, 8, 24, 29, SUBSAMP_420 }, // 1
69 { 65536, 2048, 8, 3, 3, 2, 24, 96, 41, SUBSAMP_420 }, // 2
70 { 65536, 2048, 12, 5, 5, 2, 32, 96, 42, SUBSAMP_422 }, // 3
71 { 65536, 2048, 12, 6, 7, 3, 32, 96, 62, SUBSAMP_422 }, // 4
72 { 65536, 2048, 12, 7, 8, 4, 32, 96, 77, SUBSAMP_422 }, // 5
73 { 65536, 2048, 16, 7, 8, 5, 32, 96, 79, SUBSAMP_NONE }, // 6
74 { 65536, 2048, 16, 8, 9, 6, 64, 96, 86, SUBSAMP_NONE }, // 7
75 { 65536, 2048, 24, 9, 9, 7, 64, 96, 92, SUBSAMP_NONE }, // 8
76 { 65536, 2048, 32, 9, 9, 9, 96, 96,100, SUBSAMP_NONE } // 9
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +000077};
Pierre Ossman701ad682011-11-20 15:39:17 +000078
79const int TightEncoder::defaultCompressLevel = 2;
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +000080
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +000081//
82// Including BPP-dependent implementation of the encoder.
83//
84
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +000085#define BPP 8
86#include <rfb/tightEncode.h>
87#undef BPP
88#define BPP 16
89#include <rfb/tightEncode.h>
90#undef BPP
91#define BPP 32
92#include <rfb/tightEncode.h>
93#undef BPP
94
95Encoder* TightEncoder::create(SMsgWriter* writer)
96{
97 return new TightEncoder(writer);
98}
99
100TightEncoder::TightEncoder(SMsgWriter* writer_) : writer(writer_)
101{
102 setCompressLevel(defaultCompressLevel);
103 setQualityLevel(-1);
104}
105
106TightEncoder::~TightEncoder()
107{
108}
109
110void TightEncoder::setCompressLevel(int level)
111{
112 if (level >= 0 && level <= 9) {
113 pconf = &conf[level];
114 } else {
115 pconf = &conf[defaultCompressLevel];
116 }
117}
118
119void TightEncoder::setQualityLevel(int level)
120{
121 if (level >= 0 && level <= 9) {
DRCb4a83232011-08-19 04:57:18 +0000122 jpegQuality = conf[level].jpegQuality;
123 jpegSubsampling = conf[level].jpegSubsampling;
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000124 } else {
DRCb4a83232011-08-19 04:57:18 +0000125 jpegQuality = -1;
126 jpegSubsampling = SUBSAMP_UNDEFINED;
127 }
128}
129
130void TightEncoder::setFineQualityLevel(int quality, JPEG_SUBSAMP subsampling)
131{
132 if (quality >= 1 && quality <= 100) {
133 jpegQuality = quality;
134 }
135 if (subsampling >= SUBSAMP_NONE && subsampling <= SUBSAMP_GRAY) {
136 jpegSubsampling = subsampling;
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000137 }
138}
139
DRCffe09d62011-08-17 02:27:59 +0000140bool TightEncoder::checkSolidTile(Rect& r, rdr::U32* colorPtr,
DRCcd2c5d42011-08-11 11:18:34 +0000141 bool needSameColor)
142{
DRCffe09d62011-08-17 02:27:59 +0000143 switch (serverpf.bpp) {
DRCcd2c5d42011-08-11 11:18:34 +0000144 case 32:
DRCffe09d62011-08-17 02:27:59 +0000145 return checkSolidTile32(r, colorPtr, needSameColor);
DRCcd2c5d42011-08-11 11:18:34 +0000146 case 16:
DRCffe09d62011-08-17 02:27:59 +0000147 return checkSolidTile16(r, colorPtr, needSameColor);
DRCcd2c5d42011-08-11 11:18:34 +0000148 default:
DRCffe09d62011-08-17 02:27:59 +0000149 return checkSolidTile8(r, colorPtr, needSameColor);
DRCcd2c5d42011-08-11 11:18:34 +0000150 }
151}
152
DRCffe09d62011-08-17 02:27:59 +0000153void TightEncoder::findBestSolidArea(Rect& r, rdr::U32 colorValue, Rect& bestr)
DRCcd2c5d42011-08-11 11:18:34 +0000154{
155 int dx, dy, dw, dh;
156 int w_prev;
157 Rect sr;
158 int w_best = 0, h_best = 0;
159
160 bestr.tl.x = bestr.br.x = r.tl.x;
161 bestr.tl.y = bestr.br.y = r.tl.y;
162
163 w_prev = r.width();
164
165 for (dy = r.tl.y; dy < r.br.y; dy += TIGHT_MAX_SPLIT_TILE_SIZE) {
166
167 dh = (dy + TIGHT_MAX_SPLIT_TILE_SIZE <= r.br.y) ?
168 TIGHT_MAX_SPLIT_TILE_SIZE : (r.br.y - dy);
169 dw = (w_prev > TIGHT_MAX_SPLIT_TILE_SIZE) ?
170 TIGHT_MAX_SPLIT_TILE_SIZE : w_prev;
171
172 sr.setXYWH(r.tl.x, dy, dw, dh);
DRCffe09d62011-08-17 02:27:59 +0000173 if (!checkSolidTile(sr, &colorValue, true))
DRCcd2c5d42011-08-11 11:18:34 +0000174 break;
175
176 for (dx = r.tl.x + dw; dx < r.tl.x + w_prev;) {
177 dw = (dx + TIGHT_MAX_SPLIT_TILE_SIZE <= r.tl.x + w_prev) ?
178 TIGHT_MAX_SPLIT_TILE_SIZE : (r.tl.x + w_prev - dx);
179 sr.setXYWH(dx, dy, dw, dh);
DRCffe09d62011-08-17 02:27:59 +0000180 if (!checkSolidTile(sr, &colorValue, true))
DRCcd2c5d42011-08-11 11:18:34 +0000181 break;
DRCffe09d62011-08-17 02:27:59 +0000182 dx += dw;
DRCcd2c5d42011-08-11 11:18:34 +0000183 }
184
185 w_prev = dx - r.tl.x;
186 if (w_prev * (dy + dh - r.tl.y) > w_best * h_best) {
187 w_best = w_prev;
188 h_best = dy + dh - r.tl.y;
189 }
190 }
191
192 bestr.br.x = bestr.tl.x + w_best;
193 bestr.br.y = bestr.tl.y + h_best;
194}
195
DRCffe09d62011-08-17 02:27:59 +0000196void TightEncoder::extendSolidArea(const Rect& r, rdr::U32 colorValue,
197 Rect& er)
DRCcd2c5d42011-08-11 11:18:34 +0000198{
199 int cx, cy;
200 Rect sr;
201
202 // Try to extend the area upwards.
203 for (cy = er.tl.y - 1; ; cy--) {
204 sr.setXYWH(er.tl.x, cy, er.width(), 1);
DRCffe09d62011-08-17 02:27:59 +0000205 if (cy < r.tl.y || !checkSolidTile(sr, &colorValue, true))
DRCcd2c5d42011-08-11 11:18:34 +0000206 break;
207 }
208 er.tl.y = cy + 1;
209
210 // ... downwards.
211 for (cy = er.br.y; ; cy++) {
212 sr.setXYWH(er.tl.x, cy, er.width(), 1);
DRCffe09d62011-08-17 02:27:59 +0000213 if (cy >= r.br.y || !checkSolidTile(sr, &colorValue, true))
DRCcd2c5d42011-08-11 11:18:34 +0000214 break;
215 }
216 er.br.y = cy;
217
218 // ... to the left.
219 for (cx = er.tl.x - 1; ; cx--) {
220 sr.setXYWH(cx, er.tl.y, 1, er.height());
DRCffe09d62011-08-17 02:27:59 +0000221 if (cx < r.tl.x || !checkSolidTile(sr, &colorValue, true))
DRCcd2c5d42011-08-11 11:18:34 +0000222 break;
223 }
224 er.tl.x = cx + 1;
225
226 // ... to the right.
227 for (cx = er.br.x; ; cx++) {
228 sr.setXYWH(cx, er.tl.y, 1, er.height());
DRCffe09d62011-08-17 02:27:59 +0000229 if (cx >= r.br.x || !checkSolidTile(sr, &colorValue, true))
DRCcd2c5d42011-08-11 11:18:34 +0000230 break;
231 }
232 er.br.x = cx;
233}
234
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000235int TightEncoder::getNumRects(const Rect &r)
236{
DRCcd2c5d42011-08-11 11:18:34 +0000237 ConnParams* cp = writer->getConnParams();
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000238 const unsigned int w = r.width();
239 const unsigned int h = r.height();
240
DRCcd2c5d42011-08-11 11:18:34 +0000241 // If last rect. encoding is enabled, we can use the higher-performance
242 // code that pre-computes solid rectangles. In that case, we don't care
243 // about the rectangle count.
244 if (cp->supportsLastRect && w * h >= TIGHT_MIN_SPLIT_RECT_SIZE)
245 return 0;
246
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000247 // Will this rectangle split into subrects?
248 bool rectTooBig = w > pconf->maxRectWidth || w * h > pconf->maxRectSize;
249 if (!rectTooBig)
250 return 1;
251
252 // Compute max sub-rectangle size.
253 const unsigned int subrectMaxWidth =
254 (w > pconf->maxRectWidth) ? pconf->maxRectWidth : w;
255 const unsigned int subrectMaxHeight =
256 pconf->maxRectSize / subrectMaxWidth;
257
258 // Return the number of subrects.
259 return (((w - 1) / pconf->maxRectWidth + 1) *
260 ((h - 1) / subrectMaxHeight + 1));
261}
262
DRCffe09d62011-08-17 02:27:59 +0000263void TightEncoder::sendRectSimple(const Rect& r)
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000264{
265 // Shortcuts to rectangle coordinates and dimensions.
266 const int x = r.tl.x;
267 const int y = r.tl.y;
268 const unsigned int w = r.width();
269 const unsigned int h = r.height();
270
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000271 // Encode small rects as is.
272 bool rectTooBig = w > pconf->maxRectWidth || w * h > pconf->maxRectSize;
273 if (!rectTooBig) {
DRCffe09d62011-08-17 02:27:59 +0000274 writeSubrect(r);
DRCcd2c5d42011-08-11 11:18:34 +0000275 return;
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000276 }
277
278 // Compute max sub-rectangle size.
279 const unsigned int subrectMaxWidth =
280 (w > pconf->maxRectWidth) ? pconf->maxRectWidth : w;
281 const unsigned int subrectMaxHeight =
282 pconf->maxRectSize / subrectMaxWidth;
283
284 // Split big rects into separately encoded subrects.
285 Rect sr;
286 unsigned int dx, dy, sw, sh;
287 for (dy = 0; dy < h; dy += subrectMaxHeight) {
288 for (dx = 0; dx < w; dx += pconf->maxRectWidth) {
289 sw = (dx + pconf->maxRectWidth < w) ? pconf->maxRectWidth : w - dx;
290 sh = (dy + subrectMaxHeight < h) ? subrectMaxHeight : h - dy;
291 sr.setXYWH(x + dx, y + dy, sw, sh);
DRCffe09d62011-08-17 02:27:59 +0000292 writeSubrect(sr);
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000293 }
294 }
DRCcd2c5d42011-08-11 11:18:34 +0000295}
296
DRCffe09d62011-08-17 02:27:59 +0000297bool TightEncoder::writeRect(const Rect& _r, TransImageGetter* _ig,
298 Rect* actual)
DRCcd2c5d42011-08-11 11:18:34 +0000299{
DRCffe09d62011-08-17 02:27:59 +0000300 ig = _ig;
301 serverpf = ig->getPixelBuffer()->getPF();
DRCcd2c5d42011-08-11 11:18:34 +0000302 ConnParams* cp = writer->getConnParams();
DRCffe09d62011-08-17 02:27:59 +0000303 clientpf = cp->pf();
DRCcd2c5d42011-08-11 11:18:34 +0000304
305 // Shortcuts to rectangle coordinates and dimensions.
306 Rect r = _r;
307 int x = r.tl.x;
308 int y = r.tl.y;
DRCe3ffcf72011-08-19 03:11:32 +0000309 int w = r.width();
310 int h = r.height();
DRCcd2c5d42011-08-11 11:18:34 +0000311
DRCcd2c5d42011-08-11 11:18:34 +0000312 // Encode small rects as is.
313 if (!cp->supportsLastRect || w * h < TIGHT_MIN_SPLIT_RECT_SIZE) {
DRCffe09d62011-08-17 02:27:59 +0000314 sendRectSimple(r);
DRCcd2c5d42011-08-11 11:18:34 +0000315 return true;
316 }
317
318 // Split big rects into separately encoded subrects.
319 Rect sr, bestr;
DRCe3ffcf72011-08-19 03:11:32 +0000320 int dx, dy, dw, dh;
DRCcd2c5d42011-08-11 11:18:34 +0000321 rdr::U32 colorValue;
DRCffe09d62011-08-17 02:27:59 +0000322 int maxRectWidth = pconf->maxRectWidth;
DRCcd2c5d42011-08-11 11:18:34 +0000323 int nMaxWidth = (w > maxRectWidth) ? maxRectWidth : w;
DRCffe09d62011-08-17 02:27:59 +0000324 int nMaxRows = pconf->maxRectSize / nMaxWidth;
DRCcd2c5d42011-08-11 11:18:34 +0000325
326 // Try to find large solid-color areas and send them separately.
327 for (dy = y; dy < y + h; dy += TIGHT_MAX_SPLIT_TILE_SIZE) {
328
329 // If a rectangle becomes too large, send its upper part now.
330 if (dy - y >= nMaxRows) {
331 sr.setXYWH(x, y, w, nMaxRows);
DRCffe09d62011-08-17 02:27:59 +0000332 sendRectSimple(sr);
DRCcd2c5d42011-08-11 11:18:34 +0000333 r.tl.y += nMaxRows;
334 y = r.tl.y;
335 h = r.height();
336 }
337
338 dh = (dy + TIGHT_MAX_SPLIT_TILE_SIZE <= y + h) ?
339 TIGHT_MAX_SPLIT_TILE_SIZE : (y + h - dy);
340
341 for (dx = x; dx < x + w; dx += TIGHT_MAX_SPLIT_TILE_SIZE) {
342
343 dw = (dx + TIGHT_MAX_SPLIT_TILE_SIZE <= x + w) ?
344 TIGHT_MAX_SPLIT_TILE_SIZE : (x + w - dx);
345
346 sr.setXYWH(dx, dy, dw, dh);
DRCffe09d62011-08-17 02:27:59 +0000347 if (checkSolidTile(sr, &colorValue, false)) {
DRCcd2c5d42011-08-11 11:18:34 +0000348
DRCb4a83232011-08-19 04:57:18 +0000349 if (jpegSubsampling == SUBSAMP_GRAY && jpegQuality != -1) {
350 Colour rgb;
351 serverpf.rgbFromPixel(colorValue, NULL, &rgb);
352 rdr::U32 lum = ((257 * rgb.r) + (504 * rgb.g) + (98 * rgb.b)
353 + 16500) / 1000;
354 colorValue = lum + (lum << 8) + (lum << 16);
355 }
356
DRCcd2c5d42011-08-11 11:18:34 +0000357 // Get dimensions of solid-color area.
358 sr.setXYWH(dx, dy, r.br.x - dx, r.br.y - dy);
DRCffe09d62011-08-17 02:27:59 +0000359 findBestSolidArea(sr, colorValue, bestr);
DRCcd2c5d42011-08-11 11:18:34 +0000360
361 // Make sure a solid rectangle is large enough
362 // (or the whole rectangle is of the same color).
363 if (bestr.area() != r.area()
364 && bestr.area() < TIGHT_MIN_SOLID_SUBRECT_SIZE)
365 continue;
366
367 // Try to extend solid rectangle to maximum size.
DRCffe09d62011-08-17 02:27:59 +0000368 extendSolidArea(r, colorValue, bestr);
DRCcd2c5d42011-08-11 11:18:34 +0000369
370 // Send rectangles at top and left to solid-color area.
371 if (bestr.tl.y != y) {
372 sr.setXYWH(x, y, w, bestr.tl.y - y);
DRCffe09d62011-08-17 02:27:59 +0000373 sendRectSimple(sr);
DRCcd2c5d42011-08-11 11:18:34 +0000374 }
375 if (bestr.tl.x != x) {
376 sr.setXYWH(x, bestr.tl.y, bestr.tl.x - x, bestr.height());
DRCffe09d62011-08-17 02:27:59 +0000377 writeRect(sr, _ig, NULL);
DRCcd2c5d42011-08-11 11:18:34 +0000378 }
379
380 // Send solid-color rectangle.
DRCffe09d62011-08-17 02:27:59 +0000381 writeSubrect(bestr, true);
DRCcd2c5d42011-08-11 11:18:34 +0000382
383 // Send remaining rectangles (at right and bottom).
384 if (bestr.br.x != r.br.x) {
385 sr.setXYWH(bestr.br.x, bestr.tl.y, r.br.x - bestr.br.x,
386 bestr.height());
DRCffe09d62011-08-17 02:27:59 +0000387 writeRect(sr, _ig, NULL);
DRCcd2c5d42011-08-11 11:18:34 +0000388 }
389 if (bestr.br.y != r.br.y) {
390 sr.setXYWH(x, bestr.br.y, w, r.br.y - bestr.br.y);
DRCffe09d62011-08-17 02:27:59 +0000391 writeRect(sr, _ig, NULL);
DRCcd2c5d42011-08-11 11:18:34 +0000392 }
393
394 return true;
395 }
396 }
397 }
398
399 // No suitable solid-color rectangles found.
DRCffe09d62011-08-17 02:27:59 +0000400 sendRectSimple(r);
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000401 return true;
402}
403
DRCffe09d62011-08-17 02:27:59 +0000404void TightEncoder::writeSubrect(const Rect& r, bool forceSolid)
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000405{
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000406 mos.clear();
407
DRCffe09d62011-08-17 02:27:59 +0000408 switch (clientpf.bpp) {
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000409 case 8:
DRCffe09d62011-08-17 02:27:59 +0000410 tightEncode8(r, &mos, forceSolid); break;
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000411 case 16:
DRCffe09d62011-08-17 02:27:59 +0000412 tightEncode16(r, &mos, forceSolid); break;
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000413 case 32:
DRCffe09d62011-08-17 02:27:59 +0000414 tightEncode32(r, &mos, forceSolid); break;
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000415 }
416
417 writer->startRect(r, encodingTight);
418 rdr::OutStream* os = writer->getOutStream();
419 os->writeBytes(mos.data(), mos.length());
420 writer->endRect();
421}