From 9b0809c2cf25767648376bda39dc68dbad7a33b2 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Peter=20=C3=85strand?= Date: Thu, 10 Feb 2005 15:13:38 +0000 Subject: [PATCH] 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 --- rfb/Encoder.cxx | 2 + rfb/Encoder.h | 4 + rfb/Makefile.in | 1 + rfb/SMsgWriter.cxx | 27 ++ rfb/SMsgWriter.h | 8 + rfb/SMsgWriterV3.h | 1 + rfb/TightEncoder.cxx | 194 ++++++++++++ rfb/TightEncoder.h | 79 +++++ rfb/secTypes.cxx | 2 + rfb/tightEncode.h | 719 +++++++++++++++++++++++++++++++++++++++++++ 10 files changed, 1037 insertions(+) create mode 100644 rfb/TightEncoder.cxx create mode 100644 rfb/TightEncoder.h create mode 100644 rfb/tightEncode.h 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 #include #include +#include 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 +#include +#include +#include +#include +#include + +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 +#undef BPP +#define BPP 16 +#include +#undef BPP +#define BPP 32 +#include +#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 +#include +#include + +// FIXME: Check if specifying extern "C" is really necessary. +#include +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 +#include +#include + +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 +} -- 2.39.5