blob: 0ce611e9b0509573327db55b193e94e0c8ca6a34 [file] [log] [blame]
Pierre Ossmanc0397262014-03-14 15:59:46 +01001/* Copyright (C) 2000-2003 Constantin Kaplinsky. All Rights Reserved.
2 * Copyright (C) 2011 D. R. Commander. All Rights Reserved.
Pierre Ossman8c3bd692018-03-22 15:58:54 +01003 * Copyright 2014-2018 Pierre Ossman for Cendio AB
Pierre Ossmanc0397262014-03-14 15:59:46 +01004 *
5 * This is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This software is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this software; if not, write to the Free Software
17 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
18 * USA.
19 */
Pierre Ossman6b2f1132016-11-30 08:03:35 +010020
21#include <stdlib.h>
22
Pierre Ossmanc0397262014-03-14 15:59:46 +010023#include <rfb/EncodeManager.h>
24#include <rfb/Encoder.h>
25#include <rfb/Palette.h>
26#include <rfb/SConnection.h>
27#include <rfb/SMsgWriter.h>
28#include <rfb/UpdateTracker.h>
Pierre Ossman20dd2a92015-02-11 17:43:15 +010029#include <rfb/LogWriter.h>
Pierre Ossmanc0397262014-03-14 15:59:46 +010030
31#include <rfb/RawEncoder.h>
32#include <rfb/RREEncoder.h>
33#include <rfb/HextileEncoder.h>
34#include <rfb/ZRLEEncoder.h>
35#include <rfb/TightEncoder.h>
36#include <rfb/TightJPEGEncoder.h>
37
38using namespace rfb;
39
Pierre Ossman20dd2a92015-02-11 17:43:15 +010040static LogWriter vlog("EncodeManager");
41
Pierre Ossmanc0397262014-03-14 15:59:46 +010042// Split each rectangle into smaller ones no larger than this area,
43// and no wider than this width.
44static const int SubRectMaxArea = 65536;
45static const int SubRectMaxWidth = 2048;
46
47// The size in pixels of either side of each block tested when looking
48// for solid blocks.
49static const int SolidSearchBlock = 16;
50// Don't bother with blocks smaller than this
51static const int SolidBlockMinArea = 2048;
52
53namespace rfb {
54
55enum EncoderClass {
56 encoderRaw,
57 encoderRRE,
58 encoderHextile,
59 encoderTight,
60 encoderTightJPEG,
61 encoderZRLE,
62 encoderClassMax,
63};
64
65enum EncoderType {
66 encoderSolid,
67 encoderBitmap,
68 encoderBitmapRLE,
69 encoderIndexed,
70 encoderIndexedRLE,
71 encoderFullColour,
72 encoderTypeMax,
73};
74
75struct RectInfo {
76 int rleRuns;
77 Palette palette;
78};
79
80};
81
Pierre Ossman20dd2a92015-02-11 17:43:15 +010082static const char *encoderClassName(EncoderClass klass)
83{
84 switch (klass) {
85 case encoderRaw:
86 return "Raw";
87 case encoderRRE:
88 return "RRE";
89 case encoderHextile:
90 return "Hextile";
91 case encoderTight:
92 return "Tight";
93 case encoderTightJPEG:
94 return "Tight (JPEG)";
95 case encoderZRLE:
96 return "ZRLE";
Pierre Ossman620dd952015-03-03 16:28:54 +010097 case encoderClassMax:
98 break;
Pierre Ossman20dd2a92015-02-11 17:43:15 +010099 }
100
101 return "Unknown Encoder Class";
102}
103
104static const char *encoderTypeName(EncoderType type)
105{
106 switch (type) {
107 case encoderSolid:
108 return "Solid";
109 case encoderBitmap:
110 return "Bitmap";
111 case encoderBitmapRLE:
112 return "Bitmap RLE";
113 case encoderIndexed:
114 return "Indexed";
115 case encoderIndexedRLE:
116 return "Indexed RLE";
117 case encoderFullColour:
118 return "Full Colour";
Pierre Ossman620dd952015-03-03 16:28:54 +0100119 case encoderTypeMax:
120 break;
Pierre Ossman20dd2a92015-02-11 17:43:15 +0100121 }
122
123 return "Unknown Encoder Type";
124}
125
Pierre Ossmanc0397262014-03-14 15:59:46 +0100126EncodeManager::EncodeManager(SConnection* conn_) : conn(conn_)
127{
Pierre Ossman20dd2a92015-02-11 17:43:15 +0100128 StatsVector::iterator iter;
129
Pierre Ossmanc0397262014-03-14 15:59:46 +0100130 encoders.resize(encoderClassMax, NULL);
131 activeEncoders.resize(encoderTypeMax, encoderRaw);
132
133 encoders[encoderRaw] = new RawEncoder(conn);
134 encoders[encoderRRE] = new RREEncoder(conn);
135 encoders[encoderHextile] = new HextileEncoder(conn);
136 encoders[encoderTight] = new TightEncoder(conn);
137 encoders[encoderTightJPEG] = new TightJPEGEncoder(conn);
138 encoders[encoderZRLE] = new ZRLEEncoder(conn);
Pierre Ossman20dd2a92015-02-11 17:43:15 +0100139
140 updates = 0;
Pierre Ossmane539cb82015-09-22 11:09:00 +0200141 memset(&copyStats, 0, sizeof(copyStats));
Pierre Ossman20dd2a92015-02-11 17:43:15 +0100142 stats.resize(encoderClassMax);
143 for (iter = stats.begin();iter != stats.end();++iter) {
144 StatsVector::value_type::iterator iter2;
145 iter->resize(encoderTypeMax);
146 for (iter2 = iter->begin();iter2 != iter->end();++iter2)
147 memset(&*iter2, 0, sizeof(EncoderStats));
148 }
Pierre Ossmanc0397262014-03-14 15:59:46 +0100149}
150
151EncodeManager::~EncodeManager()
152{
153 std::vector<Encoder*>::iterator iter;
154
Pierre Ossman20dd2a92015-02-11 17:43:15 +0100155 logStats();
156
Pierre Ossmanc0397262014-03-14 15:59:46 +0100157 for (iter = encoders.begin();iter != encoders.end();iter++)
158 delete *iter;
159}
160
Pierre Ossman20dd2a92015-02-11 17:43:15 +0100161void EncodeManager::logStats()
162{
Pierre Ossman5c23b9e2015-03-03 16:26:03 +0100163 size_t i, j;
Pierre Ossman20dd2a92015-02-11 17:43:15 +0100164
165 unsigned rects;
166 unsigned long long pixels, bytes, equivalent;
167
168 double ratio;
169
Pierre Ossman64624342015-03-03 16:30:13 +0100170 char a[1024], b[1024];
171
Pierre Ossman20dd2a92015-02-11 17:43:15 +0100172 rects = 0;
173 pixels = bytes = equivalent = 0;
174
175 vlog.info("Framebuffer updates: %u", updates);
176
Pierre Ossmane539cb82015-09-22 11:09:00 +0200177 if (copyStats.rects != 0) {
178 vlog.info(" %s:", "CopyRect");
179
180 rects += copyStats.rects;
181 pixels += copyStats.pixels;
182 bytes += copyStats.bytes;
183 equivalent += copyStats.equivalent;
184
185 ratio = (double)copyStats.equivalent / copyStats.bytes;
186
187 siPrefix(copyStats.rects, "rects", a, sizeof(a));
188 siPrefix(copyStats.pixels, "pixels", b, sizeof(b));
189 vlog.info(" %s: %s, %s", "Copies", a, b);
190 iecPrefix(copyStats.bytes, "B", a, sizeof(a));
191 vlog.info(" %*s %s (1:%g ratio)",
192 (int)strlen("Copies"), "",
193 a, ratio);
194 }
195
Pierre Ossman20dd2a92015-02-11 17:43:15 +0100196 for (i = 0;i < stats.size();i++) {
197 // Did this class do anything at all?
198 for (j = 0;j < stats[i].size();j++) {
199 if (stats[i][j].rects != 0)
200 break;
201 }
202 if (j == stats[i].size())
203 continue;
204
205 vlog.info(" %s:", encoderClassName((EncoderClass)i));
206
207 for (j = 0;j < stats[i].size();j++) {
208 if (stats[i][j].rects == 0)
209 continue;
210
211 rects += stats[i][j].rects;
212 pixels += stats[i][j].pixels;
213 bytes += stats[i][j].bytes;
214 equivalent += stats[i][j].equivalent;
215
216 ratio = (double)stats[i][j].equivalent / stats[i][j].bytes;
217
Pierre Ossman64624342015-03-03 16:30:13 +0100218 siPrefix(stats[i][j].rects, "rects", a, sizeof(a));
219 siPrefix(stats[i][j].pixels, "pixels", b, sizeof(b));
220 vlog.info(" %s: %s, %s", encoderTypeName((EncoderType)j), a, b);
221 iecPrefix(stats[i][j].bytes, "B", a, sizeof(a));
222 vlog.info(" %*s %s (1:%g ratio)",
223 (int)strlen(encoderTypeName((EncoderType)j)), "",
224 a, ratio);
Pierre Ossman20dd2a92015-02-11 17:43:15 +0100225 }
226 }
227
228 ratio = (double)equivalent / bytes;
229
Pierre Ossman64624342015-03-03 16:30:13 +0100230 siPrefix(rects, "rects", a, sizeof(a));
231 siPrefix(pixels, "pixels", b, sizeof(b));
232 vlog.info(" Total: %s, %s", a, b);
233 iecPrefix(bytes, "B", a, sizeof(a));
234 vlog.info(" %s (1:%g ratio)", a, ratio);
Pierre Ossman20dd2a92015-02-11 17:43:15 +0100235}
236
Pierre Ossmanc0397262014-03-14 15:59:46 +0100237bool EncodeManager::supported(int encoding)
238{
239 switch (encoding) {
240 case encodingRaw:
241 case encodingRRE:
242 case encodingHextile:
243 case encodingZRLE:
244 case encodingTight:
245 return true;
246 default:
247 return false;
248 }
249}
250
Pierre Ossman6b2f1132016-11-30 08:03:35 +0100251bool EncodeManager::needsLosslessRefresh(const Region& req)
252{
253 return !lossyRegion.intersect(req).is_empty();
254}
255
256void EncodeManager::pruneLosslessRefresh(const Region& limits)
257{
258 lossyRegion.assign_intersect(limits);
259}
260
Pierre Ossmanc0397262014-03-14 15:59:46 +0100261void EncodeManager::writeUpdate(const UpdateInfo& ui, const PixelBuffer* pb,
262 const RenderedCursor* renderedCursor)
263{
Pierre Ossman6b2f1132016-11-30 08:03:35 +0100264 doUpdate(true, ui.changed, ui.copied, ui.copy_delta, pb, renderedCursor);
265}
266
267void EncodeManager::writeLosslessRefresh(const Region& req, const PixelBuffer* pb,
Pierre Ossmana2b80d62018-03-23 09:30:09 +0100268 const RenderedCursor* renderedCursor,
269 size_t maxUpdateSize)
Pierre Ossman6b2f1132016-11-30 08:03:35 +0100270{
Pierre Ossmana2b80d62018-03-23 09:30:09 +0100271 doUpdate(false, getLosslessRefresh(req, maxUpdateSize),
Pierre Ossman6b2f1132016-11-30 08:03:35 +0100272 Region(), Point(), pb, renderedCursor);
273}
274
275void EncodeManager::doUpdate(bool allowLossy, const Region& changed_,
276 const Region& copied, const Point& copyDelta,
277 const PixelBuffer* pb,
278 const RenderedCursor* renderedCursor)
279{
Pierre Ossmanc0397262014-03-14 15:59:46 +0100280 int nRects;
Pierre Ossman8c3bd692018-03-22 15:58:54 +0100281 Region changed, cursorRegion;
Pierre Ossmanc0397262014-03-14 15:59:46 +0100282
Pierre Ossman20dd2a92015-02-11 17:43:15 +0100283 updates++;
284
Pierre Ossman6b2f1132016-11-30 08:03:35 +0100285 prepareEncoders(allowLossy);
Pierre Ossmanc0397262014-03-14 15:59:46 +0100286
Pierre Ossman6b2f1132016-11-30 08:03:35 +0100287 changed = changed_;
Pierre Ossman8c3bd692018-03-22 15:58:54 +0100288
289 /*
290 * We need to render the cursor seperately as it has its own
291 * magical pixel buffer, so split it out from the changed region.
292 */
293 if (renderedCursor != NULL) {
294 cursorRegion = changed.intersect(renderedCursor->getEffectiveRect());
295 changed.assign_subtract(renderedCursor->getEffectiveRect());
296 }
297
Pierre Ossmanc0397262014-03-14 15:59:46 +0100298 if (conn->cp.supportsLastRect)
299 nRects = 0xFFFF;
300 else {
Pierre Ossman6b2f1132016-11-30 08:03:35 +0100301 nRects = copied.numRects();
Pierre Ossman8c3bd692018-03-22 15:58:54 +0100302 nRects += computeNumRects(changed);
303 nRects += computeNumRects(cursorRegion);
Pierre Ossmanc0397262014-03-14 15:59:46 +0100304 }
305
306 conn->writer()->writeFramebufferUpdateStart(nRects);
307
Pierre Ossman6b2f1132016-11-30 08:03:35 +0100308 writeCopyRects(copied, copyDelta);
Pierre Ossmanc0397262014-03-14 15:59:46 +0100309
310 /*
311 * We start by searching for solid rects, which are then removed
312 * from the changed region.
313 */
Pierre Ossmanc0397262014-03-14 15:59:46 +0100314 if (conn->cp.supportsLastRect)
315 writeSolidRects(&changed, pb);
316
317 writeRects(changed, pb);
Pierre Ossman8c3bd692018-03-22 15:58:54 +0100318 writeRects(cursorRegion, renderedCursor);
Pierre Ossmanc0397262014-03-14 15:59:46 +0100319
320 conn->writer()->writeFramebufferUpdateEnd();
321}
322
Pierre Ossman6b2f1132016-11-30 08:03:35 +0100323void EncodeManager::prepareEncoders(bool allowLossy)
Pierre Ossmanc0397262014-03-14 15:59:46 +0100324{
325 enum EncoderClass solid, bitmap, bitmapRLE;
326 enum EncoderClass indexed, indexedRLE, fullColour;
327
328 rdr::S32 preferred;
329
330 std::vector<int>::iterator iter;
331
332 solid = bitmap = bitmapRLE = encoderRaw;
333 indexed = indexedRLE = fullColour = encoderRaw;
334
335 // Try to respect the client's wishes
Pierre Ossman48700812014-09-17 17:11:56 +0200336 preferred = conn->getPreferredEncoding();
Pierre Ossmanc0397262014-03-14 15:59:46 +0100337 switch (preferred) {
338 case encodingRRE:
339 // Horrible for anything high frequency and/or lots of colours
340 bitmapRLE = indexedRLE = encoderRRE;
341 break;
342 case encodingHextile:
343 // Slightly less horrible
344 bitmapRLE = indexedRLE = fullColour = encoderHextile;
345 break;
346 case encodingTight:
347 if (encoders[encoderTightJPEG]->isSupported() &&
Pierre Ossman6b2f1132016-11-30 08:03:35 +0100348 (conn->cp.pf().bpp >= 16) && allowLossy)
Pierre Ossmanc0397262014-03-14 15:59:46 +0100349 fullColour = encoderTightJPEG;
350 else
351 fullColour = encoderTight;
352 indexed = indexedRLE = encoderTight;
353 bitmap = bitmapRLE = encoderTight;
354 break;
355 case encodingZRLE:
356 fullColour = encoderZRLE;
357 bitmapRLE = indexedRLE = encoderZRLE;
358 bitmap = indexed = encoderZRLE;
359 break;
360 }
361
362 // Any encoders still unassigned?
363
364 if (fullColour == encoderRaw) {
365 if (encoders[encoderTightJPEG]->isSupported() &&
Pierre Ossman6b2f1132016-11-30 08:03:35 +0100366 (conn->cp.pf().bpp >= 16) && allowLossy)
Pierre Ossmanc0397262014-03-14 15:59:46 +0100367 fullColour = encoderTightJPEG;
368 else if (encoders[encoderZRLE]->isSupported())
369 fullColour = encoderZRLE;
370 else if (encoders[encoderTight]->isSupported())
371 fullColour = encoderTight;
372 else if (encoders[encoderHextile]->isSupported())
373 fullColour = encoderHextile;
374 }
375
376 if (indexed == encoderRaw) {
377 if (encoders[encoderZRLE]->isSupported())
378 indexed = encoderZRLE;
379 else if (encoders[encoderTight]->isSupported())
380 indexed = encoderTight;
381 else if (encoders[encoderHextile]->isSupported())
382 indexed = encoderHextile;
383 }
384
385 if (indexedRLE == encoderRaw)
386 indexedRLE = indexed;
387
388 if (bitmap == encoderRaw)
389 bitmap = indexed;
390 if (bitmapRLE == encoderRaw)
391 bitmapRLE = bitmap;
392
393 if (solid == encoderRaw) {
394 if (encoders[encoderTight]->isSupported())
395 solid = encoderTight;
396 else if (encoders[encoderRRE]->isSupported())
397 solid = encoderRRE;
398 else if (encoders[encoderZRLE]->isSupported())
399 solid = encoderZRLE;
400 else if (encoders[encoderHextile]->isSupported())
401 solid = encoderHextile;
402 }
403
404 // JPEG is the only encoder that can reduce things to grayscale
405 if ((conn->cp.subsampling == subsampleGray) &&
Pierre Ossman6b2f1132016-11-30 08:03:35 +0100406 encoders[encoderTightJPEG]->isSupported() && allowLossy) {
Pierre Ossmanc0397262014-03-14 15:59:46 +0100407 solid = bitmap = bitmapRLE = encoderTightJPEG;
408 indexed = indexedRLE = fullColour = encoderTightJPEG;
409 }
410
411 activeEncoders[encoderSolid] = solid;
412 activeEncoders[encoderBitmap] = bitmap;
413 activeEncoders[encoderBitmapRLE] = bitmapRLE;
414 activeEncoders[encoderIndexed] = indexed;
415 activeEncoders[encoderIndexedRLE] = indexedRLE;
416 activeEncoders[encoderFullColour] = fullColour;
417
418 for (iter = activeEncoders.begin(); iter != activeEncoders.end(); ++iter) {
419 Encoder *encoder;
420
421 encoder = encoders[*iter];
422
423 encoder->setCompressLevel(conn->cp.compressLevel);
424 encoder->setQualityLevel(conn->cp.qualityLevel);
425 encoder->setFineQualityLevel(conn->cp.fineQualityLevel,
426 conn->cp.subsampling);
427 }
428}
429
Pierre Ossmana2b80d62018-03-23 09:30:09 +0100430Region EncodeManager::getLosslessRefresh(const Region& req,
431 size_t maxUpdateSize)
Pierre Ossman6b2f1132016-11-30 08:03:35 +0100432{
433 std::vector<Rect> rects;
434 Region refresh;
435 size_t area;
436
Pierre Ossmana2b80d62018-03-23 09:30:09 +0100437 // We make a conservative guess at the compression ratio at 2:1
438 maxUpdateSize *= 2;
439
Pierre Ossman6b2f1132016-11-30 08:03:35 +0100440 area = 0;
441 lossyRegion.intersect(req).get_rects(&rects);
442 while (!rects.empty()) {
443 size_t idx;
444 Rect rect;
445
446 // Grab a random rect so we don't keep damaging and restoring the
447 // same rect over and over
448 idx = rand() % rects.size();
449
450 rect = rects[idx];
451
452 // Add rects until we exceed the threshold, then include as much as
453 // possible of the final rect
Pierre Ossmana2b80d62018-03-23 09:30:09 +0100454 if ((area + rect.area()) > maxUpdateSize) {
Pierre Ossman6b2f1132016-11-30 08:03:35 +0100455 // Use the narrowest axis to avoid getting to thin rects
456 if (rect.width() > rect.height()) {
Pierre Ossmana2b80d62018-03-23 09:30:09 +0100457 int width = (maxUpdateSize - area) / rect.height();
Pierre Ossman6b2f1132016-11-30 08:03:35 +0100458 rect.br.x = rect.tl.x + __rfbmax(1, width);
459 } else {
Pierre Ossmana2b80d62018-03-23 09:30:09 +0100460 int height = (maxUpdateSize - area) / rect.width();
Pierre Ossman6b2f1132016-11-30 08:03:35 +0100461 rect.br.y = rect.tl.y + __rfbmax(1, height);
462 }
463 refresh.assign_union(Region(rect));
464 break;
465 }
466
467 area += rect.area();
468 refresh.assign_union(Region(rect));
469
470 rects.erase(rects.begin() + idx);
471 }
472
473 return refresh;
474}
475
Pierre Ossmanc0397262014-03-14 15:59:46 +0100476int EncodeManager::computeNumRects(const Region& changed)
477{
478 int numRects;
479 std::vector<Rect> rects;
480 std::vector<Rect>::const_iterator rect;
481
482 numRects = 0;
483 changed.get_rects(&rects);
484 for (rect = rects.begin(); rect != rects.end(); ++rect) {
485 int w, h, sw, sh;
486
487 w = rect->width();
488 h = rect->height();
489
490 // No split necessary?
491 if (((w*h) < SubRectMaxArea) && (w < SubRectMaxWidth)) {
492 numRects += 1;
493 continue;
494 }
495
496 if (w <= SubRectMaxWidth)
497 sw = w;
498 else
499 sw = SubRectMaxWidth;
500
501 sh = SubRectMaxArea / sw;
502
503 // ceil(w/sw) * ceil(h/sh)
504 numRects += (((w - 1)/sw) + 1) * (((h - 1)/sh) + 1);
505 }
506
507 return numRects;
508}
509
Pierre Ossman20dd2a92015-02-11 17:43:15 +0100510Encoder *EncodeManager::startRect(const Rect& rect, int type)
511{
512 Encoder *encoder;
513 int klass, equiv;
514
515 activeType = type;
516 klass = activeEncoders[activeType];
517
518 beforeLength = conn->getOutStream()->length();
519
520 stats[klass][activeType].rects++;
521 stats[klass][activeType].pixels += rect.area();
522 equiv = 12 + rect.area() * conn->cp.pf().bpp/8;
523 stats[klass][activeType].equivalent += equiv;
524
525 encoder = encoders[klass];
526 conn->writer()->startRect(rect, encoder->encoding);
527
Pierre Ossman6b2f1132016-11-30 08:03:35 +0100528 if (encoder->flags & EncoderLossy)
529 lossyRegion.assign_union(Region(rect));
530 else
531 lossyRegion.assign_subtract(Region(rect));
532
Pierre Ossman20dd2a92015-02-11 17:43:15 +0100533 return encoder;
534}
535
536void EncodeManager::endRect()
537{
538 int klass;
539 int length;
540
541 conn->writer()->endRect();
542
543 length = conn->getOutStream()->length() - beforeLength;
544
545 klass = activeEncoders[activeType];
546 stats[klass][activeType].bytes += length;
547}
548
Pierre Ossman6b2f1132016-11-30 08:03:35 +0100549void EncodeManager::writeCopyRects(const Region& copied, const Point& delta)
Pierre Ossmanc0397262014-03-14 15:59:46 +0100550{
551 std::vector<Rect> rects;
552 std::vector<Rect>::const_iterator rect;
553
Pierre Ossman6b2f1132016-11-30 08:03:35 +0100554 Region lossyCopy;
555
Pierre Ossmane539cb82015-09-22 11:09:00 +0200556 beforeLength = conn->getOutStream()->length();
557
Pierre Ossman6b2f1132016-11-30 08:03:35 +0100558 copied.get_rects(&rects, delta.x <= 0, delta.y <= 0);
Pierre Ossmanc0397262014-03-14 15:59:46 +0100559 for (rect = rects.begin(); rect != rects.end(); ++rect) {
Pierre Ossmane539cb82015-09-22 11:09:00 +0200560 int equiv;
561
562 copyStats.rects++;
563 copyStats.pixels += rect->area();
564 equiv = 12 + rect->area() * conn->cp.pf().bpp/8;
565 copyStats.equivalent += equiv;
566
Pierre Ossman6b2f1132016-11-30 08:03:35 +0100567 conn->writer()->writeCopyRect(*rect, rect->tl.x - delta.x,
568 rect->tl.y - delta.y);
Pierre Ossmanc0397262014-03-14 15:59:46 +0100569 }
Pierre Ossmane539cb82015-09-22 11:09:00 +0200570
571 copyStats.bytes += conn->getOutStream()->length() - beforeLength;
Pierre Ossman6b2f1132016-11-30 08:03:35 +0100572
573 lossyCopy = lossyRegion;
574 lossyCopy.translate(delta);
575 lossyCopy.assign_intersect(copied);
576 lossyRegion.assign_union(lossyCopy);
Pierre Ossmanc0397262014-03-14 15:59:46 +0100577}
578
579void EncodeManager::writeSolidRects(Region *changed, const PixelBuffer* pb)
580{
581 std::vector<Rect> rects;
582 std::vector<Rect>::const_iterator rect;
583
Pierre Ossmanc0397262014-03-14 15:59:46 +0100584 changed->get_rects(&rects);
Pierre Ossmaneef55162015-02-12 13:44:22 +0100585 for (rect = rects.begin(); rect != rects.end(); ++rect)
586 findSolidRect(*rect, changed, pb);
587}
Pierre Ossmanc0397262014-03-14 15:59:46 +0100588
Pierre Ossmaneef55162015-02-12 13:44:22 +0100589void EncodeManager::findSolidRect(const Rect& rect, Region *changed,
590 const PixelBuffer* pb)
591{
592 Rect sr;
593 int dx, dy, dw, dh;
Pierre Ossmanc0397262014-03-14 15:59:46 +0100594
Pierre Ossmaneef55162015-02-12 13:44:22 +0100595 // We start by finding a solid 16x16 block
596 for (dy = rect.tl.y; dy < rect.br.y; dy += SolidSearchBlock) {
Pierre Ossmanc0397262014-03-14 15:59:46 +0100597
Pierre Ossmaneef55162015-02-12 13:44:22 +0100598 dh = SolidSearchBlock;
599 if (dy + dh > rect.br.y)
600 dh = rect.br.y - dy;
Pierre Ossmanc0397262014-03-14 15:59:46 +0100601
Pierre Ossmaneef55162015-02-12 13:44:22 +0100602 for (dx = rect.tl.x; dx < rect.br.x; dx += SolidSearchBlock) {
603 // We define it like this to guarantee alignment
604 rdr::U32 _buffer;
605 rdr::U8* colourValue = (rdr::U8*)&_buffer;
Pierre Ossmanc0397262014-03-14 15:59:46 +0100606
Pierre Ossmaneef55162015-02-12 13:44:22 +0100607 dw = SolidSearchBlock;
608 if (dx + dw > rect.br.x)
609 dw = rect.br.x - dx;
Pierre Ossmanc0397262014-03-14 15:59:46 +0100610
Pierre Ossmaneef55162015-02-12 13:44:22 +0100611 pb->getImage(colourValue, Rect(dx, dy, dx+1, dy+1));
Pierre Ossmanc0397262014-03-14 15:59:46 +0100612
Pierre Ossmaneef55162015-02-12 13:44:22 +0100613 sr.setXYWH(dx, dy, dw, dh);
614 if (checkSolidTile(sr, colourValue, pb)) {
615 Rect erb, erp;
Pierre Ossmanc0397262014-03-14 15:59:46 +0100616
Pierre Ossmaneef55162015-02-12 13:44:22 +0100617 Encoder *encoder;
Pierre Ossmanc0397262014-03-14 15:59:46 +0100618
Pierre Ossmaneef55162015-02-12 13:44:22 +0100619 // We then try extending the area by adding more blocks
620 // in both directions and pick the combination that gives
621 // the largest area.
622 sr.setXYWH(dx, dy, rect.br.x - dx, rect.br.y - dy);
623 extendSolidAreaByBlock(sr, colourValue, pb, &erb);
Pierre Ossmanc0397262014-03-14 15:59:46 +0100624
Pierre Ossmaneef55162015-02-12 13:44:22 +0100625 // Did we end up getting the entire rectangle?
626 if (erb.equals(rect))
627 erp = erb;
628 else {
629 // Don't bother with sending tiny rectangles
630 if (erb.area() < SolidBlockMinArea)
631 continue;
Pierre Ossmanc0397262014-03-14 15:59:46 +0100632
Pierre Ossmaneef55162015-02-12 13:44:22 +0100633 // Extend the area again, but this time one pixel
634 // row/column at a time.
635 extendSolidAreaByPixel(rect, erb, colourValue, pb, &erp);
Pierre Ossmanc0397262014-03-14 15:59:46 +0100636 }
Pierre Ossmanc0397262014-03-14 15:59:46 +0100637
Pierre Ossmaneef55162015-02-12 13:44:22 +0100638 // Send solid-color rectangle.
639 encoder = startRect(erp, encoderSolid);
640 if (encoder->flags & EncoderUseNativePF) {
641 encoder->writeSolidRect(erp.width(), erp.height(),
642 pb->getPF(), colourValue);
643 } else {
644 rdr::U32 _buffer2;
645 rdr::U8* converted = (rdr::U8*)&_buffer2;
646
647 conn->cp.pf().bufferFromBuffer(converted, pb->getPF(),
648 colourValue, 1);
649
650 encoder->writeSolidRect(erp.width(), erp.height(),
651 conn->cp.pf(), converted);
652 }
653 endRect();
654
655 changed->assign_subtract(Region(erp));
656
657 // Search remaining areas by recursion
658 // FIXME: Is this the best way to divide things up?
659
660 // Left? (Note that we've already searched a SolidSearchBlock
661 // pixels high strip here)
662 if ((erp.tl.x != rect.tl.x) && (erp.height() > SolidSearchBlock)) {
663 sr.setXYWH(rect.tl.x, erp.tl.y + SolidSearchBlock,
664 erp.tl.x - rect.tl.x, erp.height() - SolidSearchBlock);
665 findSolidRect(sr, changed, pb);
666 }
667
668 // Right?
669 if (erp.br.x != rect.br.x) {
670 sr.setXYWH(erp.br.x, erp.tl.y, rect.br.x - erp.br.x, erp.height());
671 findSolidRect(sr, changed, pb);
672 }
673
674 // Below?
675 if (erp.br.y != rect.br.y) {
676 sr.setXYWH(rect.tl.x, erp.br.y, rect.width(), rect.br.y - erp.br.y);
677 findSolidRect(sr, changed, pb);
678 }
679
680 return;
681 }
Pierre Ossmanc0397262014-03-14 15:59:46 +0100682 }
683 }
684}
685
686void EncodeManager::writeRects(const Region& changed, const PixelBuffer* pb)
687{
688 std::vector<Rect> rects;
689 std::vector<Rect>::const_iterator rect;
690
691 changed.get_rects(&rects);
692 for (rect = rects.begin(); rect != rects.end(); ++rect) {
693 int w, h, sw, sh;
694 Rect sr;
695
696 w = rect->width();
697 h = rect->height();
698
699 // No split necessary?
700 if (((w*h) < SubRectMaxArea) && (w < SubRectMaxWidth)) {
701 writeSubRect(*rect, pb);
702 continue;
703 }
704
705 if (w <= SubRectMaxWidth)
706 sw = w;
707 else
708 sw = SubRectMaxWidth;
709
710 sh = SubRectMaxArea / sw;
711
712 for (sr.tl.y = rect->tl.y; sr.tl.y < rect->br.y; sr.tl.y += sh) {
713 sr.br.y = sr.tl.y + sh;
714 if (sr.br.y > rect->br.y)
715 sr.br.y = rect->br.y;
716
717 for (sr.tl.x = rect->tl.x; sr.tl.x < rect->br.x; sr.tl.x += sw) {
718 sr.br.x = sr.tl.x + sw;
719 if (sr.br.x > rect->br.x)
720 sr.br.x = rect->br.x;
721
722 writeSubRect(sr, pb);
723 }
724 }
725 }
726}
727
728void EncodeManager::writeSubRect(const Rect& rect, const PixelBuffer *pb)
729{
730 PixelBuffer *ppb;
731
732 Encoder *encoder;
733
734 struct RectInfo info;
Pierre Ossman5c23b9e2015-03-03 16:26:03 +0100735 unsigned int divisor, maxColours;
Pierre Ossmanc0397262014-03-14 15:59:46 +0100736
737 bool useRLE;
738 EncoderType type;
739
740 // FIXME: This is roughly the algorithm previously used by the Tight
741 // encoder. It seems a bit backwards though, that higher
742 // compression setting means spending less effort in building
743 // a palette. It might be that they figured the increase in
744 // zlib setting compensated for the loss.
745 if (conn->cp.compressLevel == -1)
746 divisor = 2 * 8;
747 else
748 divisor = conn->cp.compressLevel * 8;
749 if (divisor < 4)
750 divisor = 4;
751
752 maxColours = rect.area()/divisor;
753
754 // Special exception inherited from the Tight encoder
755 if (activeEncoders[encoderFullColour] == encoderTightJPEG) {
Pierre Ossman4daa7b12015-02-12 14:57:05 +0100756 if ((conn->cp.compressLevel != -1) && (conn->cp.compressLevel < 2))
Pierre Ossmanc0397262014-03-14 15:59:46 +0100757 maxColours = 24;
758 else
759 maxColours = 96;
760 }
761
762 if (maxColours < 2)
763 maxColours = 2;
764
765 encoder = encoders[activeEncoders[encoderIndexedRLE]];
766 if (maxColours > encoder->maxPaletteSize)
767 maxColours = encoder->maxPaletteSize;
768 encoder = encoders[activeEncoders[encoderIndexed]];
769 if (maxColours > encoder->maxPaletteSize)
770 maxColours = encoder->maxPaletteSize;
771
772 ppb = preparePixelBuffer(rect, pb, true);
773
774 if (!analyseRect(ppb, &info, maxColours))
775 info.palette.clear();
776
777 // Different encoders might have different RLE overhead, but
778 // here we do a guess at RLE being the better choice if reduces
779 // the pixel count by 50%.
780 useRLE = info.rleRuns <= (rect.area() * 2);
781
782 switch (info.palette.size()) {
783 case 0:
784 type = encoderFullColour;
785 break;
786 case 1:
787 type = encoderSolid;
788 break;
789 case 2:
790 if (useRLE)
791 type = encoderBitmapRLE;
792 else
793 type = encoderBitmap;
794 break;
795 default:
796 if (useRLE)
797 type = encoderIndexedRLE;
798 else
799 type = encoderIndexed;
800 }
801
Pierre Ossman20dd2a92015-02-11 17:43:15 +0100802 encoder = startRect(rect, type);
Pierre Ossmanc0397262014-03-14 15:59:46 +0100803
804 if (encoder->flags & EncoderUseNativePF)
805 ppb = preparePixelBuffer(rect, pb, false);
806
Pierre Ossmanc0397262014-03-14 15:59:46 +0100807 encoder->writeRect(ppb, info.palette);
Pierre Ossman20dd2a92015-02-11 17:43:15 +0100808
809 endRect();
Pierre Ossmanc0397262014-03-14 15:59:46 +0100810}
811
812bool EncodeManager::checkSolidTile(const Rect& r, const rdr::U8* colourValue,
813 const PixelBuffer *pb)
814{
815 switch (pb->getPF().bpp) {
816 case 32:
817 return checkSolidTile(r, *(const rdr::U32*)colourValue, pb);
818 case 16:
819 return checkSolidTile(r, *(const rdr::U16*)colourValue, pb);
820 default:
821 return checkSolidTile(r, *(const rdr::U8*)colourValue, pb);
822 }
823}
824
825void EncodeManager::extendSolidAreaByBlock(const Rect& r,
826 const rdr::U8* colourValue,
827 const PixelBuffer *pb, Rect* er)
828{
829 int dx, dy, dw, dh;
830 int w_prev;
831 Rect sr;
832 int w_best = 0, h_best = 0;
833
834 w_prev = r.width();
835
836 // We search width first, back off when we hit a different colour,
837 // and restart with a larger height. We keep track of the
838 // width/height combination that gives us the largest area.
839 for (dy = r.tl.y; dy < r.br.y; dy += SolidSearchBlock) {
840
841 dh = SolidSearchBlock;
842 if (dy + dh > r.br.y)
843 dh = r.br.y - dy;
844
845 // We test one block here outside the x loop in order to break
846 // the y loop right away.
847 dw = SolidSearchBlock;
848 if (dw > w_prev)
849 dw = w_prev;
850
851 sr.setXYWH(r.tl.x, dy, dw, dh);
852 if (!checkSolidTile(sr, colourValue, pb))
853 break;
854
855 for (dx = r.tl.x + dw; dx < r.tl.x + w_prev;) {
856
857 dw = SolidSearchBlock;
858 if (dx + dw > r.tl.x + w_prev)
859 dw = r.tl.x + w_prev - dx;
860
861 sr.setXYWH(dx, dy, dw, dh);
862 if (!checkSolidTile(sr, colourValue, pb))
863 break;
864
865 dx += dw;
866 }
867
868 w_prev = dx - r.tl.x;
869 if (w_prev * (dy + dh - r.tl.y) > w_best * h_best) {
870 w_best = w_prev;
871 h_best = dy + dh - r.tl.y;
872 }
873 }
874
875 er->tl.x = r.tl.x;
876 er->tl.y = r.tl.y;
877 er->br.x = er->tl.x + w_best;
878 er->br.y = er->tl.y + h_best;
879}
880
881void EncodeManager::extendSolidAreaByPixel(const Rect& r, const Rect& sr,
882 const rdr::U8* colourValue,
883 const PixelBuffer *pb, Rect* er)
884{
885 int cx, cy;
886 Rect tr;
887
888 // Try to extend the area upwards.
889 for (cy = sr.tl.y - 1; cy >= r.tl.y; cy--) {
890 tr.setXYWH(sr.tl.x, cy, sr.width(), 1);
891 if (!checkSolidTile(tr, colourValue, pb))
892 break;
893 }
894 er->tl.y = cy + 1;
895
896 // ... downwards.
897 for (cy = sr.br.y; cy < r.br.y; cy++) {
898 tr.setXYWH(sr.tl.x, cy, sr.width(), 1);
899 if (!checkSolidTile(tr, colourValue, pb))
900 break;
901 }
902 er->br.y = cy;
903
904 // ... to the left.
905 for (cx = sr.tl.x - 1; cx >= r.tl.x; cx--) {
906 tr.setXYWH(cx, er->tl.y, 1, er->height());
907 if (!checkSolidTile(tr, colourValue, pb))
908 break;
909 }
910 er->tl.x = cx + 1;
911
912 // ... to the right.
913 for (cx = sr.br.x; cx < r.br.x; cx++) {
914 tr.setXYWH(cx, er->tl.y, 1, er->height());
915 if (!checkSolidTile(tr, colourValue, pb))
916 break;
917 }
918 er->br.x = cx;
919}
920
921PixelBuffer* EncodeManager::preparePixelBuffer(const Rect& rect,
922 const PixelBuffer *pb,
923 bool convert)
924{
925 const rdr::U8* buffer;
926 int stride;
927
928 // Do wo need to convert the data?
929 if (convert && !conn->cp.pf().equal(pb->getPF())) {
930 convertedPixelBuffer.setPF(conn->cp.pf());
931 convertedPixelBuffer.setSize(rect.width(), rect.height());
932
933 buffer = pb->getBuffer(rect, &stride);
934 convertedPixelBuffer.imageRect(pb->getPF(),
935 convertedPixelBuffer.getRect(),
936 buffer, stride);
937
938 return &convertedPixelBuffer;
939 }
940
941 // Otherwise we still need to shift the coordinates. We have our own
942 // abusive subclass of FullFramePixelBuffer for this.
943
944 buffer = pb->getBuffer(rect, &stride);
945
946 offsetPixelBuffer.update(pb->getPF(), rect.width(), rect.height(),
947 buffer, stride);
948
949 return &offsetPixelBuffer;
950}
951
952bool EncodeManager::analyseRect(const PixelBuffer *pb,
953 struct RectInfo *info, int maxColours)
954{
955 const rdr::U8* buffer;
956 int stride;
957
958 buffer = pb->getBuffer(pb->getRect(), &stride);
959
960 switch (pb->getPF().bpp) {
961 case 32:
962 return analyseRect(pb->width(), pb->height(),
963 (const rdr::U32*)buffer, stride,
964 info, maxColours);
965 case 16:
966 return analyseRect(pb->width(), pb->height(),
967 (const rdr::U16*)buffer, stride,
968 info, maxColours);
969 default:
970 return analyseRect(pb->width(), pb->height(),
971 (const rdr::U8*)buffer, stride,
972 info, maxColours);
973 }
974}
975
976void EncodeManager::OffsetPixelBuffer::update(const PixelFormat& pf,
977 int width, int height,
978 const rdr::U8* data_,
979 int stride_)
980{
981 format = pf;
982 width_ = width;
983 height_ = height;
984 // Forced cast. We never write anything though, so it should be safe.
985 data = (rdr::U8*)data_;
986 stride = stride_;
987}
988
989// Preprocessor generated, optimised methods
990
991#define BPP 8
992#include "EncodeManagerBPP.cxx"
993#undef BPP
994#define BPP 16
995#include "EncodeManagerBPP.cxx"
996#undef BPP
997#define BPP 32
998#include "EncodeManagerBPP.cxx"
999#undef BPP