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/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
 }