git-svn-id: svn://svn.code.sf.net/p/tigervnc/code/trunk@4626 3789f03b-4d11-0410-bbf8-ca57d06f2519tags/v1.1.90
@@ -55,7 +55,7 @@ namespace rdr { | |||
const void* data() { return (const void*)start; } | |||
private: | |||
protected: | |||
// overrun() either doubles the buffer or adds enough space for nItems of | |||
// size itemSize bytes. |
@@ -22,6 +22,7 @@ set(RFB_SOURCES | |||
HTTPServer.cxx | |||
HextileDecoder.cxx | |||
HextileEncoder.cxx | |||
JpegCompressor.cxx | |||
KeyRemapper.cxx | |||
LogWriter.cxx | |||
Logger.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."); | |||
} |
@@ -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 |
@@ -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 @@ void SMsgWriterV3::writeFramebufferUpdateStart(int nRects) | |||
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 @@ void SMsgWriterV3::writeFramebufferUpdateEnd() | |||
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); |
@@ -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 @@ using namespace rfb; | |||
// 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 @@ using namespace rfb; | |||
// 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 @@ void TightEncoder::setQualityLevel(int level) | |||
} | |||
} | |||
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 @@ int TightEncoder::getNumRects(const Rect &r) | |||
((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 @@ bool TightEncoder::writeRect(const Rect& r, ImageGetter* ig, Rect* actual) | |||
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 @@ bool TightEncoder::writeRect(const Rect& r, ImageGetter* ig, Rect* actual) | |||
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 @@ void TightEncoder::writeSubrect(const Rect& r, ImageGetter* ig) | |||
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); |
@@ -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 @@ extern "C" { | |||
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 @@ namespace rfb { | |||
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[]; |
@@ -722,8 +722,14 @@ void VNCSConnectionST::writeFramebufferUpdate() | |||
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); |
@@ -45,14 +45,13 @@ namespace rfb { | |||
#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 @@ static void compressData(rdr::OutStream *os, rdr::ZlibOutStream *zos, | |||
} | |||
} | |||
// | |||
// 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 @@ static void ENCODE_MONO_RECT (rdr::OutStream *os, rdr::ZlibOutStream zos[4] | |||
#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 | |||
@@ -294,42 +245,25 @@ static inline unsigned int PACK_PIXELS (PIXEL_T *buf, unsigned int count, | |||
#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 | |||
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 @@ void TIGHT_ENCODE (const Rect& r, rdr::OutStream *os, | |||
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 @@ static void ENCODE_INDEXED_RECT (rdr::OutStream *os, rdr::ZlibOutStream zos[4], | |||
// | |||
#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 @@ static void FILL_PALETTE (PIXEL_T *data, int count) | |||
} | |||
#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 | |||
} |