blob: 52673a6d3cd717d826fa30890bd4f90a4cbc955b [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>
20#include <rfb/ImageGetter.h>
21#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};
DRCcd2c5d42011-08-11 11:18:34 +000078const int TightEncoder::defaultCompressLevel = 1;
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +000079
80// FIXME: Not good to mirror TightEncoder's members here.
81static const TIGHT_CONF* s_pconf;
82static const TIGHT_CONF* s_pjconf;
83
84//
85// Including BPP-dependent implementation of the encoder.
86//
87
88#define EXTRA_ARGS ImageGetter* ig
89#define GET_IMAGE_INTO_BUF(r,buf) ig->getImage(buf, r);
90#define BPP 8
91#include <rfb/tightEncode.h>
92#undef BPP
93#define BPP 16
94#include <rfb/tightEncode.h>
95#undef BPP
96#define BPP 32
97#include <rfb/tightEncode.h>
98#undef BPP
99
100Encoder* TightEncoder::create(SMsgWriter* writer)
101{
102 return new TightEncoder(writer);
103}
104
105TightEncoder::TightEncoder(SMsgWriter* writer_) : writer(writer_)
106{
107 setCompressLevel(defaultCompressLevel);
108 setQualityLevel(-1);
109}
110
111TightEncoder::~TightEncoder()
112{
113}
114
115void TightEncoder::setCompressLevel(int level)
116{
117 if (level >= 0 && level <= 9) {
118 pconf = &conf[level];
119 } else {
120 pconf = &conf[defaultCompressLevel];
121 }
122}
123
124void TightEncoder::setQualityLevel(int level)
125{
126 if (level >= 0 && level <= 9) {
127 pjconf = &conf[level];
128 } else {
129 pjconf = NULL;
130 }
131}
132
DRCcd2c5d42011-08-11 11:18:34 +0000133bool TightEncoder::checkSolidTile(Rect& r, ImageGetter *ig, rdr::U32* colorPtr,
134 bool needSameColor)
135{
136 switch (writer->bpp()) {
137 case 32:
138 return checkSolidTile32(r, ig, writer, colorPtr, needSameColor);
139 case 16:
140 return checkSolidTile16(r, ig, writer, colorPtr, needSameColor);
141 default:
142 return checkSolidTile8(r, ig, writer, colorPtr, needSameColor);
143 }
144}
145
146void TightEncoder::findBestSolidArea(Rect& r, ImageGetter *ig,
147 rdr::U32 colorValue, Rect& bestr)
148{
149 int dx, dy, dw, dh;
150 int w_prev;
151 Rect sr;
152 int w_best = 0, h_best = 0;
153
154 bestr.tl.x = bestr.br.x = r.tl.x;
155 bestr.tl.y = bestr.br.y = r.tl.y;
156
157 w_prev = r.width();
158
159 for (dy = r.tl.y; dy < r.br.y; dy += TIGHT_MAX_SPLIT_TILE_SIZE) {
160
161 dh = (dy + TIGHT_MAX_SPLIT_TILE_SIZE <= r.br.y) ?
162 TIGHT_MAX_SPLIT_TILE_SIZE : (r.br.y - dy);
163 dw = (w_prev > TIGHT_MAX_SPLIT_TILE_SIZE) ?
164 TIGHT_MAX_SPLIT_TILE_SIZE : w_prev;
165
166 sr.setXYWH(r.tl.x, dy, dw, dh);
167 if (!checkSolidTile(sr, ig, &colorValue, true))
168 break;
169
170 for (dx = r.tl.x + dw; dx < r.tl.x + w_prev;) {
171 dw = (dx + TIGHT_MAX_SPLIT_TILE_SIZE <= r.tl.x + w_prev) ?
172 TIGHT_MAX_SPLIT_TILE_SIZE : (r.tl.x + w_prev - dx);
173 sr.setXYWH(dx, dy, dw, dh);
174 if (!checkSolidTile(sr, ig, &colorValue, true))
175 break;
176 dx += dw;
177 }
178
179 w_prev = dx - r.tl.x;
180 if (w_prev * (dy + dh - r.tl.y) > w_best * h_best) {
181 w_best = w_prev;
182 h_best = dy + dh - r.tl.y;
183 }
184 }
185
186 bestr.br.x = bestr.tl.x + w_best;
187 bestr.br.y = bestr.tl.y + h_best;
188}
189
190void TightEncoder::extendSolidArea(const Rect& r, ImageGetter *ig,
191 rdr::U32 colorValue, Rect& er)
192{
193 int cx, cy;
194 Rect sr;
195
196 // Try to extend the area upwards.
197 for (cy = er.tl.y - 1; ; cy--) {
198 sr.setXYWH(er.tl.x, cy, er.width(), 1);
199 if (cy < r.tl.y || !checkSolidTile(sr, ig, &colorValue, true))
200 break;
201 }
202 er.tl.y = cy + 1;
203
204 // ... downwards.
205 for (cy = er.br.y; ; cy++) {
206 sr.setXYWH(er.tl.x, cy, er.width(), 1);
207 if (cy >= r.br.y || !checkSolidTile(sr, ig, &colorValue, true))
208 break;
209 }
210 er.br.y = cy;
211
212 // ... to the left.
213 for (cx = er.tl.x - 1; ; cx--) {
214 sr.setXYWH(cx, er.tl.y, 1, er.height());
215 if (cx < r.tl.x || !checkSolidTile(sr, ig, &colorValue, true))
216 break;
217 }
218 er.tl.x = cx + 1;
219
220 // ... to the right.
221 for (cx = er.br.x; ; cx++) {
222 sr.setXYWH(cx, er.tl.y, 1, er.height());
223 if (cx >= r.br.x || !checkSolidTile(sr, ig, &colorValue, true))
224 break;
225 }
226 er.br.x = cx;
227}
228
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000229int TightEncoder::getNumRects(const Rect &r)
230{
DRCcd2c5d42011-08-11 11:18:34 +0000231 ConnParams* cp = writer->getConnParams();
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000232 const unsigned int w = r.width();
233 const unsigned int h = r.height();
234
DRCcd2c5d42011-08-11 11:18:34 +0000235 // If last rect. encoding is enabled, we can use the higher-performance
236 // code that pre-computes solid rectangles. In that case, we don't care
237 // about the rectangle count.
238 if (cp->supportsLastRect && w * h >= TIGHT_MIN_SPLIT_RECT_SIZE)
239 return 0;
240
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000241 // Will this rectangle split into subrects?
242 bool rectTooBig = w > pconf->maxRectWidth || w * h > pconf->maxRectSize;
243 if (!rectTooBig)
244 return 1;
245
246 // Compute max sub-rectangle size.
247 const unsigned int subrectMaxWidth =
248 (w > pconf->maxRectWidth) ? pconf->maxRectWidth : w;
249 const unsigned int subrectMaxHeight =
250 pconf->maxRectSize / subrectMaxWidth;
251
252 // Return the number of subrects.
253 return (((w - 1) / pconf->maxRectWidth + 1) *
254 ((h - 1) / subrectMaxHeight + 1));
255}
256
DRCcd2c5d42011-08-11 11:18:34 +0000257void TightEncoder::sendRectSimple(const Rect& r, ImageGetter* ig)
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000258{
259 // Shortcuts to rectangle coordinates and dimensions.
260 const int x = r.tl.x;
261 const int y = r.tl.y;
262 const unsigned int w = r.width();
263 const unsigned int h = r.height();
264
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000265 // Encode small rects as is.
266 bool rectTooBig = w > pconf->maxRectWidth || w * h > pconf->maxRectSize;
267 if (!rectTooBig) {
268 writeSubrect(r, ig);
DRCcd2c5d42011-08-11 11:18:34 +0000269 return;
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000270 }
271
272 // Compute max sub-rectangle size.
273 const unsigned int subrectMaxWidth =
274 (w > pconf->maxRectWidth) ? pconf->maxRectWidth : w;
275 const unsigned int subrectMaxHeight =
276 pconf->maxRectSize / subrectMaxWidth;
277
278 // Split big rects into separately encoded subrects.
279 Rect sr;
280 unsigned int dx, dy, sw, sh;
281 for (dy = 0; dy < h; dy += subrectMaxHeight) {
282 for (dx = 0; dx < w; dx += pconf->maxRectWidth) {
283 sw = (dx + pconf->maxRectWidth < w) ? pconf->maxRectWidth : w - dx;
284 sh = (dy + subrectMaxHeight < h) ? subrectMaxHeight : h - dy;
285 sr.setXYWH(x + dx, y + dy, sw, sh);
286 writeSubrect(sr, ig);
287 }
288 }
DRCcd2c5d42011-08-11 11:18:34 +0000289}
290
291bool TightEncoder::writeRect(const Rect& _r, ImageGetter* ig, Rect* actual)
292{
293 ConnParams* cp = writer->getConnParams();
294
295 // Shortcuts to rectangle coordinates and dimensions.
296 Rect r = _r;
297 int x = r.tl.x;
298 int y = r.tl.y;
299 unsigned int w = r.width();
300 unsigned int h = r.height();
301
302 // Copy members of current TightEncoder instance to static variables.
303 s_pconf = pconf;
304 s_pjconf = pjconf;
305
306 // Encode small rects as is.
307 if (!cp->supportsLastRect || w * h < TIGHT_MIN_SPLIT_RECT_SIZE) {
308 sendRectSimple(r, ig);
309 return true;
310 }
311
312 // Split big rects into separately encoded subrects.
313 Rect sr, bestr;
314 unsigned int dx, dy, dw, dh;
315 rdr::U32 colorValue;
316 int maxRectSize = s_pconf->maxRectSize;
317 int maxRectWidth = s_pconf->maxRectWidth;
318 int nMaxWidth = (w > maxRectWidth) ? maxRectWidth : w;
319 int nMaxRows = s_pconf->maxRectSize / nMaxWidth;
320
321 // Try to find large solid-color areas and send them separately.
322 for (dy = y; dy < y + h; dy += TIGHT_MAX_SPLIT_TILE_SIZE) {
323
324 // If a rectangle becomes too large, send its upper part now.
325 if (dy - y >= nMaxRows) {
326 sr.setXYWH(x, y, w, nMaxRows);
327 sendRectSimple(sr, ig);
328 r.tl.y += nMaxRows;
329 y = r.tl.y;
330 h = r.height();
331 }
332
333 dh = (dy + TIGHT_MAX_SPLIT_TILE_SIZE <= y + h) ?
334 TIGHT_MAX_SPLIT_TILE_SIZE : (y + h - dy);
335
336 for (dx = x; dx < x + w; dx += TIGHT_MAX_SPLIT_TILE_SIZE) {
337
338 dw = (dx + TIGHT_MAX_SPLIT_TILE_SIZE <= x + w) ?
339 TIGHT_MAX_SPLIT_TILE_SIZE : (x + w - dx);
340
341 sr.setXYWH(dx, dy, dw, dh);
342 if (checkSolidTile(sr, ig, &colorValue, false)) {
343
344 // Get dimensions of solid-color area.
345 sr.setXYWH(dx, dy, r.br.x - dx, r.br.y - dy);
346 findBestSolidArea(sr, ig, colorValue, bestr);
347
348 // Make sure a solid rectangle is large enough
349 // (or the whole rectangle is of the same color).
350 if (bestr.area() != r.area()
351 && bestr.area() < TIGHT_MIN_SOLID_SUBRECT_SIZE)
352 continue;
353
354 // Try to extend solid rectangle to maximum size.
355 extendSolidArea(r, ig, colorValue, bestr);
356
357 // Send rectangles at top and left to solid-color area.
358 if (bestr.tl.y != y) {
359 sr.setXYWH(x, y, w, bestr.tl.y - y);
360 sendRectSimple(sr, ig);
361 }
362 if (bestr.tl.x != x) {
363 sr.setXYWH(x, bestr.tl.y, bestr.tl.x - x, bestr.height());
364 writeRect(sr, ig, NULL);
365 }
366
367 // Send solid-color rectangle.
368 writeSubrect(bestr, ig, true);
369
370 // Send remaining rectangles (at right and bottom).
371 if (bestr.br.x != r.br.x) {
372 sr.setXYWH(bestr.br.x, bestr.tl.y, r.br.x - bestr.br.x,
373 bestr.height());
374 writeRect(sr, ig, NULL);
375 }
376 if (bestr.br.y != r.br.y) {
377 sr.setXYWH(x, bestr.br.y, w, r.br.y - bestr.br.y);
378 writeRect(sr, ig, NULL);
379 }
380
381 return true;
382 }
383 }
384 }
385
386 // No suitable solid-color rectangles found.
387 sendRectSimple(r, ig);
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000388 return true;
389}
390
DRCcd2c5d42011-08-11 11:18:34 +0000391void TightEncoder::writeSubrect(const Rect& r, ImageGetter* ig,
392 bool forceSolid)
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000393{
394 rdr::U8* imageBuf = writer->getImageBuf(r.area());
395 ConnParams* cp = writer->getConnParams();
396 mos.clear();
397
398 switch (writer->bpp()) {
399 case 8:
DRCcd2c5d42011-08-11 11:18:34 +0000400 tightEncode8(r, &mos, zos, jc, imageBuf, cp, ig, forceSolid); break;
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000401 case 16:
DRCcd2c5d42011-08-11 11:18:34 +0000402 tightEncode16(r, &mos, zos, jc, imageBuf, cp, ig, forceSolid); break;
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000403 case 32:
DRCcd2c5d42011-08-11 11:18:34 +0000404 tightEncode32(r, &mos, zos, jc, imageBuf, cp, ig, forceSolid); break;
Constantin Kaplinskya2adc8d2006-05-25 05:01:55 +0000405 }
406
407 writer->startRect(r, encodingTight);
408 rdr::OutStream* os = writer->getOutStream();
409 os->writeBytes(mos.data(), mos.length());
410 writer->endRect();
411}