blob: fa7ee9a7491b662fe1b2b5f20f91aeb19e2aa8ee [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>
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +000020#include <rfb/encodings.h>
21#include <rfb/ConnParams.h>
22#include <rfb/SMsgWriter.h>
23#include <rfb/TightEncoder.h>
24
25using namespace rfb;
26
27// Minimum amount of data to be compressed. This value should not be
28// changed, doing so will break compatibility with existing clients.
29#define TIGHT_MIN_TO_COMPRESS 12
30
31// Adjustable parameters.
32// FIXME: Get rid of #defines
DRCcd2c5d42011-08-11 11:18:34 +000033#define TIGHT_MAX_SPLIT_TILE_SIZE 16
34#define TIGHT_MIN_SPLIT_RECT_SIZE 4096
35#define TIGHT_MIN_SOLID_SUBRECT_SIZE 2048
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +000036
37//
38// Compression level stuff. The following array contains various
39// encoder parameters for each of 10 compression levels (0..9).
40// Last three parameters correspond to JPEG quality levels (0..9).
41//
DRCcd2c5d42011-08-11 11:18:34 +000042// NOTE: The parameters used in this encoder are the result of painstaking
43// research by The VirtualGL Project using RFB session captures from a variety
44// of both 2D and 3D applications. See http://www.VirtualGL.org for the full
45// reports.
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +000046
DRC773cf3c2009-03-12 19:26:44 +000047// NOTE: The JPEG quality and subsampling levels below were obtained
48// experimentally by the VirtualGL Project. They represent the approximate
49// average compression ratios listed below, as measured across the set of
50// every 10th frame in the SPECviewperf 9 benchmark suite.
51//
52// 9 = JPEG quality 100, no subsampling (ratio ~= 10:1)
53// [this should be lossless, except for round-off error]
54// 8 = JPEG quality 92, no subsampling (ratio ~= 20:1)
55// [this should be perceptually lossless, based on current research]
56// 7 = JPEG quality 86, no subsampling (ratio ~= 25:1)
57// 6 = JPEG quality 79, no subsampling (ratio ~= 30:1)
58// 5 = JPEG quality 77, 4:2:2 subsampling (ratio ~= 40:1)
59// 4 = JPEG quality 62, 4:2:2 subsampling (ratio ~= 50:1)
60// 3 = JPEG quality 42, 4:2:2 subsampling (ratio ~= 60:1)
61// 2 = JPEG quality 41, 4:2:0 subsampling (ratio ~= 70:1)
62// 1 = JPEG quality 29, 4:2:0 subsampling (ratio ~= 80:1)
63// 0 = JPEG quality 15, 4:2:0 subsampling (ratio ~= 100:1)
64
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +000065const TIGHT_CONF TightEncoder::conf[10] = {
DRCcd2c5d42011-08-11 11:18:34 +000066 { 65536, 2048, 6, 0, 0, 0, 4, 24, 15, SUBSAMP_420 }, // 0
67 { 65536, 2048, 6, 1, 1, 1, 8, 24, 29, SUBSAMP_420 }, // 1
68 { 65536, 2048, 8, 3, 3, 2, 24, 96, 41, SUBSAMP_420 }, // 2
69 { 65536, 2048, 12, 5, 5, 2, 32, 96, 42, SUBSAMP_422 }, // 3
70 { 65536, 2048, 12, 6, 7, 3, 32, 96, 62, SUBSAMP_422 }, // 4
71 { 65536, 2048, 12, 7, 8, 4, 32, 96, 77, SUBSAMP_422 }, // 5
72 { 65536, 2048, 16, 7, 8, 5, 32, 96, 79, SUBSAMP_NONE }, // 6
73 { 65536, 2048, 16, 8, 9, 6, 64, 96, 86, SUBSAMP_NONE }, // 7
74 { 65536, 2048, 24, 9, 9, 7, 64, 96, 92, SUBSAMP_NONE }, // 8
75 { 65536, 2048, 32, 9, 9, 9, 96, 96,100, SUBSAMP_NONE } // 9
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +000076};
DRCcd2c5d42011-08-11 11:18:34 +000077const int TightEncoder::defaultCompressLevel = 1;
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +000078
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +000079//
80// Including BPP-dependent implementation of the encoder.
81//
82
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +000083#define BPP 8
84#include <rfb/tightEncode.h>
85#undef BPP
86#define BPP 16
87#include <rfb/tightEncode.h>
88#undef BPP
89#define BPP 32
90#include <rfb/tightEncode.h>
91#undef BPP
92
93Encoder* TightEncoder::create(SMsgWriter* writer)
94{
95 return new TightEncoder(writer);
96}
97
98TightEncoder::TightEncoder(SMsgWriter* writer_) : writer(writer_)
99{
100 setCompressLevel(defaultCompressLevel);
101 setQualityLevel(-1);
102}
103
104TightEncoder::~TightEncoder()
105{
106}
107
108void TightEncoder::setCompressLevel(int level)
109{
110 if (level >= 0 && level <= 9) {
111 pconf = &conf[level];
112 } else {
113 pconf = &conf[defaultCompressLevel];
114 }
115}
116
117void TightEncoder::setQualityLevel(int level)
118{
119 if (level >= 0 && level <= 9) {
120 pjconf = &conf[level];
121 } else {
122 pjconf = NULL;
123 }
124}
125
DRCffe09d62011-08-17 02:27:59 +0000126bool TightEncoder::checkSolidTile(Rect& r, rdr::U32* colorPtr,
DRCcd2c5d42011-08-11 11:18:34 +0000127 bool needSameColor)
128{
DRCffe09d62011-08-17 02:27:59 +0000129 switch (serverpf.bpp) {
DRCcd2c5d42011-08-11 11:18:34 +0000130 case 32:
DRCffe09d62011-08-17 02:27:59 +0000131 return checkSolidTile32(r, colorPtr, needSameColor);
DRCcd2c5d42011-08-11 11:18:34 +0000132 case 16:
DRCffe09d62011-08-17 02:27:59 +0000133 return checkSolidTile16(r, colorPtr, needSameColor);
DRCcd2c5d42011-08-11 11:18:34 +0000134 default:
DRCffe09d62011-08-17 02:27:59 +0000135 return checkSolidTile8(r, colorPtr, needSameColor);
DRCcd2c5d42011-08-11 11:18:34 +0000136 }
137}
138
DRCffe09d62011-08-17 02:27:59 +0000139void TightEncoder::findBestSolidArea(Rect& r, rdr::U32 colorValue, Rect& bestr)
DRCcd2c5d42011-08-11 11:18:34 +0000140{
141 int dx, dy, dw, dh;
142 int w_prev;
143 Rect sr;
144 int w_best = 0, h_best = 0;
145
146 bestr.tl.x = bestr.br.x = r.tl.x;
147 bestr.tl.y = bestr.br.y = r.tl.y;
148
149 w_prev = r.width();
150
151 for (dy = r.tl.y; dy < r.br.y; dy += TIGHT_MAX_SPLIT_TILE_SIZE) {
152
153 dh = (dy + TIGHT_MAX_SPLIT_TILE_SIZE <= r.br.y) ?
154 TIGHT_MAX_SPLIT_TILE_SIZE : (r.br.y - dy);
155 dw = (w_prev > TIGHT_MAX_SPLIT_TILE_SIZE) ?
156 TIGHT_MAX_SPLIT_TILE_SIZE : w_prev;
157
158 sr.setXYWH(r.tl.x, dy, dw, dh);
DRCffe09d62011-08-17 02:27:59 +0000159 if (!checkSolidTile(sr, &colorValue, true))
DRCcd2c5d42011-08-11 11:18:34 +0000160 break;
161
162 for (dx = r.tl.x + dw; dx < r.tl.x + w_prev;) {
163 dw = (dx + TIGHT_MAX_SPLIT_TILE_SIZE <= r.tl.x + w_prev) ?
164 TIGHT_MAX_SPLIT_TILE_SIZE : (r.tl.x + w_prev - dx);
165 sr.setXYWH(dx, dy, dw, dh);
DRCffe09d62011-08-17 02:27:59 +0000166 if (!checkSolidTile(sr, &colorValue, true))
DRCcd2c5d42011-08-11 11:18:34 +0000167 break;
DRCffe09d62011-08-17 02:27:59 +0000168 dx += dw;
DRCcd2c5d42011-08-11 11:18:34 +0000169 }
170
171 w_prev = dx - r.tl.x;
172 if (w_prev * (dy + dh - r.tl.y) > w_best * h_best) {
173 w_best = w_prev;
174 h_best = dy + dh - r.tl.y;
175 }
176 }
177
178 bestr.br.x = bestr.tl.x + w_best;
179 bestr.br.y = bestr.tl.y + h_best;
180}
181
DRCffe09d62011-08-17 02:27:59 +0000182void TightEncoder::extendSolidArea(const Rect& r, rdr::U32 colorValue,
183 Rect& er)
DRCcd2c5d42011-08-11 11:18:34 +0000184{
185 int cx, cy;
186 Rect sr;
187
188 // Try to extend the area upwards.
189 for (cy = er.tl.y - 1; ; cy--) {
190 sr.setXYWH(er.tl.x, cy, er.width(), 1);
DRCffe09d62011-08-17 02:27:59 +0000191 if (cy < r.tl.y || !checkSolidTile(sr, &colorValue, true))
DRCcd2c5d42011-08-11 11:18:34 +0000192 break;
193 }
194 er.tl.y = cy + 1;
195
196 // ... downwards.
197 for (cy = er.br.y; ; cy++) {
198 sr.setXYWH(er.tl.x, cy, er.width(), 1);
DRCffe09d62011-08-17 02:27:59 +0000199 if (cy >= r.br.y || !checkSolidTile(sr, &colorValue, true))
DRCcd2c5d42011-08-11 11:18:34 +0000200 break;
201 }
202 er.br.y = cy;
203
204 // ... to the left.
205 for (cx = er.tl.x - 1; ; cx--) {
206 sr.setXYWH(cx, er.tl.y, 1, er.height());
DRCffe09d62011-08-17 02:27:59 +0000207 if (cx < r.tl.x || !checkSolidTile(sr, &colorValue, true))
DRCcd2c5d42011-08-11 11:18:34 +0000208 break;
209 }
210 er.tl.x = cx + 1;
211
212 // ... to the right.
213 for (cx = er.br.x; ; cx++) {
214 sr.setXYWH(cx, er.tl.y, 1, er.height());
DRCffe09d62011-08-17 02:27:59 +0000215 if (cx >= r.br.x || !checkSolidTile(sr, &colorValue, true))
DRCcd2c5d42011-08-11 11:18:34 +0000216 break;
217 }
218 er.br.x = cx;
219}
220
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000221int TightEncoder::getNumRects(const Rect &r)
222{
DRCcd2c5d42011-08-11 11:18:34 +0000223 ConnParams* cp = writer->getConnParams();
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000224 const unsigned int w = r.width();
225 const unsigned int h = r.height();
226
DRCcd2c5d42011-08-11 11:18:34 +0000227 // If last rect. encoding is enabled, we can use the higher-performance
228 // code that pre-computes solid rectangles. In that case, we don't care
229 // about the rectangle count.
230 if (cp->supportsLastRect && w * h >= TIGHT_MIN_SPLIT_RECT_SIZE)
231 return 0;
232
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000233 // Will this rectangle split into subrects?
234 bool rectTooBig = w > pconf->maxRectWidth || w * h > pconf->maxRectSize;
235 if (!rectTooBig)
236 return 1;
237
238 // Compute max sub-rectangle size.
239 const unsigned int subrectMaxWidth =
240 (w > pconf->maxRectWidth) ? pconf->maxRectWidth : w;
241 const unsigned int subrectMaxHeight =
242 pconf->maxRectSize / subrectMaxWidth;
243
244 // Return the number of subrects.
245 return (((w - 1) / pconf->maxRectWidth + 1) *
246 ((h - 1) / subrectMaxHeight + 1));
247}
248
DRCffe09d62011-08-17 02:27:59 +0000249void TightEncoder::sendRectSimple(const Rect& r)
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000250{
251 // Shortcuts to rectangle coordinates and dimensions.
252 const int x = r.tl.x;
253 const int y = r.tl.y;
254 const unsigned int w = r.width();
255 const unsigned int h = r.height();
256
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000257 // Encode small rects as is.
258 bool rectTooBig = w > pconf->maxRectWidth || w * h > pconf->maxRectSize;
259 if (!rectTooBig) {
DRCffe09d62011-08-17 02:27:59 +0000260 writeSubrect(r);
DRCcd2c5d42011-08-11 11:18:34 +0000261 return;
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000262 }
263
264 // Compute max sub-rectangle size.
265 const unsigned int subrectMaxWidth =
266 (w > pconf->maxRectWidth) ? pconf->maxRectWidth : w;
267 const unsigned int subrectMaxHeight =
268 pconf->maxRectSize / subrectMaxWidth;
269
270 // Split big rects into separately encoded subrects.
271 Rect sr;
272 unsigned int dx, dy, sw, sh;
273 for (dy = 0; dy < h; dy += subrectMaxHeight) {
274 for (dx = 0; dx < w; dx += pconf->maxRectWidth) {
275 sw = (dx + pconf->maxRectWidth < w) ? pconf->maxRectWidth : w - dx;
276 sh = (dy + subrectMaxHeight < h) ? subrectMaxHeight : h - dy;
277 sr.setXYWH(x + dx, y + dy, sw, sh);
DRCffe09d62011-08-17 02:27:59 +0000278 writeSubrect(sr);
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000279 }
280 }
DRCcd2c5d42011-08-11 11:18:34 +0000281}
282
DRCffe09d62011-08-17 02:27:59 +0000283bool TightEncoder::writeRect(const Rect& _r, TransImageGetter* _ig,
284 Rect* actual)
DRCcd2c5d42011-08-11 11:18:34 +0000285{
DRCffe09d62011-08-17 02:27:59 +0000286 ig = _ig;
287 serverpf = ig->getPixelBuffer()->getPF();
DRCcd2c5d42011-08-11 11:18:34 +0000288 ConnParams* cp = writer->getConnParams();
DRCffe09d62011-08-17 02:27:59 +0000289 clientpf = cp->pf();
DRCcd2c5d42011-08-11 11:18:34 +0000290
291 // Shortcuts to rectangle coordinates and dimensions.
292 Rect r = _r;
293 int x = r.tl.x;
294 int y = r.tl.y;
295 unsigned int w = r.width();
296 unsigned int h = r.height();
297
DRCcd2c5d42011-08-11 11:18:34 +0000298 // Encode small rects as is.
299 if (!cp->supportsLastRect || w * h < TIGHT_MIN_SPLIT_RECT_SIZE) {
DRCffe09d62011-08-17 02:27:59 +0000300 sendRectSimple(r);
DRCcd2c5d42011-08-11 11:18:34 +0000301 return true;
302 }
303
304 // Split big rects into separately encoded subrects.
305 Rect sr, bestr;
306 unsigned int dx, dy, dw, dh;
307 rdr::U32 colorValue;
DRCffe09d62011-08-17 02:27:59 +0000308 int maxRectSize = pconf->maxRectSize;
309 int maxRectWidth = pconf->maxRectWidth;
DRCcd2c5d42011-08-11 11:18:34 +0000310 int nMaxWidth = (w > maxRectWidth) ? maxRectWidth : w;
DRCffe09d62011-08-17 02:27:59 +0000311 int nMaxRows = pconf->maxRectSize / nMaxWidth;
DRCcd2c5d42011-08-11 11:18:34 +0000312
313 // Try to find large solid-color areas and send them separately.
314 for (dy = y; dy < y + h; dy += TIGHT_MAX_SPLIT_TILE_SIZE) {
315
316 // If a rectangle becomes too large, send its upper part now.
317 if (dy - y >= nMaxRows) {
318 sr.setXYWH(x, y, w, nMaxRows);
DRCffe09d62011-08-17 02:27:59 +0000319 sendRectSimple(sr);
DRCcd2c5d42011-08-11 11:18:34 +0000320 r.tl.y += nMaxRows;
321 y = r.tl.y;
322 h = r.height();
323 }
324
325 dh = (dy + TIGHT_MAX_SPLIT_TILE_SIZE <= y + h) ?
326 TIGHT_MAX_SPLIT_TILE_SIZE : (y + h - dy);
327
328 for (dx = x; dx < x + w; dx += TIGHT_MAX_SPLIT_TILE_SIZE) {
329
330 dw = (dx + TIGHT_MAX_SPLIT_TILE_SIZE <= x + w) ?
331 TIGHT_MAX_SPLIT_TILE_SIZE : (x + w - dx);
332
333 sr.setXYWH(dx, dy, dw, dh);
DRCffe09d62011-08-17 02:27:59 +0000334 if (checkSolidTile(sr, &colorValue, false)) {
DRCcd2c5d42011-08-11 11:18:34 +0000335
336 // Get dimensions of solid-color area.
337 sr.setXYWH(dx, dy, r.br.x - dx, r.br.y - dy);
DRCffe09d62011-08-17 02:27:59 +0000338 findBestSolidArea(sr, colorValue, bestr);
DRCcd2c5d42011-08-11 11:18:34 +0000339
340 // Make sure a solid rectangle is large enough
341 // (or the whole rectangle is of the same color).
342 if (bestr.area() != r.area()
343 && bestr.area() < TIGHT_MIN_SOLID_SUBRECT_SIZE)
344 continue;
345
346 // Try to extend solid rectangle to maximum size.
DRCffe09d62011-08-17 02:27:59 +0000347 extendSolidArea(r, colorValue, bestr);
DRCcd2c5d42011-08-11 11:18:34 +0000348
349 // Send rectangles at top and left to solid-color area.
350 if (bestr.tl.y != y) {
351 sr.setXYWH(x, y, w, bestr.tl.y - y);
DRCffe09d62011-08-17 02:27:59 +0000352 sendRectSimple(sr);
DRCcd2c5d42011-08-11 11:18:34 +0000353 }
354 if (bestr.tl.x != x) {
355 sr.setXYWH(x, bestr.tl.y, bestr.tl.x - x, bestr.height());
DRCffe09d62011-08-17 02:27:59 +0000356 writeRect(sr, _ig, NULL);
DRCcd2c5d42011-08-11 11:18:34 +0000357 }
358
359 // Send solid-color rectangle.
DRCffe09d62011-08-17 02:27:59 +0000360 writeSubrect(bestr, true);
DRCcd2c5d42011-08-11 11:18:34 +0000361
362 // Send remaining rectangles (at right and bottom).
363 if (bestr.br.x != r.br.x) {
364 sr.setXYWH(bestr.br.x, bestr.tl.y, r.br.x - bestr.br.x,
365 bestr.height());
DRCffe09d62011-08-17 02:27:59 +0000366 writeRect(sr, _ig, NULL);
DRCcd2c5d42011-08-11 11:18:34 +0000367 }
368 if (bestr.br.y != r.br.y) {
369 sr.setXYWH(x, bestr.br.y, w, r.br.y - bestr.br.y);
DRCffe09d62011-08-17 02:27:59 +0000370 writeRect(sr, _ig, NULL);
DRCcd2c5d42011-08-11 11:18:34 +0000371 }
372
373 return true;
374 }
375 }
376 }
377
378 // No suitable solid-color rectangles found.
DRCffe09d62011-08-17 02:27:59 +0000379 sendRectSimple(r);
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000380 return true;
381}
382
DRCffe09d62011-08-17 02:27:59 +0000383void TightEncoder::writeSubrect(const Rect& r, bool forceSolid)
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000384{
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000385 mos.clear();
386
DRCffe09d62011-08-17 02:27:59 +0000387 switch (clientpf.bpp) {
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000388 case 8:
DRCffe09d62011-08-17 02:27:59 +0000389 tightEncode8(r, &mos, forceSolid); break;
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000390 case 16:
DRCffe09d62011-08-17 02:27:59 +0000391 tightEncode16(r, &mos, forceSolid); break;
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000392 case 32:
DRCffe09d62011-08-17 02:27:59 +0000393 tightEncode32(r, &mos, forceSolid); break;
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000394 }
395
396 writer->startRect(r, encodingTight);
397 rdr::OutStream* os = writer->getOutStream();
398 os->writeBytes(mos.data(), mos.length());
399 writer->endRect();
400}