blob: ca7c7304ee174d6bc473aee11e9f7cdd2fe31524 [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
Pierre Ossman6b2f1132016-11-30 08:03:35 +010053static const int LosslessRefreshMaxArea = 4096;
54
Pierre Ossmanc0397262014-03-14 15:59:46 +010055namespace rfb {
56
57enum EncoderClass {
58 encoderRaw,
59 encoderRRE,
60 encoderHextile,
61 encoderTight,
62 encoderTightJPEG,
63 encoderZRLE,
64 encoderClassMax,
65};
66
67enum EncoderType {
68 encoderSolid,
69 encoderBitmap,
70 encoderBitmapRLE,
71 encoderIndexed,
72 encoderIndexedRLE,
73 encoderFullColour,
74 encoderTypeMax,
75};
76
77struct RectInfo {
78 int rleRuns;
79 Palette palette;
80};
81
82};
83
Pierre Ossman20dd2a92015-02-11 17:43:15 +010084static const char *encoderClassName(EncoderClass klass)
85{
86 switch (klass) {
87 case encoderRaw:
88 return "Raw";
89 case encoderRRE:
90 return "RRE";
91 case encoderHextile:
92 return "Hextile";
93 case encoderTight:
94 return "Tight";
95 case encoderTightJPEG:
96 return "Tight (JPEG)";
97 case encoderZRLE:
98 return "ZRLE";
Pierre Ossman620dd952015-03-03 16:28:54 +010099 case encoderClassMax:
100 break;
Pierre Ossman20dd2a92015-02-11 17:43:15 +0100101 }
102
103 return "Unknown Encoder Class";
104}
105
106static const char *encoderTypeName(EncoderType type)
107{
108 switch (type) {
109 case encoderSolid:
110 return "Solid";
111 case encoderBitmap:
112 return "Bitmap";
113 case encoderBitmapRLE:
114 return "Bitmap RLE";
115 case encoderIndexed:
116 return "Indexed";
117 case encoderIndexedRLE:
118 return "Indexed RLE";
119 case encoderFullColour:
120 return "Full Colour";
Pierre Ossman620dd952015-03-03 16:28:54 +0100121 case encoderTypeMax:
122 break;
Pierre Ossman20dd2a92015-02-11 17:43:15 +0100123 }
124
125 return "Unknown Encoder Type";
126}
127
Pierre Ossmanc0397262014-03-14 15:59:46 +0100128EncodeManager::EncodeManager(SConnection* conn_) : conn(conn_)
129{
Pierre Ossman20dd2a92015-02-11 17:43:15 +0100130 StatsVector::iterator iter;
131
Pierre Ossmanc0397262014-03-14 15:59:46 +0100132 encoders.resize(encoderClassMax, NULL);
133 activeEncoders.resize(encoderTypeMax, encoderRaw);
134
135 encoders[encoderRaw] = new RawEncoder(conn);
136 encoders[encoderRRE] = new RREEncoder(conn);
137 encoders[encoderHextile] = new HextileEncoder(conn);
138 encoders[encoderTight] = new TightEncoder(conn);
139 encoders[encoderTightJPEG] = new TightJPEGEncoder(conn);
140 encoders[encoderZRLE] = new ZRLEEncoder(conn);
Pierre Ossman20dd2a92015-02-11 17:43:15 +0100141
142 updates = 0;
Pierre Ossmane539cb82015-09-22 11:09:00 +0200143 memset(&copyStats, 0, sizeof(copyStats));
Pierre Ossman20dd2a92015-02-11 17:43:15 +0100144 stats.resize(encoderClassMax);
145 for (iter = stats.begin();iter != stats.end();++iter) {
146 StatsVector::value_type::iterator iter2;
147 iter->resize(encoderTypeMax);
148 for (iter2 = iter->begin();iter2 != iter->end();++iter2)
149 memset(&*iter2, 0, sizeof(EncoderStats));
150 }
Pierre Ossmanc0397262014-03-14 15:59:46 +0100151}
152
153EncodeManager::~EncodeManager()
154{
155 std::vector<Encoder*>::iterator iter;
156
Pierre Ossman20dd2a92015-02-11 17:43:15 +0100157 logStats();
158
Pierre Ossmanc0397262014-03-14 15:59:46 +0100159 for (iter = encoders.begin();iter != encoders.end();iter++)
160 delete *iter;
161}
162
Pierre Ossman20dd2a92015-02-11 17:43:15 +0100163void EncodeManager::logStats()
164{
Pierre Ossman5c23b9e2015-03-03 16:26:03 +0100165 size_t i, j;
Pierre Ossman20dd2a92015-02-11 17:43:15 +0100166
167 unsigned rects;
168 unsigned long long pixels, bytes, equivalent;
169
170 double ratio;
171
Pierre Ossman64624342015-03-03 16:30:13 +0100172 char a[1024], b[1024];
173
Pierre Ossman20dd2a92015-02-11 17:43:15 +0100174 rects = 0;
175 pixels = bytes = equivalent = 0;
176
177 vlog.info("Framebuffer updates: %u", updates);
178
Pierre Ossmane539cb82015-09-22 11:09:00 +0200179 if (copyStats.rects != 0) {
180 vlog.info(" %s:", "CopyRect");
181
182 rects += copyStats.rects;
183 pixels += copyStats.pixels;
184 bytes += copyStats.bytes;
185 equivalent += copyStats.equivalent;
186
187 ratio = (double)copyStats.equivalent / copyStats.bytes;
188
189 siPrefix(copyStats.rects, "rects", a, sizeof(a));
190 siPrefix(copyStats.pixels, "pixels", b, sizeof(b));
191 vlog.info(" %s: %s, %s", "Copies", a, b);
192 iecPrefix(copyStats.bytes, "B", a, sizeof(a));
193 vlog.info(" %*s %s (1:%g ratio)",
194 (int)strlen("Copies"), "",
195 a, ratio);
196 }
197
Pierre Ossman20dd2a92015-02-11 17:43:15 +0100198 for (i = 0;i < stats.size();i++) {
199 // Did this class do anything at all?
200 for (j = 0;j < stats[i].size();j++) {
201 if (stats[i][j].rects != 0)
202 break;
203 }
204 if (j == stats[i].size())
205 continue;
206
207 vlog.info(" %s:", encoderClassName((EncoderClass)i));
208
209 for (j = 0;j < stats[i].size();j++) {
210 if (stats[i][j].rects == 0)
211 continue;
212
213 rects += stats[i][j].rects;
214 pixels += stats[i][j].pixels;
215 bytes += stats[i][j].bytes;
216 equivalent += stats[i][j].equivalent;
217
218 ratio = (double)stats[i][j].equivalent / stats[i][j].bytes;
219
Pierre Ossman64624342015-03-03 16:30:13 +0100220 siPrefix(stats[i][j].rects, "rects", a, sizeof(a));
221 siPrefix(stats[i][j].pixels, "pixels", b, sizeof(b));
222 vlog.info(" %s: %s, %s", encoderTypeName((EncoderType)j), a, b);
223 iecPrefix(stats[i][j].bytes, "B", a, sizeof(a));
224 vlog.info(" %*s %s (1:%g ratio)",
225 (int)strlen(encoderTypeName((EncoderType)j)), "",
226 a, ratio);
Pierre Ossman20dd2a92015-02-11 17:43:15 +0100227 }
228 }
229
230 ratio = (double)equivalent / bytes;
231
Pierre Ossman64624342015-03-03 16:30:13 +0100232 siPrefix(rects, "rects", a, sizeof(a));
233 siPrefix(pixels, "pixels", b, sizeof(b));
234 vlog.info(" Total: %s, %s", a, b);
235 iecPrefix(bytes, "B", a, sizeof(a));
236 vlog.info(" %s (1:%g ratio)", a, ratio);
Pierre Ossman20dd2a92015-02-11 17:43:15 +0100237}
238
Pierre Ossmanc0397262014-03-14 15:59:46 +0100239bool EncodeManager::supported(int encoding)
240{
241 switch (encoding) {
242 case encodingRaw:
243 case encodingRRE:
244 case encodingHextile:
245 case encodingZRLE:
246 case encodingTight:
247 return true;
248 default:
249 return false;
250 }
251}
252
Pierre Ossman6b2f1132016-11-30 08:03:35 +0100253bool EncodeManager::needsLosslessRefresh(const Region& req)
254{
255 return !lossyRegion.intersect(req).is_empty();
256}
257
258void EncodeManager::pruneLosslessRefresh(const Region& limits)
259{
260 lossyRegion.assign_intersect(limits);
261}
262
Pierre Ossmanc0397262014-03-14 15:59:46 +0100263void EncodeManager::writeUpdate(const UpdateInfo& ui, const PixelBuffer* pb,
264 const RenderedCursor* renderedCursor)
265{
Pierre Ossman6b2f1132016-11-30 08:03:35 +0100266 doUpdate(true, ui.changed, ui.copied, ui.copy_delta, pb, renderedCursor);
267}
268
269void EncodeManager::writeLosslessRefresh(const Region& req, const PixelBuffer* pb,
270 const RenderedCursor* renderedCursor)
271{
272 doUpdate(false, getLosslessRefresh(req),
273 Region(), Point(), pb, renderedCursor);
274}
275
276void EncodeManager::doUpdate(bool allowLossy, const Region& changed_,
277 const Region& copied, const Point& copyDelta,
278 const PixelBuffer* pb,
279 const RenderedCursor* renderedCursor)
280{
Pierre Ossmanc0397262014-03-14 15:59:46 +0100281 int nRects;
Pierre Ossman8c3bd692018-03-22 15:58:54 +0100282 Region changed, cursorRegion;
Pierre Ossmanc0397262014-03-14 15:59:46 +0100283
Pierre Ossman20dd2a92015-02-11 17:43:15 +0100284 updates++;
285
Pierre Ossman6b2f1132016-11-30 08:03:35 +0100286 prepareEncoders(allowLossy);
Pierre Ossmanc0397262014-03-14 15:59:46 +0100287
Pierre Ossman6b2f1132016-11-30 08:03:35 +0100288 changed = changed_;
Pierre Ossman8c3bd692018-03-22 15:58:54 +0100289
290 /*
291 * We need to render the cursor seperately as it has its own
292 * magical pixel buffer, so split it out from the changed region.
293 */
294 if (renderedCursor != NULL) {
295 cursorRegion = changed.intersect(renderedCursor->getEffectiveRect());
296 changed.assign_subtract(renderedCursor->getEffectiveRect());
297 }
298
Pierre Ossmanc0397262014-03-14 15:59:46 +0100299 if (conn->cp.supportsLastRect)
300 nRects = 0xFFFF;
301 else {
Pierre Ossman6b2f1132016-11-30 08:03:35 +0100302 nRects = copied.numRects();
Pierre Ossman8c3bd692018-03-22 15:58:54 +0100303 nRects += computeNumRects(changed);
304 nRects += computeNumRects(cursorRegion);
Pierre Ossmanc0397262014-03-14 15:59:46 +0100305 }
306
307 conn->writer()->writeFramebufferUpdateStart(nRects);
308
Pierre Ossman6b2f1132016-11-30 08:03:35 +0100309 writeCopyRects(copied, copyDelta);
Pierre Ossmanc0397262014-03-14 15:59:46 +0100310
311 /*
312 * We start by searching for solid rects, which are then removed
313 * from the changed region.
314 */
Pierre Ossmanc0397262014-03-14 15:59:46 +0100315 if (conn->cp.supportsLastRect)
316 writeSolidRects(&changed, pb);
317
318 writeRects(changed, pb);
Pierre Ossman8c3bd692018-03-22 15:58:54 +0100319 writeRects(cursorRegion, renderedCursor);
Pierre Ossmanc0397262014-03-14 15:59:46 +0100320
321 conn->writer()->writeFramebufferUpdateEnd();
322}
323
Pierre Ossman6b2f1132016-11-30 08:03:35 +0100324void EncodeManager::prepareEncoders(bool allowLossy)
Pierre Ossmanc0397262014-03-14 15:59:46 +0100325{
326 enum EncoderClass solid, bitmap, bitmapRLE;
327 enum EncoderClass indexed, indexedRLE, fullColour;
328
329 rdr::S32 preferred;
330
331 std::vector<int>::iterator iter;
332
333 solid = bitmap = bitmapRLE = encoderRaw;
334 indexed = indexedRLE = fullColour = encoderRaw;
335
336 // Try to respect the client's wishes
Pierre Ossman48700812014-09-17 17:11:56 +0200337 preferred = conn->getPreferredEncoding();
Pierre Ossmanc0397262014-03-14 15:59:46 +0100338 switch (preferred) {
339 case encodingRRE:
340 // Horrible for anything high frequency and/or lots of colours
341 bitmapRLE = indexedRLE = encoderRRE;
342 break;
343 case encodingHextile:
344 // Slightly less horrible
345 bitmapRLE = indexedRLE = fullColour = encoderHextile;
346 break;
347 case encodingTight:
348 if (encoders[encoderTightJPEG]->isSupported() &&
Pierre Ossman6b2f1132016-11-30 08:03:35 +0100349 (conn->cp.pf().bpp >= 16) && allowLossy)
Pierre Ossmanc0397262014-03-14 15:59:46 +0100350 fullColour = encoderTightJPEG;
351 else
352 fullColour = encoderTight;
353 indexed = indexedRLE = encoderTight;
354 bitmap = bitmapRLE = encoderTight;
355 break;
356 case encodingZRLE:
357 fullColour = encoderZRLE;
358 bitmapRLE = indexedRLE = encoderZRLE;
359 bitmap = indexed = encoderZRLE;
360 break;
361 }
362
363 // Any encoders still unassigned?
364
365 if (fullColour == encoderRaw) {
366 if (encoders[encoderTightJPEG]->isSupported() &&
Pierre Ossman6b2f1132016-11-30 08:03:35 +0100367 (conn->cp.pf().bpp >= 16) && allowLossy)
Pierre Ossmanc0397262014-03-14 15:59:46 +0100368 fullColour = encoderTightJPEG;
369 else if (encoders[encoderZRLE]->isSupported())
370 fullColour = encoderZRLE;
371 else if (encoders[encoderTight]->isSupported())
372 fullColour = encoderTight;
373 else if (encoders[encoderHextile]->isSupported())
374 fullColour = encoderHextile;
375 }
376
377 if (indexed == encoderRaw) {
378 if (encoders[encoderZRLE]->isSupported())
379 indexed = encoderZRLE;
380 else if (encoders[encoderTight]->isSupported())
381 indexed = encoderTight;
382 else if (encoders[encoderHextile]->isSupported())
383 indexed = encoderHextile;
384 }
385
386 if (indexedRLE == encoderRaw)
387 indexedRLE = indexed;
388
389 if (bitmap == encoderRaw)
390 bitmap = indexed;
391 if (bitmapRLE == encoderRaw)
392 bitmapRLE = bitmap;
393
394 if (solid == encoderRaw) {
395 if (encoders[encoderTight]->isSupported())
396 solid = encoderTight;
397 else if (encoders[encoderRRE]->isSupported())
398 solid = encoderRRE;
399 else if (encoders[encoderZRLE]->isSupported())
400 solid = encoderZRLE;
401 else if (encoders[encoderHextile]->isSupported())
402 solid = encoderHextile;
403 }
404
405 // JPEG is the only encoder that can reduce things to grayscale
406 if ((conn->cp.subsampling == subsampleGray) &&
Pierre Ossman6b2f1132016-11-30 08:03:35 +0100407 encoders[encoderTightJPEG]->isSupported() && allowLossy) {
Pierre Ossmanc0397262014-03-14 15:59:46 +0100408 solid = bitmap = bitmapRLE = encoderTightJPEG;
409 indexed = indexedRLE = fullColour = encoderTightJPEG;
410 }
411
412 activeEncoders[encoderSolid] = solid;
413 activeEncoders[encoderBitmap] = bitmap;
414 activeEncoders[encoderBitmapRLE] = bitmapRLE;
415 activeEncoders[encoderIndexed] = indexed;
416 activeEncoders[encoderIndexedRLE] = indexedRLE;
417 activeEncoders[encoderFullColour] = fullColour;
418
419 for (iter = activeEncoders.begin(); iter != activeEncoders.end(); ++iter) {
420 Encoder *encoder;
421
422 encoder = encoders[*iter];
423
424 encoder->setCompressLevel(conn->cp.compressLevel);
425 encoder->setQualityLevel(conn->cp.qualityLevel);
426 encoder->setFineQualityLevel(conn->cp.fineQualityLevel,
427 conn->cp.subsampling);
428 }
429}
430
Pierre Ossman6b2f1132016-11-30 08:03:35 +0100431Region EncodeManager::getLosslessRefresh(const Region& req)
432{
433 std::vector<Rect> rects;
434 Region refresh;
435 size_t area;
436
437 area = 0;
438 lossyRegion.intersect(req).get_rects(&rects);
439 while (!rects.empty()) {
440 size_t idx;
441 Rect rect;
442
443 // Grab a random rect so we don't keep damaging and restoring the
444 // same rect over and over
445 idx = rand() % rects.size();
446
447 rect = rects[idx];
448
449 // Add rects until we exceed the threshold, then include as much as
450 // possible of the final rect
451 if ((area + rect.area()) > LosslessRefreshMaxArea) {
452 // Use the narrowest axis to avoid getting to thin rects
453 if (rect.width() > rect.height()) {
454 int width = (LosslessRefreshMaxArea - area) / rect.height();
455 rect.br.x = rect.tl.x + __rfbmax(1, width);
456 } else {
457 int height = (LosslessRefreshMaxArea - area) / rect.width();
458 rect.br.y = rect.tl.y + __rfbmax(1, height);
459 }
460 refresh.assign_union(Region(rect));
461 break;
462 }
463
464 area += rect.area();
465 refresh.assign_union(Region(rect));
466
467 rects.erase(rects.begin() + idx);
468 }
469
470 return refresh;
471}
472
Pierre Ossmanc0397262014-03-14 15:59:46 +0100473int EncodeManager::computeNumRects(const Region& changed)
474{
475 int numRects;
476 std::vector<Rect> rects;
477 std::vector<Rect>::const_iterator rect;
478
479 numRects = 0;
480 changed.get_rects(&rects);
481 for (rect = rects.begin(); rect != rects.end(); ++rect) {
482 int w, h, sw, sh;
483
484 w = rect->width();
485 h = rect->height();
486
487 // No split necessary?
488 if (((w*h) < SubRectMaxArea) && (w < SubRectMaxWidth)) {
489 numRects += 1;
490 continue;
491 }
492
493 if (w <= SubRectMaxWidth)
494 sw = w;
495 else
496 sw = SubRectMaxWidth;
497
498 sh = SubRectMaxArea / sw;
499
500 // ceil(w/sw) * ceil(h/sh)
501 numRects += (((w - 1)/sw) + 1) * (((h - 1)/sh) + 1);
502 }
503
504 return numRects;
505}
506
Pierre Ossman20dd2a92015-02-11 17:43:15 +0100507Encoder *EncodeManager::startRect(const Rect& rect, int type)
508{
509 Encoder *encoder;
510 int klass, equiv;
511
512 activeType = type;
513 klass = activeEncoders[activeType];
514
515 beforeLength = conn->getOutStream()->length();
516
517 stats[klass][activeType].rects++;
518 stats[klass][activeType].pixels += rect.area();
519 equiv = 12 + rect.area() * conn->cp.pf().bpp/8;
520 stats[klass][activeType].equivalent += equiv;
521
522 encoder = encoders[klass];
523 conn->writer()->startRect(rect, encoder->encoding);
524
Pierre Ossman6b2f1132016-11-30 08:03:35 +0100525 if (encoder->flags & EncoderLossy)
526 lossyRegion.assign_union(Region(rect));
527 else
528 lossyRegion.assign_subtract(Region(rect));
529
Pierre Ossman20dd2a92015-02-11 17:43:15 +0100530 return encoder;
531}
532
533void EncodeManager::endRect()
534{
535 int klass;
536 int length;
537
538 conn->writer()->endRect();
539
540 length = conn->getOutStream()->length() - beforeLength;
541
542 klass = activeEncoders[activeType];
543 stats[klass][activeType].bytes += length;
544}
545
Pierre Ossman6b2f1132016-11-30 08:03:35 +0100546void EncodeManager::writeCopyRects(const Region& copied, const Point& delta)
Pierre Ossmanc0397262014-03-14 15:59:46 +0100547{
548 std::vector<Rect> rects;
549 std::vector<Rect>::const_iterator rect;
550
Pierre Ossman6b2f1132016-11-30 08:03:35 +0100551 Region lossyCopy;
552
Pierre Ossmane539cb82015-09-22 11:09:00 +0200553 beforeLength = conn->getOutStream()->length();
554
Pierre Ossman6b2f1132016-11-30 08:03:35 +0100555 copied.get_rects(&rects, delta.x <= 0, delta.y <= 0);
Pierre Ossmanc0397262014-03-14 15:59:46 +0100556 for (rect = rects.begin(); rect != rects.end(); ++rect) {
Pierre Ossmane539cb82015-09-22 11:09:00 +0200557 int equiv;
558
559 copyStats.rects++;
560 copyStats.pixels += rect->area();
561 equiv = 12 + rect->area() * conn->cp.pf().bpp/8;
562 copyStats.equivalent += equiv;
563
Pierre Ossman6b2f1132016-11-30 08:03:35 +0100564 conn->writer()->writeCopyRect(*rect, rect->tl.x - delta.x,
565 rect->tl.y - delta.y);
Pierre Ossmanc0397262014-03-14 15:59:46 +0100566 }
Pierre Ossmane539cb82015-09-22 11:09:00 +0200567
568 copyStats.bytes += conn->getOutStream()->length() - beforeLength;
Pierre Ossman6b2f1132016-11-30 08:03:35 +0100569
570 lossyCopy = lossyRegion;
571 lossyCopy.translate(delta);
572 lossyCopy.assign_intersect(copied);
573 lossyRegion.assign_union(lossyCopy);
Pierre Ossmanc0397262014-03-14 15:59:46 +0100574}
575
576void EncodeManager::writeSolidRects(Region *changed, const PixelBuffer* pb)
577{
578 std::vector<Rect> rects;
579 std::vector<Rect>::const_iterator rect;
580
Pierre Ossmanc0397262014-03-14 15:59:46 +0100581 changed->get_rects(&rects);
Pierre Ossmaneef55162015-02-12 13:44:22 +0100582 for (rect = rects.begin(); rect != rects.end(); ++rect)
583 findSolidRect(*rect, changed, pb);
584}
Pierre Ossmanc0397262014-03-14 15:59:46 +0100585
Pierre Ossmaneef55162015-02-12 13:44:22 +0100586void EncodeManager::findSolidRect(const Rect& rect, Region *changed,
587 const PixelBuffer* pb)
588{
589 Rect sr;
590 int dx, dy, dw, dh;
Pierre Ossmanc0397262014-03-14 15:59:46 +0100591
Pierre Ossmaneef55162015-02-12 13:44:22 +0100592 // We start by finding a solid 16x16 block
593 for (dy = rect.tl.y; dy < rect.br.y; dy += SolidSearchBlock) {
Pierre Ossmanc0397262014-03-14 15:59:46 +0100594
Pierre Ossmaneef55162015-02-12 13:44:22 +0100595 dh = SolidSearchBlock;
596 if (dy + dh > rect.br.y)
597 dh = rect.br.y - dy;
Pierre Ossmanc0397262014-03-14 15:59:46 +0100598
Pierre Ossmaneef55162015-02-12 13:44:22 +0100599 for (dx = rect.tl.x; dx < rect.br.x; dx += SolidSearchBlock) {
600 // We define it like this to guarantee alignment
601 rdr::U32 _buffer;
602 rdr::U8* colourValue = (rdr::U8*)&_buffer;
Pierre Ossmanc0397262014-03-14 15:59:46 +0100603
Pierre Ossmaneef55162015-02-12 13:44:22 +0100604 dw = SolidSearchBlock;
605 if (dx + dw > rect.br.x)
606 dw = rect.br.x - dx;
Pierre Ossmanc0397262014-03-14 15:59:46 +0100607
Pierre Ossmaneef55162015-02-12 13:44:22 +0100608 pb->getImage(colourValue, Rect(dx, dy, dx+1, dy+1));
Pierre Ossmanc0397262014-03-14 15:59:46 +0100609
Pierre Ossmaneef55162015-02-12 13:44:22 +0100610 sr.setXYWH(dx, dy, dw, dh);
611 if (checkSolidTile(sr, colourValue, pb)) {
612 Rect erb, erp;
Pierre Ossmanc0397262014-03-14 15:59:46 +0100613
Pierre Ossmaneef55162015-02-12 13:44:22 +0100614 Encoder *encoder;
Pierre Ossmanc0397262014-03-14 15:59:46 +0100615
Pierre Ossmaneef55162015-02-12 13:44:22 +0100616 // We then try extending the area by adding more blocks
617 // in both directions and pick the combination that gives
618 // the largest area.
619 sr.setXYWH(dx, dy, rect.br.x - dx, rect.br.y - dy);
620 extendSolidAreaByBlock(sr, colourValue, pb, &erb);
Pierre Ossmanc0397262014-03-14 15:59:46 +0100621
Pierre Ossmaneef55162015-02-12 13:44:22 +0100622 // Did we end up getting the entire rectangle?
623 if (erb.equals(rect))
624 erp = erb;
625 else {
626 // Don't bother with sending tiny rectangles
627 if (erb.area() < SolidBlockMinArea)
628 continue;
Pierre Ossmanc0397262014-03-14 15:59:46 +0100629
Pierre Ossmaneef55162015-02-12 13:44:22 +0100630 // Extend the area again, but this time one pixel
631 // row/column at a time.
632 extendSolidAreaByPixel(rect, erb, colourValue, pb, &erp);
Pierre Ossmanc0397262014-03-14 15:59:46 +0100633 }
Pierre Ossmanc0397262014-03-14 15:59:46 +0100634
Pierre Ossmaneef55162015-02-12 13:44:22 +0100635 // Send solid-color rectangle.
636 encoder = startRect(erp, encoderSolid);
637 if (encoder->flags & EncoderUseNativePF) {
638 encoder->writeSolidRect(erp.width(), erp.height(),
639 pb->getPF(), colourValue);
640 } else {
641 rdr::U32 _buffer2;
642 rdr::U8* converted = (rdr::U8*)&_buffer2;
643
644 conn->cp.pf().bufferFromBuffer(converted, pb->getPF(),
645 colourValue, 1);
646
647 encoder->writeSolidRect(erp.width(), erp.height(),
648 conn->cp.pf(), converted);
649 }
650 endRect();
651
652 changed->assign_subtract(Region(erp));
653
654 // Search remaining areas by recursion
655 // FIXME: Is this the best way to divide things up?
656
657 // Left? (Note that we've already searched a SolidSearchBlock
658 // pixels high strip here)
659 if ((erp.tl.x != rect.tl.x) && (erp.height() > SolidSearchBlock)) {
660 sr.setXYWH(rect.tl.x, erp.tl.y + SolidSearchBlock,
661 erp.tl.x - rect.tl.x, erp.height() - SolidSearchBlock);
662 findSolidRect(sr, changed, pb);
663 }
664
665 // Right?
666 if (erp.br.x != rect.br.x) {
667 sr.setXYWH(erp.br.x, erp.tl.y, rect.br.x - erp.br.x, erp.height());
668 findSolidRect(sr, changed, pb);
669 }
670
671 // Below?
672 if (erp.br.y != rect.br.y) {
673 sr.setXYWH(rect.tl.x, erp.br.y, rect.width(), rect.br.y - erp.br.y);
674 findSolidRect(sr, changed, pb);
675 }
676
677 return;
678 }
Pierre Ossmanc0397262014-03-14 15:59:46 +0100679 }
680 }
681}
682
683void EncodeManager::writeRects(const Region& changed, const PixelBuffer* pb)
684{
685 std::vector<Rect> rects;
686 std::vector<Rect>::const_iterator rect;
687
688 changed.get_rects(&rects);
689 for (rect = rects.begin(); rect != rects.end(); ++rect) {
690 int w, h, sw, sh;
691 Rect sr;
692
693 w = rect->width();
694 h = rect->height();
695
696 // No split necessary?
697 if (((w*h) < SubRectMaxArea) && (w < SubRectMaxWidth)) {
698 writeSubRect(*rect, pb);
699 continue;
700 }
701
702 if (w <= SubRectMaxWidth)
703 sw = w;
704 else
705 sw = SubRectMaxWidth;
706
707 sh = SubRectMaxArea / sw;
708
709 for (sr.tl.y = rect->tl.y; sr.tl.y < rect->br.y; sr.tl.y += sh) {
710 sr.br.y = sr.tl.y + sh;
711 if (sr.br.y > rect->br.y)
712 sr.br.y = rect->br.y;
713
714 for (sr.tl.x = rect->tl.x; sr.tl.x < rect->br.x; sr.tl.x += sw) {
715 sr.br.x = sr.tl.x + sw;
716 if (sr.br.x > rect->br.x)
717 sr.br.x = rect->br.x;
718
719 writeSubRect(sr, pb);
720 }
721 }
722 }
723}
724
725void EncodeManager::writeSubRect(const Rect& rect, const PixelBuffer *pb)
726{
727 PixelBuffer *ppb;
728
729 Encoder *encoder;
730
731 struct RectInfo info;
Pierre Ossman5c23b9e2015-03-03 16:26:03 +0100732 unsigned int divisor, maxColours;
Pierre Ossmanc0397262014-03-14 15:59:46 +0100733
734 bool useRLE;
735 EncoderType type;
736
737 // FIXME: This is roughly the algorithm previously used by the Tight
738 // encoder. It seems a bit backwards though, that higher
739 // compression setting means spending less effort in building
740 // a palette. It might be that they figured the increase in
741 // zlib setting compensated for the loss.
742 if (conn->cp.compressLevel == -1)
743 divisor = 2 * 8;
744 else
745 divisor = conn->cp.compressLevel * 8;
746 if (divisor < 4)
747 divisor = 4;
748
749 maxColours = rect.area()/divisor;
750
751 // Special exception inherited from the Tight encoder
752 if (activeEncoders[encoderFullColour] == encoderTightJPEG) {
Pierre Ossman4daa7b12015-02-12 14:57:05 +0100753 if ((conn->cp.compressLevel != -1) && (conn->cp.compressLevel < 2))
Pierre Ossmanc0397262014-03-14 15:59:46 +0100754 maxColours = 24;
755 else
756 maxColours = 96;
757 }
758
759 if (maxColours < 2)
760 maxColours = 2;
761
762 encoder = encoders[activeEncoders[encoderIndexedRLE]];
763 if (maxColours > encoder->maxPaletteSize)
764 maxColours = encoder->maxPaletteSize;
765 encoder = encoders[activeEncoders[encoderIndexed]];
766 if (maxColours > encoder->maxPaletteSize)
767 maxColours = encoder->maxPaletteSize;
768
769 ppb = preparePixelBuffer(rect, pb, true);
770
771 if (!analyseRect(ppb, &info, maxColours))
772 info.palette.clear();
773
774 // Different encoders might have different RLE overhead, but
775 // here we do a guess at RLE being the better choice if reduces
776 // the pixel count by 50%.
777 useRLE = info.rleRuns <= (rect.area() * 2);
778
779 switch (info.palette.size()) {
780 case 0:
781 type = encoderFullColour;
782 break;
783 case 1:
784 type = encoderSolid;
785 break;
786 case 2:
787 if (useRLE)
788 type = encoderBitmapRLE;
789 else
790 type = encoderBitmap;
791 break;
792 default:
793 if (useRLE)
794 type = encoderIndexedRLE;
795 else
796 type = encoderIndexed;
797 }
798
Pierre Ossman20dd2a92015-02-11 17:43:15 +0100799 encoder = startRect(rect, type);
Pierre Ossmanc0397262014-03-14 15:59:46 +0100800
801 if (encoder->flags & EncoderUseNativePF)
802 ppb = preparePixelBuffer(rect, pb, false);
803
Pierre Ossmanc0397262014-03-14 15:59:46 +0100804 encoder->writeRect(ppb, info.palette);
Pierre Ossman20dd2a92015-02-11 17:43:15 +0100805
806 endRect();
Pierre Ossmanc0397262014-03-14 15:59:46 +0100807}
808
809bool EncodeManager::checkSolidTile(const Rect& r, const rdr::U8* colourValue,
810 const PixelBuffer *pb)
811{
812 switch (pb->getPF().bpp) {
813 case 32:
814 return checkSolidTile(r, *(const rdr::U32*)colourValue, pb);
815 case 16:
816 return checkSolidTile(r, *(const rdr::U16*)colourValue, pb);
817 default:
818 return checkSolidTile(r, *(const rdr::U8*)colourValue, pb);
819 }
820}
821
822void EncodeManager::extendSolidAreaByBlock(const Rect& r,
823 const rdr::U8* colourValue,
824 const PixelBuffer *pb, Rect* er)
825{
826 int dx, dy, dw, dh;
827 int w_prev;
828 Rect sr;
829 int w_best = 0, h_best = 0;
830
831 w_prev = r.width();
832
833 // We search width first, back off when we hit a different colour,
834 // and restart with a larger height. We keep track of the
835 // width/height combination that gives us the largest area.
836 for (dy = r.tl.y; dy < r.br.y; dy += SolidSearchBlock) {
837
838 dh = SolidSearchBlock;
839 if (dy + dh > r.br.y)
840 dh = r.br.y - dy;
841
842 // We test one block here outside the x loop in order to break
843 // the y loop right away.
844 dw = SolidSearchBlock;
845 if (dw > w_prev)
846 dw = w_prev;
847
848 sr.setXYWH(r.tl.x, dy, dw, dh);
849 if (!checkSolidTile(sr, colourValue, pb))
850 break;
851
852 for (dx = r.tl.x + dw; dx < r.tl.x + w_prev;) {
853
854 dw = SolidSearchBlock;
855 if (dx + dw > r.tl.x + w_prev)
856 dw = r.tl.x + w_prev - dx;
857
858 sr.setXYWH(dx, dy, dw, dh);
859 if (!checkSolidTile(sr, colourValue, pb))
860 break;
861
862 dx += dw;
863 }
864
865 w_prev = dx - r.tl.x;
866 if (w_prev * (dy + dh - r.tl.y) > w_best * h_best) {
867 w_best = w_prev;
868 h_best = dy + dh - r.tl.y;
869 }
870 }
871
872 er->tl.x = r.tl.x;
873 er->tl.y = r.tl.y;
874 er->br.x = er->tl.x + w_best;
875 er->br.y = er->tl.y + h_best;
876}
877
878void EncodeManager::extendSolidAreaByPixel(const Rect& r, const Rect& sr,
879 const rdr::U8* colourValue,
880 const PixelBuffer *pb, Rect* er)
881{
882 int cx, cy;
883 Rect tr;
884
885 // Try to extend the area upwards.
886 for (cy = sr.tl.y - 1; cy >= r.tl.y; cy--) {
887 tr.setXYWH(sr.tl.x, cy, sr.width(), 1);
888 if (!checkSolidTile(tr, colourValue, pb))
889 break;
890 }
891 er->tl.y = cy + 1;
892
893 // ... downwards.
894 for (cy = sr.br.y; cy < r.br.y; cy++) {
895 tr.setXYWH(sr.tl.x, cy, sr.width(), 1);
896 if (!checkSolidTile(tr, colourValue, pb))
897 break;
898 }
899 er->br.y = cy;
900
901 // ... to the left.
902 for (cx = sr.tl.x - 1; cx >= r.tl.x; cx--) {
903 tr.setXYWH(cx, er->tl.y, 1, er->height());
904 if (!checkSolidTile(tr, colourValue, pb))
905 break;
906 }
907 er->tl.x = cx + 1;
908
909 // ... to the right.
910 for (cx = sr.br.x; cx < r.br.x; cx++) {
911 tr.setXYWH(cx, er->tl.y, 1, er->height());
912 if (!checkSolidTile(tr, colourValue, pb))
913 break;
914 }
915 er->br.x = cx;
916}
917
918PixelBuffer* EncodeManager::preparePixelBuffer(const Rect& rect,
919 const PixelBuffer *pb,
920 bool convert)
921{
922 const rdr::U8* buffer;
923 int stride;
924
925 // Do wo need to convert the data?
926 if (convert && !conn->cp.pf().equal(pb->getPF())) {
927 convertedPixelBuffer.setPF(conn->cp.pf());
928 convertedPixelBuffer.setSize(rect.width(), rect.height());
929
930 buffer = pb->getBuffer(rect, &stride);
931 convertedPixelBuffer.imageRect(pb->getPF(),
932 convertedPixelBuffer.getRect(),
933 buffer, stride);
934
935 return &convertedPixelBuffer;
936 }
937
938 // Otherwise we still need to shift the coordinates. We have our own
939 // abusive subclass of FullFramePixelBuffer for this.
940
941 buffer = pb->getBuffer(rect, &stride);
942
943 offsetPixelBuffer.update(pb->getPF(), rect.width(), rect.height(),
944 buffer, stride);
945
946 return &offsetPixelBuffer;
947}
948
949bool EncodeManager::analyseRect(const PixelBuffer *pb,
950 struct RectInfo *info, int maxColours)
951{
952 const rdr::U8* buffer;
953 int stride;
954
955 buffer = pb->getBuffer(pb->getRect(), &stride);
956
957 switch (pb->getPF().bpp) {
958 case 32:
959 return analyseRect(pb->width(), pb->height(),
960 (const rdr::U32*)buffer, stride,
961 info, maxColours);
962 case 16:
963 return analyseRect(pb->width(), pb->height(),
964 (const rdr::U16*)buffer, stride,
965 info, maxColours);
966 default:
967 return analyseRect(pb->width(), pb->height(),
968 (const rdr::U8*)buffer, stride,
969 info, maxColours);
970 }
971}
972
973void EncodeManager::OffsetPixelBuffer::update(const PixelFormat& pf,
974 int width, int height,
975 const rdr::U8* data_,
976 int stride_)
977{
978 format = pf;
979 width_ = width;
980 height_ = height;
981 // Forced cast. We never write anything though, so it should be safe.
982 data = (rdr::U8*)data_;
983 stride = stride_;
984}
985
986// Preprocessor generated, optimised methods
987
988#define BPP 8
989#include "EncodeManagerBPP.cxx"
990#undef BPP
991#define BPP 16
992#include "EncodeManagerBPP.cxx"
993#undef BPP
994#define BPP 32
995#include "EncodeManagerBPP.cxx"
996#undef BPP