blob: 9122cd75a36ddcc0156e64e28ec923c86afb7df1 [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
Pierre Ossman64624342015-03-03 16:30:13 +0100166 char a[1024], b[1024];
167
Pierre Ossman20dd2a92015-02-11 17:43:15 +0100168 rects = 0;
169 pixels = bytes = equivalent = 0;
170
171 vlog.info("Framebuffer updates: %u", updates);
172
173 for (i = 0;i < stats.size();i++) {
174 // Did this class do anything at all?
175 for (j = 0;j < stats[i].size();j++) {
176 if (stats[i][j].rects != 0)
177 break;
178 }
179 if (j == stats[i].size())
180 continue;
181
182 vlog.info(" %s:", encoderClassName((EncoderClass)i));
183
184 for (j = 0;j < stats[i].size();j++) {
185 if (stats[i][j].rects == 0)
186 continue;
187
188 rects += stats[i][j].rects;
189 pixels += stats[i][j].pixels;
190 bytes += stats[i][j].bytes;
191 equivalent += stats[i][j].equivalent;
192
193 ratio = (double)stats[i][j].equivalent / stats[i][j].bytes;
194
Pierre Ossman64624342015-03-03 16:30:13 +0100195 siPrefix(stats[i][j].rects, "rects", a, sizeof(a));
196 siPrefix(stats[i][j].pixels, "pixels", b, sizeof(b));
197 vlog.info(" %s: %s, %s", encoderTypeName((EncoderType)j), a, b);
198 iecPrefix(stats[i][j].bytes, "B", a, sizeof(a));
199 vlog.info(" %*s %s (1:%g ratio)",
200 (int)strlen(encoderTypeName((EncoderType)j)), "",
201 a, ratio);
Pierre Ossman20dd2a92015-02-11 17:43:15 +0100202 }
203 }
204
205 ratio = (double)equivalent / bytes;
206
Pierre Ossman64624342015-03-03 16:30:13 +0100207 siPrefix(rects, "rects", a, sizeof(a));
208 siPrefix(pixels, "pixels", b, sizeof(b));
209 vlog.info(" Total: %s, %s", a, b);
210 iecPrefix(bytes, "B", a, sizeof(a));
211 vlog.info(" %s (1:%g ratio)", a, ratio);
Pierre Ossman20dd2a92015-02-11 17:43:15 +0100212}
213
Pierre Ossmanc0397262014-03-14 15:59:46 +0100214bool EncodeManager::supported(int encoding)
215{
216 switch (encoding) {
217 case encodingRaw:
218 case encodingRRE:
219 case encodingHextile:
220 case encodingZRLE:
221 case encodingTight:
222 return true;
223 default:
224 return false;
225 }
226}
227
228void EncodeManager::writeUpdate(const UpdateInfo& ui, const PixelBuffer* pb,
229 const RenderedCursor* renderedCursor)
230{
231 int nRects;
232 Region changed;
233
Pierre Ossman20dd2a92015-02-11 17:43:15 +0100234 updates++;
235
Pierre Ossmanc0397262014-03-14 15:59:46 +0100236 prepareEncoders();
237
238 if (conn->cp.supportsLastRect)
239 nRects = 0xFFFF;
240 else {
241 nRects = ui.copied.numRects();
242 nRects += computeNumRects(ui.changed);
243
244 if (renderedCursor != NULL)
245 nRects += 1;
246 }
247
248 conn->writer()->writeFramebufferUpdateStart(nRects);
249
250 writeCopyRects(ui);
251
252 /*
253 * We start by searching for solid rects, which are then removed
254 * from the changed region.
255 */
256 changed.copyFrom(ui.changed);
257
258 if (conn->cp.supportsLastRect)
259 writeSolidRects(&changed, pb);
260
261 writeRects(changed, pb);
262
263 if (renderedCursor != NULL) {
264 Rect renderedCursorRect;
265
266 renderedCursorRect = renderedCursor->getEffectiveRect();
267 writeSubRect(renderedCursorRect, renderedCursor);
268 }
269
270 conn->writer()->writeFramebufferUpdateEnd();
271}
272
273void EncodeManager::prepareEncoders()
274{
275 enum EncoderClass solid, bitmap, bitmapRLE;
276 enum EncoderClass indexed, indexedRLE, fullColour;
277
278 rdr::S32 preferred;
279
280 std::vector<int>::iterator iter;
281
282 solid = bitmap = bitmapRLE = encoderRaw;
283 indexed = indexedRLE = fullColour = encoderRaw;
284
285 // Try to respect the client's wishes
Pierre Ossman48700812014-09-17 17:11:56 +0200286 preferred = conn->getPreferredEncoding();
Pierre Ossmanc0397262014-03-14 15:59:46 +0100287 switch (preferred) {
288 case encodingRRE:
289 // Horrible for anything high frequency and/or lots of colours
290 bitmapRLE = indexedRLE = encoderRRE;
291 break;
292 case encodingHextile:
293 // Slightly less horrible
294 bitmapRLE = indexedRLE = fullColour = encoderHextile;
295 break;
296 case encodingTight:
297 if (encoders[encoderTightJPEG]->isSupported() &&
298 (conn->cp.pf().bpp >= 16))
299 fullColour = encoderTightJPEG;
300 else
301 fullColour = encoderTight;
302 indexed = indexedRLE = encoderTight;
303 bitmap = bitmapRLE = encoderTight;
304 break;
305 case encodingZRLE:
306 fullColour = encoderZRLE;
307 bitmapRLE = indexedRLE = encoderZRLE;
308 bitmap = indexed = encoderZRLE;
309 break;
310 }
311
312 // Any encoders still unassigned?
313
314 if (fullColour == encoderRaw) {
315 if (encoders[encoderTightJPEG]->isSupported() &&
316 (conn->cp.pf().bpp >= 16))
317 fullColour = encoderTightJPEG;
318 else if (encoders[encoderZRLE]->isSupported())
319 fullColour = encoderZRLE;
320 else if (encoders[encoderTight]->isSupported())
321 fullColour = encoderTight;
322 else if (encoders[encoderHextile]->isSupported())
323 fullColour = encoderHextile;
324 }
325
326 if (indexed == encoderRaw) {
327 if (encoders[encoderZRLE]->isSupported())
328 indexed = encoderZRLE;
329 else if (encoders[encoderTight]->isSupported())
330 indexed = encoderTight;
331 else if (encoders[encoderHextile]->isSupported())
332 indexed = encoderHextile;
333 }
334
335 if (indexedRLE == encoderRaw)
336 indexedRLE = indexed;
337
338 if (bitmap == encoderRaw)
339 bitmap = indexed;
340 if (bitmapRLE == encoderRaw)
341 bitmapRLE = bitmap;
342
343 if (solid == encoderRaw) {
344 if (encoders[encoderTight]->isSupported())
345 solid = encoderTight;
346 else if (encoders[encoderRRE]->isSupported())
347 solid = encoderRRE;
348 else if (encoders[encoderZRLE]->isSupported())
349 solid = encoderZRLE;
350 else if (encoders[encoderHextile]->isSupported())
351 solid = encoderHextile;
352 }
353
354 // JPEG is the only encoder that can reduce things to grayscale
355 if ((conn->cp.subsampling == subsampleGray) &&
356 encoders[encoderTightJPEG]->isSupported()) {
357 solid = bitmap = bitmapRLE = encoderTightJPEG;
358 indexed = indexedRLE = fullColour = encoderTightJPEG;
359 }
360
361 activeEncoders[encoderSolid] = solid;
362 activeEncoders[encoderBitmap] = bitmap;
363 activeEncoders[encoderBitmapRLE] = bitmapRLE;
364 activeEncoders[encoderIndexed] = indexed;
365 activeEncoders[encoderIndexedRLE] = indexedRLE;
366 activeEncoders[encoderFullColour] = fullColour;
367
368 for (iter = activeEncoders.begin(); iter != activeEncoders.end(); ++iter) {
369 Encoder *encoder;
370
371 encoder = encoders[*iter];
372
373 encoder->setCompressLevel(conn->cp.compressLevel);
374 encoder->setQualityLevel(conn->cp.qualityLevel);
375 encoder->setFineQualityLevel(conn->cp.fineQualityLevel,
376 conn->cp.subsampling);
377 }
378}
379
380int EncodeManager::computeNumRects(const Region& changed)
381{
382 int numRects;
383 std::vector<Rect> rects;
384 std::vector<Rect>::const_iterator rect;
385
386 numRects = 0;
387 changed.get_rects(&rects);
388 for (rect = rects.begin(); rect != rects.end(); ++rect) {
389 int w, h, sw, sh;
390
391 w = rect->width();
392 h = rect->height();
393
394 // No split necessary?
395 if (((w*h) < SubRectMaxArea) && (w < SubRectMaxWidth)) {
396 numRects += 1;
397 continue;
398 }
399
400 if (w <= SubRectMaxWidth)
401 sw = w;
402 else
403 sw = SubRectMaxWidth;
404
405 sh = SubRectMaxArea / sw;
406
407 // ceil(w/sw) * ceil(h/sh)
408 numRects += (((w - 1)/sw) + 1) * (((h - 1)/sh) + 1);
409 }
410
411 return numRects;
412}
413
Pierre Ossman20dd2a92015-02-11 17:43:15 +0100414Encoder *EncodeManager::startRect(const Rect& rect, int type)
415{
416 Encoder *encoder;
417 int klass, equiv;
418
419 activeType = type;
420 klass = activeEncoders[activeType];
421
422 beforeLength = conn->getOutStream()->length();
423
424 stats[klass][activeType].rects++;
425 stats[klass][activeType].pixels += rect.area();
426 equiv = 12 + rect.area() * conn->cp.pf().bpp/8;
427 stats[klass][activeType].equivalent += equiv;
428
429 encoder = encoders[klass];
430 conn->writer()->startRect(rect, encoder->encoding);
431
432 return encoder;
433}
434
435void EncodeManager::endRect()
436{
437 int klass;
438 int length;
439
440 conn->writer()->endRect();
441
442 length = conn->getOutStream()->length() - beforeLength;
443
444 klass = activeEncoders[activeType];
445 stats[klass][activeType].bytes += length;
446}
447
Pierre Ossmanc0397262014-03-14 15:59:46 +0100448void EncodeManager::writeCopyRects(const UpdateInfo& ui)
449{
450 std::vector<Rect> rects;
451 std::vector<Rect>::const_iterator rect;
452
453 ui.copied.get_rects(&rects, ui.copy_delta.x <= 0, ui.copy_delta.y <= 0);
454 for (rect = rects.begin(); rect != rects.end(); ++rect) {
455 conn->writer()->writeCopyRect(*rect, rect->tl.x - ui.copy_delta.x,
456 rect->tl.y - ui.copy_delta.y);
457 }
458}
459
460void EncodeManager::writeSolidRects(Region *changed, const PixelBuffer* pb)
461{
462 std::vector<Rect> rects;
463 std::vector<Rect>::const_iterator rect;
464
Pierre Ossmanc0397262014-03-14 15:59:46 +0100465 changed->get_rects(&rects);
Pierre Ossmaneef55162015-02-12 13:44:22 +0100466 for (rect = rects.begin(); rect != rects.end(); ++rect)
467 findSolidRect(*rect, changed, pb);
468}
Pierre Ossmanc0397262014-03-14 15:59:46 +0100469
Pierre Ossmaneef55162015-02-12 13:44:22 +0100470void EncodeManager::findSolidRect(const Rect& rect, Region *changed,
471 const PixelBuffer* pb)
472{
473 Rect sr;
474 int dx, dy, dw, dh;
Pierre Ossmanc0397262014-03-14 15:59:46 +0100475
Pierre Ossmaneef55162015-02-12 13:44:22 +0100476 // We start by finding a solid 16x16 block
477 for (dy = rect.tl.y; dy < rect.br.y; dy += SolidSearchBlock) {
Pierre Ossmanc0397262014-03-14 15:59:46 +0100478
Pierre Ossmaneef55162015-02-12 13:44:22 +0100479 dh = SolidSearchBlock;
480 if (dy + dh > rect.br.y)
481 dh = rect.br.y - dy;
Pierre Ossmanc0397262014-03-14 15:59:46 +0100482
Pierre Ossmaneef55162015-02-12 13:44:22 +0100483 for (dx = rect.tl.x; dx < rect.br.x; dx += SolidSearchBlock) {
484 // We define it like this to guarantee alignment
485 rdr::U32 _buffer;
486 rdr::U8* colourValue = (rdr::U8*)&_buffer;
Pierre Ossmanc0397262014-03-14 15:59:46 +0100487
Pierre Ossmaneef55162015-02-12 13:44:22 +0100488 dw = SolidSearchBlock;
489 if (dx + dw > rect.br.x)
490 dw = rect.br.x - dx;
Pierre Ossmanc0397262014-03-14 15:59:46 +0100491
Pierre Ossmaneef55162015-02-12 13:44:22 +0100492 pb->getImage(colourValue, Rect(dx, dy, dx+1, dy+1));
Pierre Ossmanc0397262014-03-14 15:59:46 +0100493
Pierre Ossmaneef55162015-02-12 13:44:22 +0100494 sr.setXYWH(dx, dy, dw, dh);
495 if (checkSolidTile(sr, colourValue, pb)) {
496 Rect erb, erp;
Pierre Ossmanc0397262014-03-14 15:59:46 +0100497
Pierre Ossmaneef55162015-02-12 13:44:22 +0100498 Encoder *encoder;
Pierre Ossmanc0397262014-03-14 15:59:46 +0100499
Pierre Ossmaneef55162015-02-12 13:44:22 +0100500 // We then try extending the area by adding more blocks
501 // in both directions and pick the combination that gives
502 // the largest area.
503 sr.setXYWH(dx, dy, rect.br.x - dx, rect.br.y - dy);
504 extendSolidAreaByBlock(sr, colourValue, pb, &erb);
Pierre Ossmanc0397262014-03-14 15:59:46 +0100505
Pierre Ossmaneef55162015-02-12 13:44:22 +0100506 // Did we end up getting the entire rectangle?
507 if (erb.equals(rect))
508 erp = erb;
509 else {
510 // Don't bother with sending tiny rectangles
511 if (erb.area() < SolidBlockMinArea)
512 continue;
Pierre Ossmanc0397262014-03-14 15:59:46 +0100513
Pierre Ossmaneef55162015-02-12 13:44:22 +0100514 // Extend the area again, but this time one pixel
515 // row/column at a time.
516 extendSolidAreaByPixel(rect, erb, colourValue, pb, &erp);
Pierre Ossmanc0397262014-03-14 15:59:46 +0100517 }
Pierre Ossmanc0397262014-03-14 15:59:46 +0100518
Pierre Ossmaneef55162015-02-12 13:44:22 +0100519 // Send solid-color rectangle.
520 encoder = startRect(erp, encoderSolid);
521 if (encoder->flags & EncoderUseNativePF) {
522 encoder->writeSolidRect(erp.width(), erp.height(),
523 pb->getPF(), colourValue);
524 } else {
525 rdr::U32 _buffer2;
526 rdr::U8* converted = (rdr::U8*)&_buffer2;
527
528 conn->cp.pf().bufferFromBuffer(converted, pb->getPF(),
529 colourValue, 1);
530
531 encoder->writeSolidRect(erp.width(), erp.height(),
532 conn->cp.pf(), converted);
533 }
534 endRect();
535
536 changed->assign_subtract(Region(erp));
537
538 // Search remaining areas by recursion
539 // FIXME: Is this the best way to divide things up?
540
541 // Left? (Note that we've already searched a SolidSearchBlock
542 // pixels high strip here)
543 if ((erp.tl.x != rect.tl.x) && (erp.height() > SolidSearchBlock)) {
544 sr.setXYWH(rect.tl.x, erp.tl.y + SolidSearchBlock,
545 erp.tl.x - rect.tl.x, erp.height() - SolidSearchBlock);
546 findSolidRect(sr, changed, pb);
547 }
548
549 // Right?
550 if (erp.br.x != rect.br.x) {
551 sr.setXYWH(erp.br.x, erp.tl.y, rect.br.x - erp.br.x, erp.height());
552 findSolidRect(sr, changed, pb);
553 }
554
555 // Below?
556 if (erp.br.y != rect.br.y) {
557 sr.setXYWH(rect.tl.x, erp.br.y, rect.width(), rect.br.y - erp.br.y);
558 findSolidRect(sr, changed, pb);
559 }
560
561 return;
562 }
Pierre Ossmanc0397262014-03-14 15:59:46 +0100563 }
564 }
565}
566
567void EncodeManager::writeRects(const Region& changed, const PixelBuffer* pb)
568{
569 std::vector<Rect> rects;
570 std::vector<Rect>::const_iterator rect;
571
572 changed.get_rects(&rects);
573 for (rect = rects.begin(); rect != rects.end(); ++rect) {
574 int w, h, sw, sh;
575 Rect sr;
576
577 w = rect->width();
578 h = rect->height();
579
580 // No split necessary?
581 if (((w*h) < SubRectMaxArea) && (w < SubRectMaxWidth)) {
582 writeSubRect(*rect, pb);
583 continue;
584 }
585
586 if (w <= SubRectMaxWidth)
587 sw = w;
588 else
589 sw = SubRectMaxWidth;
590
591 sh = SubRectMaxArea / sw;
592
593 for (sr.tl.y = rect->tl.y; sr.tl.y < rect->br.y; sr.tl.y += sh) {
594 sr.br.y = sr.tl.y + sh;
595 if (sr.br.y > rect->br.y)
596 sr.br.y = rect->br.y;
597
598 for (sr.tl.x = rect->tl.x; sr.tl.x < rect->br.x; sr.tl.x += sw) {
599 sr.br.x = sr.tl.x + sw;
600 if (sr.br.x > rect->br.x)
601 sr.br.x = rect->br.x;
602
603 writeSubRect(sr, pb);
604 }
605 }
606 }
607}
608
609void EncodeManager::writeSubRect(const Rect& rect, const PixelBuffer *pb)
610{
611 PixelBuffer *ppb;
612
613 Encoder *encoder;
614
615 struct RectInfo info;
Pierre Ossman5c23b9e2015-03-03 16:26:03 +0100616 unsigned int divisor, maxColours;
Pierre Ossmanc0397262014-03-14 15:59:46 +0100617
618 bool useRLE;
619 EncoderType type;
620
621 // FIXME: This is roughly the algorithm previously used by the Tight
622 // encoder. It seems a bit backwards though, that higher
623 // compression setting means spending less effort in building
624 // a palette. It might be that they figured the increase in
625 // zlib setting compensated for the loss.
626 if (conn->cp.compressLevel == -1)
627 divisor = 2 * 8;
628 else
629 divisor = conn->cp.compressLevel * 8;
630 if (divisor < 4)
631 divisor = 4;
632
633 maxColours = rect.area()/divisor;
634
635 // Special exception inherited from the Tight encoder
636 if (activeEncoders[encoderFullColour] == encoderTightJPEG) {
Pierre Ossman4daa7b12015-02-12 14:57:05 +0100637 if ((conn->cp.compressLevel != -1) && (conn->cp.compressLevel < 2))
Pierre Ossmanc0397262014-03-14 15:59:46 +0100638 maxColours = 24;
639 else
640 maxColours = 96;
641 }
642
643 if (maxColours < 2)
644 maxColours = 2;
645
646 encoder = encoders[activeEncoders[encoderIndexedRLE]];
647 if (maxColours > encoder->maxPaletteSize)
648 maxColours = encoder->maxPaletteSize;
649 encoder = encoders[activeEncoders[encoderIndexed]];
650 if (maxColours > encoder->maxPaletteSize)
651 maxColours = encoder->maxPaletteSize;
652
653 ppb = preparePixelBuffer(rect, pb, true);
654
655 if (!analyseRect(ppb, &info, maxColours))
656 info.palette.clear();
657
658 // Different encoders might have different RLE overhead, but
659 // here we do a guess at RLE being the better choice if reduces
660 // the pixel count by 50%.
661 useRLE = info.rleRuns <= (rect.area() * 2);
662
663 switch (info.palette.size()) {
664 case 0:
665 type = encoderFullColour;
666 break;
667 case 1:
668 type = encoderSolid;
669 break;
670 case 2:
671 if (useRLE)
672 type = encoderBitmapRLE;
673 else
674 type = encoderBitmap;
675 break;
676 default:
677 if (useRLE)
678 type = encoderIndexedRLE;
679 else
680 type = encoderIndexed;
681 }
682
Pierre Ossman20dd2a92015-02-11 17:43:15 +0100683 encoder = startRect(rect, type);
Pierre Ossmanc0397262014-03-14 15:59:46 +0100684
685 if (encoder->flags & EncoderUseNativePF)
686 ppb = preparePixelBuffer(rect, pb, false);
687
Pierre Ossmanc0397262014-03-14 15:59:46 +0100688 encoder->writeRect(ppb, info.palette);
Pierre Ossman20dd2a92015-02-11 17:43:15 +0100689
690 endRect();
Pierre Ossmanc0397262014-03-14 15:59:46 +0100691}
692
693bool EncodeManager::checkSolidTile(const Rect& r, const rdr::U8* colourValue,
694 const PixelBuffer *pb)
695{
696 switch (pb->getPF().bpp) {
697 case 32:
698 return checkSolidTile(r, *(const rdr::U32*)colourValue, pb);
699 case 16:
700 return checkSolidTile(r, *(const rdr::U16*)colourValue, pb);
701 default:
702 return checkSolidTile(r, *(const rdr::U8*)colourValue, pb);
703 }
704}
705
706void EncodeManager::extendSolidAreaByBlock(const Rect& r,
707 const rdr::U8* colourValue,
708 const PixelBuffer *pb, Rect* er)
709{
710 int dx, dy, dw, dh;
711 int w_prev;
712 Rect sr;
713 int w_best = 0, h_best = 0;
714
715 w_prev = r.width();
716
717 // We search width first, back off when we hit a different colour,
718 // and restart with a larger height. We keep track of the
719 // width/height combination that gives us the largest area.
720 for (dy = r.tl.y; dy < r.br.y; dy += SolidSearchBlock) {
721
722 dh = SolidSearchBlock;
723 if (dy + dh > r.br.y)
724 dh = r.br.y - dy;
725
726 // We test one block here outside the x loop in order to break
727 // the y loop right away.
728 dw = SolidSearchBlock;
729 if (dw > w_prev)
730 dw = w_prev;
731
732 sr.setXYWH(r.tl.x, dy, dw, dh);
733 if (!checkSolidTile(sr, colourValue, pb))
734 break;
735
736 for (dx = r.tl.x + dw; dx < r.tl.x + w_prev;) {
737
738 dw = SolidSearchBlock;
739 if (dx + dw > r.tl.x + w_prev)
740 dw = r.tl.x + w_prev - dx;
741
742 sr.setXYWH(dx, dy, dw, dh);
743 if (!checkSolidTile(sr, colourValue, pb))
744 break;
745
746 dx += dw;
747 }
748
749 w_prev = dx - r.tl.x;
750 if (w_prev * (dy + dh - r.tl.y) > w_best * h_best) {
751 w_best = w_prev;
752 h_best = dy + dh - r.tl.y;
753 }
754 }
755
756 er->tl.x = r.tl.x;
757 er->tl.y = r.tl.y;
758 er->br.x = er->tl.x + w_best;
759 er->br.y = er->tl.y + h_best;
760}
761
762void EncodeManager::extendSolidAreaByPixel(const Rect& r, const Rect& sr,
763 const rdr::U8* colourValue,
764 const PixelBuffer *pb, Rect* er)
765{
766 int cx, cy;
767 Rect tr;
768
769 // Try to extend the area upwards.
770 for (cy = sr.tl.y - 1; cy >= r.tl.y; cy--) {
771 tr.setXYWH(sr.tl.x, cy, sr.width(), 1);
772 if (!checkSolidTile(tr, colourValue, pb))
773 break;
774 }
775 er->tl.y = cy + 1;
776
777 // ... downwards.
778 for (cy = sr.br.y; cy < r.br.y; cy++) {
779 tr.setXYWH(sr.tl.x, cy, sr.width(), 1);
780 if (!checkSolidTile(tr, colourValue, pb))
781 break;
782 }
783 er->br.y = cy;
784
785 // ... to the left.
786 for (cx = sr.tl.x - 1; cx >= r.tl.x; cx--) {
787 tr.setXYWH(cx, er->tl.y, 1, er->height());
788 if (!checkSolidTile(tr, colourValue, pb))
789 break;
790 }
791 er->tl.x = cx + 1;
792
793 // ... to the right.
794 for (cx = sr.br.x; cx < r.br.x; cx++) {
795 tr.setXYWH(cx, er->tl.y, 1, er->height());
796 if (!checkSolidTile(tr, colourValue, pb))
797 break;
798 }
799 er->br.x = cx;
800}
801
802PixelBuffer* EncodeManager::preparePixelBuffer(const Rect& rect,
803 const PixelBuffer *pb,
804 bool convert)
805{
806 const rdr::U8* buffer;
807 int stride;
808
809 // Do wo need to convert the data?
810 if (convert && !conn->cp.pf().equal(pb->getPF())) {
811 convertedPixelBuffer.setPF(conn->cp.pf());
812 convertedPixelBuffer.setSize(rect.width(), rect.height());
813
814 buffer = pb->getBuffer(rect, &stride);
815 convertedPixelBuffer.imageRect(pb->getPF(),
816 convertedPixelBuffer.getRect(),
817 buffer, stride);
818
819 return &convertedPixelBuffer;
820 }
821
822 // Otherwise we still need to shift the coordinates. We have our own
823 // abusive subclass of FullFramePixelBuffer for this.
824
825 buffer = pb->getBuffer(rect, &stride);
826
827 offsetPixelBuffer.update(pb->getPF(), rect.width(), rect.height(),
828 buffer, stride);
829
830 return &offsetPixelBuffer;
831}
832
833bool EncodeManager::analyseRect(const PixelBuffer *pb,
834 struct RectInfo *info, int maxColours)
835{
836 const rdr::U8* buffer;
837 int stride;
838
839 buffer = pb->getBuffer(pb->getRect(), &stride);
840
841 switch (pb->getPF().bpp) {
842 case 32:
843 return analyseRect(pb->width(), pb->height(),
844 (const rdr::U32*)buffer, stride,
845 info, maxColours);
846 case 16:
847 return analyseRect(pb->width(), pb->height(),
848 (const rdr::U16*)buffer, stride,
849 info, maxColours);
850 default:
851 return analyseRect(pb->width(), pb->height(),
852 (const rdr::U8*)buffer, stride,
853 info, maxColours);
854 }
855}
856
857void EncodeManager::OffsetPixelBuffer::update(const PixelFormat& pf,
858 int width, int height,
859 const rdr::U8* data_,
860 int stride_)
861{
862 format = pf;
863 width_ = width;
864 height_ = height;
865 // Forced cast. We never write anything though, so it should be safe.
866 data = (rdr::U8*)data_;
867 stride = stride_;
868}
869
870// Preprocessor generated, optimised methods
871
872#define BPP 8
873#include "EncodeManagerBPP.cxx"
874#undef BPP
875#define BPP 16
876#include "EncodeManagerBPP.cxx"
877#undef BPP
878#define BPP 32
879#include "EncodeManagerBPP.cxx"
880#undef BPP