This allows us to apply a lot more server logic independently of which encoder is in use. Most of this class are things moved over from the Tight encoder.tags/v1.3.90
@@ -17,6 +17,7 @@ set(RFB_SOURCES | |||
Cursor.cxx | |||
Decoder.cxx | |||
d3des.c | |||
EncodeManager.cxx | |||
Encoder.cxx | |||
HTTPServer.cxx | |||
HextileDecoder.cxx | |||
@@ -52,6 +53,7 @@ set(RFB_SOURCES | |||
Timer.cxx | |||
TightDecoder.cxx | |||
TightEncoder.cxx | |||
TightJPEGEncoder.cxx | |||
UpdateTracker.cxx | |||
VNCSConnectionST.cxx | |||
VNCServerST.cxx |
@@ -22,7 +22,7 @@ | |||
#include <rdr/OutStream.h> | |||
#include <rfb/Exception.h> | |||
#include <rfb/encodings.h> | |||
#include <rfb/Encoder.h> | |||
#include <rfb/EncodeManager.h> | |||
#include <rfb/ConnParams.h> | |||
#include <rfb/util.h> | |||
@@ -38,7 +38,7 @@ ConnParams::ConnParams() | |||
supportsContinuousUpdates(false), | |||
compressLevel(2), qualityLevel(-1), fineQualityLevel(-1), | |||
subsampling(subsampleUndefined), name_(0), | |||
currentEncoding_(encodingRaw), verStrPos(0) | |||
preferredEncoding_(encodingRaw), verStrPos(0) | |||
{ | |||
setName(""); | |||
} | |||
@@ -101,6 +101,11 @@ void ConnParams::setCursor(const Cursor& other) | |||
memcpy(cursor_.mask.buf, other.mask.buf, cursor_.maskLen()); | |||
} | |||
bool ConnParams::supportsEncoding(rdr::S32 encoding) | |||
{ | |||
return encodings_.count(encoding) != 0; | |||
} | |||
void ConnParams::setEncodings(int nEncodings, const rdr::S32* encodings) | |||
{ | |||
useCopyRect = false; | |||
@@ -113,7 +118,10 @@ void ConnParams::setEncodings(int nEncodings, const rdr::S32* encodings) | |||
qualityLevel = -1; | |||
fineQualityLevel = -1; | |||
subsampling = subsampleUndefined; | |||
currentEncoding_ = encodingRaw; | |||
preferredEncoding_ = encodingRaw; | |||
encodings_.clear(); | |||
encodings_.insert(encodingRaw); | |||
for (int i = nEncodings-1; i >= 0; i--) { | |||
switch (encodings[i]) { | |||
@@ -176,7 +184,10 @@ void ConnParams::setEncodings(int nEncodings, const rdr::S32* encodings) | |||
encodings[i] <= pseudoEncodingFineQualityLevel100) | |||
fineQualityLevel = encodings[i] - pseudoEncodingFineQualityLevel0; | |||
if (Encoder::supported(encodings[i])) | |||
currentEncoding_ = encodings[i]; | |||
if (EncodeManager::supported(encodings[i])) | |||
preferredEncoding_ = encodings[i]; | |||
if (encodings[i] > 0) | |||
encodings_.insert(encodings[i]); | |||
} | |||
} |
@@ -23,6 +23,8 @@ | |||
#ifndef __RFB_CONNPARAMS_H__ | |||
#define __RFB_CONNPARAMS_H__ | |||
#include <set> | |||
#include <rdr/types.h> | |||
#include <rfb/Cursor.h> | |||
#include <rfb/PixelFormat.h> | |||
@@ -78,7 +80,8 @@ namespace rfb { | |||
const Cursor& cursor() { return cursor_; } | |||
void setCursor(const Cursor& cursor); | |||
rdr::S32 currentEncoding() { return currentEncoding_; } | |||
rdr::S32 preferredEncoding() { return preferredEncoding_; } | |||
bool supportsEncoding(rdr::S32 encoding); | |||
void setEncodings(int nEncodings, const rdr::S32* encodings); | |||
@@ -105,7 +108,8 @@ namespace rfb { | |||
PixelFormat pf_; | |||
char* name_; | |||
Cursor cursor_; | |||
int currentEncoding_; | |||
rdr::S32 preferredEncoding_; | |||
std::set<rdr::S32> encodings_; | |||
char verStr[13]; | |||
int verStrPos; | |||
}; |
@@ -0,0 +1,707 @@ | |||
/* Copyright (C) 2000-2003 Constantin Kaplinsky. All Rights Reserved. | |||
* Copyright (C) 2011 D. R. Commander. All Rights Reserved. | |||
* Copyright 2014 Pierre Ossman for Cendio AB | |||
* | |||
* This is free software; you can redistribute it and/or modify | |||
* it under the terms of the GNU General Public License as published by | |||
* the Free Software Foundation; either version 2 of the License, or | |||
* (at your option) any later version. | |||
* | |||
* This software is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
* GNU General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU General Public License | |||
* along with this software; if not, write to the Free Software | |||
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, | |||
* USA. | |||
*/ | |||
#include <rfb/EncodeManager.h> | |||
#include <rfb/Encoder.h> | |||
#include <rfb/Palette.h> | |||
#include <rfb/SConnection.h> | |||
#include <rfb/SMsgWriter.h> | |||
#include <rfb/UpdateTracker.h> | |||
#include <rfb/RawEncoder.h> | |||
#include <rfb/RREEncoder.h> | |||
#include <rfb/HextileEncoder.h> | |||
#include <rfb/ZRLEEncoder.h> | |||
#include <rfb/TightEncoder.h> | |||
#include <rfb/TightJPEGEncoder.h> | |||
using namespace rfb; | |||
// Split each rectangle into smaller ones no larger than this area, | |||
// and no wider than this width. | |||
static const int SubRectMaxArea = 65536; | |||
static const int SubRectMaxWidth = 2048; | |||
// The size in pixels of either side of each block tested when looking | |||
// for solid blocks. | |||
static const int SolidSearchBlock = 16; | |||
// Don't bother with blocks smaller than this | |||
static const int SolidBlockMinArea = 2048; | |||
namespace rfb { | |||
enum EncoderClass { | |||
encoderRaw, | |||
encoderRRE, | |||
encoderHextile, | |||
encoderTight, | |||
encoderTightJPEG, | |||
encoderZRLE, | |||
encoderClassMax, | |||
}; | |||
enum EncoderType { | |||
encoderSolid, | |||
encoderBitmap, | |||
encoderBitmapRLE, | |||
encoderIndexed, | |||
encoderIndexedRLE, | |||
encoderFullColour, | |||
encoderTypeMax, | |||
}; | |||
struct RectInfo { | |||
int rleRuns; | |||
Palette palette; | |||
}; | |||
}; | |||
EncodeManager::EncodeManager(SConnection* conn_) : conn(conn_) | |||
{ | |||
encoders.resize(encoderClassMax, NULL); | |||
activeEncoders.resize(encoderTypeMax, encoderRaw); | |||
encoders[encoderRaw] = new RawEncoder(conn); | |||
encoders[encoderRRE] = new RREEncoder(conn); | |||
encoders[encoderHextile] = new HextileEncoder(conn); | |||
encoders[encoderTight] = new TightEncoder(conn); | |||
encoders[encoderTightJPEG] = new TightJPEGEncoder(conn); | |||
encoders[encoderZRLE] = new ZRLEEncoder(conn); | |||
} | |||
EncodeManager::~EncodeManager() | |||
{ | |||
std::vector<Encoder*>::iterator iter; | |||
for (iter = encoders.begin();iter != encoders.end();iter++) | |||
delete *iter; | |||
} | |||
bool EncodeManager::supported(int encoding) | |||
{ | |||
switch (encoding) { | |||
case encodingRaw: | |||
case encodingRRE: | |||
case encodingHextile: | |||
case encodingZRLE: | |||
case encodingTight: | |||
return true; | |||
default: | |||
return false; | |||
} | |||
} | |||
void EncodeManager::writeUpdate(const UpdateInfo& ui, const PixelBuffer* pb, | |||
const RenderedCursor* renderedCursor) | |||
{ | |||
int nRects; | |||
Region changed; | |||
prepareEncoders(); | |||
if (conn->cp.supportsLastRect) | |||
nRects = 0xFFFF; | |||
else { | |||
nRects = ui.copied.numRects(); | |||
nRects += computeNumRects(ui.changed); | |||
if (renderedCursor != NULL) | |||
nRects += 1; | |||
} | |||
conn->writer()->writeFramebufferUpdateStart(nRects); | |||
writeCopyRects(ui); | |||
/* | |||
* We start by searching for solid rects, which are then removed | |||
* from the changed region. | |||
*/ | |||
changed.copyFrom(ui.changed); | |||
if (conn->cp.supportsLastRect) | |||
writeSolidRects(&changed, pb); | |||
writeRects(changed, pb); | |||
if (renderedCursor != NULL) { | |||
Rect renderedCursorRect; | |||
renderedCursorRect = renderedCursor->getEffectiveRect(); | |||
writeSubRect(renderedCursorRect, renderedCursor); | |||
} | |||
conn->writer()->writeFramebufferUpdateEnd(); | |||
} | |||
void EncodeManager::prepareEncoders() | |||
{ | |||
enum EncoderClass solid, bitmap, bitmapRLE; | |||
enum EncoderClass indexed, indexedRLE, fullColour; | |||
rdr::S32 preferred; | |||
std::vector<int>::iterator iter; | |||
solid = bitmap = bitmapRLE = encoderRaw; | |||
indexed = indexedRLE = fullColour = encoderRaw; | |||
// Try to respect the client's wishes | |||
preferred = conn->cp.preferredEncoding(); | |||
switch (preferred) { | |||
case encodingRRE: | |||
// Horrible for anything high frequency and/or lots of colours | |||
bitmapRLE = indexedRLE = encoderRRE; | |||
break; | |||
case encodingHextile: | |||
// Slightly less horrible | |||
bitmapRLE = indexedRLE = fullColour = encoderHextile; | |||
break; | |||
case encodingTight: | |||
if (encoders[encoderTightJPEG]->isSupported() && | |||
(conn->cp.pf().bpp >= 16)) | |||
fullColour = encoderTightJPEG; | |||
else | |||
fullColour = encoderTight; | |||
indexed = indexedRLE = encoderTight; | |||
bitmap = bitmapRLE = encoderTight; | |||
break; | |||
case encodingZRLE: | |||
fullColour = encoderZRLE; | |||
bitmapRLE = indexedRLE = encoderZRLE; | |||
bitmap = indexed = encoderZRLE; | |||
break; | |||
} | |||
// Any encoders still unassigned? | |||
if (fullColour == encoderRaw) { | |||
if (encoders[encoderTightJPEG]->isSupported() && | |||
(conn->cp.pf().bpp >= 16)) | |||
fullColour = encoderTightJPEG; | |||
else if (encoders[encoderZRLE]->isSupported()) | |||
fullColour = encoderZRLE; | |||
else if (encoders[encoderTight]->isSupported()) | |||
fullColour = encoderTight; | |||
else if (encoders[encoderHextile]->isSupported()) | |||
fullColour = encoderHextile; | |||
} | |||
if (indexed == encoderRaw) { | |||
if (encoders[encoderZRLE]->isSupported()) | |||
indexed = encoderZRLE; | |||
else if (encoders[encoderTight]->isSupported()) | |||
indexed = encoderTight; | |||
else if (encoders[encoderHextile]->isSupported()) | |||
indexed = encoderHextile; | |||
} | |||
if (indexedRLE == encoderRaw) | |||
indexedRLE = indexed; | |||
if (bitmap == encoderRaw) | |||
bitmap = indexed; | |||
if (bitmapRLE == encoderRaw) | |||
bitmapRLE = bitmap; | |||
if (solid == encoderRaw) { | |||
if (encoders[encoderTight]->isSupported()) | |||
solid = encoderTight; | |||
else if (encoders[encoderRRE]->isSupported()) | |||
solid = encoderRRE; | |||
else if (encoders[encoderZRLE]->isSupported()) | |||
solid = encoderZRLE; | |||
else if (encoders[encoderHextile]->isSupported()) | |||
solid = encoderHextile; | |||
} | |||
// JPEG is the only encoder that can reduce things to grayscale | |||
if ((conn->cp.subsampling == subsampleGray) && | |||
encoders[encoderTightJPEG]->isSupported()) { | |||
solid = bitmap = bitmapRLE = encoderTightJPEG; | |||
indexed = indexedRLE = fullColour = encoderTightJPEG; | |||
} | |||
activeEncoders[encoderSolid] = solid; | |||
activeEncoders[encoderBitmap] = bitmap; | |||
activeEncoders[encoderBitmapRLE] = bitmapRLE; | |||
activeEncoders[encoderIndexed] = indexed; | |||
activeEncoders[encoderIndexedRLE] = indexedRLE; | |||
activeEncoders[encoderFullColour] = fullColour; | |||
for (iter = activeEncoders.begin(); iter != activeEncoders.end(); ++iter) { | |||
Encoder *encoder; | |||
encoder = encoders[*iter]; | |||
encoder->setCompressLevel(conn->cp.compressLevel); | |||
encoder->setQualityLevel(conn->cp.qualityLevel); | |||
encoder->setFineQualityLevel(conn->cp.fineQualityLevel, | |||
conn->cp.subsampling); | |||
} | |||
} | |||
int EncodeManager::computeNumRects(const Region& changed) | |||
{ | |||
int numRects; | |||
std::vector<Rect> rects; | |||
std::vector<Rect>::const_iterator rect; | |||
numRects = 0; | |||
changed.get_rects(&rects); | |||
for (rect = rects.begin(); rect != rects.end(); ++rect) { | |||
int w, h, sw, sh; | |||
w = rect->width(); | |||
h = rect->height(); | |||
// No split necessary? | |||
if (((w*h) < SubRectMaxArea) && (w < SubRectMaxWidth)) { | |||
numRects += 1; | |||
continue; | |||
} | |||
if (w <= SubRectMaxWidth) | |||
sw = w; | |||
else | |||
sw = SubRectMaxWidth; | |||
sh = SubRectMaxArea / sw; | |||
// ceil(w/sw) * ceil(h/sh) | |||
numRects += (((w - 1)/sw) + 1) * (((h - 1)/sh) + 1); | |||
} | |||
return numRects; | |||
} | |||
void EncodeManager::writeCopyRects(const UpdateInfo& ui) | |||
{ | |||
std::vector<Rect> rects; | |||
std::vector<Rect>::const_iterator rect; | |||
ui.copied.get_rects(&rects, ui.copy_delta.x <= 0, ui.copy_delta.y <= 0); | |||
for (rect = rects.begin(); rect != rects.end(); ++rect) { | |||
conn->writer()->writeCopyRect(*rect, rect->tl.x - ui.copy_delta.x, | |||
rect->tl.y - ui.copy_delta.y); | |||
} | |||
} | |||
void EncodeManager::writeSolidRects(Region *changed, const PixelBuffer* pb) | |||
{ | |||
std::vector<Rect> rects; | |||
std::vector<Rect>::const_iterator rect; | |||
// FIXME: This gives up after the first rect it finds. A large update | |||
// (like a whole screen refresh) might have lots of large solid | |||
// areas. | |||
changed->get_rects(&rects); | |||
for (rect = rects.begin(); rect != rects.end(); ++rect) { | |||
Rect sr; | |||
int dx, dy, dw, dh; | |||
// We start by finding a solid 16x16 block | |||
for (dy = rect->tl.y; dy < rect->br.y; dy += SolidSearchBlock) { | |||
dh = SolidSearchBlock; | |||
if (dy + dh > rect->br.y) | |||
dh = rect->br.y - dy; | |||
for (dx = rect->tl.x; dx < rect->br.x; dx += SolidSearchBlock) { | |||
// We define it like this to guarantee alignment | |||
rdr::U32 _buffer; | |||
rdr::U8* colourValue = (rdr::U8*)&_buffer; | |||
dw = SolidSearchBlock; | |||
if (dx + dw > rect->br.x) | |||
dw = rect->br.x - dx; | |||
pb->getImage(colourValue, Rect(dx, dy, dx+1, dy+1)); | |||
sr.setXYWH(dx, dy, dw, dh); | |||
if (checkSolidTile(sr, colourValue, pb)) { | |||
Rect erb, erp; | |||
Encoder *encoder; | |||
// We then try extending the area by adding more blocks | |||
// in both directions and pick the combination that gives | |||
// the largest area. | |||
sr.setXYWH(dx, dy, rect->br.x - dx, rect->br.y - dy); | |||
extendSolidAreaByBlock(sr, colourValue, pb, &erb); | |||
// Did we end up getting the entire rectangle? | |||
if (erb.equals(*rect)) | |||
erp = erb; | |||
else { | |||
// Don't bother with sending tiny rectangles | |||
if (erb.area() < SolidBlockMinArea) | |||
continue; | |||
// Extend the area again, but this time one pixel | |||
// row/column at a time. | |||
extendSolidAreaByPixel(*rect, erb, colourValue, pb, &erp); | |||
} | |||
// Send solid-color rectangle. | |||
encoder = encoders[activeEncoders[encoderSolid]]; | |||
conn->writer()->startRect(erp, encoder->encoding); | |||
if (encoder->flags & EncoderUseNativePF) { | |||
encoder->writeSolidRect(erp.width(), erp.height(), | |||
pb->getPF(), colourValue); | |||
} else { | |||
rdr::U32 _buffer2; | |||
rdr::U8* converted = (rdr::U8*)&_buffer2; | |||
conn->cp.pf().bufferFromBuffer(converted, pb->getPF(), | |||
colourValue, 1); | |||
encoder->writeSolidRect(erp.width(), erp.height(), | |||
conn->cp.pf(), converted); | |||
} | |||
conn->writer()->endRect(); | |||
changed->assign_subtract(Region(erp)); | |||
break; | |||
} | |||
} | |||
if (dx < rect->br.x) | |||
break; | |||
} | |||
} | |||
} | |||
void EncodeManager::writeRects(const Region& changed, const PixelBuffer* pb) | |||
{ | |||
std::vector<Rect> rects; | |||
std::vector<Rect>::const_iterator rect; | |||
changed.get_rects(&rects); | |||
for (rect = rects.begin(); rect != rects.end(); ++rect) { | |||
int w, h, sw, sh; | |||
Rect sr; | |||
w = rect->width(); | |||
h = rect->height(); | |||
// No split necessary? | |||
if (((w*h) < SubRectMaxArea) && (w < SubRectMaxWidth)) { | |||
writeSubRect(*rect, pb); | |||
continue; | |||
} | |||
if (w <= SubRectMaxWidth) | |||
sw = w; | |||
else | |||
sw = SubRectMaxWidth; | |||
sh = SubRectMaxArea / sw; | |||
for (sr.tl.y = rect->tl.y; sr.tl.y < rect->br.y; sr.tl.y += sh) { | |||
sr.br.y = sr.tl.y + sh; | |||
if (sr.br.y > rect->br.y) | |||
sr.br.y = rect->br.y; | |||
for (sr.tl.x = rect->tl.x; sr.tl.x < rect->br.x; sr.tl.x += sw) { | |||
sr.br.x = sr.tl.x + sw; | |||
if (sr.br.x > rect->br.x) | |||
sr.br.x = rect->br.x; | |||
writeSubRect(sr, pb); | |||
} | |||
} | |||
} | |||
} | |||
void EncodeManager::writeSubRect(const Rect& rect, const PixelBuffer *pb) | |||
{ | |||
PixelBuffer *ppb; | |||
Encoder *encoder; | |||
struct RectInfo info; | |||
int divisor, maxColours; | |||
bool useRLE; | |||
EncoderType type; | |||
// FIXME: This is roughly the algorithm previously used by the Tight | |||
// encoder. It seems a bit backwards though, that higher | |||
// compression setting means spending less effort in building | |||
// a palette. It might be that they figured the increase in | |||
// zlib setting compensated for the loss. | |||
if (conn->cp.compressLevel == -1) | |||
divisor = 2 * 8; | |||
else | |||
divisor = conn->cp.compressLevel * 8; | |||
if (divisor < 4) | |||
divisor = 4; | |||
maxColours = rect.area()/divisor; | |||
// Special exception inherited from the Tight encoder | |||
if (activeEncoders[encoderFullColour] == encoderTightJPEG) { | |||
if (conn->cp.compressLevel < 2) | |||
maxColours = 24; | |||
else | |||
maxColours = 96; | |||
} | |||
if (maxColours < 2) | |||
maxColours = 2; | |||
encoder = encoders[activeEncoders[encoderIndexedRLE]]; | |||
if (maxColours > encoder->maxPaletteSize) | |||
maxColours = encoder->maxPaletteSize; | |||
encoder = encoders[activeEncoders[encoderIndexed]]; | |||
if (maxColours > encoder->maxPaletteSize) | |||
maxColours = encoder->maxPaletteSize; | |||
ppb = preparePixelBuffer(rect, pb, true); | |||
if (!analyseRect(ppb, &info, maxColours)) | |||
info.palette.clear(); | |||
// Different encoders might have different RLE overhead, but | |||
// here we do a guess at RLE being the better choice if reduces | |||
// the pixel count by 50%. | |||
useRLE = info.rleRuns <= (rect.area() * 2); | |||
switch (info.palette.size()) { | |||
case 0: | |||
type = encoderFullColour; | |||
break; | |||
case 1: | |||
type = encoderSolid; | |||
break; | |||
case 2: | |||
if (useRLE) | |||
type = encoderBitmapRLE; | |||
else | |||
type = encoderBitmap; | |||
break; | |||
default: | |||
if (useRLE) | |||
type = encoderIndexedRLE; | |||
else | |||
type = encoderIndexed; | |||
} | |||
encoder = encoders[activeEncoders[type]]; | |||
if (encoder->flags & EncoderUseNativePF) | |||
ppb = preparePixelBuffer(rect, pb, false); | |||
conn->writer()->startRect(rect, encoder->encoding); | |||
encoder->writeRect(ppb, info.palette); | |||
conn->writer()->endRect(); | |||
} | |||
bool EncodeManager::checkSolidTile(const Rect& r, const rdr::U8* colourValue, | |||
const PixelBuffer *pb) | |||
{ | |||
switch (pb->getPF().bpp) { | |||
case 32: | |||
return checkSolidTile(r, *(const rdr::U32*)colourValue, pb); | |||
case 16: | |||
return checkSolidTile(r, *(const rdr::U16*)colourValue, pb); | |||
default: | |||
return checkSolidTile(r, *(const rdr::U8*)colourValue, pb); | |||
} | |||
} | |||
void EncodeManager::extendSolidAreaByBlock(const Rect& r, | |||
const rdr::U8* colourValue, | |||
const PixelBuffer *pb, Rect* er) | |||
{ | |||
int dx, dy, dw, dh; | |||
int w_prev; | |||
Rect sr; | |||
int w_best = 0, h_best = 0; | |||
w_prev = r.width(); | |||
// We search width first, back off when we hit a different colour, | |||
// and restart with a larger height. We keep track of the | |||
// width/height combination that gives us the largest area. | |||
for (dy = r.tl.y; dy < r.br.y; dy += SolidSearchBlock) { | |||
dh = SolidSearchBlock; | |||
if (dy + dh > r.br.y) | |||
dh = r.br.y - dy; | |||
// We test one block here outside the x loop in order to break | |||
// the y loop right away. | |||
dw = SolidSearchBlock; | |||
if (dw > w_prev) | |||
dw = w_prev; | |||
sr.setXYWH(r.tl.x, dy, dw, dh); | |||
if (!checkSolidTile(sr, colourValue, pb)) | |||
break; | |||
for (dx = r.tl.x + dw; dx < r.tl.x + w_prev;) { | |||
dw = SolidSearchBlock; | |||
if (dx + dw > r.tl.x + w_prev) | |||
dw = r.tl.x + w_prev - dx; | |||
sr.setXYWH(dx, dy, dw, dh); | |||
if (!checkSolidTile(sr, colourValue, pb)) | |||
break; | |||
dx += dw; | |||
} | |||
w_prev = dx - r.tl.x; | |||
if (w_prev * (dy + dh - r.tl.y) > w_best * h_best) { | |||
w_best = w_prev; | |||
h_best = dy + dh - r.tl.y; | |||
} | |||
} | |||
er->tl.x = r.tl.x; | |||
er->tl.y = r.tl.y; | |||
er->br.x = er->tl.x + w_best; | |||
er->br.y = er->tl.y + h_best; | |||
} | |||
void EncodeManager::extendSolidAreaByPixel(const Rect& r, const Rect& sr, | |||
const rdr::U8* colourValue, | |||
const PixelBuffer *pb, Rect* er) | |||
{ | |||
int cx, cy; | |||
Rect tr; | |||
// Try to extend the area upwards. | |||
for (cy = sr.tl.y - 1; cy >= r.tl.y; cy--) { | |||
tr.setXYWH(sr.tl.x, cy, sr.width(), 1); | |||
if (!checkSolidTile(tr, colourValue, pb)) | |||
break; | |||
} | |||
er->tl.y = cy + 1; | |||
// ... downwards. | |||
for (cy = sr.br.y; cy < r.br.y; cy++) { | |||
tr.setXYWH(sr.tl.x, cy, sr.width(), 1); | |||
if (!checkSolidTile(tr, colourValue, pb)) | |||
break; | |||
} | |||
er->br.y = cy; | |||
// ... to the left. | |||
for (cx = sr.tl.x - 1; cx >= r.tl.x; cx--) { | |||
tr.setXYWH(cx, er->tl.y, 1, er->height()); | |||
if (!checkSolidTile(tr, colourValue, pb)) | |||
break; | |||
} | |||
er->tl.x = cx + 1; | |||
// ... to the right. | |||
for (cx = sr.br.x; cx < r.br.x; cx++) { | |||
tr.setXYWH(cx, er->tl.y, 1, er->height()); | |||
if (!checkSolidTile(tr, colourValue, pb)) | |||
break; | |||
} | |||
er->br.x = cx; | |||
} | |||
PixelBuffer* EncodeManager::preparePixelBuffer(const Rect& rect, | |||
const PixelBuffer *pb, | |||
bool convert) | |||
{ | |||
const rdr::U8* buffer; | |||
int stride; | |||
// Do wo need to convert the data? | |||
if (convert && !conn->cp.pf().equal(pb->getPF())) { | |||
convertedPixelBuffer.setPF(conn->cp.pf()); | |||
convertedPixelBuffer.setSize(rect.width(), rect.height()); | |||
buffer = pb->getBuffer(rect, &stride); | |||
convertedPixelBuffer.imageRect(pb->getPF(), | |||
convertedPixelBuffer.getRect(), | |||
buffer, stride); | |||
return &convertedPixelBuffer; | |||
} | |||
// Otherwise we still need to shift the coordinates. We have our own | |||
// abusive subclass of FullFramePixelBuffer for this. | |||
buffer = pb->getBuffer(rect, &stride); | |||
offsetPixelBuffer.update(pb->getPF(), rect.width(), rect.height(), | |||
buffer, stride); | |||
return &offsetPixelBuffer; | |||
} | |||
bool EncodeManager::analyseRect(const PixelBuffer *pb, | |||
struct RectInfo *info, int maxColours) | |||
{ | |||
const rdr::U8* buffer; | |||
int stride; | |||
buffer = pb->getBuffer(pb->getRect(), &stride); | |||
switch (pb->getPF().bpp) { | |||
case 32: | |||
return analyseRect(pb->width(), pb->height(), | |||
(const rdr::U32*)buffer, stride, | |||
info, maxColours); | |||
case 16: | |||
return analyseRect(pb->width(), pb->height(), | |||
(const rdr::U16*)buffer, stride, | |||
info, maxColours); | |||
default: | |||
return analyseRect(pb->width(), pb->height(), | |||
(const rdr::U8*)buffer, stride, | |||
info, maxColours); | |||
} | |||
} | |||
void EncodeManager::OffsetPixelBuffer::update(const PixelFormat& pf, | |||
int width, int height, | |||
const rdr::U8* data_, | |||
int stride_) | |||
{ | |||
format = pf; | |||
width_ = width; | |||
height_ = height; | |||
// Forced cast. We never write anything though, so it should be safe. | |||
data = (rdr::U8*)data_; | |||
stride = stride_; | |||
} | |||
// Preprocessor generated, optimised methods | |||
#define BPP 8 | |||
#include "EncodeManagerBPP.cxx" | |||
#undef BPP | |||
#define BPP 16 | |||
#include "EncodeManagerBPP.cxx" | |||
#undef BPP | |||
#define BPP 32 | |||
#include "EncodeManagerBPP.cxx" | |||
#undef BPP |
@@ -0,0 +1,114 @@ | |||
/* Copyright (C) 2000-2003 Constantin Kaplinsky. All Rights Reserved. | |||
* Copyright (C) 2011 D. R. Commander. All Rights Reserved. | |||
* Copyright 2014 Pierre Ossman for Cendio AB | |||
* | |||
* This is free software; you can redistribute it and/or modify | |||
* it under the terms of the GNU General Public License as published by | |||
* the Free Software Foundation; either version 2 of the License, or | |||
* (at your option) any later version. | |||
* | |||
* This software is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
* GNU General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU General Public License | |||
* along with this software; if not, write to the Free Software | |||
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, | |||
* USA. | |||
*/ | |||
#ifndef __RFB_ENCODEMANAGER_H__ | |||
#define __RFB_ENCODEMANAGER_H__ | |||
#include <vector> | |||
#include <rdr/types.h> | |||
#include <rfb/PixelBuffer.h> | |||
namespace rfb { | |||
class SConnection; | |||
class Encoder; | |||
class UpdateInfo; | |||
class PixelBuffer; | |||
class RenderedCursor; | |||
class Region; | |||
class Rect; | |||
struct RectInfo; | |||
class EncodeManager { | |||
public: | |||
EncodeManager(SConnection* conn); | |||
~EncodeManager(); | |||
// Hack to let ConnParams calculate the client's preferred encoding | |||
static bool supported(int encoding); | |||
void writeUpdate(const UpdateInfo& ui, const PixelBuffer* pb, | |||
const RenderedCursor* renderedCursor); | |||
protected: | |||
void prepareEncoders(); | |||
int computeNumRects(const Region& changed); | |||
void writeCopyRects(const UpdateInfo& ui); | |||
void writeSolidRects(Region *changed, const PixelBuffer* pb); | |||
void writeRects(const Region& changed, const PixelBuffer* pb); | |||
void writeSubRect(const Rect& rect, const PixelBuffer *pb); | |||
bool checkSolidTile(const Rect& r, const rdr::U8* colourValue, | |||
const PixelBuffer *pb); | |||
void extendSolidAreaByBlock(const Rect& r, const rdr::U8* colourValue, | |||
const PixelBuffer *pb, Rect* er); | |||
void extendSolidAreaByPixel(const Rect& r, const Rect& sr, | |||
const rdr::U8* colourValue, | |||
const PixelBuffer *pb, Rect* er); | |||
PixelBuffer* preparePixelBuffer(const Rect& rect, | |||
const PixelBuffer *pb, bool convert); | |||
bool analyseRect(const PixelBuffer *pb, | |||
struct RectInfo *info, int maxColours); | |||
protected: | |||
// Preprocessor generated, optimised methods | |||
inline bool checkSolidTile(const Rect& r, rdr::U8 colourValue, | |||
const PixelBuffer *pb); | |||
inline bool checkSolidTile(const Rect& r, rdr::U16 colourValue, | |||
const PixelBuffer *pb); | |||
inline bool checkSolidTile(const Rect& r, rdr::U32 colourValue, | |||
const PixelBuffer *pb); | |||
inline bool analyseRect(int width, int height, | |||
const rdr::U8* buffer, int stride, | |||
struct RectInfo *info, int maxColours); | |||
inline bool analyseRect(int width, int height, | |||
const rdr::U16* buffer, int stride, | |||
struct RectInfo *info, int maxColours); | |||
inline bool analyseRect(int width, int height, | |||
const rdr::U32* buffer, int stride, | |||
struct RectInfo *info, int maxColours); | |||
protected: | |||
SConnection *conn; | |||
std::vector<Encoder*> encoders; | |||
std::vector<int> activeEncoders; | |||
class OffsetPixelBuffer : public FullFramePixelBuffer { | |||
public: | |||
OffsetPixelBuffer() {} | |||
virtual ~OffsetPixelBuffer() {} | |||
void update(const PixelFormat& pf, int width, int height, | |||
const rdr::U8* data_, int stride); | |||
}; | |||
OffsetPixelBuffer offsetPixelBuffer; | |||
ManagedPixelBuffer convertedPixelBuffer; | |||
}; | |||
} | |||
#endif |
@@ -0,0 +1,101 @@ | |||
/* Copyright (C) 2000-2003 Constantin Kaplinsky. All Rights Reserved. | |||
* Copyright (C) 2011 D. R. Commander. All Rights Reserved. | |||
* Copyright 2014 Pierre Ossman for Cendio AB | |||
* | |||
* This is free software; you can redistribute it and/or modify | |||
* it under the terms of the GNU General Public License as published by | |||
* the Free Software Foundation; either version 2 of the License, or | |||
* (at your option) any later version. | |||
* | |||
* This software is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
* GNU General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU General Public License | |||
* along with this software; if not, write to the Free Software | |||
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, | |||
* USA. | |||
*/ | |||
#define CONCAT2(a,b) a##b | |||
#define CONCAT2E(a,b) CONCAT2(a,b) | |||
#define UBPP CONCAT2E(U,BPP) | |||
inline bool EncodeManager::checkSolidTile(const Rect& r, | |||
rdr::UBPP colourValue, | |||
const PixelBuffer *pb) | |||
{ | |||
int w, h; | |||
const rdr::UBPP* buffer; | |||
int stride, pad; | |||
w = r.width(); | |||
h = r.height(); | |||
buffer = (const rdr::UBPP*)pb->getBuffer(r, &stride); | |||
pad = stride - w; | |||
while (h--) { | |||
int w_ = w; | |||
while (w_--) { | |||
if (*buffer != colourValue) | |||
return false; | |||
buffer++; | |||
} | |||
buffer += pad; | |||
} | |||
return true; | |||
} | |||
inline bool EncodeManager::analyseRect(int width, int height, | |||
const rdr::UBPP* buffer, int stride, | |||
struct RectInfo *info, int maxColours) | |||
{ | |||
int pad; | |||
rdr::UBPP colour; | |||
int count; | |||
rdr::UBPP c0, c1, ci = 0; | |||
int i, n0, n1, ni; | |||
info->rleRuns = 0; | |||
info->palette.clear(); | |||
pad = stride - width; | |||
// For efficiency, we only update the palette on changes in colour | |||
colour = buffer[0]; | |||
count = 0; | |||
while (height--) { | |||
int w_ = width; | |||
while (w_--) { | |||
if (*buffer != colour) { | |||
if (!info->palette.insert(colour, count)) | |||
return false; | |||
if (info->palette.size() > maxColours) | |||
return false; | |||
// FIXME: This doesn't account for switching lines | |||
info->rleRuns++; | |||
colour = *buffer; | |||
count = 0; | |||
} | |||
buffer++; | |||
count++; | |||
} | |||
buffer += pad; | |||
} | |||
// Make sure the final pixels also get counted | |||
if (!info->palette.insert(colour, count)) | |||
return false; | |||
if (info->palette.size() > maxColours) | |||
return false; | |||
return true; | |||
} |
@@ -16,20 +16,17 @@ | |||
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, | |||
* USA. | |||
*/ | |||
#include <stdio.h> | |||
#include <rfb/encodings.h> | |||
#include <rfb/Exception.h> | |||
#include <rfb/Encoder.h> | |||
#include <rfb/RawEncoder.h> | |||
#include <rfb/RREEncoder.h> | |||
#include <rfb/HextileEncoder.h> | |||
#include <rfb/ZRLEEncoder.h> | |||
#include <rfb/TightEncoder.h> | |||
#include <rfb/SConnection.h> | |||
#include <rfb/PixelBuffer.h> | |||
#include <rfb/Palette.h> | |||
using namespace rfb; | |||
Encoder::Encoder(SConnection *conn_) : conn(conn_) | |||
Encoder::Encoder(SConnection *conn_, int encoding_, | |||
enum EncoderFlags flags_, unsigned int maxPaletteSize_) : | |||
conn(conn_), encoding(encoding_), flags(flags_), | |||
maxPaletteSize(maxPaletteSize_) | |||
{ | |||
} | |||
@@ -37,34 +34,50 @@ Encoder::~Encoder() | |||
{ | |||
} | |||
bool Encoder::supported(int encoding) | |||
void Encoder::writeSolidRect(int width, int height, | |||
const PixelFormat& pf, const rdr::U8* colour) | |||
{ | |||
switch (encoding) { | |||
case encodingRaw: | |||
case encodingRRE: | |||
case encodingHextile: | |||
case encodingZRLE: | |||
case encodingTight: | |||
return true; | |||
default: | |||
return false; | |||
} | |||
ManagedPixelBuffer buffer(pf, width, height); | |||
Pixel pixel; | |||
Palette palette; | |||
rdr::U32 palcol; | |||
pixel = pf.pixelFromBuffer(colour); | |||
buffer.fillRect(buffer.getRect(), pixel); | |||
palcol = 0; | |||
memcpy(&palcol, colour, pf.bpp/8); | |||
palette.insert(palcol, 1); | |||
writeRect(&buffer, palette); | |||
} | |||
Encoder* Encoder::createEncoder(int encoding, SConnection* conn) | |||
void Encoder::writeSolidRect(const PixelBuffer* pb, const Palette& palette) | |||
{ | |||
switch (encoding) { | |||
case encodingRaw: | |||
return new RawEncoder(conn); | |||
case encodingRRE: | |||
return new RREEncoder(conn); | |||
case encodingHextile: | |||
return new HextileEncoder(conn); | |||
case encodingZRLE: | |||
return new ZRLEEncoder(conn); | |||
case encodingTight: | |||
return new TightEncoder(conn); | |||
rdr::U32 col32; | |||
rdr::U16 col16; | |||
rdr::U8 col8; | |||
rdr::U8* buffer; | |||
assert(palette.size() == 1); | |||
// The Palette relies on implicit up and down conversion | |||
switch (pb->getPF().bpp) { | |||
case 32: | |||
col32 = (rdr::U32)palette.getColour(0); | |||
buffer = (rdr::U8*)&col32; | |||
break; | |||
case 16: | |||
col16 = (rdr::U16)palette.getColour(0); | |||
buffer = (rdr::U8*)&col16; | |||
break; | |||
default: | |||
return NULL; | |||
col8 = (rdr::U8)palette.getColour(0); | |||
buffer = (rdr::U8*)&col8; | |||
break; | |||
} | |||
writeSolidRect(pb->width(), pb->height(), pb->getPF(), buffer); | |||
} |
@@ -20,31 +20,75 @@ | |||
#ifndef __RFB_ENCODER_H__ | |||
#define __RFB_ENCODER_H__ | |||
#include <rdr/types.h> | |||
#include <rfb/Rect.h> | |||
namespace rfb { | |||
class SConnection; | |||
class PixelBuffer; | |||
class Palette; | |||
class PixelFormat; | |||
enum EncoderFlags { | |||
// A constant for encoders that don't need anything special | |||
EncoderPlain = 0, | |||
// Give us the raw frame buffer, and not something converted to | |||
// the what the client is asking for. | |||
EncoderUseNativePF = 1 << 0, | |||
}; | |||
class Encoder { | |||
public: | |||
Encoder(SConnection* conn); | |||
Encoder(SConnection* conn, int encoding, | |||
enum EncoderFlags flags, unsigned int maxPaletteSize); | |||
virtual ~Encoder(); | |||
// isSupported() should return a boolean indiciating if this encoder | |||
// is okay to use with the current connection. This usually involves | |||
// checking the list of encodings in the connection parameters. | |||
virtual bool isSupported()=0; | |||
virtual void setCompressLevel(int level) {}; | |||
virtual void setQualityLevel(int level) {}; | |||
virtual void setFineQualityLevel(int quality, int subsampling) {}; | |||
virtual int getNumRects(const Rect &r) { return 1; } | |||
// writeRect() is the main interface that encodes the given rectangle | |||
// with data from the PixelBuffer onto the SConnection given at | |||
// encoder creation. The PixelFormat of the PixelBuffer might not | |||
// match the ConnParams and it is up ot the encoder to do | |||
// any necessary conversion. | |||
virtual void writeRect(const Rect& r, PixelBuffer* pb)=0; | |||
// encoder creation. | |||
// | |||
// The PixelBuffer will be in the PixelFormat specified in ConnParams | |||
// unless the flag UseNativePF is specified. In that case the | |||
// PixelBuffer will remain in its native format and encoder will have | |||
// to handle any conversion itself. | |||
// | |||
// The Palette will always be in the PixelFormat specified in | |||
// ConnParams. An empty palette indicates a large number of colours, | |||
// but could still be less than maxPaletteSize. | |||
virtual void writeRect(const PixelBuffer* pb, const Palette& palette)=0; | |||
// writeSolidRect() is a short cut in order to encode single colour | |||
// rectangles efficiently without having to create a fake single | |||
// colour PixelBuffer. The colour argument follows the same semantics | |||
// as the PixelBuffer for writeRect(). | |||
// | |||
// Note that there is a default implementation that can be called | |||
// using Encoder::writeSolidRect() in the event that there is no | |||
// efficient short cut. | |||
virtual void writeSolidRect(int width, int height, | |||
const PixelFormat& pf, | |||
const rdr::U8* colour)=0; | |||
protected: | |||
// Helper method for redirecting a single colour palette to the | |||
// short cut method. | |||
void writeSolidRect(const PixelBuffer* pb, const Palette& palette); | |||
public: | |||
const int encoding; | |||
const enum EncoderFlags flags; | |||
static bool supported(int encoding); | |||
static Encoder* createEncoder(int encoding, SConnection* conn); | |||
// Maximum size of the palette per rect | |||
const unsigned int maxPaletteSize; | |||
protected: | |||
SConnection* conn; |
@@ -1,5 +1,6 @@ | |||
/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. | |||
* Copyright (C) 2005 Constantin Kaplinsky. All Rights Reserved. | |||
* Copyright 2014 Pierre Ossman for Cendio AB | |||
* | |||
* This is free software; you can redistribute it and/or modify | |||
* it under the terms of the GNU General Public License as published by | |||
@@ -17,10 +18,8 @@ | |||
* USA. | |||
*/ | |||
#include <rfb/encodings.h> | |||
#include <rfb/SMsgWriter.h> | |||
#include <rfb/SConnection.h> | |||
#include <rfb/HextileEncoder.h> | |||
#include <rfb/PixelFormat.h> | |||
#include <rfb/PixelBuffer.h> | |||
#include <rfb/Configuration.h> | |||
@@ -45,7 +44,8 @@ BoolParameter improvedHextile("ImprovedHextile", | |||
#include <rfb/hextileEncodeBetter.h> | |||
#undef BPP | |||
HextileEncoder::HextileEncoder(SConnection* conn) : Encoder(conn) | |||
HextileEncoder::HextileEncoder(SConnection* conn) : | |||
Encoder(conn, encodingHextile, EncoderPlain, -1) | |||
{ | |||
} | |||
@@ -53,33 +53,54 @@ HextileEncoder::~HextileEncoder() | |||
{ | |||
} | |||
void HextileEncoder::writeRect(const Rect& r, PixelBuffer* pb) | |||
bool HextileEncoder::isSupported() | |||
{ | |||
return conn->cp.supportsEncoding(encodingHextile); | |||
} | |||
void HextileEncoder::writeRect(const PixelBuffer* pb, const Palette& palette) | |||
{ | |||
conn->writer()->startRect(r, encodingHextile); | |||
rdr::OutStream* os = conn->getOutStream(); | |||
const PixelFormat& pf = conn->cp.pf(); | |||
switch (pf.bpp) { | |||
switch (pb->getPF().bpp) { | |||
case 8: | |||
if (improvedHextile) { | |||
hextileEncodeBetter8(r, os, pf, pb); | |||
hextileEncodeBetter8(os, pb); | |||
} else { | |||
hextileEncode8(r, os, pf, pb); | |||
hextileEncode8(os, pb); | |||
} | |||
break; | |||
case 16: | |||
if (improvedHextile) { | |||
hextileEncodeBetter16(r, os, pf, pb); | |||
hextileEncodeBetter16(os, pb); | |||
} else { | |||
hextileEncode16(r, os, pf, pb); | |||
hextileEncode16(os, pb); | |||
} | |||
break; | |||
case 32: | |||
if (improvedHextile) { | |||
hextileEncodeBetter32(r, os, pf, pb); | |||
hextileEncodeBetter32(os, pb); | |||
} else { | |||
hextileEncode32(r, os, pf, pb); | |||
hextileEncode32(os, pb); | |||
} | |||
break; | |||
} | |||
conn->writer()->endRect(); | |||
} | |||
void HextileEncoder::writeSolidRect(int width, int height, | |||
const PixelFormat& pf, | |||
const rdr::U8* colour) | |||
{ | |||
rdr::OutStream* os; | |||
int tiles; | |||
os = conn->getOutStream(); | |||
tiles = ((width + 15)/16) * ((height + 15)/16); | |||
os->writeU8(hextileBgSpecified); | |||
os->writeBytes(colour, pf.bpp/8); | |||
tiles--; | |||
while (tiles--) | |||
os->writeU8(0); | |||
} |
@@ -1,4 +1,5 @@ | |||
/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. | |||
* Copyright 2014 Pierre Ossman for Cendio AB | |||
* | |||
* This is free software; you can redistribute it and/or modify | |||
* it under the terms of the GNU General Public License as published by | |||
@@ -26,7 +27,11 @@ namespace rfb { | |||
public: | |||
HextileEncoder(SConnection* conn); | |||
virtual ~HextileEncoder(); | |||
virtual void writeRect(const Rect& r, PixelBuffer* pb); | |||
virtual bool isSupported(); | |||
virtual void writeRect(const PixelBuffer* pb, const Palette& palette); | |||
virtual void writeSolidRect(int width, int height, | |||
const PixelFormat& pf, | |||
const rdr::U8* colour); | |||
}; | |||
} | |||
#endif |
@@ -1,4 +1,5 @@ | |||
/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. | |||
* Copyright 2014 Pierre Ossman for Cendio AB | |||
* | |||
* This is free software; you can redistribute it and/or modify | |||
* it under the terms of the GNU General Public License as published by | |||
@@ -17,10 +18,10 @@ | |||
*/ | |||
#include <rdr/OutStream.h> | |||
#include <rfb/encodings.h> | |||
#include <rfb/SMsgWriter.h> | |||
#include <rfb/SConnection.h> | |||
#include <rfb/PixelFormat.h> | |||
#include <rfb/PixelBuffer.h> | |||
#include <rfb/Palette.h> | |||
#include <rfb/RREEncoder.h> | |||
using namespace rfb; | |||
@@ -35,7 +36,8 @@ using namespace rfb; | |||
#include <rfb/rreEncode.h> | |||
#undef BPP | |||
RREEncoder::RREEncoder(SConnection* conn) : RawEncoder(conn) | |||
RREEncoder::RREEncoder(SConnection* conn) : | |||
Encoder(conn, encodingRRE, EncoderPlain, -1) | |||
{ | |||
} | |||
@@ -43,30 +45,71 @@ RREEncoder::~RREEncoder() | |||
{ | |||
} | |||
void RREEncoder::writeRect(const Rect& r, PixelBuffer* pb) | |||
bool RREEncoder::isSupported() | |||
{ | |||
int w = r.width(); | |||
int h = r.height(); | |||
rdr::U8* imageBuf = conn->writer()->getImageBuf(w*h); | |||
pb->getImage(conn->cp.pf(), imageBuf, r); | |||
return conn->cp.supportsEncoding(encodingRRE); | |||
} | |||
mos.clear(); | |||
void RREEncoder::writeRect(const PixelBuffer* pb, const Palette& palette) | |||
{ | |||
rdr::U8* imageBuf; | |||
int stride; | |||
rdr::U32 bg; | |||
int nSubrects = -1; | |||
switch (conn->cp.pf().bpp) { | |||
case 8: nSubrects = rreEncode8(imageBuf, w, h, &mos); break; | |||
case 16: nSubrects = rreEncode16(imageBuf, w, h, &mos); break; | |||
case 32: nSubrects = rreEncode32(imageBuf, w, h, &mos); break; | |||
} | |||
if (nSubrects < 0) { | |||
RawEncoder::writeRect(r, pb); | |||
int w = pb->width(); | |||
int h = pb->height(); | |||
if (palette.size() == 1) { | |||
Encoder::writeSolidRect(pb, palette); | |||
return; | |||
} | |||
conn->writer()->startRect(r, encodingRRE); | |||
// We have to have our own copy of the data as we modify it as | |||
// we find subrects. | |||
bufferCopy.setPF(pb->getPF()); | |||
bufferCopy.setSize(w, h); | |||
imageBuf = bufferCopy.getBufferRW(pb->getRect(), &stride); | |||
pb->getImage(imageBuf, pb->getRect()); | |||
if (palette.size() > 0) | |||
bg = palette.getColour(0); | |||
else { | |||
// Some crazy person is using this encoder for high colour | |||
// data. Just pick the first pixel as the background colour. | |||
bg = 0; | |||
memcpy(&bg, imageBuf, pb->getPF().bpp/8); | |||
} | |||
int nSubrects = -1; | |||
switch (pb->getPF().bpp) { | |||
case 8: | |||
nSubrects = rreEncode8((rdr::U8*)imageBuf, w, h, &mos, bg); | |||
break; | |||
case 16: | |||
nSubrects = rreEncode16((rdr::U16*)imageBuf, w, h, &mos, bg); | |||
break; | |||
case 32: | |||
nSubrects = rreEncode32((rdr::U32*)imageBuf, w, h, &mos, bg); | |||
break; | |||
} | |||
bufferCopy.commitBufferRW(pb->getRect()); | |||
rdr::OutStream* os = conn->getOutStream(); | |||
os->writeU32(nSubrects); | |||
os->writeBytes(mos.data(), mos.length()); | |||
conn->writer()->endRect(); | |||
mos.clear(); | |||
} | |||
void RREEncoder::writeSolidRect(int width, int height, | |||
const PixelFormat& pf, | |||
const rdr::U8* colour) | |||
{ | |||
rdr::OutStream* os; | |||
os = conn->getOutStream(); | |||
os->writeU32(0); | |||
os->writeBytes(colour, pf.bpp/8); | |||
} |
@@ -1,4 +1,5 @@ | |||
/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. | |||
* Copyright 2014 Pierre Ossman for Cendio AB | |||
* | |||
* This is free software; you can redistribute it and/or modify | |||
* it under the terms of the GNU General Public License as published by | |||
@@ -19,17 +20,23 @@ | |||
#define __RFB_RREENCODER_H__ | |||
#include <rdr/MemOutStream.h> | |||
#include <rfb/RawEncoder.h> | |||
#include <rfb/Encoder.h> | |||
#include <rfb/PixelBuffer.h> | |||
namespace rfb { | |||
class RREEncoder : public RawEncoder { | |||
class RREEncoder : public Encoder { | |||
public: | |||
RREEncoder(SConnection* conn); | |||
virtual ~RREEncoder(); | |||
virtual void writeRect(const Rect& r, PixelBuffer* pb); | |||
virtual bool isSupported(); | |||
virtual void writeRect(const PixelBuffer* pb, const Palette& palette); | |||
virtual void writeSolidRect(int width, int height, | |||
const PixelFormat& pf, | |||
const rdr::U8* colour); | |||
private: | |||
rdr::MemOutStream mos; | |||
ManagedPixelBuffer bufferCopy; | |||
}; | |||
} | |||
#endif |
@@ -1,4 +1,5 @@ | |||
/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. | |||
* Copyright 2014 Pierre Ossman for Cendio AB | |||
* | |||
* This is free software; you can redistribute it and/or modify | |||
* it under the terms of the GNU General Public License as published by | |||
@@ -17,14 +18,14 @@ | |||
*/ | |||
#include <rdr/OutStream.h> | |||
#include <rfb/encodings.h> | |||
#include <rfb/SMsgWriter.h> | |||
#include <rfb/SConnection.h> | |||
#include <rfb/PixelBuffer.h> | |||
#include <rfb/RawEncoder.h> | |||
using namespace rfb; | |||
RawEncoder::RawEncoder(SConnection* conn) : Encoder(conn) | |||
RawEncoder::RawEncoder(SConnection* conn) : | |||
Encoder(conn, encodingRaw, EncoderPlain, -1) | |||
{ | |||
} | |||
@@ -32,13 +33,44 @@ RawEncoder::~RawEncoder() | |||
{ | |||
} | |||
void RawEncoder::writeRect(const Rect& r, PixelBuffer* pb) | |||
bool RawEncoder::isSupported() | |||
{ | |||
rdr::U8* buf = conn->writer()->getImageBuf(r.area()); | |||
// Implicitly required; | |||
return true; | |||
} | |||
void RawEncoder::writeRect(const PixelBuffer* pb, const Palette& palette) | |||
{ | |||
const rdr::U8* buffer; | |||
int stride; | |||
rdr::OutStream* os; | |||
int h, line_bytes, stride_bytes; | |||
buffer = pb->getBuffer(pb->getRect(), &stride); | |||
os = conn->getOutStream(); | |||
h = pb->height(); | |||
line_bytes = pb->width() * pb->getPF().bpp/8; | |||
stride_bytes = stride * pb->getPF().bpp/8; | |||
while (h--) { | |||
os->writeBytes(buffer, line_bytes); | |||
buffer += stride_bytes; | |||
} | |||
} | |||
void RawEncoder::writeSolidRect(int width, int height, | |||
const PixelFormat& pf, | |||
const rdr::U8* colour) | |||
{ | |||
rdr::OutStream* os; | |||
int pixels, pixel_size; | |||
pb->getImage(conn->cp.pf(), buf, r); | |||
os = conn->getOutStream(); | |||
conn->writer()->startRect(r, encodingRaw); | |||
conn->getOutStream()->writeBytes(buf, r.area() * conn->cp.pf().bpp/8); | |||
conn->writer()->endRect(); | |||
pixels = width*height; | |||
pixel_size = pf.bpp/8; | |||
while (pixels--) | |||
os->writeBytes(colour, pixel_size); | |||
} |
@@ -1,4 +1,5 @@ | |||
/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. | |||
* Copyright 2014 Pierre Ossman for Cendio AB | |||
* | |||
* This is free software; you can redistribute it and/or modify | |||
* it under the terms of the GNU General Public License as published by | |||
@@ -26,7 +27,11 @@ namespace rfb { | |||
public: | |||
RawEncoder(SConnection* conn); | |||
virtual ~RawEncoder(); | |||
virtual void writeRect(const Rect& r, PixelBuffer* pb); | |||
virtual bool isSupported(); | |||
virtual void writeRect(const PixelBuffer* pb, const Palette& palette); | |||
virtual void writeSolidRect(int width, int height, | |||
const PixelFormat& pf, | |||
const rdr::U8* colour); | |||
}; | |||
} | |||
#endif |
@@ -34,12 +34,11 @@ using namespace rfb; | |||
static LogWriter vlog("SMsgWriter"); | |||
SMsgWriter::SMsgWriter(ConnParams* cp_, rdr::OutStream* os_) | |||
: imageBufIdealSize(0), cp(cp_), os(os_), currentEncoding(0), | |||
: cp(cp_), os(os_), currentEncoding(0), | |||
nRectsInUpdate(0), nRectsInHeader(0), | |||
needSetDesktopSize(false), needExtendedDesktopSize(false), | |||
needSetDesktopName(false), needSetCursor(false), needSetXCursor(false), | |||
lenBeforeRect(0), updatesSent(0), rawBytesEquivalent(0), | |||
imageBuf(0), imageBufSize(0) | |||
lenBeforeRect(0), updatesSent(0), rawBytesEquivalent(0) | |||
{ | |||
for (int i = 0; i <= encodingMax; i++) { | |||
bytesSent[i] = 0; | |||
@@ -60,7 +59,6 @@ SMsgWriter::~SMsgWriter() | |||
} | |||
vlog.info(" raw bytes equivalent %llu, compression ratio %f", | |||
rawBytesEquivalent, (double)rawBytesEquivalent / bytes); | |||
delete [] imageBuf; | |||
} | |||
void SMsgWriter::writeServerInit() | |||
@@ -316,26 +314,6 @@ void SMsgWriter::endRect() | |||
} | |||
} | |||
rdr::U8* SMsgWriter::getImageBuf(int required, int requested, int* nPixels) | |||
{ | |||
int requiredBytes = required * (cp->pf().bpp / 8); | |||
int requestedBytes = requested * (cp->pf().bpp / 8); | |||
int size = requestedBytes; | |||
if (size > imageBufIdealSize) size = imageBufIdealSize; | |||
if (size < requiredBytes) | |||
size = requiredBytes; | |||
if (imageBufSize < size) { | |||
imageBufSize = size; | |||
delete [] imageBuf; | |||
imageBuf = new rdr::U8[imageBufSize]; | |||
} | |||
if (nPixels) | |||
*nPixels = imageBufSize / (cp->pf().bpp / 8); | |||
return imageBuf; | |||
} | |||
void SMsgWriter::startMsg(int type) | |||
{ | |||
os->writeU8(type); |
@@ -109,15 +109,11 @@ namespace rfb { | |||
void startRect(const Rect& r, int enc); | |||
void endRect(); | |||
rdr::U8* getImageBuf(int required, int requested=0, int* nPixels=0); | |||
int getUpdatesSent() { return updatesSent; } | |||
int getRectsSent(int encoding) { return rectsSent[encoding]; } | |||
int getBytesSent(int encoding) { return bytesSent[encoding]; } | |||
rdr::U64 getRawBytesEquivalent() { return rawBytesEquivalent; } | |||
int imageBufIdealSize; | |||
protected: | |||
void startMsg(int type); | |||
void endMsg(); | |||
@@ -159,9 +155,6 @@ namespace rfb { | |||
int rectsSent[encodingMax+1]; | |||
rdr::U64 rawBytesEquivalent; | |||
rdr::U8* imageBuf; | |||
int imageBufSize; | |||
typedef struct { | |||
rdr::U16 reason, result; | |||
int fb_width, fb_height; |
@@ -0,0 +1,32 @@ | |||
/* Copyright (C) 2000-2003 Constantin Kaplinsky. All Rights Reserved. | |||
* | |||
* This is free software; you can redistribute it and/or modify | |||
* it under the terms of the GNU General Public License as published by | |||
* the Free Software Foundation; either version 2 of the License, or | |||
* (at your option) any later version. | |||
* | |||
* This software is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
* GNU General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU General Public License | |||
* along with this software; if not, write to the Free Software | |||
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, | |||
* USA. | |||
*/ | |||
#ifndef __RFB_TIGHTCONSTANTS_H__ | |||
#define __RFB_TIGHTCONSTANTS_H__ | |||
namespace rfb { | |||
// Compression control | |||
const unsigned int tightExplicitFilter = 0x04; | |||
const unsigned int tightFill = 0x08; | |||
const unsigned int tightJpeg = 0x09; | |||
const unsigned int tightMaxSubencoding = 0x09; | |||
// Filters to improve compression efficiency | |||
const unsigned int tightFilterCopy = 0x00; | |||
const unsigned int tightFilterPalette = 0x01; | |||
const unsigned int tightFilterGradient = 0x02; | |||
} | |||
#endif |
@@ -64,17 +64,6 @@ namespace rfb { | |||
PixelFormat serverpf; | |||
bool directDecode; | |||
}; | |||
// Compression control | |||
const unsigned int rfbTightExplicitFilter = 0x04; | |||
const unsigned int rfbTightFill = 0x08; | |||
const unsigned int rfbTightJpeg = 0x09; | |||
const unsigned int rfbTightMaxSubencoding = 0x09; | |||
// Filters to improve compression efficiency | |||
const unsigned int rfbTightFilterCopy = 0x00; | |||
const unsigned int rfbTightFilterPalette = 0x01; | |||
const unsigned int rfbTightFilterGradient = 0x02; | |||
} | |||
#endif |
@@ -1,5 +1,6 @@ | |||
/* Copyright (C) 2000-2003 Constantin Kaplinsky. All Rights Reserved. | |||
* Copyright (C) 2011 D. R. Commander. All Rights Reserved. | |||
* Copyright 2014 Pierre Ossman for Cendio AB | |||
* | |||
* This is free software; you can redistribute it and/or modify | |||
* it under the terms of the GNU General Public License as published by | |||
@@ -16,399 +17,202 @@ | |||
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, | |||
* USA. | |||
*/ | |||
#include <assert.h> | |||
#include <rdr/OutStream.h> | |||
#include <rfb/PixelBuffer.h> | |||
#include <rfb/Palette.h> | |||
#include <rfb/encodings.h> | |||
#include <rfb/ConnParams.h> | |||
#include <rfb/SMsgWriter.h> | |||
#include <rfb/SConnection.h> | |||
#include <rfb/TightEncoder.h> | |||
#include <rfb/TightConstants.h> | |||
using namespace rfb; | |||
// Minimum amount of data to be compressed. This value should not be | |||
// changed, doing so will break compatibility with existing clients. | |||
#define TIGHT_MIN_TO_COMPRESS 12 | |||
// Adjustable parameters. | |||
// FIXME: Get rid of #defines | |||
#define TIGHT_MAX_SPLIT_TILE_SIZE 16 | |||
#define TIGHT_MIN_SPLIT_RECT_SIZE 4096 | |||
#define TIGHT_MIN_SOLID_SUBRECT_SIZE 2048 | |||
struct TightConf { | |||
int idxZlibLevel, monoZlibLevel, rawZlibLevel; | |||
}; | |||
// | |||
// Compression level stuff. The following array contains various | |||
// encoder parameters for each of 10 compression levels (0..9). | |||
// Last three parameters correspond to JPEG quality levels (0..9). | |||
// Compression level stuff. The following array contains zlib | |||
// settings for each of 10 compression levels (0..9). | |||
// | |||
// NOTE: The parameters used in this encoder are the result of painstaking | |||
// research by The VirtualGL Project using RFB session captures from a variety | |||
// of both 2D and 3D applications. See http://www.VirtualGL.org for the full | |||
// reports. | |||
// NOTE: The JPEG quality and subsampling levels below were obtained | |||
// experimentally by the VirtualGL Project. They represent the approximate | |||
// average compression ratios listed below, as measured across the set of | |||
// every 10th frame in the SPECviewperf 9 benchmark suite. | |||
// | |||
// 9 = JPEG quality 100, no subsampling (ratio ~= 10:1) | |||
// [this should be lossless, except for round-off error] | |||
// 8 = JPEG quality 92, no subsampling (ratio ~= 20:1) | |||
// [this should be perceptually lossless, based on current research] | |||
// 7 = JPEG quality 86, no subsampling (ratio ~= 25:1) | |||
// 6 = JPEG quality 79, no subsampling (ratio ~= 30:1) | |||
// 5 = JPEG quality 77, 4:2:2 subsampling (ratio ~= 40:1) | |||
// 4 = JPEG quality 62, 4:2:2 subsampling (ratio ~= 50:1) | |||
// 3 = JPEG quality 42, 4:2:2 subsampling (ratio ~= 60:1) | |||
// 2 = JPEG quality 41, 4:2:0 subsampling (ratio ~= 70:1) | |||
// 1 = JPEG quality 29, 4:2:0 subsampling (ratio ~= 80:1) | |||
// 0 = JPEG quality 15, 4:2:0 subsampling (ratio ~= 100:1) | |||
const TIGHT_CONF TightEncoder::conf[10] = { | |||
{ 65536, 2048, 6, 0, 0, 0, 4, 24, 15, subsample4X }, // 0 | |||
{ 65536, 2048, 6, 1, 1, 1, 8, 24, 29, subsample4X }, // 1 | |||
{ 65536, 2048, 8, 3, 3, 2, 24, 96, 41, subsample4X }, // 2 | |||
{ 65536, 2048, 12, 5, 5, 2, 32, 96, 42, subsample2X }, // 3 | |||
{ 65536, 2048, 12, 6, 7, 3, 32, 96, 62, subsample2X }, // 4 | |||
{ 65536, 2048, 12, 7, 8, 4, 32, 96, 77, subsample2X }, // 5 | |||
{ 65536, 2048, 16, 7, 8, 5, 32, 96, 79, subsampleNone }, // 6 | |||
{ 65536, 2048, 16, 8, 9, 6, 64, 96, 86, subsampleNone }, // 7 | |||
{ 65536, 2048, 24, 9, 9, 7, 64, 96, 92, subsampleNone }, // 8 | |||
{ 65536, 2048, 32, 9, 9, 9, 96, 96,100, subsampleNone } // 9 | |||
static const TightConf conf[10] = { | |||
{ 0, 0, 0 }, // 0 | |||
{ 1, 1, 1 }, // 1 | |||
{ 3, 3, 2 }, // 2 | |||
{ 5, 5, 2 }, // 3 | |||
{ 6, 7, 3 }, // 4 | |||
{ 7, 8, 4 }, // 5 | |||
{ 7, 8, 5 }, // 6 | |||
{ 8, 9, 6 }, // 7 | |||
{ 9, 9, 7 }, // 8 | |||
{ 9, 9, 9 } // 9 | |||
}; | |||
const int TightEncoder::defaultCompressLevel = 2; | |||
// | |||
// Including BPP-dependent implementation of the encoder. | |||
// | |||
#define BPP 8 | |||
#include <rfb/tightEncode.h> | |||
#undef BPP | |||
#define BPP 16 | |||
#include <rfb/tightEncode.h> | |||
#undef BPP | |||
#define BPP 32 | |||
#include <rfb/tightEncode.h> | |||
#undef BPP | |||
TightEncoder::TightEncoder(SConnection* conn) : Encoder(conn) | |||
TightEncoder::TightEncoder(SConnection* conn) : | |||
Encoder(conn, encodingTight, EncoderPlain, 256) | |||
{ | |||
setCompressLevel(defaultCompressLevel); | |||
setQualityLevel(-1); | |||
setCompressLevel(-1); | |||
} | |||
TightEncoder::~TightEncoder() | |||
{ | |||
} | |||
bool TightEncoder::isSupported() | |||
{ | |||
return conn->cp.supportsEncoding(encodingTight); | |||
} | |||
void TightEncoder::setCompressLevel(int level) | |||
{ | |||
if (level >= 0 && level <= 9) { | |||
pconf = &conf[level]; | |||
} else { | |||
pconf = &conf[defaultCompressLevel]; | |||
} | |||
if (level < 0 || level > 9) | |||
level = 2; | |||
idxZlibLevel = conf[level].idxZlibLevel; | |||
monoZlibLevel = conf[level].idxZlibLevel; | |||
rawZlibLevel = conf[level].rawZlibLevel; | |||
} | |||
void TightEncoder::setQualityLevel(int level) | |||
void TightEncoder::writeRect(const PixelBuffer* pb, const Palette& palette) | |||
{ | |||
if (level >= 0 && level <= 9) { | |||
jpegQuality = conf[level].jpegQuality; | |||
jpegSubsampling = conf[level].jpegSubsampling; | |||
} else { | |||
jpegQuality = -1; | |||
jpegSubsampling = subsampleUndefined; | |||
switch (palette.size()) { | |||
case 0: | |||
writeFullColourRect(pb, palette); | |||
break; | |||
case 1: | |||
Encoder::writeSolidRect(pb, palette); | |||
break; | |||
case 2: | |||
writeMonoRect(pb, palette); | |||
break; | |||
default: | |||
writeIndexedRect(pb, palette); | |||
} | |||
} | |||
void TightEncoder::setFineQualityLevel(int quality, int subsampling) | |||
void TightEncoder::writeSolidRect(int width, int height, | |||
const PixelFormat& pf, | |||
const rdr::U8* colour) | |||
{ | |||
jpegQuality = quality; | |||
jpegSubsampling = subsampling; | |||
rdr::OutStream* os; | |||
os = conn->getOutStream(); | |||
os->writeU8(tightFill << 4); | |||
writePixels(colour, pf, 1, os); | |||
} | |||
bool TightEncoder::checkSolidTile(Rect& r, rdr::U32* colorPtr, | |||
bool needSameColor) | |||
void TightEncoder::writeMonoRect(const PixelBuffer* pb, const Palette& palette) | |||
{ | |||
switch (serverpf.bpp) { | |||
const rdr::U8* buffer; | |||
int stride; | |||
buffer = pb->getBuffer(pb->getRect(), &stride); | |||
switch (pb->getPF().bpp) { | |||
case 32: | |||
return checkSolidTile32(r, colorPtr, needSameColor); | |||
writeMonoRect(pb->width(), pb->height(), (rdr::U32*)buffer, stride, | |||
pb->getPF(), palette); | |||
break; | |||
case 16: | |||
return checkSolidTile16(r, colorPtr, needSameColor); | |||
writeMonoRect(pb->width(), pb->height(), (rdr::U16*)buffer, stride, | |||
pb->getPF(), palette); | |||
break; | |||
default: | |||
return checkSolidTile8(r, colorPtr, needSameColor); | |||
writeMonoRect(pb->width(), pb->height(), (rdr::U8*)buffer, stride, | |||
pb->getPF(), palette); | |||
} | |||
} | |||
void TightEncoder::findBestSolidArea(Rect& r, rdr::U32 colorValue, Rect& bestr) | |||
void TightEncoder::writeIndexedRect(const PixelBuffer* pb, const Palette& palette) | |||
{ | |||
int dx, dy, dw, dh; | |||
int w_prev; | |||
Rect sr; | |||
int w_best = 0, h_best = 0; | |||
bestr.tl.x = bestr.br.x = r.tl.x; | |||
bestr.tl.y = bestr.br.y = r.tl.y; | |||
w_prev = r.width(); | |||
for (dy = r.tl.y; dy < r.br.y; dy += TIGHT_MAX_SPLIT_TILE_SIZE) { | |||
dh = (dy + TIGHT_MAX_SPLIT_TILE_SIZE <= r.br.y) ? | |||
TIGHT_MAX_SPLIT_TILE_SIZE : (r.br.y - dy); | |||
dw = (w_prev > TIGHT_MAX_SPLIT_TILE_SIZE) ? | |||
TIGHT_MAX_SPLIT_TILE_SIZE : w_prev; | |||
sr.setXYWH(r.tl.x, dy, dw, dh); | |||
if (!checkSolidTile(sr, &colorValue, true)) | |||
break; | |||
for (dx = r.tl.x + dw; dx < r.tl.x + w_prev;) { | |||
dw = (dx + TIGHT_MAX_SPLIT_TILE_SIZE <= r.tl.x + w_prev) ? | |||
TIGHT_MAX_SPLIT_TILE_SIZE : (r.tl.x + w_prev - dx); | |||
sr.setXYWH(dx, dy, dw, dh); | |||
if (!checkSolidTile(sr, &colorValue, true)) | |||
break; | |||
dx += dw; | |||
} | |||
const rdr::U8* buffer; | |||
int stride; | |||
w_prev = dx - r.tl.x; | |||
if (w_prev * (dy + dh - r.tl.y) > w_best * h_best) { | |||
w_best = w_prev; | |||
h_best = dy + dh - r.tl.y; | |||
} | |||
} | |||
buffer = pb->getBuffer(pb->getRect(), &stride); | |||
bestr.br.x = bestr.tl.x + w_best; | |||
bestr.br.y = bestr.tl.y + h_best; | |||
switch (pb->getPF().bpp) { | |||
case 32: | |||
writeIndexedRect(pb->width(), pb->height(), (rdr::U32*)buffer, stride, | |||
pb->getPF(), palette); | |||
break; | |||
case 16: | |||
writeIndexedRect(pb->width(), pb->height(), (rdr::U16*)buffer, stride, | |||
pb->getPF(), palette); | |||
break; | |||
default: | |||
// It's more efficient to just do raw pixels | |||
writeFullColourRect(pb, palette); | |||
} | |||
} | |||
void TightEncoder::extendSolidArea(const Rect& r, rdr::U32 colorValue, | |||
Rect& er) | |||
void TightEncoder::writeFullColourRect(const PixelBuffer* pb, const Palette& palette) | |||
{ | |||
int cx, cy; | |||
Rect sr; | |||
// Try to extend the area upwards. | |||
for (cy = er.tl.y - 1; ; cy--) { | |||
sr.setXYWH(er.tl.x, cy, er.width(), 1); | |||
if (cy < r.tl.y || !checkSolidTile(sr, &colorValue, true)) | |||
break; | |||
} | |||
er.tl.y = cy + 1; | |||
const int streamId = 0; | |||
// ... downwards. | |||
for (cy = er.br.y; ; cy++) { | |||
sr.setXYWH(er.tl.x, cy, er.width(), 1); | |||
if (cy >= r.br.y || !checkSolidTile(sr, &colorValue, true)) | |||
break; | |||
} | |||
er.br.y = cy; | |||
rdr::OutStream* os; | |||
rdr::OutStream* zos; | |||
int length; | |||
// ... to the left. | |||
for (cx = er.tl.x - 1; ; cx--) { | |||
sr.setXYWH(cx, er.tl.y, 1, er.height()); | |||
if (cx < r.tl.x || !checkSolidTile(sr, &colorValue, true)) | |||
break; | |||
} | |||
er.tl.x = cx + 1; | |||
const rdr::U8* buffer; | |||
int stride, w, h; | |||
// ... to the right. | |||
for (cx = er.br.x; ; cx++) { | |||
sr.setXYWH(cx, er.tl.y, 1, er.height()); | |||
if (cx >= r.br.x || !checkSolidTile(sr, &colorValue, true)) | |||
break; | |||
} | |||
er.br.x = cx; | |||
} | |||
os = conn->getOutStream(); | |||
int TightEncoder::getNumRects(const Rect &r) | |||
{ | |||
ConnParams* cp = &conn->cp; | |||
const unsigned int w = r.width(); | |||
const unsigned int h = r.height(); | |||
// If last rect. encoding is enabled, we can use the higher-performance | |||
// code that pre-computes solid rectangles. In that case, we don't care | |||
// about the rectangle count. | |||
if (cp->supportsLastRect && w * h >= TIGHT_MIN_SPLIT_RECT_SIZE) | |||
return 0; | |||
// Will this rectangle split into subrects? | |||
bool rectTooBig = w > pconf->maxRectWidth || w * h > pconf->maxRectSize; | |||
if (!rectTooBig) | |||
return 1; | |||
// Compute max sub-rectangle size. | |||
const unsigned int subrectMaxWidth = | |||
(w > pconf->maxRectWidth) ? pconf->maxRectWidth : w; | |||
const unsigned int subrectMaxHeight = | |||
pconf->maxRectSize / subrectMaxWidth; | |||
// Return the number of subrects. | |||
return (((w - 1) / pconf->maxRectWidth + 1) * | |||
((h - 1) / subrectMaxHeight + 1)); | |||
} | |||
os->writeU8(streamId << 4); | |||
void TightEncoder::sendRectSimple(const Rect& r) | |||
{ | |||
// Shortcuts to rectangle coordinates and dimensions. | |||
const int x = r.tl.x; | |||
const int y = r.tl.y; | |||
const unsigned int w = r.width(); | |||
const unsigned int h = r.height(); | |||
// Encode small rects as is. | |||
bool rectTooBig = w > pconf->maxRectWidth || w * h > pconf->maxRectSize; | |||
if (!rectTooBig) { | |||
writeSubrect(r); | |||
return; | |||
} | |||
// Set up compression | |||
if ((pb->getPF().bpp != 32) || !pb->getPF().is888()) | |||
length = pb->getRect().area() * pb->getPF().bpp/8; | |||
else | |||
length = pb->getRect().area() * 3; | |||
// Compute max sub-rectangle size. | |||
const unsigned int subrectMaxWidth = | |||
(w > pconf->maxRectWidth) ? pconf->maxRectWidth : w; | |||
const unsigned int subrectMaxHeight = | |||
pconf->maxRectSize / subrectMaxWidth; | |||
// Split big rects into separately encoded subrects. | |||
Rect sr; | |||
unsigned int dx, dy, sw, sh; | |||
for (dy = 0; dy < h; dy += subrectMaxHeight) { | |||
for (dx = 0; dx < w; dx += pconf->maxRectWidth) { | |||
sw = (dx + pconf->maxRectWidth < w) ? pconf->maxRectWidth : w - dx; | |||
sh = (dy + subrectMaxHeight < h) ? subrectMaxHeight : h - dy; | |||
sr.setXYWH(x + dx, y + dy, sw, sh); | |||
writeSubrect(sr); | |||
} | |||
zos = getZlibOutStream(streamId, rawZlibLevel, length); | |||
// And then just dump all the raw pixels | |||
buffer = pb->getBuffer(pb->getRect(), &stride); | |||
h = pb->height(); | |||
while (h--) { | |||
writePixels(buffer, pb->getPF(), pb->width(), zos); | |||
buffer += stride * pb->getPF().bpp/8; | |||
} | |||
// Finish the zlib stream | |||
flushZlibOutStream(zos); | |||
} | |||
void TightEncoder::writeRect(const Rect& _r, PixelBuffer* _pb) | |||
void TightEncoder::writePixels(const rdr::U8* buffer, const PixelFormat& pf, | |||
unsigned int count, rdr::OutStream* os) | |||
{ | |||
pb = _pb; | |||
serverpf = pb->getPF(); | |||
ConnParams* cp = &conn->cp; | |||
clientpf = cp->pf(); | |||
// Shortcuts to rectangle coordinates and dimensions. | |||
Rect r = _r; | |||
int x = r.tl.x; | |||
int y = r.tl.y; | |||
int w = r.width(); | |||
int h = r.height(); | |||
// Encode small rects as is. | |||
if (!cp->supportsLastRect || w * h < TIGHT_MIN_SPLIT_RECT_SIZE) { | |||
sendRectSimple(r); | |||
rdr::U8 rgb[2048]; | |||
if ((pf.bpp != 32) || !pf.is888()) { | |||
os->writeBytes(buffer, count * pf.bpp/8); | |||
return; | |||
} | |||
// Split big rects into separately encoded subrects. | |||
Rect sr, bestr; | |||
int dx, dy, dw, dh; | |||
rdr::U32 colorValue; | |||
int maxRectWidth = pconf->maxRectWidth; | |||
int nMaxWidth = (w > maxRectWidth) ? maxRectWidth : w; | |||
int nMaxRows = pconf->maxRectSize / nMaxWidth; | |||
// Try to find large solid-color areas and send them separately. | |||
for (dy = y; dy < y + h; dy += TIGHT_MAX_SPLIT_TILE_SIZE) { | |||
// If a rectangle becomes too large, send its upper part now. | |||
if (dy - y >= nMaxRows) { | |||
sr.setXYWH(x, y, w, nMaxRows); | |||
sendRectSimple(sr); | |||
r.tl.y += nMaxRows; | |||
y = r.tl.y; | |||
h = r.height(); | |||
} | |||
while (count) { | |||
int iter_count; | |||
dh = (dy + TIGHT_MAX_SPLIT_TILE_SIZE <= y + h) ? | |||
TIGHT_MAX_SPLIT_TILE_SIZE : (y + h - dy); | |||
for (dx = x; dx < x + w; dx += TIGHT_MAX_SPLIT_TILE_SIZE) { | |||
dw = (dx + TIGHT_MAX_SPLIT_TILE_SIZE <= x + w) ? | |||
TIGHT_MAX_SPLIT_TILE_SIZE : (x + w - dx); | |||
sr.setXYWH(dx, dy, dw, dh); | |||
if (checkSolidTile(sr, &colorValue, false)) { | |||
if (jpegSubsampling == subsampleGray && jpegQuality != -1) { | |||
rdr::U16 r, g, b; | |||
serverpf.rgbFromPixel(colorValue, &r, &g, &b); | |||
rdr::U32 lum = ((257 * r) + (504 * g) + (98 * b) | |||
+ 16500) / 1000; | |||
colorValue = lum + (lum << 8) + (lum << 16); | |||
} | |||
// Get dimensions of solid-color area. | |||
sr.setXYWH(dx, dy, r.br.x - dx, r.br.y - dy); | |||
findBestSolidArea(sr, colorValue, bestr); | |||
// Make sure a solid rectangle is large enough | |||
// (or the whole rectangle is of the same color). | |||
if (bestr.area() != r.area() | |||
&& bestr.area() < TIGHT_MIN_SOLID_SUBRECT_SIZE) | |||
continue; | |||
// Try to extend solid rectangle to maximum size. | |||
extendSolidArea(r, colorValue, bestr); | |||
// Send rectangles at top and left to solid-color area. | |||
if (bestr.tl.y != y) { | |||
sr.setXYWH(x, y, w, bestr.tl.y - y); | |||
sendRectSimple(sr); | |||
} | |||
if (bestr.tl.x != x) { | |||
sr.setXYWH(x, bestr.tl.y, bestr.tl.x - x, bestr.height()); | |||
writeRect(sr, _pb); | |||
} | |||
// Send solid-color rectangle. | |||
writeSubrect(bestr, true); | |||
// Send remaining rectangles (at right and bottom). | |||
if (bestr.br.x != r.br.x) { | |||
sr.setXYWH(bestr.br.x, bestr.tl.y, r.br.x - bestr.br.x, | |||
bestr.height()); | |||
writeRect(sr, _pb); | |||
} | |||
if (bestr.br.y != r.br.y) { | |||
sr.setXYWH(x, bestr.br.y, w, r.br.y - bestr.br.y); | |||
writeRect(sr, _pb); | |||
} | |||
return; | |||
} | |||
} | |||
} | |||
iter_count = sizeof(rgb)/3; | |||
if (iter_count > count) | |||
iter_count = count; | |||
// No suitable solid-color rectangles found. | |||
sendRectSimple(r); | |||
return; | |||
} | |||
void TightEncoder::writeSubrect(const Rect& r, bool forceSolid) | |||
{ | |||
mos.clear(); | |||
pf.rgbFromBuffer(rgb, buffer, iter_count); | |||
os->writeBytes(rgb, iter_count * 3); | |||
switch (clientpf.bpp) { | |||
case 8: | |||
tightEncode8(r, &mos, forceSolid); break; | |||
case 16: | |||
tightEncode16(r, &mos, forceSolid); break; | |||
case 32: | |||
tightEncode32(r, &mos, forceSolid); break; | |||
buffer += iter_count * pf.bpp/8; | |||
count -= iter_count; | |||
} | |||
conn->writer()->startRect(r, encodingTight); | |||
rdr::OutStream* os = conn->getOutStream(); | |||
os->writeBytes(mos.data(), mos.length()); | |||
conn->writer()->endRect(); | |||
} | |||
void TightEncoder::writeCompact(rdr::OutStream* os, rdr::U32 value) | |||
@@ -428,3 +232,52 @@ void TightEncoder::writeCompact(rdr::OutStream* os, rdr::U32 value) | |||
} | |||
} | |||
} | |||
rdr::OutStream* TightEncoder::getZlibOutStream(int streamId, int level, size_t length) | |||
{ | |||
// Minimum amount of data to be compressed. This value should not be | |||
// changed, doing so will break compatibility with existing clients. | |||
if (length < 12) | |||
return conn->getOutStream(); | |||
assert(streamId >= 0); | |||
assert(streamId < 4); | |||
zlibStreams[streamId].setUnderlying(&memStream); | |||
zlibStreams[streamId].setCompressionLevel(level); | |||
return &zlibStreams[streamId]; | |||
} | |||
void TightEncoder::flushZlibOutStream(rdr::OutStream* os_) | |||
{ | |||
rdr::OutStream* os; | |||
rdr::ZlibOutStream* zos; | |||
zos = dynamic_cast<rdr::ZlibOutStream*>(os_); | |||
if (zos == NULL) | |||
return; | |||
zos->flush(); | |||
zos->setUnderlying(NULL); | |||
os = conn->getOutStream(); | |||
writeCompact(os, memStream.length()); | |||
os->writeBytes(memStream.data(), memStream.length()); | |||
memStream.clear(); | |||
} | |||
// | |||
// Including BPP-dependent implementation of the encoder. | |||
// | |||
#define BPP 8 | |||
#include <rfb/TightEncoderBPP.cxx> | |||
#undef BPP | |||
#define BPP 16 | |||
#include <rfb/TightEncoderBPP.cxx> | |||
#undef BPP | |||
#define BPP 32 | |||
#include <rfb/TightEncoderBPP.cxx> | |||
#undef BPP |
@@ -1,5 +1,6 @@ | |||
/* Copyright (C) 2000-2003 Constantin Kaplinsky. All Rights Reserved. | |||
* Copyright (C) 2011 D. R. Commander | |||
* Copyright 2014 Pierre Ossman for Cendio AB | |||
* | |||
* This is free software; you can redistribute it and/or modify | |||
* it under the terms of the GNU General Public License as published by | |||
@@ -22,120 +23,59 @@ | |||
#include <rdr/MemOutStream.h> | |||
#include <rdr/ZlibOutStream.h> | |||
#include <rfb/Encoder.h> | |||
#include <rfb/JpegCompressor.h> | |||
#include <rfb/Palette.h> | |||
// FIXME: Check if specifying extern "C" is really necessary. | |||
#include <stdio.h> | |||
extern "C" { | |||
#include <jpeglib.h> | |||
} | |||
namespace rfb { | |||
class PixelBuffer; | |||
struct TIGHT_CONF { | |||
unsigned int maxRectSize, maxRectWidth; | |||
unsigned int monoMinRectSize; | |||
int idxZlibLevel, monoZlibLevel, rawZlibLevel; | |||
int idxMaxColorsDivisor; | |||
int palMaxColorsWithJPEG; | |||
int jpegQuality; | |||
int jpegSubsampling; | |||
}; | |||
// | |||
// Compression level stuff. The following array contains various | |||
// encoder parameters for each of 10 compression levels (0..9). | |||
// Last three parameters correspond to JPEG quality levels (0..9). | |||
// | |||
// NOTE: s_conf[9].maxRectSize should be >= s_conf[i].maxRectSize, | |||
// where i in [0..8]. RequiredBuffSize() method depends on this. | |||
// FIXME: Is this comment obsolete? | |||
// | |||
class TightEncoder : public Encoder { | |||
public: | |||
TightEncoder(SConnection* conn); | |||
virtual ~TightEncoder(); | |||
virtual void setCompressLevel(int level); | |||
virtual void setQualityLevel(int level); | |||
virtual void setFineQualityLevel(int quality, int subsampling); | |||
virtual int getNumRects(const Rect &r); | |||
virtual void writeRect(const Rect& r, PixelBuffer* pb); | |||
private: | |||
bool checkSolidTile(Rect& r, rdr::U32* colorPtr, bool needSameColor); | |||
void extendSolidArea(const Rect& r, rdr::U32 colorValue, Rect& er); | |||
void findBestSolidArea(Rect& r, rdr::U32 colorValue, Rect& bestr); | |||
void sendRectSimple(const Rect& r); | |||
void writeSubrect(const Rect& r, bool forceSolid = false); | |||
void writeCompact(rdr::OutStream* os, rdr::U32 value); | |||
void compressData(const void *buf, unsigned int length, | |||
rdr::ZlibOutStream *zos, int zlibLevel, | |||
rdr::OutStream *os); | |||
virtual bool isSupported(); | |||
void fastFillPalette8(const rdr::U8 *data, int stride, const Rect &r); | |||
void fastFillPalette16(const rdr::U16 *data, int stride, const Rect &r); | |||
void fastFillPalette32(const rdr::U32 *data, int stride, const Rect &r); | |||
void fillPalette8(rdr::U8 *data, int count); | |||
void fillPalette16(rdr::U16 *data, int count); | |||
void fillPalette32(rdr::U32 *data, int count); | |||
unsigned int packPixels8(rdr::U8 *buf, unsigned int count); | |||
unsigned int packPixels16(rdr::U16 *buf, unsigned int count); | |||
unsigned int packPixels32(rdr::U32 *buf, unsigned int count); | |||
void tightEncode8(const Rect& r, rdr::OutStream *os, bool forceSolid); | |||
void tightEncode16(const Rect& r, rdr::OutStream *os, bool forceSolid); | |||
void tightEncode32(const Rect& r, rdr::OutStream *os, bool forceSolid); | |||
bool checkSolidTile8(Rect& r, rdr::U32 *colorPtr, bool needSameColor); | |||
bool checkSolidTile16(Rect& r, rdr::U32 *colorPtr, bool needSameColor); | |||
bool checkSolidTile32(Rect& r, rdr::U32 *colorPtr, bool needSameColor); | |||
void encodeSolidRect8(rdr::U8 *buf, rdr::OutStream *os); | |||
void encodeSolidRect16(rdr::U16 *buf, rdr::OutStream *os); | |||
void encodeSolidRect32(rdr::U32 *buf, rdr::OutStream *os); | |||
void encodeFullColorRect8(rdr::U8 *buf, const Rect& r, rdr::OutStream *os); | |||
void encodeFullColorRect16(rdr::U16 *buf, const Rect& r, rdr::OutStream *os); | |||
void encodeFullColorRect32(rdr::U32 *buf, const Rect& r, rdr::OutStream *os); | |||
void encodeMonoRect8(rdr::U8 *buf, const Rect& r, rdr::OutStream *os); | |||
void encodeMonoRect16(rdr::U16 *buf, const Rect& r, rdr::OutStream *os); | |||
void encodeMonoRect32(rdr::U32 *buf, const Rect& r, rdr::OutStream *os); | |||
virtual void setCompressLevel(int level); | |||
void encodeIndexedRect16(rdr::U16 *buf, const Rect& r, rdr::OutStream *os); | |||
void encodeIndexedRect32(rdr::U32 *buf, const Rect& r, rdr::OutStream *os); | |||
virtual void writeRect(const PixelBuffer* pb, const Palette& palette); | |||
virtual void writeSolidRect(int width, int height, | |||
const PixelFormat& pf, | |||
const rdr::U8* colour); | |||
void encodeJpegRect16(rdr::U16 *buf, int stride, const Rect& r, | |||
rdr::OutStream *os); | |||
void encodeJpegRect32(rdr::U32 *buf, int stride, const Rect& r, | |||
rdr::OutStream *os); | |||
protected: | |||
void writeMonoRect(const PixelBuffer* pb, const Palette& palette); | |||
void writeIndexedRect(const PixelBuffer* pb, const Palette& palette); | |||
void writeFullColourRect(const PixelBuffer* pb, const Palette& palette); | |||
rdr::MemOutStream mos; | |||
rdr::ZlibOutStream zos[4]; | |||
JpegCompressor jc; | |||
PixelBuffer *pb; | |||
PixelFormat serverpf, clientpf; | |||
void writePixels(const rdr::U8* buffer, const PixelFormat& pf, | |||
unsigned int count, rdr::OutStream* os); | |||
bool pack24; | |||
int palMaxColors; | |||
Palette palette; | |||
void writeCompact(rdr::OutStream* os, rdr::U32 value); | |||
static const int defaultCompressLevel; | |||
static const TIGHT_CONF conf[]; | |||
rdr::OutStream* getZlibOutStream(int streamId, int level, size_t length); | |||
void flushZlibOutStream(rdr::OutStream* os); | |||
protected: | |||
// Preprocessor generated, optimised methods | |||
void writeMonoRect(int width, int height, | |||
const rdr::U8* buffer, int stride, | |||
const PixelFormat& pf, const Palette& palette); | |||
void writeMonoRect(int width, int height, | |||
const rdr::U16* buffer, int stride, | |||
const PixelFormat& pf, const Palette& palette); | |||
void writeMonoRect(int width, int height, | |||
const rdr::U32* buffer, int stride, | |||
const PixelFormat& pf, const Palette& palette); | |||
void writeIndexedRect(int width, int height, | |||
const rdr::U16* buffer, int stride, | |||
const PixelFormat& pf, const Palette& palette); | |||
void writeIndexedRect(int width, int height, | |||
const rdr::U32* buffer, int stride, | |||
const PixelFormat& pf, const Palette& palette); | |||
rdr::ZlibOutStream zlibStreams[4]; | |||
rdr::MemOutStream memStream; | |||
const TIGHT_CONF* pconf; | |||
int jpegQuality; | |||
int jpegSubsampling; | |||
int idxZlibLevel, monoZlibLevel, rawZlibLevel; | |||
}; | |||
} |
@@ -0,0 +1,165 @@ | |||
/* Copyright (C) 2000-2003 Constantin Kaplinsky. All Rights Reserved. | |||
* Copyright (C) 2011 D. R. Commander. All Rights Reserved. | |||
* Copyright 2014 Pierre Ossman for Cendio AB | |||
* | |||
* This is free software; you can redistribute it and/or modify | |||
* it under the terms of the GNU General Public License as published by | |||
* the Free Software Foundation; either version 2 of the License, or | |||
* (at your option) any later version. | |||
* | |||
* This software is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
* GNU General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU General Public License | |||
* along with this software; if not, write to the Free Software | |||
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, | |||
* USA. | |||
*/ | |||
#define CONCAT2(a,b) a##b | |||
#define CONCAT2E(a,b) CONCAT2(a,b) | |||
#define UBPP CONCAT2E(U,BPP) | |||
void TightEncoder::writeMonoRect(int width, int height, | |||
const rdr::UBPP* buffer, int stride, | |||
const PixelFormat& pf, | |||
const Palette& palette) | |||
{ | |||
rdr::OutStream* os; | |||
const int streamId = 1; | |||
rdr::UBPP pal[2]; | |||
int length; | |||
rdr::OutStream* zos; | |||
assert(palette.size() == 2); | |||
os = conn->getOutStream(); | |||
os->writeU8((streamId | tightExplicitFilter) << 4); | |||
os->writeU8(tightFilterPalette); | |||
// Write the palette | |||
pal[0] = (rdr::UBPP)palette.getColour(0); | |||
pal[1] = (rdr::UBPP)palette.getColour(1); | |||
os->writeU8(1); | |||
writePixels((rdr::U8*)pal, pf, 2, os); | |||
// Set up compression | |||
length = (width + 7)/8 * height; | |||
zos = getZlibOutStream(streamId, monoZlibLevel, length); | |||
// Encode the data | |||
rdr::UBPP bg; | |||
unsigned int value, mask; | |||
int pad, aligned_width; | |||
int x, y, bg_bits; | |||
bg = pal[0]; | |||
aligned_width = width - width % 8; | |||
pad = stride - width; | |||
for (y = 0; y < height; y++) { | |||
for (x = 0; x < aligned_width; x += 8) { | |||
for (bg_bits = 0; bg_bits < 8; bg_bits++) { | |||
if (*buffer++ != bg) | |||
break; | |||
} | |||
if (bg_bits == 8) { | |||
zos->writeU8(0); | |||
continue; | |||
} | |||
mask = 0x80 >> bg_bits; | |||
value = mask; | |||
for (bg_bits++; bg_bits < 8; bg_bits++) { | |||
mask >>= 1; | |||
if (*buffer++ != bg) { | |||
value |= mask; | |||
} | |||
} | |||
zos->writeU8(value); | |||
} | |||
if (x < width) { | |||
mask = 0x80; | |||
value = 0; | |||
for (; x < width; x++) { | |||
if (*buffer++ != bg) { | |||
value |= mask; | |||
} | |||
mask >>= 1; | |||
} | |||
zos->writeU8(value); | |||
} | |||
buffer += pad; | |||
} | |||
// Finish the zlib stream | |||
flushZlibOutStream(zos); | |||
} | |||
#if (BPP != 8) | |||
void TightEncoder::writeIndexedRect(int width, int height, | |||
const rdr::UBPP* buffer, int stride, | |||
const PixelFormat& pf, | |||
const Palette& palette) | |||
{ | |||
rdr::OutStream* os; | |||
const int streamId = 2; | |||
rdr::UBPP pal[256]; | |||
rdr::OutStream* zos; | |||
int pad; | |||
rdr::UBPP prevColour; | |||
unsigned char idx; | |||
assert(palette.size() > 0); | |||
assert(palette.size() <= 256); | |||
os = conn->getOutStream(); | |||
os->writeU8((streamId | tightExplicitFilter) << 4); | |||
os->writeU8(tightFilterPalette); | |||
// Write the palette | |||
for (int i = 0; i < palette.size(); i++) | |||
pal[i] = (rdr::UBPP)palette.getColour(i); | |||
os->writeU8(palette.size() - 1); | |||
writePixels((rdr::U8*)pal, pf, palette.size(), os); | |||
// Set up compression | |||
zos = getZlibOutStream(streamId, idxZlibLevel, width * height); | |||
// Encode the data | |||
pad = stride - width; | |||
prevColour = *buffer; | |||
idx = palette.lookup(*buffer); | |||
while (height--) { | |||
int w = width; | |||
while (w--) { | |||
if (*buffer != prevColour) { | |||
prevColour = *buffer; | |||
idx = palette.lookup(*buffer); | |||
} | |||
zos->writeU8(idx); | |||
buffer++; | |||
} | |||
buffer += pad; | |||
} | |||
// Finish the zlib stream | |||
flushZlibOutStream(zos); | |||
} | |||
#endif // #if (BPP != 8) |
@@ -0,0 +1,168 @@ | |||
/* Copyright (C) 2000-2003 Constantin Kaplinsky. All Rights Reserved. | |||
* Copyright (C) 2011 D. R. Commander. All Rights Reserved. | |||
* Copyright 2014 Pierre Ossman for Cendio AB | |||
* | |||
* This is free software; you can redistribute it and/or modify | |||
* it under the terms of the GNU General Public License as published by | |||
* the Free Software Foundation; either version 2 of the License, or | |||
* (at your option) any later version. | |||
* | |||
* This software is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
* GNU General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU General Public License | |||
* along with this software; if not, write to the Free Software | |||
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, | |||
* USA. | |||
*/ | |||
#include <rdr/OutStream.h> | |||
#include <rfb/encodings.h> | |||
#include <rfb/SConnection.h> | |||
#include <rfb/PixelBuffer.h> | |||
#include <rfb/TightJPEGEncoder.h> | |||
#include <rfb/TightConstants.h> | |||
using namespace rfb; | |||
struct TightJPEGConfiguration { | |||
int quality; | |||
int subsampling; | |||
}; | |||
// NOTE: The JPEG quality and subsampling levels below were obtained | |||
// experimentally by the VirtualGL Project. They represent the approximate | |||
// average compression ratios listed below, as measured across the set of | |||
// every 10th frame in the SPECviewperf 9 benchmark suite. | |||
// | |||
// 9 = JPEG quality 100, no subsampling (ratio ~= 10:1) | |||
// [this should be lossless, except for round-off error] | |||
// 8 = JPEG quality 92, no subsampling (ratio ~= 20:1) | |||
// [this should be perceptually lossless, based on current research] | |||
// 7 = JPEG quality 86, no subsampling (ratio ~= 25:1) | |||
// 6 = JPEG quality 79, no subsampling (ratio ~= 30:1) | |||
// 5 = JPEG quality 77, 4:2:2 subsampling (ratio ~= 40:1) | |||
// 4 = JPEG quality 62, 4:2:2 subsampling (ratio ~= 50:1) | |||
// 3 = JPEG quality 42, 4:2:2 subsampling (ratio ~= 60:1) | |||
// 2 = JPEG quality 41, 4:2:0 subsampling (ratio ~= 70:1) | |||
// 1 = JPEG quality 29, 4:2:0 subsampling (ratio ~= 80:1) | |||
// 0 = JPEG quality 15, 4:2:0 subsampling (ratio ~= 100:1) | |||
static const struct TightJPEGConfiguration conf[10] = { | |||
{ 15, subsample4X }, // 0 | |||
{ 29, subsample4X }, // 1 | |||
{ 41, subsample4X }, // 2 | |||
{ 42, subsample2X }, // 3 | |||
{ 62, subsample2X }, // 4 | |||
{ 77, subsample2X }, // 5 | |||
{ 79, subsampleNone }, // 6 | |||
{ 86, subsampleNone }, // 7 | |||
{ 92, subsampleNone }, // 8 | |||
{ 100, subsampleNone } // 9 | |||
}; | |||
TightJPEGEncoder::TightJPEGEncoder(SConnection* conn) : | |||
Encoder(conn, encodingTight, EncoderUseNativePF, -1), | |||
qualityLevel(-1), fineQuality(-1), fineSubsampling(subsampleUndefined) | |||
{ | |||
} | |||
TightJPEGEncoder::~TightJPEGEncoder() | |||
{ | |||
} | |||
bool TightJPEGEncoder::isSupported() | |||
{ | |||
if (!conn->cp.supportsEncoding(encodingTight)) | |||
return false; | |||
// Any one of these indicates support for JPEG | |||
if (conn->cp.qualityLevel != -1) | |||
return true; | |||
if (conn->cp.fineQualityLevel != -1) | |||
return true; | |||
if (conn->cp.subsampling != -1) | |||
return true; | |||
// Tight support, but not JPEG | |||
return false; | |||
} | |||
void TightJPEGEncoder::setQualityLevel(int level) | |||
{ | |||
qualityLevel = level; | |||
} | |||
void TightJPEGEncoder::setFineQualityLevel(int quality, int subsampling) | |||
{ | |||
fineQuality = quality; | |||
fineSubsampling = subsampling; | |||
} | |||
void TightJPEGEncoder::writeRect(const PixelBuffer* pb, const Palette& palette) | |||
{ | |||
const rdr::U8* buffer; | |||
int stride; | |||
int quality, subsampling; | |||
rdr::OutStream* os; | |||
buffer = pb->getBuffer(pb->getRect(), &stride); | |||
if (qualityLevel >= 0 && qualityLevel <= 9) { | |||
quality = conf[qualityLevel].quality; | |||
subsampling = conf[qualityLevel].subsampling; | |||
} else { | |||
quality = -1; | |||
subsampling = subsampleUndefined; | |||
} | |||
// Fine settings trump level | |||
if (fineQuality != -1) | |||
quality = fineQuality; | |||
if (fineSubsampling != subsampleUndefined) | |||
subsampling = fineSubsampling; | |||
jc.clear(); | |||
jc.compress(buffer, stride, pb->getRect(), | |||
pb->getPF(), quality, subsampling); | |||
os = conn->getOutStream(); | |||
os->writeU8(tightJpeg << 4); | |||
writeCompact(jc.length(), os); | |||
os->writeBytes(jc.data(), jc.length()); | |||
} | |||
void TightJPEGEncoder::writeSolidRect(int width, int height, | |||
const PixelFormat& pf, | |||
const rdr::U8* colour) | |||
{ | |||
// FIXME: Add a shortcut in the JPEG compressor to handle this case | |||
// without having to use the default fallback which is very slow. | |||
Encoder::writeSolidRect(width, height, pf, colour); | |||
} | |||
void TightJPEGEncoder::writeCompact(rdr::U32 value, rdr::OutStream* os) | |||
{ | |||
// Copied from TightEncoder as it's overkill to inherit just for this | |||
rdr::U8 b; | |||
b = value & 0x7F; | |||
if (value <= 0x7F) { | |||
os->writeU8(b); | |||
} else { | |||
os->writeU8(b | 0x80); | |||
b = value >> 7 & 0x7F; | |||
if (value <= 0x3FFF) { | |||
os->writeU8(b); | |||
} else { | |||
os->writeU8(b | 0x80); | |||
os->writeU8(value >> 14 & 0xFF); | |||
} | |||
} | |||
} |
@@ -0,0 +1,54 @@ | |||
/* Copyright (C) 2000-2003 Constantin Kaplinsky. All Rights Reserved. | |||
* Copyright (C) 2011 D. R. Commander | |||
* Copyright 2014 Pierre Ossman for Cendio AB | |||
* | |||
* This is free software; you can redistribute it and/or modify | |||
* it under the terms of the GNU General Public License as published by | |||
* the Free Software Foundation; either version 2 of the License, or | |||
* (at your option) any later version. | |||
* | |||
* This software is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
* GNU General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU General Public License | |||
* along with this software; if not, write to the Free Software | |||
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, | |||
* USA. | |||
*/ | |||
#ifndef __RFB_TIGHTJPEGENCODER_H__ | |||
#define __RFB_TIGHTJPEGENCODER_H__ | |||
#include <rfb/Encoder.h> | |||
#include <rfb/JpegCompressor.h> | |||
namespace rfb { | |||
class TightJPEGEncoder : public Encoder { | |||
public: | |||
TightJPEGEncoder(SConnection* conn); | |||
virtual ~TightJPEGEncoder(); | |||
virtual bool isSupported(); | |||
virtual void setQualityLevel(int level); | |||
virtual void setFineQualityLevel(int quality, int subsampling); | |||
virtual void writeRect(const PixelBuffer* pb, const Palette& palette); | |||
virtual void writeSolidRect(int width, int height, | |||
const PixelFormat& pf, | |||
const rdr::U8* colour); | |||
protected: | |||
void writeCompact(rdr::U32 value, rdr::OutStream* os); | |||
protected: | |||
JpegCompressor jc; | |||
int qualityLevel; | |||
int fineQuality; | |||
int fineSubsampling; | |||
}; | |||
} | |||
#endif |
@@ -73,7 +73,7 @@ VNCSConnectionST::VNCSConnectionST(VNCServerST* server_, network::Socket *s, | |||
ackedOffset(0), sentOffset(0), congWindow(0), congestionTimer(this), | |||
server(server_), updates(false), | |||
drawRenderedCursor(false), removeRenderedCursor(false), | |||
continuousUpdates(false), | |||
continuousUpdates(false), encodeManager(this), | |||
updateTimer(this), pointerEventTime(0), | |||
accessRights(AccessDefault), startTime(time(0)) | |||
{ | |||
@@ -81,8 +81,6 @@ VNCSConnectionST::VNCSConnectionST(VNCServerST* server_, network::Socket *s, | |||
peerEndpoint.buf = sock->getPeerEndpoint(); | |||
VNCServerST::connectionsLog.write(1,"accepted: %s", peerEndpoint.buf); | |||
memset(encoders, 0, sizeof(encoders)); | |||
// Configure the socket | |||
setSocketTimeouts(); | |||
lastEventTime = time(0); | |||
@@ -108,9 +106,6 @@ VNCSConnectionST::~VNCSConnectionST() | |||
// Remove this client from the server | |||
server->clients.remove(this); | |||
for (int i = 0; i <= encodingMax; i++) | |||
delete encoders[i]; | |||
delete [] fenceData; | |||
} | |||
@@ -1027,72 +1022,19 @@ void VNCSConnectionST::writeFramebufferUpdate() | |||
} | |||
if (!ui.is_empty() || writer()->needFakeUpdate() || drawRenderedCursor) { | |||
std::vector<Rect> rects; | |||
std::vector<Rect>::const_iterator i; | |||
int encoding; | |||
Encoder* encoder; | |||
PixelBuffer* pb; | |||
// Make sure the encoder has the latest settings | |||
encoding = cp.currentEncoding(); | |||
if (!encoders[encoding]) | |||
encoders[encoding] = Encoder::createEncoder(encoding, this); | |||
encoder = encoders[encoding]; | |||
encoder->setCompressLevel(cp.compressLevel); | |||
encoder->setQualityLevel(cp.qualityLevel); | |||
encoder->setFineQualityLevel(cp.fineQualityLevel, cp.subsampling); | |||
// Compute the number of rectangles. Tight encoder makes the things more | |||
// complicated as compared to the original VNC4. | |||
int nRects = (ui.copied.numRects() + | |||
(drawRenderedCursor ? 1 : 0)); | |||
ui.changed.get_rects(&rects); | |||
for (i = rects.begin(); i != rects.end(); i++) { | |||
if (i->width() && i->height()) { | |||
int nUpdateRects = encoder->getNumRects(*i); | |||
if (nUpdateRects == 0 && cp.currentEncoding() == encodingTight) { | |||
// With Tight encoding and LastRect support, the client does not | |||
// care about the number of rectangles in the update - it will | |||
// stop parsing when it encounters a LastRect "rectangle". | |||
// In this case, pretend to send 65535 rectangles. | |||
nRects = 0xFFFF; break; | |||
} | |||
else | |||
nRects += nUpdateRects; | |||
} | |||
} | |||
writeRTTPing(); | |||
writer()->writeFramebufferUpdateStart(nRects); | |||
RenderedCursor *cursor; | |||
ui.copied.get_rects(&rects); | |||
for (i = rects.begin(); i != rects.end(); i++) | |||
writer()->writeCopyRect(*i, i->tl.x - ui.copy_delta.x, | |||
i->tl.y - ui.copy_delta.y); | |||
cursor = NULL; | |||
if (drawRenderedCursor) | |||
cursor = &server->renderedCursor; | |||
pb = server->getPixelBuffer(); | |||
ui.changed.get_rects(&rects); | |||
for (i = rects.begin(); i != rects.end(); i++) | |||
encoder->writeRect(*i, pb); | |||
if (drawRenderedCursor) { | |||
renderedCursorRect = server->renderedCursor.getEffectiveRect(); | |||
encoder->writeRect(renderedCursorRect, &server->renderedCursor); | |||
drawRenderedCursor = false; | |||
} | |||
writeRTTPing(); | |||
writer()->writeFramebufferUpdateEnd(); | |||
encodeManager.writeUpdate(ui, server->getPixelBuffer(), cursor); | |||
writeRTTPing(); | |||
drawRenderedCursor = false; | |||
requested.clear(); | |||
updates.clear(); | |||
} |
@@ -32,11 +32,11 @@ | |||
#include <rfb/SMsgWriter.h> | |||
#include <rfb/VNCServerST.h> | |||
#include <rfb/Timer.h> | |||
#include <rfb/EncodeManager.h> | |||
struct RTTInfo; | |||
namespace rfb { | |||
class Encoder; | |||
class VNCSConnectionST : public SConnection, | |||
public Timer::Callback { | |||
@@ -197,7 +197,7 @@ namespace rfb { | |||
Rect renderedCursorRect; | |||
bool continuousUpdates; | |||
Region cuRegion; | |||
Encoder* encoders[encodingMax+1]; | |||
EncodeManager encodeManager; | |||
Timer updateTimer; | |||
@@ -1,4 +1,5 @@ | |||
/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. | |||
* Copyright 2014 Pierre Ossman for Cendio AB | |||
* | |||
* This is free software; you can redistribute it and/or modify | |||
* it under the terms of the GNU General Public License as published by | |||
@@ -19,7 +20,7 @@ | |||
#include <rfb/Exception.h> | |||
#include <rfb/encodings.h> | |||
#include <rfb/ConnParams.h> | |||
#include <rfb/SMsgWriter.h> | |||
#include <rfb/Palette.h> | |||
#include <rfb/SConnection.h> | |||
#include <rfb/ZRLEEncoder.h> | |||
#include <rfb/Configuration.h> | |||
@@ -28,87 +29,223 @@ using namespace rfb; | |||
IntParameter zlibLevel("ZlibLevel","Zlib compression level",-1); | |||
static inline void writeOpaque24A(rdr::OutStream* os, rdr::U32 u) | |||
ZRLEEncoder::ZRLEEncoder(SConnection* conn) | |||
: Encoder(conn, encodingZRLE, EncoderPlain, 127), | |||
zos(0,0,zlibLevel), mos(129*1024) | |||
{ | |||
os->check(3); | |||
os->writeU8(((rdr::U8*)&u)[0]); | |||
os->writeU8(((rdr::U8*)&u)[1]); | |||
os->writeU8(((rdr::U8*)&u)[2]); | |||
zos.setUnderlying(&mos); | |||
} | |||
static inline void writeOpaque24B(rdr::OutStream* os, rdr::U32 u) | |||
ZRLEEncoder::~ZRLEEncoder() | |||
{ | |||
os->check(3); | |||
os->writeU8(((rdr::U8*)&u)[1]); | |||
os->writeU8(((rdr::U8*)&u)[2]); | |||
os->writeU8(((rdr::U8*)&u)[3]); | |||
zos.setUnderlying(NULL); | |||
} | |||
#define BPP 8 | |||
#include <rfb/zrleEncode.h> | |||
#undef BPP | |||
#define BPP 16 | |||
#include <rfb/zrleEncode.h> | |||
#undef BPP | |||
#define BPP 32 | |||
#include <rfb/zrleEncode.h> | |||
#define CPIXEL 24A | |||
#include <rfb/zrleEncode.h> | |||
#undef CPIXEL | |||
#define CPIXEL 24B | |||
#include <rfb/zrleEncode.h> | |||
#undef CPIXEL | |||
#undef BPP | |||
ZRLEEncoder::ZRLEEncoder(SConnection* conn) | |||
: Encoder(conn), zos(0,0,zlibLevel), mos(129*1024) | |||
bool ZRLEEncoder::isSupported() | |||
{ | |||
return conn->cp.supportsEncoding(encodingZRLE); | |||
} | |||
ZRLEEncoder::~ZRLEEncoder() | |||
void ZRLEEncoder::writeRect(const PixelBuffer* pb, const Palette& palette) | |||
{ | |||
int x, y; | |||
Rect tile; | |||
rdr::OutStream* os; | |||
// A bit of a special case | |||
if (palette.size() == 1) { | |||
Encoder::writeSolidRect(pb, palette); | |||
return; | |||
} | |||
for (y = 0;y < pb->height();y += 64) { | |||
tile.tl.y = y; | |||
tile.br.y = y + 64; | |||
if (tile.br.y > pb->height()) | |||
tile.br.y = pb->height(); | |||
for (x = 0;x < pb->width();x += 64) { | |||
tile.tl.x = x; | |||
tile.br.x = x + 64; | |||
if (tile.br.x > pb->width()) | |||
tile.br.x = pb->width(); | |||
if (palette.size() == 0) | |||
writeRawTile(tile, pb, palette); | |||
else if (palette.size() <= 16) | |||
writePaletteTile(tile, pb, palette); | |||
else | |||
writePaletteRLETile(tile, pb, palette); | |||
} | |||
} | |||
zos.flush(); | |||
os = conn->getOutStream(); | |||
os->writeU32(mos.length()); | |||
os->writeBytes(mos.data(), mos.length()); | |||
mos.clear(); | |||
} | |||
void ZRLEEncoder::writeRect(const Rect& r, PixelBuffer* pb) | |||
void ZRLEEncoder::writeSolidRect(int width, int height, | |||
const PixelFormat& pf, | |||
const rdr::U8* colour) | |||
{ | |||
const PixelFormat& pf = conn->cp.pf(); | |||
int tiles; | |||
rdr::OutStream* os; | |||
tiles = ((width + 63)/64) * ((height + 63)/64); | |||
while (tiles--) { | |||
zos.writeU8(1); | |||
writePixels(colour, pf, 1); | |||
} | |||
zos.flush(); | |||
os = conn->getOutStream(); | |||
os->writeU32(mos.length()); | |||
os->writeBytes(mos.data(), mos.length()); | |||
rdr::U8* imageBuf = conn->writer()->getImageBuf(64 * 64 * 4 + 4); | |||
mos.clear(); | |||
} | |||
switch (pf.bpp) { | |||
case 8: | |||
zrleEncode8(r, &mos, &zos, imageBuf, pf, pb); | |||
void ZRLEEncoder::writePaletteTile(const Rect& tile, const PixelBuffer* pb, | |||
const Palette& palette) | |||
{ | |||
const rdr::U8* buffer; | |||
int stride; | |||
buffer = pb->getBuffer(tile, &stride); | |||
switch (pb->getPF().bpp) { | |||
case 32: | |||
writePaletteTile(tile.width(), tile.height(), | |||
(rdr::U32*)buffer, stride, | |||
pb->getPF(), palette); | |||
break; | |||
case 16: | |||
zrleEncode16(r, &mos, &zos, imageBuf, pf, pb); | |||
writePaletteTile(tile.width(), tile.height(), | |||
(rdr::U16*)buffer, stride, | |||
pb->getPF(), palette); | |||
break; | |||
default: | |||
writePaletteTile(tile.width(), tile.height(), | |||
(rdr::U8*)buffer, stride, | |||
pb->getPF(), palette); | |||
} | |||
} | |||
void ZRLEEncoder::writePaletteRLETile(const Rect& tile, const PixelBuffer* pb, | |||
const Palette& palette) | |||
{ | |||
const rdr::U8* buffer; | |||
int stride; | |||
buffer = pb->getBuffer(tile, &stride); | |||
switch (pb->getPF().bpp) { | |||
case 32: | |||
{ | |||
Pixel maxPixel = pf.pixelFromRGB((rdr::U16)-1, (rdr::U16)-1, (rdr::U16)-1); | |||
bool fitsInLS3Bytes = maxPixel < (1<<24); | |||
bool fitsInMS3Bytes = (maxPixel & 0xff) == 0; | |||
if ((fitsInLS3Bytes && pf.isLittleEndian()) || | |||
(fitsInMS3Bytes && pf.isBigEndian())) | |||
{ | |||
zrleEncode24A(r, &mos, &zos, imageBuf, pf, pb); | |||
} | |||
else if ((fitsInLS3Bytes && pf.isBigEndian()) || | |||
(fitsInMS3Bytes && pf.isLittleEndian())) | |||
{ | |||
zrleEncode24B(r, &mos, &zos, imageBuf, pf, pb); | |||
} | |||
else | |||
{ | |||
zrleEncode32(r, &mos, &zos, imageBuf, pf, pb); | |||
} | |||
break; | |||
} | |||
writePaletteRLETile(tile.width(), tile.height(), | |||
(rdr::U32*)buffer, stride, | |||
pb->getPF(), palette); | |||
break; | |||
case 16: | |||
writePaletteRLETile(tile.width(), tile.height(), | |||
(rdr::U16*)buffer, stride, | |||
pb->getPF(), palette); | |||
break; | |||
default: | |||
writePaletteRLETile(tile.width(), tile.height(), | |||
(rdr::U8*)buffer, stride, | |||
pb->getPF(), palette); | |||
} | |||
} | |||
conn->writer()->startRect(r, encodingZRLE); | |||
rdr::OutStream* os = conn->getOutStream(); | |||
os->writeU32(mos.length()); | |||
os->writeBytes(mos.data(), mos.length()); | |||
conn->writer()->endRect(); | |||
void ZRLEEncoder::writeRawTile(const Rect& tile, const PixelBuffer* pb, | |||
const Palette& palette) | |||
{ | |||
const rdr::U8* buffer; | |||
int stride; | |||
int w, h, stride_bytes; | |||
buffer = pb->getBuffer(tile, &stride); | |||
zos.writeU8(0); // Empty palette (i.e. raw pixels) | |||
w = tile.width(); | |||
h = tile.height(); | |||
stride_bytes = stride * pb->getPF().bpp/8; | |||
while (h--) { | |||
writePixels(buffer, pb->getPF(), w); | |||
buffer += stride_bytes; | |||
} | |||
} | |||
void ZRLEEncoder::writePalette(const PixelFormat& pf, const Palette& palette) | |||
{ | |||
rdr::U8 buffer[256*4]; | |||
int i; | |||
if (pf.bpp == 32) { | |||
rdr::U32* buf; | |||
buf = (rdr::U32*)buffer; | |||
for (i = 0;i < palette.size();i++) | |||
*buf++ = palette.getColour(i); | |||
} else if (pf.bpp == 16) { | |||
rdr::U16* buf; | |||
buf = (rdr::U16*)buffer; | |||
for (i = 0;i < palette.size();i++) | |||
*buf++ = palette.getColour(i); | |||
} else { | |||
rdr::U8* buf; | |||
buf = (rdr::U8*)buffer; | |||
for (i = 0;i < palette.size();i++) | |||
*buf++ = palette.getColour(i); | |||
} | |||
writePixels(buffer, pf, palette.size()); | |||
} | |||
void ZRLEEncoder::writePixels(const rdr::U8* buffer, const PixelFormat& pf, | |||
unsigned int count) | |||
{ | |||
Pixel maxPixel; | |||
rdr::U8 pixBuf[4]; | |||
maxPixel = pf.pixelFromRGB((rdr::U16)-1, (rdr::U16)-1, (rdr::U16)-1); | |||
pf.bufferFromPixel(pixBuf, maxPixel); | |||
if ((pf.bpp != 32) || ((pixBuf[0] != 0) && (pixBuf[3] != 0))) { | |||
zos.writeBytes(buffer, count * pf.bpp/8); | |||
return; | |||
} | |||
if (pixBuf[0] == 0) | |||
buffer++; | |||
while (count--) { | |||
zos.writeBytes(buffer, 3); | |||
buffer += 4; | |||
} | |||
} | |||
// | |||
// Including BPP-dependent implementation of the encoder. | |||
// | |||
#define BPP 8 | |||
#include <rfb/ZRLEEncoderBPP.cxx> | |||
#undef BPP | |||
#define BPP 16 | |||
#include <rfb/ZRLEEncoderBPP.cxx> | |||
#undef BPP | |||
#define BPP 32 | |||
#include <rfb/ZRLEEncoderBPP.cxx> | |||
#undef BPP |
@@ -1,4 +1,5 @@ | |||
/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. | |||
* Copyright 2014 Pierre Ossman for Cendio AB | |||
* | |||
* This is free software; you can redistribute it and/or modify | |||
* it under the terms of the GNU General Public License as published by | |||
@@ -28,8 +29,51 @@ namespace rfb { | |||
public: | |||
ZRLEEncoder(SConnection* conn); | |||
virtual ~ZRLEEncoder(); | |||
virtual void writeRect(const Rect& r, PixelBuffer* pb); | |||
private: | |||
virtual bool isSupported(); | |||
virtual void writeRect(const PixelBuffer* pb, const Palette& palette); | |||
virtual void writeSolidRect(int width, int height, | |||
const PixelFormat& pf, | |||
const rdr::U8* colour); | |||
protected: | |||
void writePaletteTile(const Rect& tile, const PixelBuffer* pb, | |||
const Palette& palette); | |||
void writePaletteRLETile(const Rect& tile, const PixelBuffer* pb, | |||
const Palette& palette); | |||
void writeRawTile(const Rect& tile, const PixelBuffer* pb, | |||
const Palette& palette); | |||
void writePalette(const PixelFormat& pf, const Palette& palette); | |||
void writePixels(const rdr::U8* buffer, const PixelFormat& pf, | |||
unsigned int count); | |||
protected: | |||
// Preprocessor generated, optimised methods | |||
void writePaletteTile(int width, int height, | |||
const rdr::U8* buffer, int stride, | |||
const PixelFormat& pf, const Palette& palette); | |||
void writePaletteTile(int width, int height, | |||
const rdr::U16* buffer, int stride, | |||
const PixelFormat& pf, const Palette& palette); | |||
void writePaletteTile(int width, int height, | |||
const rdr::U32* buffer, int stride, | |||
const PixelFormat& pf, const Palette& palette); | |||
void writePaletteRLETile(int width, int height, | |||
const rdr::U8* buffer, int stride, | |||
const PixelFormat& pf, const Palette& palette); | |||
void writePaletteRLETile(int width, int height, | |||
const rdr::U16* buffer, int stride, | |||
const PixelFormat& pf, const Palette& palette); | |||
void writePaletteRLETile(int width, int height, | |||
const rdr::U32* buffer, int stride, | |||
const PixelFormat& pf, const Palette& palette); | |||
protected: | |||
rdr::ZlibOutStream zos; | |||
rdr::MemOutStream mos; | |||
}; |
@@ -0,0 +1,128 @@ | |||
/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. | |||
* | |||
* This is free software; you can redistribute it and/or modify | |||
* it under the terms of the GNU General Public License as published by | |||
* the Free Software Foundation; either version 2 of the License, or | |||
* (at your option) any later version. | |||
* | |||
* This software is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
* GNU General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU General Public License | |||
* along with this software; if not, write to the Free Software | |||
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, | |||
* USA. | |||
*/ | |||
#define CONCAT2(a,b) a##b | |||
#define CONCAT2E(a,b) CONCAT2(a,b) | |||
#define UBPP CONCAT2E(U,BPP) | |||
void ZRLEEncoder::writePaletteTile(int width, int height, | |||
const rdr::UBPP* buffer, int stride, | |||
const PixelFormat& pf, | |||
const Palette& palette) | |||
{ | |||
const int bitsPerPackedPixel[] = { | |||
0, 1, 2, 2, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4 | |||
}; | |||
int bppp; | |||
int pad; | |||
assert(palette.size() > 1); | |||
assert(palette.size() <= 16); | |||
zos.writeU8(palette.size()); | |||
writePalette(pf, palette); | |||
bppp = bitsPerPackedPixel[palette.size()-1]; | |||
pad = stride - width; | |||
for (int i = 0; i < height; i++) { | |||
int w; | |||
rdr::U8 nbits = 0; | |||
rdr::U8 byte = 0; | |||
w = width; | |||
while (w--) { | |||
rdr::UBPP pix = *buffer++; | |||
rdr::U8 index = palette.lookup(pix); | |||
byte = (byte << bppp) | index; | |||
nbits += bppp; | |||
if (nbits >= 8) { | |||
zos.writeU8(byte); | |||
nbits = 0; | |||
} | |||
} | |||
if (nbits > 0) { | |||
byte <<= 8 - nbits; | |||
zos.writeU8(byte); | |||
} | |||
buffer += pad; | |||
} | |||
} | |||
void ZRLEEncoder::writePaletteRLETile(int width, int height, | |||
const rdr::UBPP* buffer, int stride, | |||
const PixelFormat& pf, | |||
const Palette& palette) | |||
{ | |||
int pad; | |||
rdr::UBPP prevColour; | |||
int runLength; | |||
assert(palette.size() > 1); | |||
assert(palette.size() <= 127); | |||
zos.writeU8(palette.size() | 0x80); | |||
writePalette(pf, palette); | |||
pad = stride - width; | |||
prevColour = *buffer; | |||
runLength = 0; | |||
while (height--) { | |||
int w = width; | |||
while (w--) { | |||
if (prevColour != *buffer) { | |||
if (runLength == 1) | |||
zos.writeU8(palette.lookup(prevColour)); | |||
else { | |||
zos.writeU8(palette.lookup(prevColour) | 0x80); | |||
while (runLength > 255) { | |||
zos.writeU8(255); | |||
runLength -= 255; | |||
} | |||
zos.writeU8(runLength - 1); | |||
} | |||
prevColour = *buffer; | |||
runLength = 0; | |||
} | |||
runLength++; | |||
buffer++; | |||
} | |||
buffer += pad; | |||
} | |||
if (runLength == 1) | |||
zos.writeU8(palette.lookup(prevColour)); | |||
else { | |||
zos.writeU8(palette.lookup(prevColour) | 0x80); | |||
while (runLength > 255) { | |||
zos.writeU8(255); | |||
runLength -= 255; | |||
} | |||
zos.writeU8(runLength - 1); | |||
} | |||
} |
@@ -44,8 +44,7 @@ int TEST_TILE_TYPE (PIXEL_T* data, int w, int h, PIXEL_T* bg, PIXEL_T* fg); | |||
int HEXTILE_ENCODE_TILE (PIXEL_T* data, int w, int h, int tileType, | |||
rdr::U8* encoded, PIXEL_T bg); | |||
void HEXTILE_ENCODE(const Rect& r, rdr::OutStream* os, | |||
const PixelFormat& pf, PixelBuffer* pb) | |||
void HEXTILE_ENCODE(rdr::OutStream* os, const PixelBuffer* pb) | |||
{ | |||
Rect t; | |||
PIXEL_T buf[256]; | |||
@@ -54,15 +53,15 @@ void HEXTILE_ENCODE(const Rect& r, rdr::OutStream* os, | |||
bool oldFgValid = false; | |||
rdr::U8 encoded[256*(BPP/8)]; | |||
for (t.tl.y = r.tl.y; t.tl.y < r.br.y; t.tl.y += 16) { | |||
for (t.tl.y = 0; t.tl.y < pb->height(); t.tl.y += 16) { | |||
t.br.y = __rfbmin(r.br.y, t.tl.y + 16); | |||
t.br.y = __rfbmin(pb->height(), t.tl.y + 16); | |||
for (t.tl.x = r.tl.x; t.tl.x < r.br.x; t.tl.x += 16) { | |||
for (t.tl.x = 0; t.tl.x < pb->width(); t.tl.x += 16) { | |||
t.br.x = __rfbmin(r.br.x, t.tl.x + 16); | |||
t.br.x = __rfbmin(pb->width(), t.tl.x + 16); | |||
pb->getImage(pf, buf, t); | |||
pb->getImage(buf, t); | |||
PIXEL_T bg = 0, fg = 0; | |||
int tileType = TEST_TILE_TYPE(buf, t.width(), t.height(), &bg, &fg); | |||
@@ -91,7 +90,7 @@ void HEXTILE_ENCODE(const Rect& r, rdr::OutStream* os, | |||
encoded, bg); | |||
if (encodedLen < 0) { | |||
pb->getImage(pf, buf, t); | |||
pb->getImage(buf, t); | |||
os->writeU8(hextileRaw); | |||
os->writeBytes(buf, t.width() * t.height() * (BPP/8)); | |||
oldBgValid = oldFgValid = false; |
@@ -275,8 +275,7 @@ void HEXTILE_TILE::encode(rdr::U8 *dst) const | |||
// Main encoding function. | |||
// | |||
void HEXTILE_ENCODE(const Rect& r, rdr::OutStream* os, | |||
const PixelFormat& pf, PixelBuffer* pb) | |||
void HEXTILE_ENCODE(rdr::OutStream* os, const PixelBuffer* pb) | |||
{ | |||
Rect t; | |||
PIXEL_T buf[256]; | |||
@@ -287,15 +286,15 @@ void HEXTILE_ENCODE(const Rect& r, rdr::OutStream* os, | |||
HEXTILE_TILE tile; | |||
for (t.tl.y = r.tl.y; t.tl.y < r.br.y; t.tl.y += 16) { | |||
for (t.tl.y = 0; t.tl.y < pb->height(); t.tl.y += 16) { | |||
t.br.y = __rfbmin(r.br.y, t.tl.y + 16); | |||
t.br.y = __rfbmin(pb->height(), t.tl.y + 16); | |||
for (t.tl.x = r.tl.x; t.tl.x < r.br.x; t.tl.x += 16) { | |||
for (t.tl.x = 0; t.tl.x < pb->width(); t.tl.x += 16) { | |||
t.br.x = __rfbmin(r.br.x, t.tl.x + 16); | |||
t.br.x = __rfbmin(pb->width(), t.tl.x + 16); | |||
pb->getImage(pf, buf, t); | |||
pb->getImage(buf, t); | |||
tile.newTile(buf, t.width(), t.height()); | |||
int tileType = tile.getFlags(); |
@@ -41,47 +41,8 @@ namespace rfb { | |||
#define WRITE_PIXEL CONCAT2E(writeOpaque,BPP) | |||
#define RRE_ENCODE CONCAT2E(rreEncode,BPP) | |||
int RRE_ENCODE (PIXEL_T* data, int w, int h, rdr::OutStream* os, PIXEL_T bg); | |||
int RRE_ENCODE (void* data, int w, int h, rdr::OutStream* os) | |||
{ | |||
// Find the background colour - count occurrences of up to 4 different pixel | |||
// values, and choose the one which occurs most often. | |||
const int nCols = 4; | |||
PIXEL_T pix[nCols]; | |||
int count[nCols] = { 0, }; | |||
PIXEL_T* ptr = (PIXEL_T*)data; | |||
PIXEL_T* end = ptr + w*h; | |||
while (ptr < end) { | |||
int i; | |||
for (i = 0; i < nCols; i++) { | |||
if (count[i] == 0) | |||
pix[i] = *ptr; | |||
if (pix[i] == *ptr) { | |||
count[i]++; | |||
break; | |||
} | |||
} | |||
if (i == nCols) break; | |||
ptr++; | |||
} | |||
int bg = 0; | |||
for (int i = 1; i < nCols; i++) | |||
if (count[i] > count[bg]) bg = i; | |||
// Now call the function to do the encoding. | |||
return RRE_ENCODE ((PIXEL_T*)data, w, h, os, pix[bg]); | |||
} | |||
int RRE_ENCODE (PIXEL_T* data, int w, int h, rdr::OutStream* os, PIXEL_T bg) | |||
{ | |||
int oldLen = os->length(); | |||
os->WRITE_PIXEL(bg); | |||
int nSubrects = 0; | |||
@@ -141,7 +102,6 @@ int RRE_ENCODE (PIXEL_T* data, int w, int h, rdr::OutStream* os, PIXEL_T bg) | |||
os->writeU16(y); | |||
os->writeU16(sw); | |||
os->writeU16(sh); | |||
if (os->length() > oldLen + w*h) return -1; | |||
ptr = data+w; | |||
PIXEL_T* eor = data+w*sh; |
@@ -27,6 +27,7 @@ | |||
#include <rdr/InStream.h> | |||
#include <rdr/ZlibInStream.h> | |||
#include <rfb/Exception.h> | |||
#include <rfb/TightConstants.h> | |||
#include <assert.h> | |||
namespace rfb { | |||
@@ -68,7 +69,7 @@ void TIGHT_DECODE (const Rect& r) | |||
} | |||
// "Fill" compression type. | |||
if (comp_ctl == rfbTightFill) { | |||
if (comp_ctl == tightFill) { | |||
PIXEL_T pix; | |||
if (cutZeros) { | |||
rdr::U8 bytebuf[3]; | |||
@@ -82,13 +83,13 @@ void TIGHT_DECODE (const Rect& r) | |||
} | |||
// "JPEG" compression type. | |||
if (comp_ctl == rfbTightJpeg) { | |||
if (comp_ctl == tightJpeg) { | |||
DECOMPRESS_JPEG_RECT(r); | |||
return; | |||
} | |||
// Quit on unsupported compression type. | |||
if (comp_ctl > rfbTightMaxSubencoding) { | |||
if (comp_ctl > tightMaxSubencoding) { | |||
throw Exception("TightDecoder: bad subencoding value received"); | |||
return; | |||
} | |||
@@ -98,11 +99,11 @@ void TIGHT_DECODE (const Rect& r) | |||
static PIXEL_T palette[256]; | |||
bool useGradient = false; | |||
if ((comp_ctl & rfbTightExplicitFilter) != 0) { | |||
if ((comp_ctl & tightExplicitFilter) != 0) { | |||
rdr::U8 filterId = is->readU8(); | |||
switch (filterId) { | |||
case rfbTightFilterPalette: | |||
case tightFilterPalette: | |||
palSize = is->readU8() + 1; | |||
if (cutZeros) { | |||
rdr::U8 tightPalette[256 * 3]; | |||
@@ -112,10 +113,10 @@ void TIGHT_DECODE (const Rect& r) | |||
is->readBytes(palette, palSize * sizeof(PIXEL_T)); | |||
} | |||
break; | |||
case rfbTightFilterGradient: | |||
case tightFilterGradient: | |||
useGradient = true; | |||
break; | |||
case rfbTightFilterCopy: | |||
case tightFilterCopy: | |||
break; | |||
default: | |||
throw Exception("TightDecoder: unknown filter code received"); |
@@ -1,580 +0,0 @@ | |||
/* Copyright (C) 2000-2003 Constantin Kaplinsky. All Rights Reserved. | |||
* Copyright (C) 2011 D. R. Commander. All Rights Reserved. | |||
* Copyright 2014 Pierre Ossman for Cendio AB. | |||
* | |||
* This is free software; you can redistribute it and/or modify | |||
* it under the terms of the GNU General Public License as published by | |||
* the Free Software Foundation; either version 2 of the License, or | |||
* (at your option) any later version. | |||
* | |||
* This software is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
* GNU General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU General Public License | |||
* along with this software; if not, write to the Free Software | |||
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, | |||
* USA. | |||
*/ | |||
// | |||
// tightEncode.h - Tight encoding function. | |||
// | |||
// This file is #included after having set the following macro: | |||
// BPP - 8, 16 or 32 | |||
// | |||
#include <assert.h> | |||
namespace rfb { | |||
// CONCAT2E concatenates its arguments, expanding them if they are macros | |||
#ifndef CONCAT2E | |||
#define CONCAT2(a,b) a##b | |||
#define CONCAT2E(a,b) CONCAT2(a,b) | |||
#endif | |||
#define PIXEL_T rdr::CONCAT2E(U,BPP) | |||
#define TIGHT_ENCODE TightEncoder::CONCAT2E(tightEncode,BPP) | |||
#define HASH_FUNCTION CONCAT2E(HASH_FUNC,BPP) | |||
#define PACK_PIXELS TightEncoder::CONCAT2E(packPixels,BPP) | |||
#define ENCODE_SOLID_RECT TightEncoder::CONCAT2E(encodeSolidRect,BPP) | |||
#define ENCODE_FULLCOLOR_RECT TightEncoder::CONCAT2E(encodeFullColorRect,BPP) | |||
#define ENCODE_MONO_RECT TightEncoder::CONCAT2E(encodeMonoRect,BPP) | |||
#define ENCODE_INDEXED_RECT TightEncoder::CONCAT2E(encodeIndexedRect,BPP) | |||
#define ENCODE_JPEG_RECT TightEncoder::CONCAT2E(encodeJpegRect,BPP) | |||
#define FAST_FILL_PALETTE TightEncoder::CONCAT2E(fastFillPalette,BPP) | |||
#define FILL_PALETTE TightEncoder::CONCAT2E(fillPalette,BPP) | |||
#define CHECK_SOLID_TILE TightEncoder::CONCAT2E(checkSolidTile,BPP) | |||
#ifndef TIGHT_ONCE | |||
#define TIGHT_ONCE | |||
// | |||
// Compress the data (but do not perform actual compression if the data | |||
// size is less than TIGHT_MIN_TO_COMPRESS bytes. | |||
// | |||
void TightEncoder::compressData(const void *buf, unsigned int length, | |||
rdr::ZlibOutStream *zos, int zlibLevel, | |||
rdr::OutStream *os) | |||
{ | |||
if (length < TIGHT_MIN_TO_COMPRESS) { | |||
os->writeBytes(buf, length); | |||
} else { | |||
// FIXME: Using a temporary MemOutStream may be not efficient. | |||
// Maybe use the same static object used in the JPEG coder? | |||
int maxBeforeSize = pconf->maxRectSize * (clientpf.bpp / 8); | |||
int maxAfterSize = maxBeforeSize + (maxBeforeSize + 99) / 100 + 12; | |||
rdr::MemOutStream mem_os(maxAfterSize); | |||
zos->setUnderlying(&mem_os); | |||
zos->setCompressionLevel(zlibLevel); | |||
zos->writeBytes(buf, length); | |||
zos->flush(); | |||
zos->setUnderlying(NULL); | |||
writeCompact(os, mem_os.length()); | |||
os->writeBytes(mem_os.data(), mem_os.length()); | |||
} | |||
} | |||
#endif // #ifndef TIGHT_ONCE | |||
// | |||
// Convert 32-bit color samples into 24-bit colors, in place. | |||
// Performs packing only when redMax, greenMax and blueMax are all 255. | |||
// Color components are assumed to be byte-aligned. | |||
// | |||
unsigned int PACK_PIXELS (PIXEL_T *buf, unsigned int count) | |||
{ | |||
#if (BPP != 32) | |||
return count * sizeof(PIXEL_T); | |||
#else | |||
if (!pack24) | |||
return count * sizeof(PIXEL_T); | |||
rdr::U32 pix; | |||
rdr::U8 *dst = (rdr::U8 *)buf; | |||
for (unsigned int i = 0; i < count; i++) { | |||
pix = *buf++; | |||
clientpf.rgbFromBuffer(dst, (rdr::U8*)&pix, 1); | |||
dst += 3; | |||
} | |||
return count * 3; | |||
#endif | |||
} | |||
// | |||
// Main function of the Tight encoder | |||
// | |||
void TIGHT_ENCODE (const Rect& r, rdr::OutStream *os, bool forceSolid) | |||
{ | |||
int stride; | |||
rdr::U32 solidColor; | |||
const PIXEL_T *rawPixels = (const PIXEL_T *)pb->getBuffer(r, &stride); | |||
PIXEL_T *pixels = NULL; | |||
bool grayScaleJPEG = (jpegSubsampling == subsampleGray && jpegQuality != -1); | |||
#if (BPP == 32) | |||
// Check if it's necessary to pack 24-bit pixels, and | |||
// compute appropriate shift values if necessary. | |||
pack24 = clientpf.is888(); | |||
#endif | |||
if (forceSolid) { | |||
// Subrectangle has already been determined to be solid. | |||
clientpf.bufferFromBuffer((rdr::U8*)&solidColor, serverpf, | |||
(const rdr::U8*)rawPixels, 1); | |||
pixels = (PIXEL_T *)&solidColor; | |||
palette.clear(); | |||
palette.insert(solidColor, 1); | |||
} else { | |||
// Analyze subrectangle's colors to determine best encoding method. | |||
palMaxColors = r.area() / pconf->idxMaxColorsDivisor; | |||
if (jpegQuality != -1) | |||
palMaxColors = pconf->palMaxColorsWithJPEG; | |||
if (palMaxColors < 2 && r.area() >= pconf->monoMinRectSize) | |||
palMaxColors = 2; | |||
if (clientpf.equal(serverpf) && clientpf.bpp >= 16) { | |||
// Count the colors in the raw buffer, so we can avoid unnecessary pixel | |||
// translation when encoding with JPEG. | |||
if (grayScaleJPEG) palette.clear(); | |||
else FAST_FILL_PALETTE(rawPixels, stride, r); | |||
// JPEG can read from the raw buffer, but for the other methods, we need | |||
// to translate the raw pixels into an intermediate buffer. | |||
if(palette.size() != 0 || jpegQuality == -1) { | |||
pixels = (PIXEL_T *)conn->writer()->getImageBuf(r.area()); | |||
stride = r.width(); | |||
pb->getImage(clientpf, pixels, r); | |||
} | |||
} else { | |||
// Pixel translation will be required, so create an intermediate buffer, | |||
// translate the raw pixels into it, and count its colors. | |||
pixels = (PIXEL_T *)conn->writer()->getImageBuf(r.area()); | |||
stride = r.width(); | |||
pb->getImage(clientpf, pixels, r); | |||
if (grayScaleJPEG) palette.clear(); | |||
else FILL_PALETTE(pixels, r.area()); | |||
} | |||
} | |||
switch (palette.size()) { | |||
case 0: | |||
// Truecolor image | |||
#if (BPP != 8) | |||
if (jpegQuality != -1) { | |||
if (pixels) | |||
ENCODE_JPEG_RECT(pixels, stride, r, os); | |||
else | |||
ENCODE_JPEG_RECT((PIXEL_T *)rawPixels, stride, r, os); | |||
break; | |||
} | |||
#endif | |||
ENCODE_FULLCOLOR_RECT(pixels, r, os); | |||
break; | |||
case 1: | |||
// Solid rectangle | |||
ENCODE_SOLID_RECT(pixels, os); | |||
break; | |||
case 2: | |||
// Two-color rectangle | |||
ENCODE_MONO_RECT(pixels, r, os); | |||
break; | |||
#if (BPP != 8) | |||
default: | |||
// Up to 256 different colors | |||
ENCODE_INDEXED_RECT(pixels, r, os); | |||
#endif | |||
} | |||
} | |||
// | |||
// Subencoding implementations. | |||
// | |||
void ENCODE_SOLID_RECT (PIXEL_T *buf, rdr::OutStream *os) | |||
{ | |||
os->writeU8(0x08 << 4); | |||
int length = PACK_PIXELS(buf, 1); | |||
os->writeBytes(buf, length); | |||
} | |||
void ENCODE_FULLCOLOR_RECT (PIXEL_T *buf, const Rect& r, rdr::OutStream *os) | |||
{ | |||
const int streamId = 0; | |||
os->writeU8(streamId << 4); | |||
int length = PACK_PIXELS(buf, r.area()); | |||
compressData(buf, length, &zos[streamId], pconf->rawZlibLevel, os); | |||
} | |||
void ENCODE_MONO_RECT (PIXEL_T *buf, const Rect& r, rdr::OutStream *os) | |||
{ | |||
const int streamId = 1; | |||
os->writeU8((streamId | 0x04) << 4); | |||
os->writeU8(0x01); | |||
// Write the palette | |||
PIXEL_T pal[2] = { (PIXEL_T)palette.getColour(0), | |||
(PIXEL_T)palette.getColour(1) }; | |||
os->writeU8(1); | |||
os->writeBytes(pal, PACK_PIXELS(pal, 2)); | |||
// Encode the data in-place | |||
PIXEL_T *src = buf; | |||
rdr::U8 *dst = (rdr::U8 *)buf; | |||
int w = r.width(); | |||
int h = r.height(); | |||
PIXEL_T bg; | |||
unsigned int value, mask; | |||
int aligned_width; | |||
int x, y, bg_bits; | |||
bg = (PIXEL_T) pal[0]; | |||
aligned_width = w - w % 8; | |||
for (y = 0; y < h; y++) { | |||
for (x = 0; x < aligned_width; x += 8) { | |||
for (bg_bits = 0; bg_bits < 8; bg_bits++) { | |||
if (*src++ != bg) | |||
break; | |||
} | |||
if (bg_bits == 8) { | |||
*dst++ = 0; | |||
continue; | |||
} | |||
mask = 0x80 >> bg_bits; | |||
value = mask; | |||
for (bg_bits++; bg_bits < 8; bg_bits++) { | |||
mask >>= 1; | |||
if (*src++ != bg) { | |||
value |= mask; | |||
} | |||
} | |||
*dst++ = (rdr::U8)value; | |||
} | |||
mask = 0x80; | |||
value = 0; | |||
if (x >= w) | |||
continue; | |||
for (; x < w; x++) { | |||
if (*src++ != bg) { | |||
value |= mask; | |||
} | |||
mask >>= 1; | |||
} | |||
*dst++ = (rdr::U8)value; | |||
} | |||
// Write the data | |||
int length = (w + 7) / 8; | |||
length *= h; | |||
compressData(buf, length, &zos[streamId], pconf->monoZlibLevel, os); | |||
} | |||
#if (BPP != 8) | |||
void ENCODE_INDEXED_RECT (PIXEL_T *buf, const Rect& r, rdr::OutStream *os) | |||
{ | |||
const int streamId = 2; | |||
os->writeU8((streamId | 0x04) << 4); | |||
os->writeU8(0x01); | |||
// Write the palette | |||
{ | |||
PIXEL_T pal[256]; | |||
for (int i = 0; i < palette.size(); i++) | |||
pal[i] = (PIXEL_T)palette.getColour(i); | |||
os->writeU8((rdr::U8)(palette.size() - 1)); | |||
os->writeBytes(pal, PACK_PIXELS(pal, palette.size())); | |||
} | |||
// Encode data in-place | |||
PIXEL_T *src = buf; | |||
rdr::U8 *dst = (rdr::U8 *)buf; | |||
int count = r.area(); | |||
PIXEL_T rgb; | |||
int rep = 0; | |||
unsigned char idx; | |||
while (count--) { | |||
rgb = *src++; | |||
while (count && *src == rgb) { | |||
rep++, src++, count--; | |||
} | |||
idx = palette.lookup(rgb); | |||
*dst++ = idx; | |||
while (rep) { | |||
*dst++ = idx; | |||
rep--; | |||
} | |||
} | |||
// Write the data | |||
compressData(buf, r.area(), &zos[streamId], pconf->idxZlibLevel, os); | |||
} | |||
#endif // #if (BPP != 8) | |||
// | |||
// JPEG compression. | |||
// | |||
#if (BPP != 8) | |||
void ENCODE_JPEG_RECT (PIXEL_T *buf, int stride, const Rect& r, | |||
rdr::OutStream *os) | |||
{ | |||
jc.clear(); | |||
jc.compress((rdr::U8 *)buf, stride, r, clientpf, | |||
jpegQuality, jpegSubsampling); | |||
os->writeU8(0x09 << 4); | |||
writeCompact(os, jc.length()); | |||
os->writeBytes(jc.data(), jc.length()); | |||
} | |||
#endif // #if (BPP != 8) | |||
// | |||
// Determine the number of colors in the rectangle, and fill in the palette. | |||
// | |||
#if (BPP == 8) | |||
void FILL_PALETTE (PIXEL_T *data, int count) | |||
{ | |||
PIXEL_T c0, c1; | |||
int i, n0, n1; | |||
palette.clear(); | |||
c0 = data[0]; | |||
for (i = 1; i < count && data[i] == c0; i++); | |||
if (i == count) { | |||
palette.insert(c0, i); | |||
return; // Solid rectangle | |||
} | |||
if (palMaxColors < 2) | |||
return; | |||
n0 = i; | |||
c1 = data[i]; | |||
n1 = 0; | |||
for (i++; i < count; i++) { | |||
if (data[i] == c0) { | |||
n0++; | |||
} else if (data[i] == c1) { | |||
n1++; | |||
} else | |||
break; | |||
} | |||
if (i == count) { | |||
palette.insert(c0, n0); // Two colors | |||
palette.insert(c1, n1); | |||
} | |||
} | |||
void FAST_FILL_PALETTE (const PIXEL_T *data, int stride, const Rect& r) | |||
{ | |||
} | |||
#else // (BPP != 8) | |||
void FILL_PALETTE (PIXEL_T *data, int count) | |||
{ | |||
PIXEL_T c0, c1, ci = 0; | |||
int i, n0, n1, ni; | |||
palette.clear(); | |||
c0 = data[0]; | |||
for (i = 1; i < count && data[i] == c0; i++); | |||
if (i >= count) { | |||
palette.insert(c0, i); // Solid rectangle | |||
return; | |||
} | |||
if (palMaxColors < 2) | |||
return; // Full-color format preferred | |||
n0 = i; | |||
c1 = data[i]; | |||
n1 = 0; | |||
for (i++; i < count; i++) { | |||
ci = data[i]; | |||
if (ci == c0) { | |||
n0++; | |||
} else if (ci == c1) { | |||
n1++; | |||
} else | |||
break; | |||
} | |||
palette.insert(c0, n0); | |||
palette.insert(c1, n1); | |||
if (i >= count) | |||
return; // Two colors | |||
ni = 1; | |||
for (i++; i < count; i++) { | |||
if (data[i] == ci) { | |||
ni++; | |||
} else { | |||
if (!palette.insert (ci, ni) || (palette.size() > palMaxColors)) { | |||
palette.clear(); | |||
return; | |||
} | |||
ci = data[i]; | |||
ni = 1; | |||
} | |||
} | |||
if (!palette.insert (ci, ni) || (palette.size() > palMaxColors)) | |||
palette.clear(); | |||
} | |||
void FAST_FILL_PALETTE (const PIXEL_T *data, int stride, const Rect& r) | |||
{ | |||
PIXEL_T c0, c1, ci = 0, mask, c0t, c1t, cit; | |||
int n0, n1, ni; | |||
int w = r.width(), h = r.height(); | |||
const PIXEL_T *rowptr, *colptr, *rowptr2, *colptr2, | |||
*dataend = &data[stride * h]; | |||
bool willTransform = !serverpf.equal(clientpf); | |||
serverpf.bufferFromPixel((rdr::U8*)&mask, ~0); | |||
palette.clear(); | |||
c0 = data[0] & mask; | |||
n0 = 0; | |||
for (rowptr = data; rowptr < dataend; rowptr += stride) { | |||
for (colptr = rowptr; colptr < &rowptr[w]; colptr++) { | |||
if (((*colptr) & mask) != c0) | |||
goto soliddone; | |||
n0++; | |||
} | |||
} | |||
soliddone: | |||
if (rowptr >= dataend) { | |||
palette.insert(c0, 1); // Solid rectangle | |||
return; | |||
} | |||
if (palMaxColors < 2) | |||
return; // Full-color format preferred | |||
c1 = *colptr & mask; | |||
n1 = 0; | |||
colptr++; | |||
if (colptr >= &rowptr[w]) { | |||
rowptr += stride; colptr = rowptr; | |||
} | |||
colptr2 = colptr; | |||
for (rowptr2 = rowptr; rowptr2 < dataend;) { | |||
for (; colptr2 < &rowptr2[w]; colptr2++) { | |||
ci = (*colptr2) & mask; | |||
if (ci == c0) { | |||
n0++; | |||
} else if (ci == c1) { | |||
n1++; | |||
} else | |||
goto monodone; | |||
} | |||
rowptr2 += stride; | |||
colptr2 = rowptr2; | |||
} | |||
monodone: | |||
if (willTransform) { | |||
clientpf.bufferFromBuffer((rdr::U8*)&c0t, serverpf, (rdr::U8*)&c0, 1); | |||
clientpf.bufferFromBuffer((rdr::U8*)&c1t, serverpf, (rdr::U8*)&c1, 1); | |||
} | |||
else { | |||
c0t = c0; c1t = c1; | |||
} | |||
palette.insert(c0t, n0); | |||
palette.insert(c1t, n1); | |||
if (colptr2 >= dataend) | |||
return; // Two colors | |||
ni = 1; | |||
colptr2++; | |||
if (colptr2 >= &rowptr2[w]) { | |||
rowptr2 += stride; colptr2 = rowptr2; | |||
} | |||
colptr = colptr2; | |||
for (rowptr = rowptr2; rowptr < dataend;) { | |||
for (; colptr < &rowptr[w]; colptr++) { | |||
if (((*colptr) & mask) == ci) { | |||
ni++; | |||
} else { | |||
if (willTransform) | |||
clientpf.bufferFromBuffer((rdr::U8*)&cit, serverpf, (rdr::U8*)&ci, 1); | |||
else | |||
cit = ci; | |||
if (!palette.insert (cit, ni) || (palette.size() > palMaxColors)) { | |||
palette.clear(); | |||
return; | |||
} | |||
ci = (*colptr) & mask; | |||
ni = 1; | |||
} | |||
} | |||
rowptr += stride; | |||
colptr = rowptr; | |||
} | |||
clientpf.bufferFromBuffer((rdr::U8*)&cit, serverpf, (rdr::U8*)&ci, 1); | |||
if (!palette.insert (cit, ni) || (palette.size() > palMaxColors)) | |||
palette.clear(); | |||
} | |||
#endif // #if (BPP == 8) | |||
bool CHECK_SOLID_TILE(Rect& r, rdr::U32 *colorPtr, bool needSameColor) | |||
{ | |||
const PIXEL_T *buf; | |||
PIXEL_T colorValue; | |||
int w = r.width(), h = r.height(); | |||
int stride = w; | |||
buf = (const PIXEL_T *)pb->getBuffer(r, &stride); | |||
colorValue = *buf; | |||
if (needSameColor && (rdr::U32)colorValue != *colorPtr) | |||
return false; | |||
int bufPad = stride - w; | |||
while (h > 0) { | |||
const PIXEL_T *bufEndOfRow = buf + w; | |||
while (buf < bufEndOfRow) { | |||
if (colorValue != *(buf++)) | |||
return false; | |||
} | |||
buf += bufPad; | |||
h--; | |||
} | |||
*colorPtr = (rdr::U32)colorValue; | |||
return true; | |||
} | |||
#undef PIXEL_T | |||
#undef TIGHT_ENCODE | |||
#undef HASH_FUNCTION | |||
#undef PACK_PIXELS | |||
#undef ENCODE_SOLID_RECT | |||
#undef ENCODE_FULLCOLOR_RECT | |||
#undef ENCODE_MONO_RECT | |||
#undef ENCODE_INDEXED_RECT | |||
#undef ENCODE_JPEG_RECT | |||
#undef FAST_FILL_PALETTE | |||
#undef FILL_PALETTE | |||
#undef CHECK_SOLID_TILE | |||
} |
@@ -1,266 +0,0 @@ | |||
/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. | |||
* | |||
* This is free software; you can redistribute it and/or modify | |||
* it under the terms of the GNU General Public License as published by | |||
* the Free Software Foundation; either version 2 of the License, or | |||
* (at your option) any later version. | |||
* | |||
* This software is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
* GNU General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU General Public License | |||
* along with this software; if not, write to the Free Software | |||
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, | |||
* USA. | |||
*/ | |||
// | |||
// zrleEncode.h - zrle encoding function. | |||
// | |||
// This file is #included after having set the following macro: | |||
// BPP - 8, 16 or 32 | |||
// | |||
// Note that the buf argument to ZRLE_ENCODE needs to be at least one pixel | |||
// bigger than the largest tile of pixel data, since the ZRLE encoding | |||
// algorithm writes to the position one past the end of the pixel data. | |||
// | |||
#include <rdr/OutStream.h> | |||
#include <rdr/ZlibOutStream.h> | |||
#include <rfb/Palette.h> | |||
#include <rfb/PixelBuffer.h> | |||
#include <assert.h> | |||
namespace rfb { | |||
// CONCAT2E concatenates its arguments, expanding them if they are macros | |||
#ifndef CONCAT2E | |||
#define CONCAT2(a,b) a##b | |||
#define CONCAT2E(a,b) CONCAT2(a,b) | |||
#endif | |||
#ifdef CPIXEL | |||
#define PIXEL_T rdr::CONCAT2E(U,BPP) | |||
#define WRITE_PIXEL(os, u) CONCAT2E(writeOpaque,CPIXEL)(os, u) | |||
#define ZRLE_ENCODE CONCAT2E(zrleEncode,CPIXEL) | |||
#define ZRLE_ENCODE_TILE CONCAT2E(zrleEncodeTile,CPIXEL) | |||
#define BPPOUT 24 | |||
#else | |||
#define PIXEL_T rdr::CONCAT2E(U,BPP) | |||
#define WRITE_PIXEL(os, u) os->CONCAT2E(writeOpaque,BPP)(u) | |||
#define ZRLE_ENCODE CONCAT2E(zrleEncode,BPP) | |||
#define ZRLE_ENCODE_TILE CONCAT2E(zrleEncodeTile,BPP) | |||
#define BPPOUT BPP | |||
#endif | |||
#ifndef ZRLE_ONCE | |||
#define ZRLE_ONCE | |||
static const int bitsPerPackedPixel[] = { | |||
0, 1, 2, 2, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4 | |||
}; | |||
#endif | |||
void ZRLE_ENCODE_TILE (PIXEL_T* data, int w, int h, rdr::OutStream* os); | |||
void ZRLE_ENCODE (const Rect& r, rdr::OutStream* os, | |||
rdr::ZlibOutStream* zos, void* buf, | |||
const PixelFormat& pf, PixelBuffer* pb) | |||
{ | |||
zos->setUnderlying(os); | |||
// RLE overhead is at worst 1 byte per 64x64 (4Kpixel) block | |||
int worstCaseLine = r.width() * 64 * (BPPOUT/8) + 1 + r.width() / 64; | |||
// Zlib overhead is at worst 6 bytes plus 5 bytes per 32Kbyte block. | |||
worstCaseLine += 11 + 5 * (worstCaseLine >> 15); | |||
Rect t; | |||
for (t.tl.y = r.tl.y; t.tl.y < r.br.y; t.tl.y += 64) { | |||
t.br.y = __rfbmin(r.br.y, t.tl.y + 64); | |||
for (t.tl.x = r.tl.x; t.tl.x < r.br.x; t.tl.x += 64) { | |||
t.br.x = __rfbmin(r.br.x, t.tl.x + 64); | |||
pb->getImage(pf, buf, t); | |||
ZRLE_ENCODE_TILE((PIXEL_T*)buf, t.width(), t.height(), zos); | |||
} | |||
zos->flush(); | |||
} | |||
} | |||
void ZRLE_ENCODE_TILE (PIXEL_T* data, int w, int h, rdr::OutStream* os) | |||
{ | |||
// First find the palette and the number of runs | |||
Palette palette; | |||
int runs = 0; | |||
int singlePixels = 0; | |||
PIXEL_T* ptr = data; | |||
PIXEL_T* end = ptr + h * w; | |||
*end = ~*(end-1); // one past the end is different so the while loop ends | |||
while (ptr < end) { | |||
PIXEL_T pix = *ptr; | |||
if (*++ptr != pix) { | |||
singlePixels++; | |||
} else { | |||
while (*++ptr == pix) ; | |||
runs++; | |||
} | |||
palette.insert(pix, 1); | |||
} | |||
//fprintf(stderr,"runs %d, single pixels %d, paletteSize %d\n", | |||
// runs, singlePixels, ph.size); | |||
// Solid tile is a special case | |||
if (palette.size() == 1) { | |||
os->writeU8(1); | |||
WRITE_PIXEL(os, palette.getColour(0)); | |||
return; | |||
} | |||
// Try to work out whether to use RLE and/or a palette. We do this by | |||
// estimating the number of bytes which will be generated and picking the | |||
// method which results in the fewest bytes. Of course this may not result | |||
// in the fewest bytes after compression... | |||
bool useRle = false; | |||
bool usePalette = false; | |||
int estimatedBytes = w * h * (BPPOUT/8); // start assuming raw | |||
int plainRleBytes = ((BPPOUT/8)+1) * (runs + singlePixels); | |||
if (plainRleBytes < estimatedBytes) { | |||
useRle = true; | |||
estimatedBytes = plainRleBytes; | |||
} | |||
if (palette.size() < 128) { | |||
int paletteRleBytes = (BPPOUT/8) * palette.size() + 2 * runs + singlePixels; | |||
if (paletteRleBytes < estimatedBytes) { | |||
useRle = true; | |||
usePalette = true; | |||
estimatedBytes = paletteRleBytes; | |||
} | |||
if (palette.size() < 17) { | |||
int packedBytes = ((BPPOUT/8) * palette.size() + | |||
w * h * bitsPerPackedPixel[palette.size()-1] / 8); | |||
if (packedBytes < estimatedBytes) { | |||
useRle = false; | |||
usePalette = true; | |||
estimatedBytes = packedBytes; | |||
} | |||
} | |||
} | |||
if (!usePalette) palette.clear(); | |||
os->writeU8((useRle ? 128 : 0) | palette.size()); | |||
for (int i = 0; i < palette.size(); i++) { | |||
WRITE_PIXEL(os, palette.getColour(i)); | |||
} | |||
if (useRle) { | |||
PIXEL_T* ptr = data; | |||
PIXEL_T* end = ptr + w * h; | |||
PIXEL_T* runStart; | |||
PIXEL_T pix; | |||
while (ptr < end) { | |||
runStart = ptr; | |||
pix = *ptr++; | |||
while (*ptr == pix && ptr < end) | |||
ptr++; | |||
int len = ptr - runStart; | |||
if (len <= 2 && usePalette) { | |||
int index = palette.lookup(pix); | |||
if (len == 2) | |||
os->writeU8(index); | |||
os->writeU8(index); | |||
continue; | |||
} | |||
if (usePalette) { | |||
int index = palette.lookup(pix); | |||
os->writeU8(index | 128); | |||
} else { | |||
WRITE_PIXEL(os, pix); | |||
} | |||
len -= 1; | |||
while (len >= 255) { | |||
os->writeU8(255); | |||
len -= 255; | |||
} | |||
os->writeU8(len); | |||
} | |||
} else { | |||
// no RLE | |||
if (usePalette) { | |||
// packed pixels | |||
assert (palette.size() < 17); | |||
int bppp = bitsPerPackedPixel[palette.size()-1]; | |||
PIXEL_T* ptr = data; | |||
for (int i = 0; i < h; i++) { | |||
rdr::U8 nbits = 0; | |||
rdr::U8 byte = 0; | |||
PIXEL_T* eol = ptr + w; | |||
while (ptr < eol) { | |||
PIXEL_T pix = *ptr++; | |||
rdr::U8 index = palette.lookup(pix); | |||
byte = (byte << bppp) | index; | |||
nbits += bppp; | |||
if (nbits >= 8) { | |||
os->writeU8(byte); | |||
nbits = 0; | |||
} | |||
} | |||
if (nbits > 0) { | |||
byte <<= 8 - nbits; | |||
os->writeU8(byte); | |||
} | |||
} | |||
} else { | |||
// raw | |||
#ifdef CPIXEL | |||
for (PIXEL_T* ptr = data; ptr < data+w*h; ptr++) { | |||
WRITE_PIXEL(os, *ptr); | |||
} | |||
#else | |||
os->writeBytes(data, w*h*(BPP/8)); | |||
#endif | |||
} | |||
} | |||
} | |||
#undef PIXEL_T | |||
#undef WRITE_PIXEL | |||
#undef ZRLE_ENCODE | |||
#undef ZRLE_ENCODE_TILE | |||
#undef BPPOUT | |||
} |