|
|
@@ -17,6 +17,9 @@ |
|
|
|
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, |
|
|
|
* USA. |
|
|
|
*/ |
|
|
|
|
|
|
|
#include <stdlib.h> |
|
|
|
|
|
|
|
#include <rfb/EncodeManager.h> |
|
|
|
#include <rfb/Encoder.h> |
|
|
|
#include <rfb/Palette.h> |
|
|
@@ -47,6 +50,8 @@ static const int SolidSearchBlock = 16; |
|
|
|
// Don't bother with blocks smaller than this |
|
|
|
static const int SolidBlockMinArea = 2048; |
|
|
|
|
|
|
|
static const int LosslessRefreshMaxArea = 4096; |
|
|
|
|
|
|
|
namespace rfb { |
|
|
|
|
|
|
|
enum EncoderClass { |
|
|
@@ -245,17 +250,42 @@ bool EncodeManager::supported(int encoding) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
bool EncodeManager::needsLosslessRefresh(const Region& req) |
|
|
|
{ |
|
|
|
return !lossyRegion.intersect(req).is_empty(); |
|
|
|
} |
|
|
|
|
|
|
|
void EncodeManager::pruneLosslessRefresh(const Region& limits) |
|
|
|
{ |
|
|
|
lossyRegion.assign_intersect(limits); |
|
|
|
} |
|
|
|
|
|
|
|
void EncodeManager::writeUpdate(const UpdateInfo& ui, const PixelBuffer* pb, |
|
|
|
const RenderedCursor* renderedCursor) |
|
|
|
{ |
|
|
|
doUpdate(true, ui.changed, ui.copied, ui.copy_delta, pb, renderedCursor); |
|
|
|
} |
|
|
|
|
|
|
|
void EncodeManager::writeLosslessRefresh(const Region& req, const PixelBuffer* pb, |
|
|
|
const RenderedCursor* renderedCursor) |
|
|
|
{ |
|
|
|
doUpdate(false, getLosslessRefresh(req), |
|
|
|
Region(), Point(), pb, renderedCursor); |
|
|
|
} |
|
|
|
|
|
|
|
void EncodeManager::doUpdate(bool allowLossy, const Region& changed_, |
|
|
|
const Region& copied, const Point& copyDelta, |
|
|
|
const PixelBuffer* pb, |
|
|
|
const RenderedCursor* renderedCursor) |
|
|
|
{ |
|
|
|
int nRects; |
|
|
|
Region changed, cursorRegion; |
|
|
|
|
|
|
|
updates++; |
|
|
|
|
|
|
|
prepareEncoders(); |
|
|
|
prepareEncoders(allowLossy); |
|
|
|
|
|
|
|
changed = ui.changed; |
|
|
|
changed = changed_; |
|
|
|
|
|
|
|
/* |
|
|
|
* We need to render the cursor seperately as it has its own |
|
|
@@ -269,14 +299,14 @@ void EncodeManager::writeUpdate(const UpdateInfo& ui, const PixelBuffer* pb, |
|
|
|
if (conn->cp.supportsLastRect) |
|
|
|
nRects = 0xFFFF; |
|
|
|
else { |
|
|
|
nRects = ui.copied.numRects(); |
|
|
|
nRects = copied.numRects(); |
|
|
|
nRects += computeNumRects(changed); |
|
|
|
nRects += computeNumRects(cursorRegion); |
|
|
|
} |
|
|
|
|
|
|
|
conn->writer()->writeFramebufferUpdateStart(nRects); |
|
|
|
|
|
|
|
writeCopyRects(ui); |
|
|
|
writeCopyRects(copied, copyDelta); |
|
|
|
|
|
|
|
/* |
|
|
|
* We start by searching for solid rects, which are then removed |
|
|
@@ -291,7 +321,7 @@ void EncodeManager::writeUpdate(const UpdateInfo& ui, const PixelBuffer* pb, |
|
|
|
conn->writer()->writeFramebufferUpdateEnd(); |
|
|
|
} |
|
|
|
|
|
|
|
void EncodeManager::prepareEncoders() |
|
|
|
void EncodeManager::prepareEncoders(bool allowLossy) |
|
|
|
{ |
|
|
|
enum EncoderClass solid, bitmap, bitmapRLE; |
|
|
|
enum EncoderClass indexed, indexedRLE, fullColour; |
|
|
@@ -316,7 +346,7 @@ void EncodeManager::prepareEncoders() |
|
|
|
break; |
|
|
|
case encodingTight: |
|
|
|
if (encoders[encoderTightJPEG]->isSupported() && |
|
|
|
(conn->cp.pf().bpp >= 16)) |
|
|
|
(conn->cp.pf().bpp >= 16) && allowLossy) |
|
|
|
fullColour = encoderTightJPEG; |
|
|
|
else |
|
|
|
fullColour = encoderTight; |
|
|
@@ -334,7 +364,7 @@ void EncodeManager::prepareEncoders() |
|
|
|
|
|
|
|
if (fullColour == encoderRaw) { |
|
|
|
if (encoders[encoderTightJPEG]->isSupported() && |
|
|
|
(conn->cp.pf().bpp >= 16)) |
|
|
|
(conn->cp.pf().bpp >= 16) && allowLossy) |
|
|
|
fullColour = encoderTightJPEG; |
|
|
|
else if (encoders[encoderZRLE]->isSupported()) |
|
|
|
fullColour = encoderZRLE; |
|
|
@@ -374,7 +404,7 @@ void EncodeManager::prepareEncoders() |
|
|
|
|
|
|
|
// JPEG is the only encoder that can reduce things to grayscale |
|
|
|
if ((conn->cp.subsampling == subsampleGray) && |
|
|
|
encoders[encoderTightJPEG]->isSupported()) { |
|
|
|
encoders[encoderTightJPEG]->isSupported() && allowLossy) { |
|
|
|
solid = bitmap = bitmapRLE = encoderTightJPEG; |
|
|
|
indexed = indexedRLE = fullColour = encoderTightJPEG; |
|
|
|
} |
|
|
@@ -398,6 +428,48 @@ void EncodeManager::prepareEncoders() |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
Region EncodeManager::getLosslessRefresh(const Region& req) |
|
|
|
{ |
|
|
|
std::vector<Rect> rects; |
|
|
|
Region refresh; |
|
|
|
size_t area; |
|
|
|
|
|
|
|
area = 0; |
|
|
|
lossyRegion.intersect(req).get_rects(&rects); |
|
|
|
while (!rects.empty()) { |
|
|
|
size_t idx; |
|
|
|
Rect rect; |
|
|
|
|
|
|
|
// Grab a random rect so we don't keep damaging and restoring the |
|
|
|
// same rect over and over |
|
|
|
idx = rand() % rects.size(); |
|
|
|
|
|
|
|
rect = rects[idx]; |
|
|
|
|
|
|
|
// Add rects until we exceed the threshold, then include as much as |
|
|
|
// possible of the final rect |
|
|
|
if ((area + rect.area()) > LosslessRefreshMaxArea) { |
|
|
|
// Use the narrowest axis to avoid getting to thin rects |
|
|
|
if (rect.width() > rect.height()) { |
|
|
|
int width = (LosslessRefreshMaxArea - area) / rect.height(); |
|
|
|
rect.br.x = rect.tl.x + __rfbmax(1, width); |
|
|
|
} else { |
|
|
|
int height = (LosslessRefreshMaxArea - area) / rect.width(); |
|
|
|
rect.br.y = rect.tl.y + __rfbmax(1, height); |
|
|
|
} |
|
|
|
refresh.assign_union(Region(rect)); |
|
|
|
break; |
|
|
|
} |
|
|
|
|
|
|
|
area += rect.area(); |
|
|
|
refresh.assign_union(Region(rect)); |
|
|
|
|
|
|
|
rects.erase(rects.begin() + idx); |
|
|
|
} |
|
|
|
|
|
|
|
return refresh; |
|
|
|
} |
|
|
|
|
|
|
|
int EncodeManager::computeNumRects(const Region& changed) |
|
|
|
{ |
|
|
|
int numRects; |
|
|
@@ -450,6 +522,11 @@ Encoder *EncodeManager::startRect(const Rect& rect, int type) |
|
|
|
encoder = encoders[klass]; |
|
|
|
conn->writer()->startRect(rect, encoder->encoding); |
|
|
|
|
|
|
|
if (encoder->flags & EncoderLossy) |
|
|
|
lossyRegion.assign_union(Region(rect)); |
|
|
|
else |
|
|
|
lossyRegion.assign_subtract(Region(rect)); |
|
|
|
|
|
|
|
return encoder; |
|
|
|
} |
|
|
|
|
|
|
@@ -466,14 +543,16 @@ void EncodeManager::endRect() |
|
|
|
stats[klass][activeType].bytes += length; |
|
|
|
} |
|
|
|
|
|
|
|
void EncodeManager::writeCopyRects(const UpdateInfo& ui) |
|
|
|
void EncodeManager::writeCopyRects(const Region& copied, const Point& delta) |
|
|
|
{ |
|
|
|
std::vector<Rect> rects; |
|
|
|
std::vector<Rect>::const_iterator rect; |
|
|
|
|
|
|
|
Region lossyCopy; |
|
|
|
|
|
|
|
beforeLength = conn->getOutStream()->length(); |
|
|
|
|
|
|
|
ui.copied.get_rects(&rects, ui.copy_delta.x <= 0, ui.copy_delta.y <= 0); |
|
|
|
copied.get_rects(&rects, delta.x <= 0, delta.y <= 0); |
|
|
|
for (rect = rects.begin(); rect != rects.end(); ++rect) { |
|
|
|
int equiv; |
|
|
|
|
|
|
@@ -482,11 +561,16 @@ void EncodeManager::writeCopyRects(const UpdateInfo& ui) |
|
|
|
equiv = 12 + rect->area() * conn->cp.pf().bpp/8; |
|
|
|
copyStats.equivalent += equiv; |
|
|
|
|
|
|
|
conn->writer()->writeCopyRect(*rect, rect->tl.x - ui.copy_delta.x, |
|
|
|
rect->tl.y - ui.copy_delta.y); |
|
|
|
conn->writer()->writeCopyRect(*rect, rect->tl.x - delta.x, |
|
|
|
rect->tl.y - delta.y); |
|
|
|
} |
|
|
|
|
|
|
|
copyStats.bytes += conn->getOutStream()->length() - beforeLength; |
|
|
|
|
|
|
|
lossyCopy = lossyRegion; |
|
|
|
lossyCopy.translate(delta); |
|
|
|
lossyCopy.assign_intersect(copied); |
|
|
|
lossyRegion.assign_union(lossyCopy); |
|
|
|
} |
|
|
|
|
|
|
|
void EncodeManager::writeSolidRects(Region *changed, const PixelBuffer* pb) |