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