blob: 954f29b1b8b5b7516af888bbfd9205042d8b25ba [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.
3 * Copyright 2014 Pierre Ossman for Cendio AB
4 *
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 */
20#include <rfb/EncodeManager.h>
21#include <rfb/Encoder.h>
22#include <rfb/Palette.h>
23#include <rfb/SConnection.h>
24#include <rfb/SMsgWriter.h>
25#include <rfb/UpdateTracker.h>
Pierre Ossman20dd2a92015-02-11 17:43:15 +010026#include <rfb/LogWriter.h>
Pierre Ossmanc0397262014-03-14 15:59:46 +010027
28#include <rfb/RawEncoder.h>
29#include <rfb/RREEncoder.h>
30#include <rfb/HextileEncoder.h>
31#include <rfb/ZRLEEncoder.h>
32#include <rfb/TightEncoder.h>
33#include <rfb/TightJPEGEncoder.h>
34
35using namespace rfb;
36
Pierre Ossman20dd2a92015-02-11 17:43:15 +010037static LogWriter vlog("EncodeManager");
38
Pierre Ossmanc0397262014-03-14 15:59:46 +010039// Split each rectangle into smaller ones no larger than this area,
40// and no wider than this width.
41static const int SubRectMaxArea = 65536;
42static const int SubRectMaxWidth = 2048;
43
44// The size in pixels of either side of each block tested when looking
45// for solid blocks.
46static const int SolidSearchBlock = 16;
47// Don't bother with blocks smaller than this
48static const int SolidBlockMinArea = 2048;
49
50namespace rfb {
51
52enum EncoderClass {
53 encoderRaw,
54 encoderRRE,
55 encoderHextile,
56 encoderTight,
57 encoderTightJPEG,
58 encoderZRLE,
59 encoderClassMax,
60};
61
62enum EncoderType {
63 encoderSolid,
64 encoderBitmap,
65 encoderBitmapRLE,
66 encoderIndexed,
67 encoderIndexedRLE,
68 encoderFullColour,
69 encoderTypeMax,
70};
71
72struct RectInfo {
73 int rleRuns;
74 Palette palette;
75};
76
77};
78
Pierre Ossman20dd2a92015-02-11 17:43:15 +010079static const char *encoderClassName(EncoderClass klass)
80{
81 switch (klass) {
82 case encoderRaw:
83 return "Raw";
84 case encoderRRE:
85 return "RRE";
86 case encoderHextile:
87 return "Hextile";
88 case encoderTight:
89 return "Tight";
90 case encoderTightJPEG:
91 return "Tight (JPEG)";
92 case encoderZRLE:
93 return "ZRLE";
Pierre Ossman620dd952015-03-03 16:28:54 +010094 case encoderClassMax:
95 break;
Pierre Ossman20dd2a92015-02-11 17:43:15 +010096 }
97
98 return "Unknown Encoder Class";
99}
100
101static const char *encoderTypeName(EncoderType type)
102{
103 switch (type) {
104 case encoderSolid:
105 return "Solid";
106 case encoderBitmap:
107 return "Bitmap";
108 case encoderBitmapRLE:
109 return "Bitmap RLE";
110 case encoderIndexed:
111 return "Indexed";
112 case encoderIndexedRLE:
113 return "Indexed RLE";
114 case encoderFullColour:
115 return "Full Colour";
Pierre Ossman620dd952015-03-03 16:28:54 +0100116 case encoderTypeMax:
117 break;
Pierre Ossman20dd2a92015-02-11 17:43:15 +0100118 }
119
120 return "Unknown Encoder Type";
121}
122
Pierre Ossmanc0397262014-03-14 15:59:46 +0100123EncodeManager::EncodeManager(SConnection* conn_) : conn(conn_)
124{
Pierre Ossman20dd2a92015-02-11 17:43:15 +0100125 StatsVector::iterator iter;
126
Pierre Ossmanc0397262014-03-14 15:59:46 +0100127 encoders.resize(encoderClassMax, NULL);
128 activeEncoders.resize(encoderTypeMax, encoderRaw);
129
130 encoders[encoderRaw] = new RawEncoder(conn);
131 encoders[encoderRRE] = new RREEncoder(conn);
132 encoders[encoderHextile] = new HextileEncoder(conn);
133 encoders[encoderTight] = new TightEncoder(conn);
134 encoders[encoderTightJPEG] = new TightJPEGEncoder(conn);
135 encoders[encoderZRLE] = new ZRLEEncoder(conn);
Pierre Ossman20dd2a92015-02-11 17:43:15 +0100136
137 updates = 0;
138 stats.resize(encoderClassMax);
139 for (iter = stats.begin();iter != stats.end();++iter) {
140 StatsVector::value_type::iterator iter2;
141 iter->resize(encoderTypeMax);
142 for (iter2 = iter->begin();iter2 != iter->end();++iter2)
143 memset(&*iter2, 0, sizeof(EncoderStats));
144 }
Pierre Ossmanc0397262014-03-14 15:59:46 +0100145}
146
147EncodeManager::~EncodeManager()
148{
149 std::vector<Encoder*>::iterator iter;
150
Pierre Ossman20dd2a92015-02-11 17:43:15 +0100151 logStats();
152
Pierre Ossmanc0397262014-03-14 15:59:46 +0100153 for (iter = encoders.begin();iter != encoders.end();iter++)
154 delete *iter;
155}
156
Pierre Ossman20dd2a92015-02-11 17:43:15 +0100157void EncodeManager::logStats()
158{
Pierre Ossman5c23b9e2015-03-03 16:26:03 +0100159 size_t i, j;
Pierre Ossman20dd2a92015-02-11 17:43:15 +0100160
161 unsigned rects;
162 unsigned long long pixels, bytes, equivalent;
163
164 double ratio;
165
166 rects = 0;
167 pixels = bytes = equivalent = 0;
168
169 vlog.info("Framebuffer updates: %u", updates);
170
171 for (i = 0;i < stats.size();i++) {
172 // Did this class do anything at all?
173 for (j = 0;j < stats[i].size();j++) {
174 if (stats[i][j].rects != 0)
175 break;
176 }
177 if (j == stats[i].size())
178 continue;
179
180 vlog.info(" %s:", encoderClassName((EncoderClass)i));
181
182 for (j = 0;j < stats[i].size();j++) {
183 if (stats[i][j].rects == 0)
184 continue;
185
186 rects += stats[i][j].rects;
187 pixels += stats[i][j].pixels;
188 bytes += stats[i][j].bytes;
189 equivalent += stats[i][j].equivalent;
190
191 ratio = (double)stats[i][j].equivalent / stats[i][j].bytes;
192
193 vlog.info(" %s: %u rects, %llu pixels",
194 encoderTypeName((EncoderType)j),
195 stats[i][j].rects, stats[i][j].pixels);
196 vlog.info(" %*s %llu bytes (%g ratio)",
197 strlen(encoderTypeName((EncoderType)j)), "",
198 stats[i][j].bytes, ratio);
199 }
200 }
201
202 ratio = (double)equivalent / bytes;
203
204 vlog.info(" Total: %u rects, %llu pixels", rects, pixels);
205 vlog.info(" %llu bytes (%g ratio)", bytes, ratio);
206}
207
Pierre Ossmanc0397262014-03-14 15:59:46 +0100208bool EncodeManager::supported(int encoding)
209{
210 switch (encoding) {
211 case encodingRaw:
212 case encodingRRE:
213 case encodingHextile:
214 case encodingZRLE:
215 case encodingTight:
216 return true;
217 default:
218 return false;
219 }
220}
221
222void EncodeManager::writeUpdate(const UpdateInfo& ui, const PixelBuffer* pb,
223 const RenderedCursor* renderedCursor)
224{
225 int nRects;
226 Region changed;
227
Pierre Ossman20dd2a92015-02-11 17:43:15 +0100228 updates++;
229
Pierre Ossmanc0397262014-03-14 15:59:46 +0100230 prepareEncoders();
231
232 if (conn->cp.supportsLastRect)
233 nRects = 0xFFFF;
234 else {
235 nRects = ui.copied.numRects();
236 nRects += computeNumRects(ui.changed);
237
238 if (renderedCursor != NULL)
239 nRects += 1;
240 }
241
242 conn->writer()->writeFramebufferUpdateStart(nRects);
243
244 writeCopyRects(ui);
245
246 /*
247 * We start by searching for solid rects, which are then removed
248 * from the changed region.
249 */
250 changed.copyFrom(ui.changed);
251
252 if (conn->cp.supportsLastRect)
253 writeSolidRects(&changed, pb);
254
255 writeRects(changed, pb);
256
257 if (renderedCursor != NULL) {
258 Rect renderedCursorRect;
259
260 renderedCursorRect = renderedCursor->getEffectiveRect();
261 writeSubRect(renderedCursorRect, renderedCursor);
262 }
263
264 conn->writer()->writeFramebufferUpdateEnd();
265}
266
267void EncodeManager::prepareEncoders()
268{
269 enum EncoderClass solid, bitmap, bitmapRLE;
270 enum EncoderClass indexed, indexedRLE, fullColour;
271
272 rdr::S32 preferred;
273
274 std::vector<int>::iterator iter;
275
276 solid = bitmap = bitmapRLE = encoderRaw;
277 indexed = indexedRLE = fullColour = encoderRaw;
278
279 // Try to respect the client's wishes
Pierre Ossman48700812014-09-17 17:11:56 +0200280 preferred = conn->getPreferredEncoding();
Pierre Ossmanc0397262014-03-14 15:59:46 +0100281 switch (preferred) {
282 case encodingRRE:
283 // Horrible for anything high frequency and/or lots of colours
284 bitmapRLE = indexedRLE = encoderRRE;
285 break;
286 case encodingHextile:
287 // Slightly less horrible
288 bitmapRLE = indexedRLE = fullColour = encoderHextile;
289 break;
290 case encodingTight:
291 if (encoders[encoderTightJPEG]->isSupported() &&
292 (conn->cp.pf().bpp >= 16))
293 fullColour = encoderTightJPEG;
294 else
295 fullColour = encoderTight;
296 indexed = indexedRLE = encoderTight;
297 bitmap = bitmapRLE = encoderTight;
298 break;
299 case encodingZRLE:
300 fullColour = encoderZRLE;
301 bitmapRLE = indexedRLE = encoderZRLE;
302 bitmap = indexed = encoderZRLE;
303 break;
304 }
305
306 // Any encoders still unassigned?
307
308 if (fullColour == encoderRaw) {
309 if (encoders[encoderTightJPEG]->isSupported() &&
310 (conn->cp.pf().bpp >= 16))
311 fullColour = encoderTightJPEG;
312 else if (encoders[encoderZRLE]->isSupported())
313 fullColour = encoderZRLE;
314 else if (encoders[encoderTight]->isSupported())
315 fullColour = encoderTight;
316 else if (encoders[encoderHextile]->isSupported())
317 fullColour = encoderHextile;
318 }
319
320 if (indexed == encoderRaw) {
321 if (encoders[encoderZRLE]->isSupported())
322 indexed = encoderZRLE;
323 else if (encoders[encoderTight]->isSupported())
324 indexed = encoderTight;
325 else if (encoders[encoderHextile]->isSupported())
326 indexed = encoderHextile;
327 }
328
329 if (indexedRLE == encoderRaw)
330 indexedRLE = indexed;
331
332 if (bitmap == encoderRaw)
333 bitmap = indexed;
334 if (bitmapRLE == encoderRaw)
335 bitmapRLE = bitmap;
336
337 if (solid == encoderRaw) {
338 if (encoders[encoderTight]->isSupported())
339 solid = encoderTight;
340 else if (encoders[encoderRRE]->isSupported())
341 solid = encoderRRE;
342 else if (encoders[encoderZRLE]->isSupported())
343 solid = encoderZRLE;
344 else if (encoders[encoderHextile]->isSupported())
345 solid = encoderHextile;
346 }
347
348 // JPEG is the only encoder that can reduce things to grayscale
349 if ((conn->cp.subsampling == subsampleGray) &&
350 encoders[encoderTightJPEG]->isSupported()) {
351 solid = bitmap = bitmapRLE = encoderTightJPEG;
352 indexed = indexedRLE = fullColour = encoderTightJPEG;
353 }
354
355 activeEncoders[encoderSolid] = solid;
356 activeEncoders[encoderBitmap] = bitmap;
357 activeEncoders[encoderBitmapRLE] = bitmapRLE;
358 activeEncoders[encoderIndexed] = indexed;
359 activeEncoders[encoderIndexedRLE] = indexedRLE;
360 activeEncoders[encoderFullColour] = fullColour;
361
362 for (iter = activeEncoders.begin(); iter != activeEncoders.end(); ++iter) {
363 Encoder *encoder;
364
365 encoder = encoders[*iter];
366
367 encoder->setCompressLevel(conn->cp.compressLevel);
368 encoder->setQualityLevel(conn->cp.qualityLevel);
369 encoder->setFineQualityLevel(conn->cp.fineQualityLevel,
370 conn->cp.subsampling);
371 }
372}
373
374int EncodeManager::computeNumRects(const Region& changed)
375{
376 int numRects;
377 std::vector<Rect> rects;
378 std::vector<Rect>::const_iterator rect;
379
380 numRects = 0;
381 changed.get_rects(&rects);
382 for (rect = rects.begin(); rect != rects.end(); ++rect) {
383 int w, h, sw, sh;
384
385 w = rect->width();
386 h = rect->height();
387
388 // No split necessary?
389 if (((w*h) < SubRectMaxArea) && (w < SubRectMaxWidth)) {
390 numRects += 1;
391 continue;
392 }
393
394 if (w <= SubRectMaxWidth)
395 sw = w;
396 else
397 sw = SubRectMaxWidth;
398
399 sh = SubRectMaxArea / sw;
400
401 // ceil(w/sw) * ceil(h/sh)
402 numRects += (((w - 1)/sw) + 1) * (((h - 1)/sh) + 1);
403 }
404
405 return numRects;
406}
407
Pierre Ossman20dd2a92015-02-11 17:43:15 +0100408Encoder *EncodeManager::startRect(const Rect& rect, int type)
409{
410 Encoder *encoder;
411 int klass, equiv;
412
413 activeType = type;
414 klass = activeEncoders[activeType];
415
416 beforeLength = conn->getOutStream()->length();
417
418 stats[klass][activeType].rects++;
419 stats[klass][activeType].pixels += rect.area();
420 equiv = 12 + rect.area() * conn->cp.pf().bpp/8;
421 stats[klass][activeType].equivalent += equiv;
422
423 encoder = encoders[klass];
424 conn->writer()->startRect(rect, encoder->encoding);
425
426 return encoder;
427}
428
429void EncodeManager::endRect()
430{
431 int klass;
432 int length;
433
434 conn->writer()->endRect();
435
436 length = conn->getOutStream()->length() - beforeLength;
437
438 klass = activeEncoders[activeType];
439 stats[klass][activeType].bytes += length;
440}
441
Pierre Ossmanc0397262014-03-14 15:59:46 +0100442void EncodeManager::writeCopyRects(const UpdateInfo& ui)
443{
444 std::vector<Rect> rects;
445 std::vector<Rect>::const_iterator rect;
446
447 ui.copied.get_rects(&rects, ui.copy_delta.x <= 0, ui.copy_delta.y <= 0);
448 for (rect = rects.begin(); rect != rects.end(); ++rect) {
449 conn->writer()->writeCopyRect(*rect, rect->tl.x - ui.copy_delta.x,
450 rect->tl.y - ui.copy_delta.y);
451 }
452}
453
454void EncodeManager::writeSolidRects(Region *changed, const PixelBuffer* pb)
455{
456 std::vector<Rect> rects;
457 std::vector<Rect>::const_iterator rect;
458
Pierre Ossmanc0397262014-03-14 15:59:46 +0100459 changed->get_rects(&rects);
Pierre Ossmaneef55162015-02-12 13:44:22 +0100460 for (rect = rects.begin(); rect != rects.end(); ++rect)
461 findSolidRect(*rect, changed, pb);
462}
Pierre Ossmanc0397262014-03-14 15:59:46 +0100463
Pierre Ossmaneef55162015-02-12 13:44:22 +0100464void EncodeManager::findSolidRect(const Rect& rect, Region *changed,
465 const PixelBuffer* pb)
466{
467 Rect sr;
468 int dx, dy, dw, dh;
Pierre Ossmanc0397262014-03-14 15:59:46 +0100469
Pierre Ossmaneef55162015-02-12 13:44:22 +0100470 // We start by finding a solid 16x16 block
471 for (dy = rect.tl.y; dy < rect.br.y; dy += SolidSearchBlock) {
Pierre Ossmanc0397262014-03-14 15:59:46 +0100472
Pierre Ossmaneef55162015-02-12 13:44:22 +0100473 dh = SolidSearchBlock;
474 if (dy + dh > rect.br.y)
475 dh = rect.br.y - dy;
Pierre Ossmanc0397262014-03-14 15:59:46 +0100476
Pierre Ossmaneef55162015-02-12 13:44:22 +0100477 for (dx = rect.tl.x; dx < rect.br.x; dx += SolidSearchBlock) {
478 // We define it like this to guarantee alignment
479 rdr::U32 _buffer;
480 rdr::U8* colourValue = (rdr::U8*)&_buffer;
Pierre Ossmanc0397262014-03-14 15:59:46 +0100481
Pierre Ossmaneef55162015-02-12 13:44:22 +0100482 dw = SolidSearchBlock;
483 if (dx + dw > rect.br.x)
484 dw = rect.br.x - dx;
Pierre Ossmanc0397262014-03-14 15:59:46 +0100485
Pierre Ossmaneef55162015-02-12 13:44:22 +0100486 pb->getImage(colourValue, Rect(dx, dy, dx+1, dy+1));
Pierre Ossmanc0397262014-03-14 15:59:46 +0100487
Pierre Ossmaneef55162015-02-12 13:44:22 +0100488 sr.setXYWH(dx, dy, dw, dh);
489 if (checkSolidTile(sr, colourValue, pb)) {
490 Rect erb, erp;
Pierre Ossmanc0397262014-03-14 15:59:46 +0100491
Pierre Ossmaneef55162015-02-12 13:44:22 +0100492 Encoder *encoder;
Pierre Ossmanc0397262014-03-14 15:59:46 +0100493
Pierre Ossmaneef55162015-02-12 13:44:22 +0100494 // We then try extending the area by adding more blocks
495 // in both directions and pick the combination that gives
496 // the largest area.
497 sr.setXYWH(dx, dy, rect.br.x - dx, rect.br.y - dy);
498 extendSolidAreaByBlock(sr, colourValue, pb, &erb);
Pierre Ossmanc0397262014-03-14 15:59:46 +0100499
Pierre Ossmaneef55162015-02-12 13:44:22 +0100500 // Did we end up getting the entire rectangle?
501 if (erb.equals(rect))
502 erp = erb;
503 else {
504 // Don't bother with sending tiny rectangles
505 if (erb.area() < SolidBlockMinArea)
506 continue;
Pierre Ossmanc0397262014-03-14 15:59:46 +0100507
Pierre Ossmaneef55162015-02-12 13:44:22 +0100508 // Extend the area again, but this time one pixel
509 // row/column at a time.
510 extendSolidAreaByPixel(rect, erb, colourValue, pb, &erp);
Pierre Ossmanc0397262014-03-14 15:59:46 +0100511 }
Pierre Ossmanc0397262014-03-14 15:59:46 +0100512
Pierre Ossmaneef55162015-02-12 13:44:22 +0100513 // Send solid-color rectangle.
514 encoder = startRect(erp, encoderSolid);
515 if (encoder->flags & EncoderUseNativePF) {
516 encoder->writeSolidRect(erp.width(), erp.height(),
517 pb->getPF(), colourValue);
518 } else {
519 rdr::U32 _buffer2;
520 rdr::U8* converted = (rdr::U8*)&_buffer2;
521
522 conn->cp.pf().bufferFromBuffer(converted, pb->getPF(),
523 colourValue, 1);
524
525 encoder->writeSolidRect(erp.width(), erp.height(),
526 conn->cp.pf(), converted);
527 }
528 endRect();
529
530 changed->assign_subtract(Region(erp));
531
532 // Search remaining areas by recursion
533 // FIXME: Is this the best way to divide things up?
534
535 // Left? (Note that we've already searched a SolidSearchBlock
536 // pixels high strip here)
537 if ((erp.tl.x != rect.tl.x) && (erp.height() > SolidSearchBlock)) {
538 sr.setXYWH(rect.tl.x, erp.tl.y + SolidSearchBlock,
539 erp.tl.x - rect.tl.x, erp.height() - SolidSearchBlock);
540 findSolidRect(sr, changed, pb);
541 }
542
543 // Right?
544 if (erp.br.x != rect.br.x) {
545 sr.setXYWH(erp.br.x, erp.tl.y, rect.br.x - erp.br.x, erp.height());
546 findSolidRect(sr, changed, pb);
547 }
548
549 // Below?
550 if (erp.br.y != rect.br.y) {
551 sr.setXYWH(rect.tl.x, erp.br.y, rect.width(), rect.br.y - erp.br.y);
552 findSolidRect(sr, changed, pb);
553 }
554
555 return;
556 }
Pierre Ossmanc0397262014-03-14 15:59:46 +0100557 }
558 }
559}
560
561void EncodeManager::writeRects(const Region& changed, const PixelBuffer* pb)
562{
563 std::vector<Rect> rects;
564 std::vector<Rect>::const_iterator rect;
565
566 changed.get_rects(&rects);
567 for (rect = rects.begin(); rect != rects.end(); ++rect) {
568 int w, h, sw, sh;
569 Rect sr;
570
571 w = rect->width();
572 h = rect->height();
573
574 // No split necessary?
575 if (((w*h) < SubRectMaxArea) && (w < SubRectMaxWidth)) {
576 writeSubRect(*rect, pb);
577 continue;
578 }
579
580 if (w <= SubRectMaxWidth)
581 sw = w;
582 else
583 sw = SubRectMaxWidth;
584
585 sh = SubRectMaxArea / sw;
586
587 for (sr.tl.y = rect->tl.y; sr.tl.y < rect->br.y; sr.tl.y += sh) {
588 sr.br.y = sr.tl.y + sh;
589 if (sr.br.y > rect->br.y)
590 sr.br.y = rect->br.y;
591
592 for (sr.tl.x = rect->tl.x; sr.tl.x < rect->br.x; sr.tl.x += sw) {
593 sr.br.x = sr.tl.x + sw;
594 if (sr.br.x > rect->br.x)
595 sr.br.x = rect->br.x;
596
597 writeSubRect(sr, pb);
598 }
599 }
600 }
601}
602
603void EncodeManager::writeSubRect(const Rect& rect, const PixelBuffer *pb)
604{
605 PixelBuffer *ppb;
606
607 Encoder *encoder;
608
609 struct RectInfo info;
Pierre Ossman5c23b9e2015-03-03 16:26:03 +0100610 unsigned int divisor, maxColours;
Pierre Ossmanc0397262014-03-14 15:59:46 +0100611
612 bool useRLE;
613 EncoderType type;
614
615 // FIXME: This is roughly the algorithm previously used by the Tight
616 // encoder. It seems a bit backwards though, that higher
617 // compression setting means spending less effort in building
618 // a palette. It might be that they figured the increase in
619 // zlib setting compensated for the loss.
620 if (conn->cp.compressLevel == -1)
621 divisor = 2 * 8;
622 else
623 divisor = conn->cp.compressLevel * 8;
624 if (divisor < 4)
625 divisor = 4;
626
627 maxColours = rect.area()/divisor;
628
629 // Special exception inherited from the Tight encoder
630 if (activeEncoders[encoderFullColour] == encoderTightJPEG) {
Pierre Ossman4daa7b12015-02-12 14:57:05 +0100631 if ((conn->cp.compressLevel != -1) && (conn->cp.compressLevel < 2))
Pierre Ossmanc0397262014-03-14 15:59:46 +0100632 maxColours = 24;
633 else
634 maxColours = 96;
635 }
636
637 if (maxColours < 2)
638 maxColours = 2;
639
640 encoder = encoders[activeEncoders[encoderIndexedRLE]];
641 if (maxColours > encoder->maxPaletteSize)
642 maxColours = encoder->maxPaletteSize;
643 encoder = encoders[activeEncoders[encoderIndexed]];
644 if (maxColours > encoder->maxPaletteSize)
645 maxColours = encoder->maxPaletteSize;
646
647 ppb = preparePixelBuffer(rect, pb, true);
648
649 if (!analyseRect(ppb, &info, maxColours))
650 info.palette.clear();
651
652 // Different encoders might have different RLE overhead, but
653 // here we do a guess at RLE being the better choice if reduces
654 // the pixel count by 50%.
655 useRLE = info.rleRuns <= (rect.area() * 2);
656
657 switch (info.palette.size()) {
658 case 0:
659 type = encoderFullColour;
660 break;
661 case 1:
662 type = encoderSolid;
663 break;
664 case 2:
665 if (useRLE)
666 type = encoderBitmapRLE;
667 else
668 type = encoderBitmap;
669 break;
670 default:
671 if (useRLE)
672 type = encoderIndexedRLE;
673 else
674 type = encoderIndexed;
675 }
676
Pierre Ossman20dd2a92015-02-11 17:43:15 +0100677 encoder = startRect(rect, type);
Pierre Ossmanc0397262014-03-14 15:59:46 +0100678
679 if (encoder->flags & EncoderUseNativePF)
680 ppb = preparePixelBuffer(rect, pb, false);
681
Pierre Ossmanc0397262014-03-14 15:59:46 +0100682 encoder->writeRect(ppb, info.palette);
Pierre Ossman20dd2a92015-02-11 17:43:15 +0100683
684 endRect();
Pierre Ossmanc0397262014-03-14 15:59:46 +0100685}
686
687bool EncodeManager::checkSolidTile(const Rect& r, const rdr::U8* colourValue,
688 const PixelBuffer *pb)
689{
690 switch (pb->getPF().bpp) {
691 case 32:
692 return checkSolidTile(r, *(const rdr::U32*)colourValue, pb);
693 case 16:
694 return checkSolidTile(r, *(const rdr::U16*)colourValue, pb);
695 default:
696 return checkSolidTile(r, *(const rdr::U8*)colourValue, pb);
697 }
698}
699
700void EncodeManager::extendSolidAreaByBlock(const Rect& r,
701 const rdr::U8* colourValue,
702 const PixelBuffer *pb, Rect* er)
703{
704 int dx, dy, dw, dh;
705 int w_prev;
706 Rect sr;
707 int w_best = 0, h_best = 0;
708
709 w_prev = r.width();
710
711 // We search width first, back off when we hit a different colour,
712 // and restart with a larger height. We keep track of the
713 // width/height combination that gives us the largest area.
714 for (dy = r.tl.y; dy < r.br.y; dy += SolidSearchBlock) {
715
716 dh = SolidSearchBlock;
717 if (dy + dh > r.br.y)
718 dh = r.br.y - dy;
719
720 // We test one block here outside the x loop in order to break
721 // the y loop right away.
722 dw = SolidSearchBlock;
723 if (dw > w_prev)
724 dw = w_prev;
725
726 sr.setXYWH(r.tl.x, dy, dw, dh);
727 if (!checkSolidTile(sr, colourValue, pb))
728 break;
729
730 for (dx = r.tl.x + dw; dx < r.tl.x + w_prev;) {
731
732 dw = SolidSearchBlock;
733 if (dx + dw > r.tl.x + w_prev)
734 dw = r.tl.x + w_prev - dx;
735
736 sr.setXYWH(dx, dy, dw, dh);
737 if (!checkSolidTile(sr, colourValue, pb))
738 break;
739
740 dx += dw;
741 }
742
743 w_prev = dx - r.tl.x;
744 if (w_prev * (dy + dh - r.tl.y) > w_best * h_best) {
745 w_best = w_prev;
746 h_best = dy + dh - r.tl.y;
747 }
748 }
749
750 er->tl.x = r.tl.x;
751 er->tl.y = r.tl.y;
752 er->br.x = er->tl.x + w_best;
753 er->br.y = er->tl.y + h_best;
754}
755
756void EncodeManager::extendSolidAreaByPixel(const Rect& r, const Rect& sr,
757 const rdr::U8* colourValue,
758 const PixelBuffer *pb, Rect* er)
759{
760 int cx, cy;
761 Rect tr;
762
763 // Try to extend the area upwards.
764 for (cy = sr.tl.y - 1; cy >= r.tl.y; cy--) {
765 tr.setXYWH(sr.tl.x, cy, sr.width(), 1);
766 if (!checkSolidTile(tr, colourValue, pb))
767 break;
768 }
769 er->tl.y = cy + 1;
770
771 // ... downwards.
772 for (cy = sr.br.y; cy < r.br.y; cy++) {
773 tr.setXYWH(sr.tl.x, cy, sr.width(), 1);
774 if (!checkSolidTile(tr, colourValue, pb))
775 break;
776 }
777 er->br.y = cy;
778
779 // ... to the left.
780 for (cx = sr.tl.x - 1; cx >= r.tl.x; cx--) {
781 tr.setXYWH(cx, er->tl.y, 1, er->height());
782 if (!checkSolidTile(tr, colourValue, pb))
783 break;
784 }
785 er->tl.x = cx + 1;
786
787 // ... to the right.
788 for (cx = sr.br.x; cx < r.br.x; cx++) {
789 tr.setXYWH(cx, er->tl.y, 1, er->height());
790 if (!checkSolidTile(tr, colourValue, pb))
791 break;
792 }
793 er->br.x = cx;
794}
795
796PixelBuffer* EncodeManager::preparePixelBuffer(const Rect& rect,
797 const PixelBuffer *pb,
798 bool convert)
799{
800 const rdr::U8* buffer;
801 int stride;
802
803 // Do wo need to convert the data?
804 if (convert && !conn->cp.pf().equal(pb->getPF())) {
805 convertedPixelBuffer.setPF(conn->cp.pf());
806 convertedPixelBuffer.setSize(rect.width(), rect.height());
807
808 buffer = pb->getBuffer(rect, &stride);
809 convertedPixelBuffer.imageRect(pb->getPF(),
810 convertedPixelBuffer.getRect(),
811 buffer, stride);
812
813 return &convertedPixelBuffer;
814 }
815
816 // Otherwise we still need to shift the coordinates. We have our own
817 // abusive subclass of FullFramePixelBuffer for this.
818
819 buffer = pb->getBuffer(rect, &stride);
820
821 offsetPixelBuffer.update(pb->getPF(), rect.width(), rect.height(),
822 buffer, stride);
823
824 return &offsetPixelBuffer;
825}
826
827bool EncodeManager::analyseRect(const PixelBuffer *pb,
828 struct RectInfo *info, int maxColours)
829{
830 const rdr::U8* buffer;
831 int stride;
832
833 buffer = pb->getBuffer(pb->getRect(), &stride);
834
835 switch (pb->getPF().bpp) {
836 case 32:
837 return analyseRect(pb->width(), pb->height(),
838 (const rdr::U32*)buffer, stride,
839 info, maxColours);
840 case 16:
841 return analyseRect(pb->width(), pb->height(),
842 (const rdr::U16*)buffer, stride,
843 info, maxColours);
844 default:
845 return analyseRect(pb->width(), pb->height(),
846 (const rdr::U8*)buffer, stride,
847 info, maxColours);
848 }
849}
850
851void EncodeManager::OffsetPixelBuffer::update(const PixelFormat& pf,
852 int width, int height,
853 const rdr::U8* data_,
854 int stride_)
855{
856 format = pf;
857 width_ = width;
858 height_ = height;
859 // Forced cast. We never write anything though, so it should be safe.
860 data = (rdr::U8*)data_;
861 stride = stride_;
862}
863
864// Preprocessor generated, optimised methods
865
866#define BPP 8
867#include "EncodeManagerBPP.cxx"
868#undef BPP
869#define BPP 16
870#include "EncodeManagerBPP.cxx"
871#undef BPP
872#define BPP 32
873#include "EncodeManagerBPP.cxx"
874#undef BPP