Browse Source

Ported encoding optimizations from TurboVNC. The changes to the Tight parameters were determined through extensive low-level profiling (see http://www.virtualgl.org/pmwiki/uploads/About/turbototiger.pdf). The other enhancements involved: (1) porting the solid subrectangle pre-computation code from TightVNC/TurboVNC (it makes a pretty big difference-- see report), (2) encapsulating the JPEG encoder in its own class (this eliminates a buffer copy, and the JPEG buffer is now set to a decent size where it shouldn't ever need to be paged or re-allocated, except in rare corner cases), (3) adding support for last rect. encoding (necessary to support the solid rectangle pre-computation enhancements.


git-svn-id: svn://svn.code.sf.net/p/tigervnc/code/trunk@4626 3789f03b-4d11-0410-bbf8-ca57d06f2519
tags/v1.1.90
DRC 12 years ago
parent
commit
cd2c5d46c2

+ 1
- 1
common/rdr/MemOutStream.h View File

@@ -55,7 +55,7 @@ namespace rdr {

const void* data() { return (const void*)start; }

private:
protected:

// overrun() either doubles the buffer or adds enough space for nItems of
// size itemSize bytes.

+ 1
- 0
common/rfb/CMakeLists.txt View File

@@ -22,6 +22,7 @@ set(RFB_SOURCES
HTTPServer.cxx
HextileDecoder.cxx
HextileEncoder.cxx
JpegCompressor.cxx
KeyRemapper.cxx
LogWriter.cxx
Logger.cxx

+ 216
- 0
common/rfb/JpegCompressor.cxx View File

@@ -0,0 +1,216 @@
/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved.
* Copyright (C) 2011 D. R. Commander. 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.
*/

#include <rfb/JpegCompressor.h>
#include <rdr/Exception.h>
#include <rfb/Rect.h>
#include <rfb/PixelFormat.h>

using namespace rfb;

//
// Error manager implmentation for the JPEG library
//

static void
JpegErrorExit(j_common_ptr cinfo)
{
JPEG_ERROR_MGR *err = (JPEG_ERROR_MGR *)cinfo->err;

(*cinfo->err->output_message)(cinfo);
longjmp(err->jmpBuffer, 1);
}

static void
JpegOutputMessage(j_common_ptr cinfo)
{
JPEG_ERROR_MGR *err = (JPEG_ERROR_MGR *)cinfo->err;

(*cinfo->err->format_message)(cinfo, err->lastError);
}

//
// Destination manager implementation for the JPEG library.
//

static void
JpegInitDestination(j_compress_ptr cinfo)
{
JPEG_DEST_MGR *dest = (JPEG_DEST_MGR *)cinfo->dest;
JpegCompressor *jc = dest->instance;

jc->clear();
dest->pub.next_output_byte = jc->getptr();
dest->pub.free_in_buffer = jc->getend() - jc->getptr();
}

static boolean
JpegEmptyOutputBuffer(j_compress_ptr cinfo)
{
JPEG_DEST_MGR *dest = (JPEG_DEST_MGR *)cinfo->dest;
JpegCompressor *jc = dest->instance;

jc->setptr(dest->pub.next_output_byte);
jc->overrun(jc->getend() - jc->getstart(), 1);
dest->pub.next_output_byte = jc->getptr();
dest->pub.free_in_buffer = jc->getend() - jc->getptr();

return TRUE;
}

static void
JpegTermDestination(j_compress_ptr cinfo)
{
JPEG_DEST_MGR *dest = (JPEG_DEST_MGR *)cinfo->dest;
JpegCompressor *jc = dest->instance;

jc->setptr(dest->pub.next_output_byte);
}

JpegCompressor::JpegCompressor(int bufferLen) : MemOutStream(bufferLen)
{
cinfo.err = jpeg_std_error(&err.pub);
snprintf(err.lastError, JMSG_LENGTH_MAX, "No error");
err.pub.error_exit = JpegErrorExit;
err.pub.output_message = JpegOutputMessage;

if(setjmp(err.jmpBuffer)) {
// this will execute if libjpeg has an error
throw rdr::Exception(err.lastError);
}

jpeg_create_compress(&cinfo);

dest.pub.init_destination = JpegInitDestination;
dest.pub.empty_output_buffer = JpegEmptyOutputBuffer;
dest.pub.term_destination = JpegTermDestination;
dest.instance = this;
cinfo.dest = (struct jpeg_destination_mgr *)&dest;
}

JpegCompressor::~JpegCompressor(void)
{
if(setjmp(err.jmpBuffer)) {
// this will execute if libjpeg has an error
return;
}

jpeg_destroy_compress(&cinfo);
}

void JpegCompressor::compress(rdr::U8 *buf, const Rect& r,
const PixelFormat& pf, int quality, JPEG_SUBSAMP subsamp)
{
int w = r.width();
int h = r.height();
int pixelsize;
rdr::U8 *srcBuf = NULL;
bool srcBufIsTemp = false;
JSAMPROW *rowPointer = NULL;

if(setjmp(err.jmpBuffer)) {
// this will execute if libjpeg has an error
jpeg_abort_compress(&cinfo);
if (srcBufIsTemp && srcBuf) delete[] srcBuf;
if (rowPointer) delete[] rowPointer;
throw rdr::Exception(err.lastError);
}

cinfo.image_width = w;
cinfo.image_height = h;
cinfo.in_color_space = JCS_RGB;
pixelsize = 3;

#ifdef JCS_EXTENSIONS
// Try to have libjpeg read directly from our native format
if(pf.is888()) {
int redShift, greenShift, blueShift;

if(pf.bigEndian) {
redShift = 24 - pf.redShift;
greenShift = 24 - pf.greenShift;
blueShift = 24 - pf.blueShift;
} else {
redShift = pf.redShift;
greenShift = pf.greenShift;
blueShift = pf.blueShift;
}

if(redShift == 0 && greenShift == 8 && blueShift == 16)
cinfo.in_color_space = JCS_EXT_RGBX;
if(redShift == 16 && greenShift == 8 && blueShift == 0)
cinfo.in_color_space = JCS_EXT_BGRX;
if(redShift == 24 && greenShift == 16 && blueShift == 8)
cinfo.in_color_space = JCS_EXT_XBGR;
if(redShift == 8 && greenShift == 16 && blueShift == 24)
cinfo.in_color_space = JCS_EXT_XRGB;

if (cinfo.in_color_space != JCS_RGB) {
srcBuf = (rdr::U8 *)buf;
pixelsize = 4;
}
}
#endif

if (cinfo.in_color_space == JCS_RGB) {
srcBuf = new rdr::U8[w * h * pixelsize];
srcBufIsTemp = true;
pf.rgbFromBuffer(srcBuf, (const rdr::U8 *)buf, w * h);
}

cinfo.input_components = pixelsize;

jpeg_set_defaults(&cinfo);
jpeg_set_quality(&cinfo, quality, TRUE);
if(quality >= 96) cinfo.dct_method = JDCT_ISLOW;
else cinfo.dct_method = JDCT_FASTEST;

switch (subsamp) {
case SUBSAMP_420:
cinfo.comp_info[0].h_samp_factor = 2;
cinfo.comp_info[0].v_samp_factor = 2;
break;
case SUBSAMP_422:
cinfo.comp_info[0].h_samp_factor = 2;
cinfo.comp_info[0].v_samp_factor = 1;
break;
default:
cinfo.comp_info[0].h_samp_factor = 1;
cinfo.comp_info[0].v_samp_factor = 1;
}

rowPointer = new JSAMPROW[h];
for (int dy = 0; dy < h; dy++)
rowPointer[dy] = (JSAMPROW)(&srcBuf[dy * w * pixelsize]);

jpeg_start_compress(&cinfo, TRUE);
while (cinfo.next_scanline < cinfo.image_height)
jpeg_write_scanlines(&cinfo, &rowPointer[cinfo.next_scanline],
cinfo.image_height - cinfo.next_scanline);

jpeg_finish_compress(&cinfo);

if (srcBufIsTemp) delete[] srcBuf;
delete[] rowPointer;
}

void JpegCompressor::writeBytes(const void* data, int length)
{
throw rdr::Exception("writeBytes() is not valid with a JpegCompressor instance. Use compress() instead.");
}

+ 87
- 0
common/rfb/JpegCompressor.h View File

@@ -0,0 +1,87 @@
/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved.
* Copyright (C) 2011 D. R. Commander. 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.
*/

//
// JpegCompressor compresses RGB input into a JPEG image and stores it in
// an underlying MemOutStream
//

#ifndef __RFB_JPEGCOMPRESSOR_H__
#define __RFB_JPEGCOMPRESSOR_H__

#include <rdr/MemOutStream.h>
#include <rfb/PixelFormat.h>
#include <rfb/Rect.h>

#include <stdio.h>
extern "C" {
#include <jpeglib.h>
}
#include <setjmp.h>

namespace rfb {

typedef struct {
struct jpeg_error_mgr pub;
jmp_buf jmpBuffer;
char lastError[JMSG_LENGTH_MAX];
} JPEG_ERROR_MGR;

class JpegCompressor;

typedef struct {
struct jpeg_destination_mgr pub;
JpegCompressor *instance;
} JPEG_DEST_MGR;

enum JPEG_SUBSAMP {
SUBSAMP_NONE,
SUBSAMP_422,
SUBSAMP_420
};

class JpegCompressor : public rdr::MemOutStream {

public:

JpegCompressor(int bufferLen = 128*1024);
virtual ~JpegCompressor();

void compress(rdr::U8 *, const Rect&, const PixelFormat&, int,
JPEG_SUBSAMP);

void writeBytes(const void*, int);

inline rdr::U8* getstart() { return start; }

inline int overrun(int itemSize, int nItems) {
return MemOutStream::overrun(itemSize, nItems);
}

private:

struct jpeg_compress_struct cinfo;
JPEG_ERROR_MGR err;
JPEG_DEST_MGR dest;

};

} // end of namespace rfb

#endif

+ 20
- 5
common/rfb/SMsgWriterV3.cxx View File

@@ -1,5 +1,6 @@
/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved.
* Copyright 2009 Pierre Ossman for Cendio AB
* Copyright (C) 2011 D. R. Commander. 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
@@ -178,15 +179,20 @@ void SMsgWriterV3::writeFramebufferUpdateStart(int nRects)
startMsg(msgTypeFramebufferUpdate);
os->pad(1);

if (wsccb)
nRects++;
if (needSetDesktopName)
nRects++;
if (nRects != 0xFFFF) {
if (wsccb)
nRects++;
if (needSetDesktopName)
nRects++;
}

os->writeU16(nRects);

nRectsInUpdate = 0;
nRectsInHeader = nRects;
if (nRects == 0xFFFF)
nRectsInHeader = 0;
else
nRectsInHeader = nRects;

writePseudoRects();
}
@@ -208,6 +214,15 @@ void SMsgWriterV3::writeFramebufferUpdateEnd()
throw Exception("SMsgWriterV3::writeFramebufferUpdateEnd: "
"nRects out of sync");

if (nRectsInHeader == 0) {
// Send last rect. marker
os->writeS16(0);
os->writeS16(0);
os->writeU16(0);
os->writeU16(0);
os->writeU32(pseudoEncodingLastRect);
}

if (os == updateOS) {
os = realOS;
startMsg(msgTypeFramebufferUpdate);

+ 228
- 28
common/rfb/TightEncoder.cxx View File

@@ -1,4 +1,5 @@
/* Copyright (C) 2000-2003 Constantin Kaplinsky. All Rights Reserved.
* Copyright (C) 2011 D. R. Commander. 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
@@ -30,19 +31,19 @@ using namespace rfb;

// Adjustable parameters.
// FIXME: Get rid of #defines
#define TIGHT_JPEG_MIN_RECT_SIZE 1024
#define TIGHT_DETECT_MIN_WIDTH 8
#define TIGHT_DETECT_MIN_HEIGHT 8
#define TIGHT_MAX_SPLIT_TILE_SIZE 16
#define TIGHT_MIN_SPLIT_RECT_SIZE 4096
#define TIGHT_MIN_SOLID_SUBRECT_SIZE 2048

//
// 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?
//
// 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
@@ -63,18 +64,18 @@ using namespace rfb;
// 0 = JPEG quality 15, 4:2:0 subsampling (ratio ~= 100:1)

const TIGHT_CONF TightEncoder::conf[10] = {
{ 512, 32, 6, 0, 0, 0, 4, 15, SUBSAMP_420 }, // 0
{ 2048, 64, 6, 1, 1, 1, 8, 29, SUBSAMP_420 }, // 1
{ 4096, 128, 8, 3, 3, 2, 24, 41, SUBSAMP_420 }, // 2
{ 8192, 256, 12, 5, 5, 2, 32, 42, SUBSAMP_422 }, // 3
{ 16384, 512, 12, 6, 7, 3, 32, 62, SUBSAMP_422 }, // 4
{ 32768, 512, 12, 7, 8, 4, 32, 77, SUBSAMP_422 }, // 5
{ 65536, 1024, 16, 7, 8, 5, 32, 79, SUBSAMP_NONE }, // 6
{ 65536, 1024, 16, 8, 9, 6, 64, 86, SUBSAMP_NONE }, // 7
{ 65536, 2048, 24, 9, 9, 7, 64, 92, SUBSAMP_NONE }, // 8
{ 65536, 2048, 32, 9, 9, 9, 96,100, SUBSAMP_NONE } // 9
{ 65536, 2048, 6, 0, 0, 0, 4, 24, 15, SUBSAMP_420 }, // 0
{ 65536, 2048, 6, 1, 1, 1, 8, 24, 29, SUBSAMP_420 }, // 1
{ 65536, 2048, 8, 3, 3, 2, 24, 96, 41, SUBSAMP_420 }, // 2
{ 65536, 2048, 12, 5, 5, 2, 32, 96, 42, SUBSAMP_422 }, // 3
{ 65536, 2048, 12, 6, 7, 3, 32, 96, 62, SUBSAMP_422 }, // 4
{ 65536, 2048, 12, 7, 8, 4, 32, 96, 77, SUBSAMP_422 }, // 5
{ 65536, 2048, 16, 7, 8, 5, 32, 96, 79, SUBSAMP_NONE }, // 6
{ 65536, 2048, 16, 8, 9, 6, 64, 96, 86, SUBSAMP_NONE }, // 7
{ 65536, 2048, 24, 9, 9, 7, 64, 96, 92, SUBSAMP_NONE }, // 8
{ 65536, 2048, 32, 9, 9, 9, 96, 96,100, SUBSAMP_NONE } // 9
};
const int TightEncoder::defaultCompressLevel = 6;
const int TightEncoder::defaultCompressLevel = 1;

// FIXME: Not good to mirror TightEncoder's members here.
static const TIGHT_CONF* s_pconf;
@@ -129,11 +130,114 @@ void TightEncoder::setQualityLevel(int level)
}
}

bool TightEncoder::checkSolidTile(Rect& r, ImageGetter *ig, rdr::U32* colorPtr,
bool needSameColor)
{
switch (writer->bpp()) {
case 32:
return checkSolidTile32(r, ig, writer, colorPtr, needSameColor);
case 16:
return checkSolidTile16(r, ig, writer, colorPtr, needSameColor);
default:
return checkSolidTile8(r, ig, writer, colorPtr, needSameColor);
}
}

void TightEncoder::findBestSolidArea(Rect& r, ImageGetter *ig,
rdr::U32 colorValue, Rect& bestr)
{
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, ig, &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, ig, &colorValue, true))
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;
}
}

bestr.br.x = bestr.tl.x + w_best;
bestr.br.y = bestr.tl.y + h_best;
}

void TightEncoder::extendSolidArea(const Rect& r, ImageGetter *ig,
rdr::U32 colorValue, Rect& er)
{
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, ig, &colorValue, true))
break;
}
er.tl.y = cy + 1;

// ... downwards.
for (cy = er.br.y; ; cy++) {
sr.setXYWH(er.tl.x, cy, er.width(), 1);
if (cy >= r.br.y || !checkSolidTile(sr, ig, &colorValue, true))
break;
}
er.br.y = cy;

// ... 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, ig, &colorValue, true))
break;
}
er.tl.x = cx + 1;

// ... 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, ig, &colorValue, true))
break;
}
er.br.x = cx;
}

int TightEncoder::getNumRects(const Rect &r)
{
ConnParams* cp = writer->getConnParams();
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)
@@ -150,7 +254,7 @@ int TightEncoder::getNumRects(const Rect &r)
((h - 1) / subrectMaxHeight + 1));
}

bool TightEncoder::writeRect(const Rect& r, ImageGetter* ig, Rect* actual)
void TightEncoder::sendRectSimple(const Rect& r, ImageGetter* ig)
{
// Shortcuts to rectangle coordinates and dimensions.
const int x = r.tl.x;
@@ -158,15 +262,11 @@ bool TightEncoder::writeRect(const Rect& r, ImageGetter* ig, Rect* actual)
const unsigned int w = r.width();
const unsigned int h = r.height();

// Copy members of current TightEncoder instance to static variables.
s_pconf = pconf;
s_pjconf = pjconf;

// Encode small rects as is.
bool rectTooBig = w > pconf->maxRectWidth || w * h > pconf->maxRectSize;
if (!rectTooBig) {
writeSubrect(r, ig);
return true;
return;
}

// Compute max sub-rectangle size.
@@ -186,10 +286,110 @@ bool TightEncoder::writeRect(const Rect& r, ImageGetter* ig, Rect* actual)
writeSubrect(sr, ig);
}
}
}

bool TightEncoder::writeRect(const Rect& _r, ImageGetter* ig, Rect* actual)
{
ConnParams* cp = writer->getConnParams();

// Shortcuts to rectangle coordinates and dimensions.
Rect r = _r;
int x = r.tl.x;
int y = r.tl.y;
unsigned int w = r.width();
unsigned int h = r.height();

// Copy members of current TightEncoder instance to static variables.
s_pconf = pconf;
s_pjconf = pjconf;

// Encode small rects as is.
if (!cp->supportsLastRect || w * h < TIGHT_MIN_SPLIT_RECT_SIZE) {
sendRectSimple(r, ig);
return true;
}

// Split big rects into separately encoded subrects.
Rect sr, bestr;
unsigned int dx, dy, dw, dh;
rdr::U32 colorValue;
int maxRectSize = s_pconf->maxRectSize;
int maxRectWidth = s_pconf->maxRectWidth;
int nMaxWidth = (w > maxRectWidth) ? maxRectWidth : w;
int nMaxRows = s_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, ig);
r.tl.y += nMaxRows;
y = r.tl.y;
h = r.height();
}

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, ig, &colorValue, false)) {

// Get dimensions of solid-color area.
sr.setXYWH(dx, dy, r.br.x - dx, r.br.y - dy);
findBestSolidArea(sr, ig, 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, ig, 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, ig);
}
if (bestr.tl.x != x) {
sr.setXYWH(x, bestr.tl.y, bestr.tl.x - x, bestr.height());
writeRect(sr, ig, NULL);
}

// Send solid-color rectangle.
writeSubrect(bestr, ig, 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, ig, NULL);
}
if (bestr.br.y != r.br.y) {
sr.setXYWH(x, bestr.br.y, w, r.br.y - bestr.br.y);
writeRect(sr, ig, NULL);
}

return true;
}
}
}

// No suitable solid-color rectangles found.
sendRectSimple(r, ig);
return true;
}

void TightEncoder::writeSubrect(const Rect& r, ImageGetter* ig)
void TightEncoder::writeSubrect(const Rect& r, ImageGetter* ig,
bool forceSolid)
{
rdr::U8* imageBuf = writer->getImageBuf(r.area());
ConnParams* cp = writer->getConnParams();
@@ -197,11 +397,11 @@ void TightEncoder::writeSubrect(const Rect& r, ImageGetter* ig)

switch (writer->bpp()) {
case 8:
tightEncode8(r, &mos, zos, imageBuf, cp, ig); break;
tightEncode8(r, &mos, zos, jc, imageBuf, cp, ig, forceSolid); break;
case 16:
tightEncode16(r, &mos, zos, imageBuf, cp, ig); break;
tightEncode16(r, &mos, zos, jc, imageBuf, cp, ig, forceSolid); break;
case 32:
tightEncode32(r, &mos, zos, imageBuf, cp, ig); break;
tightEncode32(r, &mos, zos, jc, imageBuf, cp, ig, forceSolid); break;
}

writer->startRect(r, encodingTight);

+ 13
- 8
common/rfb/TightEncoder.h View File

@@ -1,4 +1,5 @@
/* Copyright (C) 2000-2003 Constantin Kaplinsky. All Rights Reserved.
* Copyright (C) 2011 D. R. Commander
*
* This is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -20,6 +21,7 @@

#include <rdr/MemOutStream.h>
#include <rdr/ZlibOutStream.h>
#include <rfb/JpegCompressor.h>
#include <rfb/Encoder.h>

// FIXME: Check if specifying extern "C" is really necessary.
@@ -30,19 +32,14 @@ extern "C" {

namespace rfb {

enum subsampEnum {
SUBSAMP_NONE,
SUBSAMP_422,
SUBSAMP_420
};

struct TIGHT_CONF {
unsigned int maxRectSize, maxRectWidth;
unsigned int monoMinRectSize;
int idxZlibLevel, monoZlibLevel, rawZlibLevel;
int idxMaxColorsDivisor;
int palMaxColorsWithJPEG;
int jpegQuality;
subsampEnum jpegSubSample;
JPEG_SUBSAMP jpegSubSample;
};

//
@@ -67,11 +64,19 @@ namespace rfb {

private:
TightEncoder(SMsgWriter* writer);
void writeSubrect(const Rect& r, ImageGetter* ig);
bool checkSolidTile(Rect& r, ImageGetter *ig, rdr::U32* colorPtr,
bool needSameColor);
void extendSolidArea(const Rect& r, ImageGetter *ig,
rdr::U32 colorValue, Rect& er);
void findBestSolidArea(Rect& r, ImageGetter* ig, rdr::U32 colorValue,
Rect& bestr);
void sendRectSimple(const Rect& r, ImageGetter* ig);
void writeSubrect(const Rect& r, ImageGetter* ig, bool forceSolid = false);

SMsgWriter* writer;
rdr::MemOutStream mos;
rdr::ZlibOutStream zos[4];
JpegCompressor jc;

static const int defaultCompressLevel;
static const TIGHT_CONF conf[];

+ 8
- 2
common/rfb/VNCSConnectionST.cxx View File

@@ -722,8 +722,14 @@ void VNCSConnectionST::writeFramebufferUpdate()
std::vector<Rect>::const_iterator i;
ui.changed.get_rects(&rects);
for (i = rects.begin(); i != rects.end(); i++) {
if (i->width() && i->height())
nRects += writer()->getNumRects(*i);
if (i->width() && i->height()) {
int nUpdateRects = writer()->getNumRects(*i);
if (nUpdateRects == 0 && cp.currentEncoding() == encodingTight) {
nRects = 0xFFFF; break;
}
else
nRects += nUpdateRects;
}
}
writer()->writeFramebufferUpdateStart(nRects);

+ 63
- 189
common/rfb/tightEncode.h View File

@@ -45,14 +45,13 @@ namespace rfb {
#define SWAP_PIXEL CONCAT2E(SWAP,BPP)
#define HASH_FUNCTION CONCAT2E(HASH_FUNC,BPP)
#define PACK_PIXELS CONCAT2E(packPixels,BPP)
#define DETECT_SMOOTH_IMAGE CONCAT2E(detectSmoothImage,BPP)
#define ENCODE_SOLID_RECT CONCAT2E(encodeSolidRect,BPP)
#define ENCODE_FULLCOLOR_RECT CONCAT2E(encodeFullColorRect,BPP)
#define ENCODE_MONO_RECT CONCAT2E(encodeMonoRect,BPP)
#define ENCODE_INDEXED_RECT CONCAT2E(encodeIndexedRect,BPP)
#define PREPARE_JPEG_ROW CONCAT2E(prepareJpegRow,BPP)
#define ENCODE_JPEG_RECT CONCAT2E(encodeJpegRect,BPP)
#define FILL_PALETTE CONCAT2E(fillPalette,BPP)
#define CHECK_SOLID_TILE CONCAT2E(checkSolidTile,BPP)

#ifndef TIGHT_ONCE
#define TIGHT_ONCE
@@ -203,54 +202,6 @@ static void compressData(rdr::OutStream *os, rdr::ZlibOutStream *zos,
}
}

//
// Destination manager implementation for the JPEG library.
// FIXME: Implement JPEG compression in new rdr::JpegOutStream class.
//

// FIXME: Keeping a MemOutStream instance may consume too much space.
rdr::MemOutStream s_jpeg_os;

static struct jpeg_destination_mgr s_jpegDstManager;
static JOCTET *s_jpegDstBuffer;
static size_t s_jpegDstBufferLen;

static void
JpegInitDestination(j_compress_ptr cinfo)
{
s_jpeg_os.clear();
s_jpegDstManager.next_output_byte = s_jpegDstBuffer;
s_jpegDstManager.free_in_buffer = s_jpegDstBufferLen;
}

static boolean
JpegEmptyOutputBuffer(j_compress_ptr cinfo)
{
s_jpeg_os.writeBytes(s_jpegDstBuffer, s_jpegDstBufferLen);
s_jpegDstManager.next_output_byte = s_jpegDstBuffer;
s_jpegDstManager.free_in_buffer = s_jpegDstBufferLen;

return TRUE;
}

static void
JpegTermDestination(j_compress_ptr cinfo)
{
int dataLen = s_jpegDstBufferLen - s_jpegDstManager.free_in_buffer;
s_jpeg_os.writeBytes(s_jpegDstBuffer, dataLen);
}

static void
JpegSetDstManager(j_compress_ptr cinfo, JOCTET *buf, size_t buflen)
{
s_jpegDstBuffer = buf;
s_jpegDstBufferLen = buflen;
s_jpegDstManager.init_destination = JpegInitDestination;
s_jpegDstManager.empty_output_buffer = JpegEmptyOutputBuffer;
s_jpegDstManager.term_destination = JpegTermDestination;
cinfo->dest = &s_jpegDstManager;
}

#endif // #ifndef TIGHT_ONCE

static void ENCODE_SOLID_RECT (rdr::OutStream *os,
@@ -262,7 +213,7 @@ static void ENCODE_MONO_RECT (rdr::OutStream *os, rdr::ZlibOutStream zos[4]
#if (BPP != 8)
static void ENCODE_INDEXED_RECT (rdr::OutStream *os, rdr::ZlibOutStream zos[4],
PIXEL_T *buf, const PixelFormat& pf, const Rect& r);
static void ENCODE_JPEG_RECT (rdr::OutStream *os,
static void ENCODE_JPEG_RECT (rdr::OutStream *os, JpegCompressor& jc,
PIXEL_T *buf, const PixelFormat& pf, const Rect& r);
#endif

@@ -294,42 +245,25 @@ static inline unsigned int PACK_PIXELS (PIXEL_T *buf, unsigned int count,
#endif
}

//
// Function to guess if a given rectangle is suitable for JPEG compression.
// Returns true is it looks like a good data for JPEG, false otherwise.
//
// FIXME: Scan the image and determine is it really good for JPEG.
//

#if (BPP != 8)
static bool DETECT_SMOOTH_IMAGE (PIXEL_T *buf, const Rect& r)
{
if (r.width() < TIGHT_DETECT_MIN_WIDTH ||
r.height() < TIGHT_DETECT_MIN_HEIGHT ||
r.area() < TIGHT_JPEG_MIN_RECT_SIZE ||
s_pjconf == NULL)
return 0;

return 1;
}
#endif

// FIXME: Split rectangles into smaller ones!
// FIXME: Compare encoder code with 1.3 before the final version.

//
// Main function of the Tight encoder
//

void TIGHT_ENCODE (const Rect& r, rdr::OutStream *os,
rdr::ZlibOutStream zos[4], void* buf, ConnParams* cp
rdr::ZlibOutStream zos[4], JpegCompressor &jc, void* buf,
ConnParams* cp
#ifdef EXTRA_ARGS
, EXTRA_ARGS
, EXTRA_ARGS,
#endif
)
bool forceSolid)
{
const PixelFormat& pf = cp->pf();
GET_IMAGE_INTO_BUF(r, buf);
if(forceSolid) {
GET_IMAGE_INTO_BUF(Rect(r.tl.x, r.tl.y, r.tl.x + 1, r.tl.y + 1), buf);
}
else {
GET_IMAGE_INTO_BUF(r, buf);
}
PIXEL_T* pixels = (PIXEL_T*)buf;

#if (BPP == 32)
@@ -338,23 +272,24 @@ void TIGHT_ENCODE (const Rect& r, rdr::OutStream *os,
s_pack24 = pf.is888();
#endif

s_palMaxColors = r.area() / s_pconf->idxMaxColorsDivisor;
if (s_palMaxColors < 2 && r.area() >= s_pconf->monoMinRectSize) {
s_palMaxColors = 2;
}
// FIXME: Temporary limitation for switching to JPEG earlier.
if (s_palMaxColors > 96 && s_pjconf != NULL) {
s_palMaxColors = 96;
}
if (forceSolid)
s_palNumColors = 1;
else {
s_palMaxColors = r.area() / s_pconf->idxMaxColorsDivisor;
if (s_pjconf != NULL) s_palMaxColors = s_pconf->palMaxColorsWithJPEG;
if (s_palMaxColors < 2 && r.area() >= s_pconf->monoMinRectSize) {
s_palMaxColors = 2;
}

FILL_PALETTE(pixels, r.area());
FILL_PALETTE(pixels, r.area());
}

switch (s_palNumColors) {
case 0:
// Truecolor image
#if (BPP != 8)
if (s_pjconf != NULL && DETECT_SMOOTH_IMAGE(pixels, r)) {
ENCODE_JPEG_RECT(os, pixels, pf, r);
if (s_pjconf != NULL) {
ENCODE_JPEG_RECT(os, jc, pixels, pf, r);
break;
}
#endif
@@ -518,106 +453,16 @@ static void ENCODE_INDEXED_RECT (rdr::OutStream *os, rdr::ZlibOutStream zos[4],
//

#if (BPP != 8)
static void ENCODE_JPEG_RECT (rdr::OutStream *os, PIXEL_T *buf,
const PixelFormat& pf, const Rect& r)
static void ENCODE_JPEG_RECT (rdr::OutStream *os, JpegCompressor& jc,
PIXEL_T *buf, const PixelFormat& pf,
const Rect& r)
{
int w = r.width();
int h = r.height();
int pixelsize;
rdr::U8 *srcBuf = NULL;
bool srcBufIsTemp = false;

struct jpeg_compress_struct cinfo;
struct jpeg_error_mgr jerr;

cinfo.err = jpeg_std_error(&jerr);
jpeg_create_compress(&cinfo);

cinfo.image_width = w;
cinfo.image_height = h;
cinfo.in_color_space = JCS_RGB;
pixelsize = 3;

#ifdef JCS_EXTENSIONS
// Try to have libjpeg read directly from our native format
if(pf.is888()) {
int redShift, greenShift, blueShift;

if(pf.bigEndian) {
redShift = 24 - pf.redShift;
greenShift = 24 - pf.greenShift;
blueShift = 24 - pf.blueShift;
} else {
redShift = pf.redShift;
greenShift = pf.greenShift;
blueShift = pf.blueShift;
}

if(redShift == 0 && greenShift == 8 && blueShift == 16)
cinfo.in_color_space = JCS_EXT_RGBX;
if(redShift == 16 && greenShift == 8 && blueShift == 0)
cinfo.in_color_space = JCS_EXT_BGRX;
if(redShift == 24 && greenShift == 16 && blueShift == 8)
cinfo.in_color_space = JCS_EXT_XBGR;
if(redShift == 8 && greenShift == 16 && blueShift == 24)
cinfo.in_color_space = JCS_EXT_XRGB;

if (cinfo.in_color_space != JCS_RGB) {
srcBuf = (rdr::U8 *)buf;
pixelsize = 4;
}
}
#endif

if (cinfo.in_color_space == JCS_RGB) {
srcBuf = new rdr::U8[w * h * pixelsize];
srcBufIsTemp = true;
pf.rgbFromBuffer(srcBuf, (const rdr::U8 *)buf, w * h);
}

cinfo.input_components = pixelsize;

jpeg_set_defaults(&cinfo);
jpeg_set_quality(&cinfo, s_pjconf->jpegQuality, TRUE);
if(s_pjconf->jpegQuality >= 96) cinfo.dct_method = JDCT_ISLOW;
else cinfo.dct_method = JDCT_FASTEST;

switch (s_pjconf->jpegSubSample) {
case SUBSAMP_420:
cinfo.comp_info[0].h_samp_factor = 2;
cinfo.comp_info[0].v_samp_factor = 2;
break;
case SUBSAMP_422:
cinfo.comp_info[0].h_samp_factor = 2;
cinfo.comp_info[0].v_samp_factor = 1;
break;
default:
cinfo.comp_info[0].h_samp_factor = 1;
cinfo.comp_info[0].v_samp_factor = 1;
}

rdr::U8 *dstBuf = new rdr::U8[2048];
JpegSetDstManager(&cinfo, dstBuf, 2048);

JSAMPROW *rowPointer = new JSAMPROW[h];
for (int dy = 0; dy < h; dy++)
rowPointer[dy] = (JSAMPROW)(&srcBuf[dy * w * pixelsize]);

jpeg_start_compress(&cinfo, TRUE);
while (cinfo.next_scanline < cinfo.image_height)
jpeg_write_scanlines(&cinfo, &rowPointer[cinfo.next_scanline],
cinfo.image_height - cinfo.next_scanline);

jpeg_finish_compress(&cinfo);
jpeg_destroy_compress(&cinfo);

if (srcBufIsTemp) delete[] srcBuf;
delete[] dstBuf;
delete[] rowPointer;

jc.clear();
jc.compress((rdr::U8 *)buf, r, pf, s_pjconf->jpegQuality,
s_pjconf->jpegSubSample);
os->writeU8(0x09 << 4);
os->writeCompactLength(s_jpeg_os.length());
os->writeBytes(s_jpeg_os.data(), s_jpeg_os.length());
os->writeCompactLength(jc.length());
os->writeBytes(jc.data(), jc.length());
}
#endif // #if (BPP != 8)

@@ -726,18 +571,47 @@ static void FILL_PALETTE (PIXEL_T *data, int count)
}
#endif // #if (BPP == 8)

bool CHECK_SOLID_TILE(Rect& r, ImageGetter* ig, SMsgWriter* writer,
rdr::U32 *colorPtr, bool needSameColor)
{
PIXEL_T *buf;
PIXEL_T colorValue;
int dx, dy;
Rect sr;

buf = (PIXEL_T *)writer->getImageBuf(r.area());
sr.setXYWH(r.tl.x, r.tl.y, 1, 1);
GET_IMAGE_INTO_BUF(sr, buf);

colorValue = *buf;
if (needSameColor && (rdr::U32)colorValue != *colorPtr)
return false;

for (dy = 0; dy < r.height(); dy++) {
Rect sr;
sr.setXYWH(r.tl.x, r.tl.y + dy, r.width(), 1);
GET_IMAGE_INTO_BUF(sr, buf);
for (dx = 0; dx < r.width(); dx++) {
if (colorValue != buf[dx])
return false;
}
}

*colorPtr = (rdr::U32)colorValue;
return true;
}

#undef PIXEL_T
#undef WRITE_PIXEL
#undef TIGHT_ENCODE
#undef SWAP_PIXEL
#undef HASH_FUNCTION
#undef PACK_PIXELS
#undef DETECT_SMOOTH_IMAGE
#undef ENCODE_SOLID_RECT
#undef ENCODE_FULLCOLOR_RECT
#undef ENCODE_MONO_RECT
#undef ENCODE_INDEXED_RECT
#undef PREPARE_JPEG_ROW
#undef ENCODE_JPEG_RECT
#undef FILL_PALETTE
#undef CHECK_SOLID_TILE
}

Loading…
Cancel
Save