1 /* Copyright (C) 2000-2003 Constantin Kaplinsky. All Rights Reserved.
2 * Copyright (C) 2011 D. R. Commander. All Rights Reserved.
3 * Copyright 2014-2018 Pierre Ossman for Cendio AB
4 * Copyright 2018 Peter Astrand for Cendio AB
5 *
6 * This is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This software is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this software; if not, write to the Free Software
18 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
19 * USA.
20 */
21
22 #include <stdlib.h>
23
24 #include <rfb/EncodeManager.h>
25 #include <rfb/Encoder.h>
26 #include <rfb/Palette.h>
27 #include <rfb/SConnection.h>
28 #include <rfb/SMsgWriter.h>
29 #include <rfb/UpdateTracker.h>
30 #include <rfb/LogWriter.h>
31 #include <rfb/Exception.h>
32
33 #include <rfb/RawEncoder.h>
34 #include <rfb/RREEncoder.h>
35 #include <rfb/HextileEncoder.h>
36 #include <rfb/ZRLEEncoder.h>
37 #include <rfb/TightEncoder.h>
38 #include <rfb/TightJPEGEncoder.h>
39
40 using namespace rfb;
41
42 static LogWriter vlog("EncodeManager");
43
44 // Split each rectangle into smaller ones no larger than this area,
45 // and no wider than this width.
46 static const int SubRectMaxArea = 65536;
47 static const int SubRectMaxWidth = 2048;
48
49 // The size in pixels of either side of each block tested when looking
50 // for solid blocks.
51 static const int SolidSearchBlock = 16;
52 // Don't bother with blocks smaller than this
53 static const int SolidBlockMinArea = 2048;
54
55 // How long we consider a region recently changed (in ms)
56 static const int RecentChangeTimeout = 50;
57
58 namespace rfb {
59
60 enum EncoderClass {
61 encoderRaw,
62 encoderRRE,
63 encoderHextile,
64 encoderTight,
65 encoderTightJPEG,
66 encoderZRLE,
67 encoderClassMax,
68 };
69
70 enum EncoderType {
71 encoderSolid,
72 encoderBitmap,
73 encoderBitmapRLE,
74 encoderIndexed,
75 encoderIndexedRLE,
76 encoderFullColour,
77 encoderTypeMax,
78 };
79
80 struct RectInfo {
81 int rleRuns;
82 Palette palette;
83 };
84
85 };
86
encoderClassName(EncoderClass klass)87 static const char *encoderClassName(EncoderClass klass)
88 {
89 switch (klass) {
90 case encoderRaw:
91 return "Raw";
92 case encoderRRE:
93 return "RRE";
94 case encoderHextile:
95 return "Hextile";
96 case encoderTight:
97 return "Tight";
98 case encoderTightJPEG:
99 return "Tight (JPEG)";
100 case encoderZRLE:
101 return "ZRLE";
102 case encoderClassMax:
103 break;
104 }
105
106 return "Unknown Encoder Class";
107 }
108
encoderTypeName(EncoderType type)109 static const char *encoderTypeName(EncoderType type)
110 {
111 switch (type) {
112 case encoderSolid:
113 return "Solid";
114 case encoderBitmap:
115 return "Bitmap";
116 case encoderBitmapRLE:
117 return "Bitmap RLE";
118 case encoderIndexed:
119 return "Indexed";
120 case encoderIndexedRLE:
121 return "Indexed RLE";
122 case encoderFullColour:
123 return "Full Colour";
124 case encoderTypeMax:
125 break;
126 }
127
128 return "Unknown Encoder Type";
129 }
130
EncodeManager(SConnection * conn_)131 EncodeManager::EncodeManager(SConnection* conn_)
132 : conn(conn_), recentChangeTimer(this)
133 {
134 StatsVector::iterator iter;
135
136 encoders.resize(encoderClassMax, NULL);
137 activeEncoders.resize(encoderTypeMax, encoderRaw);
138
139 encoders[encoderRaw] = new RawEncoder(conn);
140 encoders[encoderRRE] = new RREEncoder(conn);
141 encoders[encoderHextile] = new HextileEncoder(conn);
142 encoders[encoderTight] = new TightEncoder(conn);
143 encoders[encoderTightJPEG] = new TightJPEGEncoder(conn);
144 encoders[encoderZRLE] = new ZRLEEncoder(conn);
145
146 updates = 0;
147 memset(©Stats, 0, sizeof(copyStats));
148 stats.resize(encoderClassMax);
149 for (iter = stats.begin();iter != stats.end();++iter) {
150 StatsVector::value_type::iterator iter2;
151 iter->resize(encoderTypeMax);
152 for (iter2 = iter->begin();iter2 != iter->end();++iter2)
153 memset(&*iter2, 0, sizeof(EncoderStats));
154 }
155 }
156
~EncodeManager()157 EncodeManager::~EncodeManager()
158 {
159 std::vector<Encoder*>::iterator iter;
160
161 logStats();
162
163 for (iter = encoders.begin();iter != encoders.end();iter++)
164 delete *iter;
165 }
166
logStats()167 void EncodeManager::logStats()
168 {
169 size_t i, j;
170
171 unsigned rects;
172 unsigned long long pixels, bytes, equivalent;
173
174 double ratio;
175
176 char a[1024], b[1024];
177
178 rects = 0;
179 pixels = bytes = equivalent = 0;
180
181 vlog.info("Framebuffer updates: %u", updates);
182
183 if (copyStats.rects != 0) {
184 vlog.info(" %s:", "CopyRect");
185
186 rects += copyStats.rects;
187 pixels += copyStats.pixels;
188 bytes += copyStats.bytes;
189 equivalent += copyStats.equivalent;
190
191 ratio = (double)copyStats.equivalent / copyStats.bytes;
192
193 siPrefix(copyStats.rects, "rects", a, sizeof(a));
194 siPrefix(copyStats.pixels, "pixels", b, sizeof(b));
195 vlog.info(" %s: %s, %s", "Copies", a, b);
196 iecPrefix(copyStats.bytes, "B", a, sizeof(a));
197 vlog.info(" %*s %s (1:%g ratio)",
198 (int)strlen("Copies"), "",
199 a, ratio);
200 }
201
202 for (i = 0;i < stats.size();i++) {
203 // Did this class do anything at all?
204 for (j = 0;j < stats[i].size();j++) {
205 if (stats[i][j].rects != 0)
206 break;
207 }
208 if (j == stats[i].size())
209 continue;
210
211 vlog.info(" %s:", encoderClassName((EncoderClass)i));
212
213 for (j = 0;j < stats[i].size();j++) {
214 if (stats[i][j].rects == 0)
215 continue;
216
217 rects += stats[i][j].rects;
218 pixels += stats[i][j].pixels;
219 bytes += stats[i][j].bytes;
220 equivalent += stats[i][j].equivalent;
221
222 ratio = (double)stats[i][j].equivalent / stats[i][j].bytes;
223
224 siPrefix(stats[i][j].rects, "rects", a, sizeof(a));
225 siPrefix(stats[i][j].pixels, "pixels", b, sizeof(b));
226 vlog.info(" %s: %s, %s", encoderTypeName((EncoderType)j), a, b);
227 iecPrefix(stats[i][j].bytes, "B", a, sizeof(a));
228 vlog.info(" %*s %s (1:%g ratio)",
229 (int)strlen(encoderTypeName((EncoderType)j)), "",
230 a, ratio);
231 }
232 }
233
234 ratio = (double)equivalent / bytes;
235
236 siPrefix(rects, "rects", a, sizeof(a));
237 siPrefix(pixels, "pixels", b, sizeof(b));
238 vlog.info(" Total: %s, %s", a, b);
239 iecPrefix(bytes, "B", a, sizeof(a));
240 vlog.info(" %s (1:%g ratio)", a, ratio);
241 }
242
supported(int encoding)243 bool EncodeManager::supported(int encoding)
244 {
245 switch (encoding) {
246 case encodingRaw:
247 case encodingRRE:
248 case encodingHextile:
249 case encodingZRLE:
250 case encodingTight:
251 return true;
252 default:
253 return false;
254 }
255 }
256
needsLosslessRefresh(const Region & req)257 bool EncodeManager::needsLosslessRefresh(const Region& req)
258 {
259 return !lossyRegion.intersect(req).is_empty();
260 }
261
getNextLosslessRefresh(const Region & req)262 int EncodeManager::getNextLosslessRefresh(const Region& req)
263 {
264 // Do we have something we can send right away?
265 if (!pendingRefreshRegion.intersect(req).is_empty())
266 return 0;
267
268 assert(needsLosslessRefresh(req));
269 assert(recentChangeTimer.isStarted());
270
271 return recentChangeTimer.getNextTimeout();
272 }
273
pruneLosslessRefresh(const Region & limits)274 void EncodeManager::pruneLosslessRefresh(const Region& limits)
275 {
276 lossyRegion.assign_intersect(limits);
277 pendingRefreshRegion.assign_intersect(limits);
278 }
279
writeUpdate(const UpdateInfo & ui,const PixelBuffer * pb,const RenderedCursor * renderedCursor)280 void EncodeManager::writeUpdate(const UpdateInfo& ui, const PixelBuffer* pb,
281 const RenderedCursor* renderedCursor)
282 {
283 doUpdate(true, ui.changed, ui.copied, ui.copy_delta, pb, renderedCursor);
284
285 recentlyChangedRegion.assign_union(ui.changed);
286 recentlyChangedRegion.assign_union(ui.copied);
287 if (!recentChangeTimer.isStarted())
288 recentChangeTimer.start(RecentChangeTimeout);
289 }
290
writeLosslessRefresh(const Region & req,const PixelBuffer * pb,const RenderedCursor * renderedCursor,size_t maxUpdateSize)291 void EncodeManager::writeLosslessRefresh(const Region& req, const PixelBuffer* pb,
292 const RenderedCursor* renderedCursor,
293 size_t maxUpdateSize)
294 {
295 doUpdate(false, getLosslessRefresh(req, maxUpdateSize),
296 Region(), Point(), pb, renderedCursor);
297 }
298
handleTimeout(Timer * t)299 bool EncodeManager::handleTimeout(Timer* t)
300 {
301 if (t == &recentChangeTimer) {
302 // Any lossy region that wasn't recently updated can
303 // now be scheduled for a refresh
304 pendingRefreshRegion.assign_union(lossyRegion.subtract(recentlyChangedRegion));
305 recentlyChangedRegion.clear();
306
307 // Will there be more to do? (i.e. do we need another round)
308 if (!lossyRegion.subtract(pendingRefreshRegion).is_empty())
309 return true;
310 }
311
312 return false;
313 }
314
doUpdate(bool allowLossy,const Region & changed_,const Region & copied,const Point & copyDelta,const PixelBuffer * pb,const RenderedCursor * renderedCursor)315 void EncodeManager::doUpdate(bool allowLossy, const Region& changed_,
316 const Region& copied, const Point& copyDelta,
317 const PixelBuffer* pb,
318 const RenderedCursor* renderedCursor)
319 {
320 int nRects;
321 Region changed, cursorRegion;
322
323 updates++;
324
325 prepareEncoders(allowLossy);
326
327 changed = changed_;
328
329 if (!conn->client.supportsEncoding(encodingCopyRect))
330 changed.assign_union(copied);
331
332 /*
333 * We need to render the cursor seperately as it has its own
334 * magical pixel buffer, so split it out from the changed region.
335 */
336 if (renderedCursor != NULL) {
337 cursorRegion = changed.intersect(renderedCursor->getEffectiveRect());
338 changed.assign_subtract(renderedCursor->getEffectiveRect());
339 }
340
341 if (conn->client.supportsEncoding(pseudoEncodingLastRect))
342 nRects = 0xFFFF;
343 else {
344 nRects = 0;
345 if (conn->client.supportsEncoding(encodingCopyRect))
346 nRects += copied.numRects();
347 nRects += computeNumRects(changed);
348 nRects += computeNumRects(cursorRegion);
349 }
350
351 conn->writer()->writeFramebufferUpdateStart(nRects);
352
353 if (conn->client.supportsEncoding(encodingCopyRect))
354 writeCopyRects(copied, copyDelta);
355
356 /*
357 * We start by searching for solid rects, which are then removed
358 * from the changed region.
359 */
360 if (conn->client.supportsEncoding(pseudoEncodingLastRect))
361 writeSolidRects(&changed, pb);
362
363 writeRects(changed, pb);
364 writeRects(cursorRegion, renderedCursor);
365
366 conn->writer()->writeFramebufferUpdateEnd();
367 }
368
prepareEncoders(bool allowLossy)369 void EncodeManager::prepareEncoders(bool allowLossy)
370 {
371 enum EncoderClass solid, bitmap, bitmapRLE;
372 enum EncoderClass indexed, indexedRLE, fullColour;
373
374 bool allowJPEG;
375
376 rdr::S32 preferred;
377
378 std::vector<int>::iterator iter;
379
380 solid = bitmap = bitmapRLE = encoderRaw;
381 indexed = indexedRLE = fullColour = encoderRaw;
382
383 allowJPEG = conn->client.pf().bpp >= 16;
384 if (!allowLossy) {
385 if (encoders[encoderTightJPEG]->losslessQuality == -1)
386 allowJPEG = false;
387 }
388
389 // Try to respect the client's wishes
390 preferred = conn->getPreferredEncoding();
391 switch (preferred) {
392 case encodingRRE:
393 // Horrible for anything high frequency and/or lots of colours
394 bitmapRLE = indexedRLE = encoderRRE;
395 break;
396 case encodingHextile:
397 // Slightly less horrible
398 bitmapRLE = indexedRLE = fullColour = encoderHextile;
399 break;
400 case encodingTight:
401 if (encoders[encoderTightJPEG]->isSupported() && allowJPEG)
402 fullColour = encoderTightJPEG;
403 else
404 fullColour = encoderTight;
405 indexed = indexedRLE = encoderTight;
406 bitmap = bitmapRLE = encoderTight;
407 break;
408 case encodingZRLE:
409 fullColour = encoderZRLE;
410 bitmapRLE = indexedRLE = encoderZRLE;
411 bitmap = indexed = encoderZRLE;
412 break;
413 }
414
415 // Any encoders still unassigned?
416
417 if (fullColour == encoderRaw) {
418 if (encoders[encoderTightJPEG]->isSupported() && allowJPEG)
419 fullColour = encoderTightJPEG;
420 else if (encoders[encoderZRLE]->isSupported())
421 fullColour = encoderZRLE;
422 else if (encoders[encoderTight]->isSupported())
423 fullColour = encoderTight;
424 else if (encoders[encoderHextile]->isSupported())
425 fullColour = encoderHextile;
426 }
427
428 if (indexed == encoderRaw) {
429 if (encoders[encoderZRLE]->isSupported())
430 indexed = encoderZRLE;
431 else if (encoders[encoderTight]->isSupported())
432 indexed = encoderTight;
433 else if (encoders[encoderHextile]->isSupported())
434 indexed = encoderHextile;
435 }
436
437 if (indexedRLE == encoderRaw)
438 indexedRLE = indexed;
439
440 if (bitmap == encoderRaw)
441 bitmap = indexed;
442 if (bitmapRLE == encoderRaw)
443 bitmapRLE = bitmap;
444
445 if (solid == encoderRaw) {
446 if (encoders[encoderTight]->isSupported())
447 solid = encoderTight;
448 else if (encoders[encoderRRE]->isSupported())
449 solid = encoderRRE;
450 else if (encoders[encoderZRLE]->isSupported())
451 solid = encoderZRLE;
452 else if (encoders[encoderHextile]->isSupported())
453 solid = encoderHextile;
454 }
455
456 // JPEG is the only encoder that can reduce things to grayscale
457 if ((conn->client.subsampling == subsampleGray) &&
458 encoders[encoderTightJPEG]->isSupported() && allowLossy) {
459 solid = bitmap = bitmapRLE = encoderTightJPEG;
460 indexed = indexedRLE = fullColour = encoderTightJPEG;
461 }
462
463 activeEncoders[encoderSolid] = solid;
464 activeEncoders[encoderBitmap] = bitmap;
465 activeEncoders[encoderBitmapRLE] = bitmapRLE;
466 activeEncoders[encoderIndexed] = indexed;
467 activeEncoders[encoderIndexedRLE] = indexedRLE;
468 activeEncoders[encoderFullColour] = fullColour;
469
470 for (iter = activeEncoders.begin(); iter != activeEncoders.end(); ++iter) {
471 Encoder *encoder;
472
473 encoder = encoders[*iter];
474
475 encoder->setCompressLevel(conn->client.compressLevel);
476
477 if (allowLossy) {
478 encoder->setQualityLevel(conn->client.qualityLevel);
479 encoder->setFineQualityLevel(conn->client.fineQualityLevel,
480 conn->client.subsampling);
481 } else {
482 int level = __rfbmax(conn->client.qualityLevel,
483 encoder->losslessQuality);
484 encoder->setQualityLevel(level);
485 encoder->setFineQualityLevel(-1, subsampleUndefined);
486 }
487 }
488 }
489
getLosslessRefresh(const Region & req,size_t maxUpdateSize)490 Region EncodeManager::getLosslessRefresh(const Region& req,
491 size_t maxUpdateSize)
492 {
493 std::vector<Rect> rects;
494 Region refresh;
495 size_t area;
496
497 // We make a conservative guess at the compression ratio at 2:1
498 maxUpdateSize *= 2;
499
500 // We will measure pixels, not bytes (assume 32 bpp)
501 maxUpdateSize /= 4;
502
503 area = 0;
504 pendingRefreshRegion.intersect(req).get_rects(&rects);
505 while (!rects.empty()) {
506 size_t idx;
507 Rect rect;
508
509 // Grab a random rect so we don't keep damaging and restoring the
510 // same rect over and over
511 idx = rand() % rects.size();
512
513 rect = rects[idx];
514
515 // Add rects until we exceed the threshold, then include as much as
516 // possible of the final rect
517 if ((area + rect.area()) > maxUpdateSize) {
518 // Use the narrowest axis to avoid getting to thin rects
519 if (rect.width() > rect.height()) {
520 int width = (maxUpdateSize - area) / rect.height();
521 rect.br.x = rect.tl.x + __rfbmax(1, width);
522 } else {
523 int height = (maxUpdateSize - area) / rect.width();
524 rect.br.y = rect.tl.y + __rfbmax(1, height);
525 }
526 refresh.assign_union(Region(rect));
527 break;
528 }
529
530 area += rect.area();
531 refresh.assign_union(Region(rect));
532
533 rects.erase(rects.begin() + idx);
534 }
535
536 return refresh;
537 }
538
computeNumRects(const Region & changed)539 int EncodeManager::computeNumRects(const Region& changed)
540 {
541 int numRects;
542 std::vector<Rect> rects;
543 std::vector<Rect>::const_iterator rect;
544
545 numRects = 0;
546 changed.get_rects(&rects);
547 for (rect = rects.begin(); rect != rects.end(); ++rect) {
548 int w, h, sw, sh;
549
550 w = rect->width();
551 h = rect->height();
552
553 // No split necessary?
554 if (((w*h) < SubRectMaxArea) && (w < SubRectMaxWidth)) {
555 numRects += 1;
556 continue;
557 }
558
559 if (w <= SubRectMaxWidth)
560 sw = w;
561 else
562 sw = SubRectMaxWidth;
563
564 sh = SubRectMaxArea / sw;
565
566 // ceil(w/sw) * ceil(h/sh)
567 numRects += (((w - 1)/sw) + 1) * (((h - 1)/sh) + 1);
568 }
569
570 return numRects;
571 }
572
startRect(const Rect & rect,int type)573 Encoder *EncodeManager::startRect(const Rect& rect, int type)
574 {
575 Encoder *encoder;
576 int klass, equiv;
577
578 activeType = type;
579 klass = activeEncoders[activeType];
580
581 beforeLength = conn->getOutStream()->length();
582
583 stats[klass][activeType].rects++;
584 stats[klass][activeType].pixels += rect.area();
585 equiv = 12 + rect.area() * (conn->client.pf().bpp/8);
586 stats[klass][activeType].equivalent += equiv;
587
588 encoder = encoders[klass];
589 conn->writer()->startRect(rect, encoder->encoding);
590
591 if ((encoder->flags & EncoderLossy) &&
592 ((encoder->losslessQuality == -1) ||
593 (encoder->getQualityLevel() < encoder->losslessQuality)))
594 lossyRegion.assign_union(Region(rect));
595 else
596 lossyRegion.assign_subtract(Region(rect));
597
598 // This was either a rect getting refreshed, or a rect that just got
599 // new content. Either way we should not try to refresh it anymore.
600 pendingRefreshRegion.assign_subtract(Region(rect));
601
602 return encoder;
603 }
604
endRect()605 void EncodeManager::endRect()
606 {
607 int klass;
608 int length;
609
610 conn->writer()->endRect();
611
612 length = conn->getOutStream()->length() - beforeLength;
613
614 klass = activeEncoders[activeType];
615 stats[klass][activeType].bytes += length;
616 }
617
writeCopyRects(const Region & copied,const Point & delta)618 void EncodeManager::writeCopyRects(const Region& copied, const Point& delta)
619 {
620 std::vector<Rect> rects;
621 std::vector<Rect>::const_iterator rect;
622
623 Region lossyCopy;
624
625 beforeLength = conn->getOutStream()->length();
626
627 copied.get_rects(&rects, delta.x <= 0, delta.y <= 0);
628 for (rect = rects.begin(); rect != rects.end(); ++rect) {
629 int equiv;
630
631 copyStats.rects++;
632 copyStats.pixels += rect->area();
633 equiv = 12 + rect->area() * (conn->client.pf().bpp/8);
634 copyStats.equivalent += equiv;
635
636 conn->writer()->writeCopyRect(*rect, rect->tl.x - delta.x,
637 rect->tl.y - delta.y);
638 }
639
640 copyStats.bytes += conn->getOutStream()->length() - beforeLength;
641
642 lossyCopy = lossyRegion;
643 lossyCopy.translate(delta);
644 lossyCopy.assign_intersect(copied);
645 lossyRegion.assign_union(lossyCopy);
646
647 // Stop any pending refresh as a copy is enough that we consider
648 // this region to be recently changed
649 pendingRefreshRegion.assign_subtract(copied);
650 }
651
writeSolidRects(Region * changed,const PixelBuffer * pb)652 void EncodeManager::writeSolidRects(Region *changed, const PixelBuffer* pb)
653 {
654 std::vector<Rect> rects;
655 std::vector<Rect>::const_iterator rect;
656
657 changed->get_rects(&rects);
658 for (rect = rects.begin(); rect != rects.end(); ++rect)
659 findSolidRect(*rect, changed, pb);
660 }
661
findSolidRect(const Rect & rect,Region * changed,const PixelBuffer * pb)662 void EncodeManager::findSolidRect(const Rect& rect, Region *changed,
663 const PixelBuffer* pb)
664 {
665 Rect sr;
666 int dx, dy, dw, dh;
667
668 // We start by finding a solid 16x16 block
669 for (dy = rect.tl.y; dy < rect.br.y; dy += SolidSearchBlock) {
670
671 dh = SolidSearchBlock;
672 if (dy + dh > rect.br.y)
673 dh = rect.br.y - dy;
674
675 for (dx = rect.tl.x; dx < rect.br.x; dx += SolidSearchBlock) {
676 // We define it like this to guarantee alignment
677 rdr::U32 _buffer;
678 rdr::U8* colourValue = (rdr::U8*)&_buffer;
679
680 dw = SolidSearchBlock;
681 if (dx + dw > rect.br.x)
682 dw = rect.br.x - dx;
683
684 pb->getImage(colourValue, Rect(dx, dy, dx+1, dy+1));
685
686 sr.setXYWH(dx, dy, dw, dh);
687 if (checkSolidTile(sr, colourValue, pb)) {
688 Rect erb, erp;
689
690 Encoder *encoder;
691
692 // We then try extending the area by adding more blocks
693 // in both directions and pick the combination that gives
694 // the largest area.
695 sr.setXYWH(dx, dy, rect.br.x - dx, rect.br.y - dy);
696 extendSolidAreaByBlock(sr, colourValue, pb, &erb);
697
698 // Did we end up getting the entire rectangle?
699 if (erb.equals(rect))
700 erp = erb;
701 else {
702 // Don't bother with sending tiny rectangles
703 if (erb.area() < SolidBlockMinArea)
704 continue;
705
706 // Extend the area again, but this time one pixel
707 // row/column at a time.
708 extendSolidAreaByPixel(rect, erb, colourValue, pb, &erp);
709 }
710
711 // Send solid-color rectangle.
712 encoder = startRect(erp, encoderSolid);
713 if (encoder->flags & EncoderUseNativePF) {
714 encoder->writeSolidRect(erp.width(), erp.height(),
715 pb->getPF(), colourValue);
716 } else {
717 rdr::U32 _buffer2;
718 rdr::U8* converted = (rdr::U8*)&_buffer2;
719
720 conn->client.pf().bufferFromBuffer(converted, pb->getPF(),
721 colourValue, 1);
722
723 encoder->writeSolidRect(erp.width(), erp.height(),
724 conn->client.pf(), converted);
725 }
726 endRect();
727
728 changed->assign_subtract(Region(erp));
729
730 // Search remaining areas by recursion
731 // FIXME: Is this the best way to divide things up?
732
733 // Left? (Note that we've already searched a SolidSearchBlock
734 // pixels high strip here)
735 if ((erp.tl.x != rect.tl.x) && (erp.height() > SolidSearchBlock)) {
736 sr.setXYWH(rect.tl.x, erp.tl.y + SolidSearchBlock,
737 erp.tl.x - rect.tl.x, erp.height() - SolidSearchBlock);
738 findSolidRect(sr, changed, pb);
739 }
740
741 // Right?
742 if (erp.br.x != rect.br.x) {
743 sr.setXYWH(erp.br.x, erp.tl.y, rect.br.x - erp.br.x, erp.height());
744 findSolidRect(sr, changed, pb);
745 }
746
747 // Below?
748 if (erp.br.y != rect.br.y) {
749 sr.setXYWH(rect.tl.x, erp.br.y, rect.width(), rect.br.y - erp.br.y);
750 findSolidRect(sr, changed, pb);
751 }
752
753 return;
754 }
755 }
756 }
757 }
758
writeRects(const Region & changed,const PixelBuffer * pb)759 void EncodeManager::writeRects(const Region& changed, const PixelBuffer* pb)
760 {
761 std::vector<Rect> rects;
762 std::vector<Rect>::const_iterator rect;
763
764 changed.get_rects(&rects);
765 for (rect = rects.begin(); rect != rects.end(); ++rect) {
766 int w, h, sw, sh;
767 Rect sr;
768
769 w = rect->width();
770 h = rect->height();
771
772 // No split necessary?
773 if (((w*h) < SubRectMaxArea) && (w < SubRectMaxWidth)) {
774 writeSubRect(*rect, pb);
775 continue;
776 }
777
778 if (w <= SubRectMaxWidth)
779 sw = w;
780 else
781 sw = SubRectMaxWidth;
782
783 sh = SubRectMaxArea / sw;
784
785 for (sr.tl.y = rect->tl.y; sr.tl.y < rect->br.y; sr.tl.y += sh) {
786 sr.br.y = sr.tl.y + sh;
787 if (sr.br.y > rect->br.y)
788 sr.br.y = rect->br.y;
789
790 for (sr.tl.x = rect->tl.x; sr.tl.x < rect->br.x; sr.tl.x += sw) {
791 sr.br.x = sr.tl.x + sw;
792 if (sr.br.x > rect->br.x)
793 sr.br.x = rect->br.x;
794
795 writeSubRect(sr, pb);
796 }
797 }
798 }
799 }
800
writeSubRect(const Rect & rect,const PixelBuffer * pb)801 void EncodeManager::writeSubRect(const Rect& rect, const PixelBuffer *pb)
802 {
803 PixelBuffer *ppb;
804
805 Encoder *encoder;
806
807 struct RectInfo info;
808 unsigned int divisor, maxColours;
809
810 bool useRLE;
811 EncoderType type;
812
813 // FIXME: This is roughly the algorithm previously used by the Tight
814 // encoder. It seems a bit backwards though, that higher
815 // compression setting means spending less effort in building
816 // a palette. It might be that they figured the increase in
817 // zlib setting compensated for the loss.
818 if (conn->client.compressLevel == -1)
819 divisor = 2 * 8;
820 else
821 divisor = conn->client.compressLevel * 8;
822 if (divisor < 4)
823 divisor = 4;
824
825 maxColours = rect.area()/divisor;
826
827 // Special exception inherited from the Tight encoder
828 if (activeEncoders[encoderFullColour] == encoderTightJPEG) {
829 if ((conn->client.compressLevel != -1) && (conn->client.compressLevel < 2))
830 maxColours = 24;
831 else
832 maxColours = 96;
833 }
834
835 if (maxColours < 2)
836 maxColours = 2;
837
838 encoder = encoders[activeEncoders[encoderIndexedRLE]];
839 if (maxColours > encoder->maxPaletteSize)
840 maxColours = encoder->maxPaletteSize;
841 encoder = encoders[activeEncoders[encoderIndexed]];
842 if (maxColours > encoder->maxPaletteSize)
843 maxColours = encoder->maxPaletteSize;
844
845 ppb = preparePixelBuffer(rect, pb, true);
846
847 if (!analyseRect(ppb, &info, maxColours))
848 info.palette.clear();
849
850 // Different encoders might have different RLE overhead, but
851 // here we do a guess at RLE being the better choice if reduces
852 // the pixel count by 50%.
853 useRLE = info.rleRuns <= (rect.area() * 2);
854
855 switch (info.palette.size()) {
856 case 0:
857 type = encoderFullColour;
858 break;
859 case 1:
860 type = encoderSolid;
861 break;
862 case 2:
863 if (useRLE)
864 type = encoderBitmapRLE;
865 else
866 type = encoderBitmap;
867 break;
868 default:
869 if (useRLE)
870 type = encoderIndexedRLE;
871 else
872 type = encoderIndexed;
873 }
874
875 encoder = startRect(rect, type);
876
877 if (encoder->flags & EncoderUseNativePF)
878 ppb = preparePixelBuffer(rect, pb, false);
879
880 encoder->writeRect(ppb, info.palette);
881
882 endRect();
883 }
884
checkSolidTile(const Rect & r,const rdr::U8 * colourValue,const PixelBuffer * pb)885 bool EncodeManager::checkSolidTile(const Rect& r, const rdr::U8* colourValue,
886 const PixelBuffer *pb)
887 {
888 switch (pb->getPF().bpp) {
889 case 32:
890 return checkSolidTile(r, *(const rdr::U32*)colourValue, pb);
891 case 16:
892 return checkSolidTile(r, *(const rdr::U16*)colourValue, pb);
893 default:
894 return checkSolidTile(r, *(const rdr::U8*)colourValue, pb);
895 }
896 }
897
extendSolidAreaByBlock(const Rect & r,const rdr::U8 * colourValue,const PixelBuffer * pb,Rect * er)898 void EncodeManager::extendSolidAreaByBlock(const Rect& r,
899 const rdr::U8* colourValue,
900 const PixelBuffer *pb, Rect* er)
901 {
902 int dx, dy, dw, dh;
903 int w_prev;
904 Rect sr;
905 int w_best = 0, h_best = 0;
906
907 w_prev = r.width();
908
909 // We search width first, back off when we hit a different colour,
910 // and restart with a larger height. We keep track of the
911 // width/height combination that gives us the largest area.
912 for (dy = r.tl.y; dy < r.br.y; dy += SolidSearchBlock) {
913
914 dh = SolidSearchBlock;
915 if (dy + dh > r.br.y)
916 dh = r.br.y - dy;
917
918 // We test one block here outside the x loop in order to break
919 // the y loop right away.
920 dw = SolidSearchBlock;
921 if (dw > w_prev)
922 dw = w_prev;
923
924 sr.setXYWH(r.tl.x, dy, dw, dh);
925 if (!checkSolidTile(sr, colourValue, pb))
926 break;
927
928 for (dx = r.tl.x + dw; dx < r.tl.x + w_prev;) {
929
930 dw = SolidSearchBlock;
931 if (dx + dw > r.tl.x + w_prev)
932 dw = r.tl.x + w_prev - dx;
933
934 sr.setXYWH(dx, dy, dw, dh);
935 if (!checkSolidTile(sr, colourValue, pb))
936 break;
937
938 dx += dw;
939 }
940
941 w_prev = dx - r.tl.x;
942 if (w_prev * (dy + dh - r.tl.y) > w_best * h_best) {
943 w_best = w_prev;
944 h_best = dy + dh - r.tl.y;
945 }
946 }
947
948 er->tl.x = r.tl.x;
949 er->tl.y = r.tl.y;
950 er->br.x = er->tl.x + w_best;
951 er->br.y = er->tl.y + h_best;
952 }
953
extendSolidAreaByPixel(const Rect & r,const Rect & sr,const rdr::U8 * colourValue,const PixelBuffer * pb,Rect * er)954 void EncodeManager::extendSolidAreaByPixel(const Rect& r, const Rect& sr,
955 const rdr::U8* colourValue,
956 const PixelBuffer *pb, Rect* er)
957 {
958 int cx, cy;
959 Rect tr;
960
961 // Try to extend the area upwards.
962 for (cy = sr.tl.y - 1; cy >= r.tl.y; cy--) {
963 tr.setXYWH(sr.tl.x, cy, sr.width(), 1);
964 if (!checkSolidTile(tr, colourValue, pb))
965 break;
966 }
967 er->tl.y = cy + 1;
968
969 // ... downwards.
970 for (cy = sr.br.y; cy < r.br.y; cy++) {
971 tr.setXYWH(sr.tl.x, cy, sr.width(), 1);
972 if (!checkSolidTile(tr, colourValue, pb))
973 break;
974 }
975 er->br.y = cy;
976
977 // ... to the left.
978 for (cx = sr.tl.x - 1; cx >= r.tl.x; cx--) {
979 tr.setXYWH(cx, er->tl.y, 1, er->height());
980 if (!checkSolidTile(tr, colourValue, pb))
981 break;
982 }
983 er->tl.x = cx + 1;
984
985 // ... to the right.
986 for (cx = sr.br.x; cx < r.br.x; cx++) {
987 tr.setXYWH(cx, er->tl.y, 1, er->height());
988 if (!checkSolidTile(tr, colourValue, pb))
989 break;
990 }
991 er->br.x = cx;
992 }
993
preparePixelBuffer(const Rect & rect,const PixelBuffer * pb,bool convert)994 PixelBuffer* EncodeManager::preparePixelBuffer(const Rect& rect,
995 const PixelBuffer *pb,
996 bool convert)
997 {
998 const rdr::U8* buffer;
999 int stride;
1000
1001 // Do wo need to convert the data?
1002 if (convert && !conn->client.pf().equal(pb->getPF())) {
1003 convertedPixelBuffer.setPF(conn->client.pf());
1004 convertedPixelBuffer.setSize(rect.width(), rect.height());
1005
1006 buffer = pb->getBuffer(rect, &stride);
1007 convertedPixelBuffer.imageRect(pb->getPF(),
1008 convertedPixelBuffer.getRect(),
1009 buffer, stride);
1010
1011 return &convertedPixelBuffer;
1012 }
1013
1014 // Otherwise we still need to shift the coordinates. We have our own
1015 // abusive subclass of FullFramePixelBuffer for this.
1016
1017 buffer = pb->getBuffer(rect, &stride);
1018
1019 offsetPixelBuffer.update(pb->getPF(), rect.width(), rect.height(),
1020 buffer, stride);
1021
1022 return &offsetPixelBuffer;
1023 }
1024
analyseRect(const PixelBuffer * pb,struct RectInfo * info,int maxColours)1025 bool EncodeManager::analyseRect(const PixelBuffer *pb,
1026 struct RectInfo *info, int maxColours)
1027 {
1028 const rdr::U8* buffer;
1029 int stride;
1030
1031 buffer = pb->getBuffer(pb->getRect(), &stride);
1032
1033 switch (pb->getPF().bpp) {
1034 case 32:
1035 return analyseRect(pb->width(), pb->height(),
1036 (const rdr::U32*)buffer, stride,
1037 info, maxColours);
1038 case 16:
1039 return analyseRect(pb->width(), pb->height(),
1040 (const rdr::U16*)buffer, stride,
1041 info, maxColours);
1042 default:
1043 return analyseRect(pb->width(), pb->height(),
1044 (const rdr::U8*)buffer, stride,
1045 info, maxColours);
1046 }
1047 }
1048
update(const PixelFormat & pf,int width,int height,const rdr::U8 * data_,int stride_)1049 void EncodeManager::OffsetPixelBuffer::update(const PixelFormat& pf,
1050 int width, int height,
1051 const rdr::U8* data_,
1052 int stride_)
1053 {
1054 format = pf;
1055 // Forced cast. We never write anything though, so it should be safe.
1056 setBuffer(width, height, (rdr::U8*)data_, stride_);
1057 }
1058
getBufferRW(const Rect & r,int * stride)1059 rdr::U8* EncodeManager::OffsetPixelBuffer::getBufferRW(const Rect& r, int* stride)
1060 {
1061 throw rfb::Exception("Invalid write attempt to OffsetPixelBuffer");
1062 }
1063
1064 // Preprocessor generated, optimised methods
1065
1066 #define BPP 8
1067 #include "EncodeManagerBPP.cxx"
1068 #undef BPP
1069 #define BPP 16
1070 #include "EncodeManagerBPP.cxx"
1071 #undef BPP
1072 #define BPP 32
1073 #include "EncodeManagerBPP.cxx"
1074 #undef BPP
1075