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
diff --git a/common/rdr/MemOutStream.h b/common/rdr/MemOutStream.h
index ee3e950..3b17e55 100644
--- a/common/rdr/MemOutStream.h
+++ b/common/rdr/MemOutStream.h
@@ -55,7 +55,7 @@
const void* data() { return (const void*)start; }
- private:
+ protected:
// overrun() either doubles the buffer or adds enough space for nItems of
// size itemSize bytes.
diff --git a/common/rfb/CMakeLists.txt b/common/rfb/CMakeLists.txt
index c28b1ac..be03c64 100644
--- a/common/rfb/CMakeLists.txt
+++ b/common/rfb/CMakeLists.txt
@@ -22,6 +22,7 @@
HTTPServer.cxx
HextileDecoder.cxx
HextileEncoder.cxx
+ JpegCompressor.cxx
KeyRemapper.cxx
LogWriter.cxx
Logger.cxx
diff --git a/common/rfb/JpegCompressor.cxx b/common/rfb/JpegCompressor.cxx
new file mode 100644
index 0000000..e203560
--- /dev/null
+++ b/common/rfb/JpegCompressor.cxx
@@ -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.");
+}
diff --git a/common/rfb/JpegCompressor.h b/common/rfb/JpegCompressor.h
new file mode 100644
index 0000000..5a9c2fd
--- /dev/null
+++ b/common/rfb/JpegCompressor.h
@@ -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
diff --git a/common/rfb/SMsgWriterV3.cxx b/common/rfb/SMsgWriterV3.cxx
index 08f5a2e..af1187e 100644
--- a/common/rfb/SMsgWriterV3.cxx
+++ b/common/rfb/SMsgWriterV3.cxx
@@ -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 @@
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 @@
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);
diff --git a/common/rfb/TightEncoder.cxx b/common/rfb/TightEncoder.cxx
index f9684b9..52673a6 100644
--- a/common/rfb/TightEncoder.cxx
+++ b/common/rfb/TightEncoder.cxx
@@ -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 @@
// 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 @@
// 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 @@
}
}
+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 @@
((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 @@
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 @@
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 @@
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);
diff --git a/common/rfb/TightEncoder.h b/common/rfb/TightEncoder.h
index a36340f..064a834 100644
--- a/common/rfb/TightEncoder.h
+++ b/common/rfb/TightEncoder.h
@@ -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 @@
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 @@
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[];
diff --git a/common/rfb/VNCSConnectionST.cxx b/common/rfb/VNCSConnectionST.cxx
index 5add7ea..4ca58ac 100644
--- a/common/rfb/VNCSConnectionST.cxx
+++ b/common/rfb/VNCSConnectionST.cxx
@@ -722,8 +722,14 @@
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);
diff --git a/common/rfb/tightEncode.h b/common/rfb/tightEncode.h
index 28d6dc2..d4c8f6e 100644
--- a/common/rfb/tightEncode.h
+++ b/common/rfb/tightEncode.h
@@ -45,14 +45,13 @@
#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 @@
}
}
-//
-// 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 @@
#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
@@ -295,41 +246,24 @@
}
//
-// 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 @@
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 @@
//
#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 @@
}
#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
}