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/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.");
+}