aboutsummaryrefslogtreecommitdiffstats
path: root/rfb
diff options
context:
space:
mode:
authorPeter Åstrand <astrand@cendio.se>2005-02-10 15:13:38 +0000
committerPeter Åstrand <astrand@cendio.se>2005-02-10 15:13:38 +0000
commit9b0809c2cf25767648376bda39dc68dbad7a33b2 (patch)
treef4bfef58feb514a2956bc48ac8b749110c9a9d8d /rfb
parent1029ea358f3cb4a5f24cb49f81d709aadd7acf49 (diff)
downloadtigervnc-9b0809c2cf25767648376bda39dc68dbad7a33b2.tar.gz
tigervnc-9b0809c2cf25767648376bda39dc68dbad7a33b2.zip
Tight encoding support, except for rfb/SMsgWriterV3.cxx
git-svn-id: svn://svn.code.sf.net/p/tigervnc/code/trunk@164 3789f03b-4d11-0410-bbf8-ca57d06f2519
Diffstat (limited to 'rfb')
-rw-r--r--rfb/Encoder.cxx2
-rw-r--r--rfb/Encoder.h4
-rw-r--r--rfb/Makefile.in1
-rw-r--r--rfb/SMsgWriter.cxx27
-rw-r--r--rfb/SMsgWriter.h8
-rw-r--r--rfb/SMsgWriterV3.h1
-rw-r--r--rfb/TightEncoder.cxx194
-rw-r--r--rfb/TightEncoder.h79
-rw-r--r--rfb/secTypes.cxx2
-rw-r--r--rfb/tightEncode.h719
10 files changed, 1037 insertions, 0 deletions
diff --git a/rfb/Encoder.cxx b/rfb/Encoder.cxx
index f2d6d3d2..aba38b33 100644
--- a/rfb/Encoder.cxx
+++ b/rfb/Encoder.cxx
@@ -22,6 +22,7 @@
#include <rfb/RREEncoder.h>
#include <rfb/HextileEncoder.h>
#include <rfb/ZRLEEncoder.h>
+#include <rfb/TightEncoder.h>
using namespace rfb;
@@ -72,4 +73,5 @@ EncoderInit::EncoderInit()
Encoder::registerEncoder(encodingRRE, RREEncoder::create);
Encoder::registerEncoder(encodingHextile, HextileEncoder::create);
Encoder::registerEncoder(encodingZRLE, ZRLEEncoder::create);
+ Encoder::registerEncoder(encodingTight, TightEncoder::create);
}
diff --git a/rfb/Encoder.h b/rfb/Encoder.h
index 7795d90b..71739ba2 100644
--- a/rfb/Encoder.h
+++ b/rfb/Encoder.h
@@ -31,6 +31,10 @@ namespace rfb {
public:
virtual ~Encoder();
+ virtual void setCompressLevel(int level) {};
+ virtual void setQualityLevel(int level) {};
+ virtual int getNumRects(const Rect &r) { return 1; }
+
// writeRect() tries to write the given rectangle. If it is unable to
// write the whole rectangle it returns false and sets actual to the actual
// rectangle which was updated.
diff --git a/rfb/Makefile.in b/rfb/Makefile.in
index a373eb7c..9919c179 100644
--- a/rfb/Makefile.in
+++ b/rfb/Makefile.in
@@ -38,6 +38,7 @@ CXXSRCS = \
SSecurityFactoryStandard.cxx \
SSecurityVncAuth.cxx \
TightDecoder.cxx \
+ TightEncoder.cxx \
TransImageGetter.cxx \
UpdateTracker.cxx \
VNCSConnectionST.cxx \
diff --git a/rfb/SMsgWriter.cxx b/rfb/SMsgWriter.cxx
index ac743826..067fb117 100644
--- a/rfb/SMsgWriter.cxx
+++ b/rfb/SMsgWriter.cxx
@@ -90,6 +90,33 @@ void SMsgWriter::writeServerCutText(const char* str, int len)
endMsg();
}
+void SMsgWriter::setupCurrentEncoder()
+{
+ unsigned int encoding = cp->currentEncoding();
+
+ // FIXME: Code duplication, see writeRect().
+ if (!encoders[encoding]) {
+ encoders[encoding] = Encoder::createEncoder(encoding, this);
+ assert(encoders[encoding]);
+ }
+
+ encoders[encoding]->setCompressLevel(cp->compressLevel);
+ encoders[encoding]->setQualityLevel(cp->qualityLevel);
+}
+
+int SMsgWriter::getNumRects(const Rect &r)
+{
+ unsigned int encoding = cp->currentEncoding();
+
+ if (!encoders[encoding])
+ setupCurrentEncoder();
+
+ return encoders[encoding]->getNumRects(r);
+}
+
+// FIXME: This functions does not compute the number of rectangles correctly
+// if the Tight encoder is used (but currently that does not matter
+// because this function is never used).
void SMsgWriter::writeFramebufferUpdate(const UpdateInfo& ui, ImageGetter* ig,
Region* updatedRegion)
{
diff --git a/rfb/SMsgWriter.h b/rfb/SMsgWriter.h
index 6eba0682..5c15867f 100644
--- a/rfb/SMsgWriter.h
+++ b/rfb/SMsgWriter.h
@@ -62,6 +62,14 @@ namespace rfb {
virtual void writeBell();
virtual void writeServerCutText(const char* str, int len);
+ // setupCurrentEncoder() should be called before each framebuffer update,
+ // prior to calling getNumRects() or writeFramebufferUpdateStart().
+ void setupCurrentEncoder();
+
+ // getNumRects() computes the number of sub-rectangles that will compose a
+ // given rectangle, for current encoder.
+ int getNumRects(const Rect &r);
+
// writeSetDesktopSize() on a V3 writer won't actually write immediately,
// but will write the relevant pseudo-rectangle as part of the next update.
virtual bool writeSetDesktopSize()=0;
diff --git a/rfb/SMsgWriterV3.h b/rfb/SMsgWriterV3.h
index 3881061f..0bad8f17 100644
--- a/rfb/SMsgWriterV3.h
+++ b/rfb/SMsgWriterV3.h
@@ -51,6 +51,7 @@ namespace rfb {
int nRectsInHeader;
WriteSetCursorCallback* wsccb;
bool needSetDesktopSize;
+ bool needLastRect;
};
}
#endif
diff --git a/rfb/TightEncoder.cxx b/rfb/TightEncoder.cxx
new file mode 100644
index 00000000..fe23a971
--- /dev/null
+++ b/rfb/TightEncoder.cxx
@@ -0,0 +1,194 @@
+/* Copyright (C) 2000-2003 Constantin Kaplinsky. All Rights Reserved.
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
+ * USA.
+ */
+#include <rdr/OutStream.h>
+#include <rfb/ImageGetter.h>
+#include <rfb/encodings.h>
+#include <rfb/ConnParams.h>
+#include <rfb/SMsgWriter.h>
+#include <rfb/TightEncoder.h>
+
+using namespace rfb;
+
+// Minimum amount of data to be compressed. This value should not be
+// changed, doing so will break compatibility with existing clients.
+#define TIGHT_MIN_TO_COMPRESS 12
+
+// Adjustable parameters.
+// FIXME: Get rid of #defines
+#define TIGHT_JPEG_MIN_RECT_SIZE 2048
+#define TIGHT_DETECT_SUBROW_WIDTH 7
+#define TIGHT_DETECT_MIN_WIDTH 8
+#define TIGHT_DETECT_MIN_HEIGHT 8
+
+//
+// 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?
+//
+
+const TIGHT_CONF TightEncoder::conf[10] = {
+ { 512, 32, 6, 0, 0, 0, 4, 5, 10000, 23000 },
+ { 768, 32, 6, 1, 1, 1, 8, 10, 8000, 18000 },
+ { 1024, 32, 8, 3, 3, 2, 24, 15, 6500, 15000 },
+ { 1536, 48, 12, 5, 5, 3, 32, 25, 5000, 12000 },
+ { 2048, 48, 12, 6, 6, 4, 32, 37, 4000, 10000 },
+ { 3072, 64, 12, 7, 7, 5, 32, 50, 3000, 8000 },
+ { 4096, 64, 16, 7, 7, 6, 48, 60, 2000, 5000 },
+ { 6144, 64, 16, 8, 8, 7, 64, 70, 1000, 2500 },
+ { 8192, 128, 24, 9, 9, 8, 64, 75, 500, 1200 },
+ { 10240, 128, 32, 9, 9, 9, 96, 80, 200, 500 }
+};
+const int TightEncoder::defaultCompressLevel = 6;
+
+// FIXME: Not good to mirror TightEncoder's members here.
+static const TIGHT_CONF* s_pconf;
+static const TIGHT_CONF* s_pjconf;
+
+//
+// Including BPP-dependent implementation of the encoder.
+//
+
+#define EXTRA_ARGS ImageGetter* ig
+#define GET_IMAGE_INTO_BUF(r,buf) ig->getImage(buf, r);
+#define BPP 8
+#include <rfb/tightEncode.h>
+#undef BPP
+#define BPP 16
+#include <rfb/tightEncode.h>
+#undef BPP
+#define BPP 32
+#include <rfb/tightEncode.h>
+#undef BPP
+
+Encoder* TightEncoder::create(SMsgWriter* writer)
+{
+ return new TightEncoder(writer);
+}
+
+TightEncoder::TightEncoder(SMsgWriter* writer_) : writer(writer_)
+{
+ setCompressLevel(defaultCompressLevel);
+ setQualityLevel(-1);
+}
+
+TightEncoder::~TightEncoder()
+{
+}
+
+void TightEncoder::setCompressLevel(int level)
+{
+ if (level >= 0 && level <= 9) {
+ pconf = &conf[level];
+ } else {
+ pconf = &conf[defaultCompressLevel];
+ }
+}
+
+void TightEncoder::setQualityLevel(int level)
+{
+ if (level >= 0 && level <= 9) {
+ pjconf = &conf[level];
+ } else {
+ pjconf = NULL;
+ }
+}
+
+//int TightEncoder::getNumRects(const Rect &r)
+//{
+// const unsigned int w = r.width();
+// const unsigned int h = r.height();
+//
+// // Will this rectangle split into subrects?
+// bool rectTooBig = w > pconf->maxRectWidth || w * h > pconf->maxRectSize;
+// if (!rectTooBig)
+// return 1;
+//
+// // Compute max sub-rectangle size.
+// const unsigned int subrectMaxWidth =
+// (w > pconf->maxRectWidth) ? pconf->maxRectWidth : w;
+// const unsigned int subrectMaxHeight =
+// pconf->maxRectSize / subrectMaxWidth;
+//
+// // Return the number of subrects.
+// return (((w - 1) / pconf->maxRectWidth + 1) *
+// ((h - 1) / subrectMaxHeight + 1));
+//}
+//
+bool TightEncoder::writeRect(const Rect& r, ImageGetter* ig, Rect* actual)
+{
+ // Shortcuts to rectangle coordinates and dimensions.
+ const int x = r.tl.x;
+ const int y = r.tl.y;
+ const unsigned int w = r.width();
+ const unsigned int h = r.height();
+
+ // 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;
+ }
+
+ // Compute max sub-rectangle size.
+ const unsigned int subrectMaxWidth =
+ (w > pconf->maxRectWidth) ? pconf->maxRectWidth : w;
+ const unsigned int subrectMaxHeight =
+ pconf->maxRectSize / subrectMaxWidth;
+
+ // Split big rects into separately encoded subrects.
+ Rect sr;
+ unsigned int dx, dy, sw, sh;
+ for (dy = 0; dy < h; dy += subrectMaxHeight) {
+ for (dx = 0; dx < w; dx += pconf->maxRectWidth) {
+ sw = (dx + pconf->maxRectWidth < w) ? pconf->maxRectWidth : w - dx;
+ sh = (dy + subrectMaxHeight < h) ? subrectMaxHeight : h - dy;
+ sr.setXYWH(x + dx, y + dy, sw, sh);
+ writeSubrect(sr, ig);
+ }
+ }
+ return true;
+}
+
+void TightEncoder::writeSubrect(const Rect& r, ImageGetter* ig)
+{
+ rdr::U8* imageBuf = writer->getImageBuf(r.area());
+ ConnParams* cp = writer->getConnParams();
+ mos.clear();
+
+ switch (writer->bpp()) {
+ case 8:
+ tightEncode8(r, &mos, zos, imageBuf, cp, ig); break;
+ case 16:
+ tightEncode16(r, &mos, zos, imageBuf, cp, ig); break;
+ case 32:
+ tightEncode32(r, &mos, zos, imageBuf, cp, ig); break;
+ }
+
+ writer->startRect(r, encodingTight);
+ rdr::OutStream* os = writer->getOutStream();
+ os->writeBytes(mos.data(), mos.length());
+ writer->endRect();
+}
diff --git a/rfb/TightEncoder.h b/rfb/TightEncoder.h
new file mode 100644
index 00000000..181db72a
--- /dev/null
+++ b/rfb/TightEncoder.h
@@ -0,0 +1,79 @@
+/* Copyright (C) 2000-2003 Constantin Kaplinsky. All Rights Reserved.
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
+ * USA.
+ */
+#ifndef __RFB_TIGHTENCODER_H__
+#define __RFB_TIGHTENCODER_H__
+
+#include <rdr/MemOutStream.h>
+#include <rdr/ZlibOutStream.h>
+#include <rfb/Encoder.h>
+
+// FIXME: Check if specifying extern "C" is really necessary.
+#include <stdio.h>
+extern "C" {
+#include "jpeg/jpeglib.h"
+}
+
+namespace rfb {
+
+ struct TIGHT_CONF {
+ unsigned int maxRectSize, maxRectWidth;
+ unsigned int monoMinRectSize;
+ int idxZlibLevel, monoZlibLevel, rawZlibLevel;
+ int idxMaxColorsDivisor;
+ int jpegQuality;
+ unsigned long jpegThreshold, jpegThreshold24;
+ };
+
+ //
+ // Compression level stuff. The following array contains various
+ // encoder parameters for each of 10 compression levels (0..9).
+ // Last three parameters correspond to JPEG quality levels (0..9).
+ //
+ // NOTE: s_conf[9].maxRectSize should be >= s_conf[i].maxRectSize,
+ // where i in [0..8]. RequiredBuffSize() method depends on this.
+ // FIXME: Is this comment obsolete?
+ //
+
+
+ class TightEncoder : public Encoder {
+ public:
+ static Encoder* create(SMsgWriter* writer);
+ virtual void setCompressLevel(int level);
+ virtual void setQualityLevel(int level);
+ // virtual int getNumRects(const Rect &r);
+ virtual bool writeRect(const Rect& r, ImageGetter* ig, Rect* actual);
+ virtual ~TightEncoder();
+
+ private:
+ TightEncoder(SMsgWriter* writer);
+ void writeSubrect(const Rect& r, ImageGetter* ig);
+
+ SMsgWriter* writer;
+ rdr::MemOutStream mos;
+ rdr::ZlibOutStream zos[4];
+
+ static const int defaultCompressLevel;
+ static const TIGHT_CONF conf[];
+
+ const TIGHT_CONF* pconf;
+ const TIGHT_CONF* pjconf;
+ };
+
+}
+
+#endif
diff --git a/rfb/secTypes.cxx b/rfb/secTypes.cxx
index 7c6c25ca..c850684a 100644
--- a/rfb/secTypes.cxx
+++ b/rfb/secTypes.cxx
@@ -26,6 +26,7 @@ int rfb::secTypeNum(const char* name)
{
if (strcasecmp(name, "None") == 0) return secTypeNone;
if (strcasecmp(name, "VncAuth") == 0) return secTypeVncAuth;
+ if (strcasecmp(name, "Tight") == 0) return secTypeTight;
if (strcasecmp(name, "RA2") == 0) return secTypeRA2;
if (strcasecmp(name, "RA2ne") == 0) return secTypeRA2ne;
return secTypeInvalid;
@@ -36,6 +37,7 @@ const char* rfb::secTypeName(int num)
switch (num) {
case secTypeNone: return "None";
case secTypeVncAuth: return "VncAuth";
+ case secTypeTight: return "Tight";
case secTypeRA2: return "RA2";
case secTypeRA2ne: return "RA2ne";
default: return "[unknown secType]";
diff --git a/rfb/tightEncode.h b/rfb/tightEncode.h
new file mode 100644
index 00000000..7479d7e2
--- /dev/null
+++ b/rfb/tightEncode.h
@@ -0,0 +1,719 @@
+/* Copyright (C) 2000-2003 Constantin Kaplinsky. All Rights Reserved.
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
+ * USA.
+ */
+
+//
+// tightEncode.h - Tight encoding function.
+//
+// This file is #included after having set the following macros:
+// BPP - 8, 16 or 32
+// EXTRA_ARGS - optional extra arguments
+// GET_IMAGE_INTO_BUF - gets a rectangle of pixel data into a buffer
+//
+
+#include <rdr/OutStream.h>
+#include <rdr/ZlibOutStream.h>
+#include <assert.h>
+
+namespace rfb {
+
+// CONCAT2E concatenates its arguments, expanding them if they are macros
+
+#ifndef CONCAT2E
+#define CONCAT2(a,b) a##b
+#define CONCAT2E(a,b) CONCAT2(a,b)
+#endif
+
+#define PIXEL_T rdr::CONCAT2E(U,BPP)
+#define WRITE_PIXEL CONCAT2E(writeOpaque,BPP)
+#define TIGHT_ENCODE CONCAT2E(tightEncode,BPP)
+#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)
+
+#ifndef TIGHT_ONCE
+#define TIGHT_ONCE
+
+//
+// C-style structures to store palette entries and compression paramentes.
+// Such code probably should be converted into C++ classes.
+//
+
+struct TIGHT_COLOR_LIST {
+ TIGHT_COLOR_LIST *next;
+ int idx;
+ rdr::U32 rgb;
+};
+
+struct TIGHT_PALETTE_ENTRY {
+ TIGHT_COLOR_LIST *listNode;
+ int numPixels;
+};
+
+struct TIGHT_PALETTE {
+ TIGHT_PALETTE_ENTRY entry[256];
+ TIGHT_COLOR_LIST *hash[256];
+ TIGHT_COLOR_LIST list[256];
+};
+
+// FIXME: Is it really a good idea to use static variables for this?
+static int s_endianMismatch; // local/remote formats differ in byte order
+static bool s_pack24; // use 24-bit packing for 32-bit pixels
+static int s_rs, s_gs, s_bs; // shifts for 24-bit pixel conversion
+
+// FIXME: Make a separate class for palette operations.
+static int s_palMaxColors, s_palNumColors;
+static rdr::U32 s_monoBackground, s_monoForeground;
+static TIGHT_PALETTE s_palette;
+
+//
+// Swapping bytes in pixels.
+// FIXME: Use a sort of ImageGetter that does not convert pixel format?
+//
+
+#ifndef SWAP16
+#define SWAP16(n) ((((n) & 0xff) << 8) | (((n) >> 8) & 0xff))
+#endif
+#ifndef SWAP32
+#define SWAP32(n) (((n) >> 24) | (((n) & 0x00ff0000) >> 8) | \
+ (((n) & 0x0000ff00) << 8) | ((n) << 24))
+#endif
+
+//
+// Functions to operate on palette structures.
+//
+
+#define HASH_FUNC16(rgb) ((int)(((rgb >> 8) + rgb) & 0xFF))
+#define HASH_FUNC32(rgb) ((int)(((rgb >> 16) + (rgb >> 8)) & 0xFF))
+
+static void paletteReset(void)
+{
+ s_palNumColors = 0;
+ memset(s_palette.hash, 0, 256 * sizeof(TIGHT_COLOR_LIST *));
+}
+
+static int paletteInsert(rdr::U32 rgb, int numPixels, int bpp)
+{
+ TIGHT_COLOR_LIST *pnode;
+ TIGHT_COLOR_LIST *prev_pnode = NULL;
+ int hash_key, idx, new_idx, count;
+
+ hash_key = (bpp == 16) ? HASH_FUNC16(rgb) : HASH_FUNC32(rgb);
+
+ pnode = s_palette.hash[hash_key];
+
+ while (pnode != NULL) {
+ if (pnode->rgb == rgb) {
+ // Such palette entry already exists.
+ new_idx = idx = pnode->idx;
+ count = s_palette.entry[idx].numPixels + numPixels;
+ if (new_idx && s_palette.entry[new_idx-1].numPixels < count) {
+ do {
+ s_palette.entry[new_idx] = s_palette.entry[new_idx-1];
+ s_palette.entry[new_idx].listNode->idx = new_idx;
+ new_idx--;
+ }
+ while (new_idx &&
+ s_palette.entry[new_idx-1].numPixels < count);
+ s_palette.entry[new_idx].listNode = pnode;
+ pnode->idx = new_idx;
+ }
+ s_palette.entry[new_idx].numPixels = count;
+ return s_palNumColors;
+ }
+ prev_pnode = pnode;
+ pnode = pnode->next;
+ }
+
+ // Check if palette is full.
+ if ( s_palNumColors == 256 || s_palNumColors == s_palMaxColors ) {
+ s_palNumColors = 0;
+ return 0;
+ }
+
+ // Move palette entries with lesser pixel counts.
+ for ( idx = s_palNumColors;
+ idx > 0 && s_palette.entry[idx-1].numPixels < numPixels;
+ idx-- ) {
+ s_palette.entry[idx] = s_palette.entry[idx-1];
+ s_palette.entry[idx].listNode->idx = idx;
+ }
+
+ // Add new palette entry into the freed slot.
+ pnode = &s_palette.list[s_palNumColors];
+ if (prev_pnode != NULL) {
+ prev_pnode->next = pnode;
+ } else {
+ s_palette.hash[hash_key] = pnode;
+ }
+ pnode->next = NULL;
+ pnode->idx = idx;
+ pnode->rgb = rgb;
+ s_palette.entry[idx].listNode = pnode;
+ s_palette.entry[idx].numPixels = numPixels;
+
+ return (++s_palNumColors);
+}
+
+//
+// Compress the data (but do not perform actual compression if the data
+// size is less than TIGHT_MIN_TO_COMPRESS bytes.
+//
+
+static void compressData(rdr::OutStream *os, rdr::ZlibOutStream *zos,
+ const void *buf, unsigned int length, int zlibLevel)
+{
+ if (length < TIGHT_MIN_TO_COMPRESS) {
+ os->writeBytes(buf, length);
+ } else {
+ // FIXME: Using a temporary MemOutStream may be not efficient.
+ // Maybe use the same static object used in the JPEG coder?
+ rdr::MemOutStream mem_os;
+ zos->setUnderlying(&mem_os);
+ zos->writeBytes(buf, length);
+ zos->flush();
+ os->writeCompactLength(mem_os.length());
+ os->writeBytes(mem_os.data(), mem_os.length());
+ }
+}
+
+//
+// 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,
+ PIXEL_T *buf);
+static void ENCODE_FULLCOLOR_RECT (rdr::OutStream *os, rdr::ZlibOutStream zos[4],
+ PIXEL_T *buf, const Rect& r);
+static void ENCODE_MONO_RECT (rdr::OutStream *os, rdr::ZlibOutStream zos[4],
+ PIXEL_T *buf, const Rect& r);
+#if (BPP != 8)
+static void ENCODE_INDEXED_RECT (rdr::OutStream *os, rdr::ZlibOutStream zos[4],
+ PIXEL_T *buf, const Rect& r);
+static void ENCODE_JPEG_RECT (rdr::OutStream *os,
+ PIXEL_T *buf, const PixelFormat& pf, const Rect& r);
+#endif
+
+static void FILL_PALETTE (PIXEL_T *data, int count);
+
+//
+// Convert 32-bit color samples into 24-bit colors, in place.
+// Performs packing only when redMax, greenMax and blueMax are all 255.
+// Color components are assumed to be byte-aligned.
+//
+
+static inline unsigned int PACK_PIXELS (PIXEL_T *buf, unsigned int count)
+{
+#if (BPP != 32)
+ return count * sizeof(PIXEL_T);
+#else
+ if (!s_pack24)
+ return count * sizeof(PIXEL_T);
+
+ rdr::U32 pix;
+ rdr::U8 *dst = (rdr::U8 *)buf;
+ for (unsigned int i = 0; i < count; i++) {
+ pix = *buf++;
+ *dst++ = (rdr::U8)(pix >> s_rs);
+ *dst++ = (rdr::U8)(pix >> s_gs);
+ *dst++ = (rdr::U8)(pix >> s_bs);
+ }
+ return count * 3;
+#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
+#ifdef EXTRA_ARGS
+ , EXTRA_ARGS
+#endif
+ )
+{
+ const PixelFormat& pf = cp->pf();
+ GET_IMAGE_INTO_BUF(r, buf);
+ PIXEL_T* pixels = (PIXEL_T*)buf;
+
+#if (BPP != 8)
+ union {
+ rdr::U32 value32;
+ rdr::U8 test;
+ } littleEndian;
+ littleEndian.value32 = 1;
+ s_endianMismatch = (littleEndian.test != !pf.bigEndian);
+#endif
+
+#if (BPP == 32)
+ // Check if it's necessary to pack 24-bit pixels, and
+ // compute appropriate shift values if necessary.
+ s_pack24 = (pf.depth == 24 && pf.redMax == 0xFF &&
+ pf.greenMax == 0xFF && pf.blueMax == 0xFF);
+ if (s_pack24) {
+ if (!s_endianMismatch) {
+ s_rs = pf.redShift;
+ s_gs = pf.greenShift;
+ s_bs = pf.blueShift;
+ } else {
+ s_rs = 24 - pf.redShift;
+ s_gs = 24 - pf.greenShift;
+ s_bs = 24 - pf.blueShift;
+ }
+ }
+#endif
+
+ s_palMaxColors = r.area() / s_pconf->idxMaxColorsDivisor;
+ if (s_palMaxColors < 2 && r.area() >= s_pconf->monoMinRectSize) {
+ s_palMaxColors = 2;
+ }
+ 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);
+ break;
+ }
+#endif
+ ENCODE_FULLCOLOR_RECT(os, zos, pixels, r);
+ break;
+ case 1:
+ // Solid rectangle
+ ENCODE_SOLID_RECT(os, pixels);
+ break;
+ case 2:
+ // Two-color rectangle
+ ENCODE_MONO_RECT(os, zos, pixels, r);
+ break;
+#if (BPP != 8)
+ default:
+ // Up to 256 different colors
+ if (s_palNumColors > 96 && s_pjconf != NULL &&
+ DETECT_SMOOTH_IMAGE(pixels, r)) {
+ ENCODE_JPEG_RECT(os, pixels, pf, r);
+ } else {
+ ENCODE_INDEXED_RECT(os, zos, pixels, r);
+ }
+#endif
+ }
+}
+
+//
+// Subencoding implementations.
+//
+
+static void ENCODE_SOLID_RECT (rdr::OutStream *os, PIXEL_T *buf)
+{
+ os->writeU8(0x08 << 4);
+
+ int length = PACK_PIXELS(buf, 1);
+ os->writeBytes(buf, length);
+}
+
+static void ENCODE_FULLCOLOR_RECT (rdr::OutStream *os, rdr::ZlibOutStream zos[4],
+ PIXEL_T *buf, const Rect& r)
+{
+ const int streamId = 0;
+ os->writeU8(streamId << 4);
+
+ int length = PACK_PIXELS(buf, r.area());
+ compressData(os, &zos[streamId], buf, length, s_pconf->rawZlibLevel);
+}
+
+static void ENCODE_MONO_RECT (rdr::OutStream *os, rdr::ZlibOutStream zos[4],
+ PIXEL_T *buf, const Rect& r)
+{
+ const int streamId = 1;
+ os->writeU8((streamId | 0x04) << 4);
+ os->writeU8(0x01);
+
+ // Write the palette
+ PIXEL_T pal[2] = { (PIXEL_T)s_monoBackground, (PIXEL_T)s_monoForeground };
+ os->writeU8(1);
+ os->writeBytes(pal, PACK_PIXELS(pal, 2));
+
+ // Encode the data in-place
+ PIXEL_T *src = buf;
+ rdr::U8 *dst = (rdr::U8 *)buf;
+ int w = r.width();
+ int h = r.height();
+ PIXEL_T bg;
+ unsigned int value, mask;
+ int aligned_width;
+ int x, y, bg_bits;
+
+ bg = (PIXEL_T) s_monoBackground;
+ aligned_width = w - w % 8;
+
+ for (y = 0; y < h; y++) {
+ for (x = 0; x < aligned_width; x += 8) {
+ for (bg_bits = 0; bg_bits < 8; bg_bits++) {
+ if (*src++ != bg)
+ break;
+ }
+ if (bg_bits == 8) {
+ *dst++ = 0;
+ continue;
+ }
+ mask = 0x80 >> bg_bits;
+ value = mask;
+ for (bg_bits++; bg_bits < 8; bg_bits++) {
+ mask >>= 1;
+ if (*src++ != bg) {
+ value |= mask;
+ }
+ }
+ *dst++ = (rdr::U8)value;
+ }
+
+ mask = 0x80;
+ value = 0;
+ if (x >= w)
+ continue;
+
+ for (; x < w; x++) {
+ if (*src++ != bg) {
+ value |= mask;
+ }
+ mask >>= 1;
+ }
+ *dst++ = (rdr::U8)value;
+ }
+
+ // Write the data
+ int length = (w + 7) / 8;
+ length *= h;
+ compressData(os, &zos[streamId], buf, length, s_pconf->monoZlibLevel);
+}
+
+#if (BPP != 8)
+static void ENCODE_INDEXED_RECT (rdr::OutStream *os, rdr::ZlibOutStream zos[4],
+ PIXEL_T *buf, const Rect& r)
+{
+ const int streamId = 2;
+ os->writeU8((streamId | 0x04) << 4);
+ os->writeU8(0x01);
+
+ // Write the palette
+ {
+ PIXEL_T pal[256];
+ for (int i = 0; i < s_palNumColors; i++)
+ pal[i] = (PIXEL_T)s_palette.entry[i].listNode->rgb;
+ os->writeU8((rdr::U8)(s_palNumColors - 1));
+ os->writeBytes(pal, PACK_PIXELS(pal, s_palNumColors));
+ }
+
+ // Encode data in-place
+ PIXEL_T *src = buf;
+ rdr::U8 *dst = (rdr::U8 *)buf;
+ int count = r.area();
+ PIXEL_T rgb;
+ TIGHT_COLOR_LIST *pnode;
+ int rep = 0;
+
+ while (count--) {
+ rgb = *src++;
+ while (count && *src == rgb) {
+ rep++, src++, count--;
+ }
+ pnode = s_palette.hash[HASH_FUNCTION(rgb)];
+ while (pnode != NULL) {
+ if ((PIXEL_T)pnode->rgb == rgb) {
+ *dst++ = (rdr::U8)pnode->idx;
+ while (rep) {
+ *dst++ = (rdr::U8)pnode->idx;
+ rep--;
+ }
+ break;
+ }
+ pnode = pnode->next;
+ }
+ }
+
+ // Write the data
+ compressData(os, &zos[streamId], buf, r.area(), s_pconf->idxZlibLevel);
+}
+#endif // #if (BPP != 8)
+
+//
+// JPEG compression.
+//
+
+#if (BPP != 8)
+static void PREPARE_JPEG_ROW (PIXEL_T *src, const PixelFormat& pf,
+ rdr::U8 *dst, int count)
+{
+ // FIXME: Add a version of this function optimized for 24-bit colors?
+ PIXEL_T pix;
+ while (count--) {
+ pix = *src++;
+ if (s_endianMismatch)
+ pix = SWAP_PIXEL(pix);
+ *dst++ = (rdr::U8)((pix >> pf.redShift & pf.redMax) * 255 / pf.redMax);
+ *dst++ = (rdr::U8)((pix >> pf.greenShift & pf.greenMax) * 255 / pf.greenMax);
+ *dst++ = (rdr::U8)((pix >> pf.blueShift & pf.blueMax) * 255 / pf.blueMax);
+ }
+}
+#endif // #if (BPP != 8)
+
+#if (BPP != 8)
+static void ENCODE_JPEG_RECT (rdr::OutStream *os, PIXEL_T *buf,
+ const PixelFormat& pf, const Rect& r)
+{
+ int w = r.width();
+ int h = r.height();
+
+ struct jpeg_compress_struct cinfo;
+ struct jpeg_error_mgr jerr;
+
+ // FIXME: Make srcBuf[] and/or dstBuf[] static?
+ rdr::U8 *srcBuf = new rdr::U8[w * 3];
+ JSAMPROW rowPointer[1];
+ rowPointer[0] = (JSAMPROW)srcBuf;
+
+ cinfo.err = jpeg_std_error(&jerr);
+ jpeg_create_compress(&cinfo);
+
+ cinfo.image_width = w;
+ cinfo.image_height = h;
+ cinfo.input_components = 3;
+ cinfo.in_color_space = JCS_RGB;
+
+ jpeg_set_defaults(&cinfo);
+ jpeg_set_quality(&cinfo, s_pjconf->jpegQuality, TRUE);
+
+ rdr::U8 *dstBuf = new rdr::U8[2048];
+ JpegSetDstManager(&cinfo, dstBuf, 2048);
+
+ jpeg_start_compress(&cinfo, TRUE);
+ for (int dy = 0; dy < h; dy++) {
+ PREPARE_JPEG_ROW(&buf[dy * w], pf, srcBuf, w);
+ jpeg_write_scanlines(&cinfo, rowPointer, 1);
+ }
+ jpeg_finish_compress(&cinfo);
+ jpeg_destroy_compress(&cinfo);
+
+ delete[] srcBuf;
+ delete[] dstBuf;
+
+ os->writeU8(0x09 << 4);
+ os->writeCompactLength(s_jpeg_os.length());
+ os->writeBytes(s_jpeg_os.data(), s_jpeg_os.length());
+}
+#endif // #if (BPP != 8)
+
+//
+// Determine the number of colors in the rectangle, and fill in the palette.
+//
+
+#if (BPP == 8)
+static void FILL_PALETTE (PIXEL_T *data, int count)
+{
+ PIXEL_T c0, c1;
+ int i, n0, n1;
+
+ s_palNumColors = 0;
+
+ c0 = data[0];
+ for (i = 1; i < count && data[i] == c0; i++);
+ if (i == count) {
+ s_palNumColors = 1;
+ return; // Solid rectangle
+ }
+
+ if (s_palMaxColors < 2)
+ return;
+
+ n0 = i;
+ c1 = data[i];
+ n1 = 0;
+ for (i++; i < count; i++) {
+ if (data[i] == c0) {
+ n0++;
+ } else if (data[i] == c1) {
+ n1++;
+ } else
+ break;
+ }
+ if (i == count) {
+ if (n0 > n1) {
+ s_monoBackground = (rdr::U32)c0;
+ s_monoForeground = (rdr::U32)c1;
+ } else {
+ s_monoBackground = (rdr::U32)c1;
+ s_monoForeground = (rdr::U32)c0;
+ }
+ s_palNumColors = 2; // Two colors
+ }
+}
+#else // (BPP != 8)
+static void FILL_PALETTE (PIXEL_T *data, int count)
+{
+ PIXEL_T c0, c1, ci = 0;
+ int i, n0, n1, ni;
+
+ c0 = data[0];
+ for (i = 1; i < count && data[i] == c0; i++);
+ if (i >= count) {
+ s_palNumColors = 1; // Solid rectangle
+ return;
+ }
+
+ if (s_palMaxColors < 2) {
+ s_palNumColors = 0; // Full-color format preferred
+ return;
+ }
+
+ n0 = i;
+ c1 = data[i];
+ n1 = 0;
+ for (i++; i < count; i++) {
+ ci = data[i];
+ if (ci == c0) {
+ n0++;
+ } else if (ci == c1) {
+ n1++;
+ } else
+ break;
+ }
+ if (i >= count) {
+ if (n0 > n1) {
+ s_monoBackground = (rdr::U32)c0;
+ s_monoForeground = (rdr::U32)c1;
+ } else {
+ s_monoBackground = (rdr::U32)c1;
+ s_monoForeground = (rdr::U32)c0;
+ }
+ s_palNumColors = 2; // Two colors
+ return;
+ }
+
+ paletteReset();
+ paletteInsert (c0, (rdr::U32)n0, BPP);
+ paletteInsert (c1, (rdr::U32)n1, BPP);
+
+ ni = 1;
+ for (i++; i < count; i++) {
+ if (data[i] == ci) {
+ ni++;
+ } else {
+ if (!paletteInsert (ci, (rdr::U32)ni, BPP))
+ return;
+ ci = data[i];
+ ni = 1;
+ }
+ }
+ paletteInsert (ci, (rdr::U32)ni, BPP);
+}
+#endif // #if (BPP == 8)
+
+#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
+}