// Copyright (C) 2000 Constantin Kaplinsky. All Rights Reserved. // Copyright (C) 2000 Tridia Corporation. All Rights Reserved. // Copyright (C) 1999 AT&T Laboratories Cambridge. All Rights Reserved. // // This file is part of the VNC system. // // The VNC system 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 program 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 program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, // USA. // // TightVNC distribution homepage on the Web: http://www.tightvnc.com/ // // If the source code for the VNC system is not available from the place // whence you received this file, check http://www.uk.research.att.com/vnc or contact // the authors on vnc@uk.research.att.com for information on obtaining it. // vncEncodeTight // This file implements the vncEncoder-derived vncEncodeTight class. // This class overrides some vncEncoder functions to produce a bitmap // to Tight encoder. Tight is much more efficient than RAW format on // most screen data and usually 2..10 times as efficient as hextile. // It's also more efficient than Zlib encoding in most cases. // But note that tight compression may use more CPU time on the server. // However, over slower (128kbps or less) connections, the reduction // in data transmitted usually outweighs the extra latency added // while the server CPU performs the compression algorithms. #include "vncEncodeTight.h" // 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: m_conf[9].maxRectSize should be >= m_conf[i].maxRectSize, // where i in [0..8]. RequiredBuffSize() method depends on this. const TIGHT_CONF vncEncodeTight::m_conf[10] = { { 512, 32, 6, 65536, 0, 0, 0, 0, 0, 0, 4, 5, 10000, 23000 }, { 2048, 128, 6, 65536, 1, 1, 1, 0, 0, 0, 8, 10, 8000, 18000 }, { 6144, 256, 8, 65536, 3, 3, 2, 0, 0, 0, 24, 15, 6500, 15000 }, { 10240, 1024, 12, 65536, 5, 5, 3, 0, 0, 0, 32, 25, 5000, 12000 }, { 16384, 2048, 12, 65536, 6, 6, 4, 0, 0, 0, 32, 37, 4000, 10000 }, { 32768, 2048, 12, 4096, 7, 7, 5, 4, 150, 380, 32, 50, 3000, 8000 }, { 65536, 2048, 16, 4096, 7, 7, 6, 4, 170, 420, 48, 60, 2000, 5000 }, { 65536, 2048, 16, 4096, 8, 8, 7, 5, 180, 450, 64, 70, 1000, 2500 }, { 65536, 2048, 32, 8192, 9, 9, 8, 6, 190, 475, 64, 75, 500, 1200 }, { 65536, 2048, 32, 8192, 9, 9, 9, 6, 200, 500, 96, 80, 200, 500 } }; vncEncodeTight::vncEncodeTight() { m_buffer = NULL; m_bufflen = 0; m_hdrBuffer = new BYTE [sz_rfbFramebufferUpdateRectHeader + 8 + 256*4]; m_prevRowBuf = NULL; for (int i = 0; i < 4; i++) m_zsActive[i] = false; } vncEncodeTight::~vncEncodeTight() { if (m_buffer != NULL) { delete[] m_buffer; m_buffer = NULL; } delete[] m_hdrBuffer; for (int i = 0; i < 4; i++) { if (m_zsActive[i]) deflateEnd(&m_zsStruct[i]); m_zsActive[i] = false; } } void vncEncodeTight::Init() { vncEncoder::Init(); } /***************************************************************************** * * Routines to implement Tight Encoding. * */ UINT vncEncodeTight::RequiredBuffSize(UINT width, UINT height) { // FIXME: Use actual compression level instead of 9? int result = m_conf[9].maxRectSize * (m_remoteformat.bitsPerPixel / 8); result += result / 100 + 16; return result; } UINT vncEncodeTight::NumCodedRects(RECT &rect) { const int w = rect.right - rect.left; const int h = rect.bottom - rect.top; // No matter how many rectangles we will send if LastRect markers // are used to terminate rectangle stream. if (m_use_lastrect && w * h >= MIN_SPLIT_RECT_SIZE) { return 0; } const int maxRectSize = m_conf[m_compresslevel].maxRectSize; const int maxRectWidth = m_conf[m_compresslevel].maxRectWidth; if (w > maxRectWidth || w * h > maxRectSize) { const int subrectMaxWidth = (w > maxRectWidth) ? maxRectWidth : w; const int subrectMaxHeight = maxRectSize / subrectMaxWidth; return (((w - 1) / maxRectWidth + 1) * ((h - 1) / subrectMaxHeight + 1)); } else { return 1; } } UINT vncEncodeTight::EncodeRect(BYTE *source, VSocket *outConn, BYTE *dest, const RECT &rect, int offx, int offy) { int x = rect.left, y = rect.top; int w = rect.right - x, h = rect.bottom - y; offsetx = offx; offsety = offy; const int maxRectSize = m_conf[m_compresslevel].maxRectSize; const int rawDataSize = maxRectSize * (m_remoteformat.bitsPerPixel / 8); if (m_bufflen < rawDataSize) { if (m_buffer != NULL) delete [] m_buffer; m_buffer = new BYTE [rawDataSize+1]; if (m_buffer == NULL) return vncEncoder::EncodeRect(source, dest, rect, offsetx, offsety); m_bufflen = rawDataSize; } if ( m_remoteformat.depth == 24 && m_remoteformat.redMax == 0xFF && m_remoteformat.greenMax == 0xFF && m_remoteformat.blueMax == 0xFF ) { m_usePixelFormat24 = true; } else { m_usePixelFormat24 = false; } if (!m_use_lastrect || w * h < MIN_SPLIT_RECT_SIZE) return EncodeRectSimple(source, outConn, dest, rect); // Calculate maximum number of rows in one non-solid rectangle. int nMaxRows; { int maxRectSize = m_conf[m_compresslevel].maxRectSize; int maxRectWidth = m_conf[m_compresslevel].maxRectWidth; int nMaxWidth = (w > maxRectWidth) ? maxRectWidth : w; nMaxRows = maxRectSize / nMaxWidth; } // Try to find large solid-color areas and send them separately. CARD32 colorValue; int x_best, y_best, w_best, h_best; int dx, dy, dw, dh; for (dy = y; dy < y + h; dy += MAX_SPLIT_TILE_SIZE) { // If a rectangle becomes too large, send its upper part now. if (dy - y >= nMaxRows) { RECT upperRect; SetRect(&upperRect, x, y, x + w, y + nMaxRows); int size = EncodeRectSimple(source, outConn, dest, upperRect); outConn->SendQueued((char *)dest, size); y += nMaxRows; h -= nMaxRows; } dh = (dy + MAX_SPLIT_TILE_SIZE <= y + h) ? MAX_SPLIT_TILE_SIZE : (y + h - dy); for (dx = x; dx < x + w; dx += MAX_SPLIT_TILE_SIZE) { dw = (dx + MAX_SPLIT_TILE_SIZE <= x + w) ? MAX_SPLIT_TILE_SIZE : (x + w - dx); if (CheckSolidTile(source, dx, dy, dw, dh, &colorValue, FALSE)) { // Get dimensions of solid-color area. FindBestSolidArea(source, dx, dy, w - (dx - x), h - (dy - y), colorValue, &w_best, &h_best); // Make sure a solid rectangle is large enough // (or the whole rectangle is of the same color). if ( w_best * h_best != w * h && w_best * h_best < MIN_SOLID_SUBRECT_SIZE ) continue; // Try to extend solid rectangle to maximum size. x_best = dx; y_best = dy; ExtendSolidArea(source, x, y, w, h, colorValue, &x_best, &y_best, &w_best, &h_best); // Compute dimensions of surrounding rectangles. RECT rects[4]; SetRect(&rects[0], x, y, x + w, y_best); SetRect(&rects[1], x, y_best, x_best, y_best + h_best); SetRect(&rects[2], x_best + w_best, y_best, x + w, y_best + h_best); SetRect(&rects[3], x, y_best + h_best, x + w, y + h); // Send solid-color area and surrounding rectangles. for (int i = 0; i < 4; i++) { if (i == 2) { RECT onePixel; SetRect(&onePixel, x_best, y_best, x_best + 1, y_best + 1); Translate(source, m_buffer, onePixel); SendTightHeader(x_best, y_best, w_best, h_best); int size = SendSolidRect(dest); outConn->SendQueued((char *)m_hdrBuffer, m_hdrBufferBytes); outConn->SendQueued((char *)dest, size); encodedSize += (m_hdrBufferBytes + size - sz_rfbFramebufferUpdateRectHeader); transmittedSize += (m_hdrBufferBytes + size); } if ( rects[i].left == rects[i].right || rects[i].top == rects[i].bottom ) { continue; } int size = EncodeRect(source, outConn, dest, rects[i], offsetx, offsety); outConn->SendQueued((char *)dest, size); } // Return after all recursive calls done (0 == data sent). return 0; } } } // No suitable solid-color rectangles found. return EncodeRectSimple(source, outConn, dest, rect); } void vncEncodeTight::FindBestSolidArea(BYTE *source, int x, int y, int w, int h, CARD32 colorValue, int *w_ptr, int *h_ptr) { int dx, dy, dw, dh; int w_prev; int w_best = 0, h_best = 0; w_prev = w; for (dy = y; dy < y + h; dy += MAX_SPLIT_TILE_SIZE) { dh = (dy + MAX_SPLIT_TILE_SIZE <= y + h) ? MAX_SPLIT_TILE_SIZE : (y + h - dy); dw = (w_prev > MAX_SPLIT_TILE_SIZE) ? MAX_SPLIT_TILE_SIZE : w_prev; if (!CheckSolidTile(source, x, dy, dw, dh, &colorValue, TRUE)) break; for (dx = x + dw; dx < x + w_prev;) { dw = (dx + MAX_SPLIT_TILE_SIZE <= x + w_prev) ? MAX_SPLIT_TILE_SIZE : (x + w_prev - dx); if (!CheckSolidTile(source, dx, dy, dw, dh, &colorValue, TRUE)) break; dx += dw; } w_prev = dx - x; if (w_prev * (dy + dh - y) > w_best * h_best) { w_best = w_prev; h_best = dy + dh - y; } } *w_ptr = w_best; *h_ptr = h_best; } void vncEncodeTight::ExtendSolidArea(BYTE *source, int x, int y, int w, int h, CARD32 colorValue, int *x_ptr, int *y_ptr, int *w_ptr, int *h_ptr) { int cx, cy; // Try to extend the area upwards. for ( cy = *y_ptr - 1; cy >= y && CheckSolidTile(source, *x_ptr, cy, *w_ptr, 1, &colorValue, TRUE); cy-- ); *h_ptr += *y_ptr - (cy + 1); *y_ptr = cy + 1; // ... downwards. for ( cy = *y_ptr + *h_ptr; cy < y + h && CheckSolidTile(source, *x_ptr, cy, *w_ptr, 1, &colorValue, TRUE); cy++ ); *h_ptr += cy - (*y_ptr + *h_ptr); // ... to the left. for ( cx = *x_ptr - 1; cx >= x && CheckSolidTile(source, cx, *y_ptr, 1, *h_ptr, &colorValue, TRUE); cx-- ); *w_ptr += *x_ptr - (cx + 1); *x_ptr = cx + 1; // ... to the right. for ( cx = *x_ptr + *w_ptr; cx < x + w && CheckSolidTile(source, cx, *y_ptr, 1, *h_ptr, &colorValue, TRUE); cx++ ); *w_ptr += cx - (*x_ptr + *w_ptr); } bool vncEncodeTight::CheckSolidTile(BYTE *source, int x, int y, int w, int h, CARD32 *colorPtr, bool needSameColor) { switch(m_localformat.bitsPerPixel) { case 32: return CheckSolidTile32(source, x, y, w, h, colorPtr, needSameColor); case 16: return CheckSolidTile16(source, x, y, w, h, colorPtr, needSameColor); default: return CheckSolidTile8(source, x, y, w, h, colorPtr, needSameColor); } } #define DEFINE_CHECK_SOLID_FUNCTION(bpp) \ \ bool \ vncEncodeTight::CheckSolidTile##bpp(BYTE *source, int x, int y, int w, int h, \ CARD32 *colorPtr, bool needSameColor) \ { \ CARD##bpp *fbptr; \ CARD##bpp colorValue; \ int dx, dy; \ \ fbptr = (CARD##bpp *) \ &source[y * m_bytesPerRow + x * (bpp/8)]; \ \ colorValue = *fbptr; \ if (needSameColor && (CARD32)colorValue != *colorPtr) \ return false; \ \ for (dy = 0; dy < h; dy++) { \ for (dx = 0; dx < w; dx++) { \ if (colorValue != fbptr[dx]) \ return false; \ } \ fbptr = (CARD##bpp *)((BYTE *)fbptr + m_bytesPerRow); \ } \ \ *colorPtr = (CARD32)colorValue; \ return true; \ } DEFINE_CHECK_SOLID_FUNCTION(8) DEFINE_CHECK_SOLID_FUNCTION(16) DEFINE_CHECK_SOLID_FUNCTION(32) UINT vncEncodeTight::EncodeRectSimple(BYTE *source, VSocket *outConn, BYTE *dest, const RECT &rect) { const int x = rect.left, y = rect.top; const int w = rect.right - x, h = rect.bottom - y; const int maxRectSize = m_conf[m_compresslevel].maxRectSize; const int maxRectWidth = m_conf[m_compresslevel].maxRectWidth; int partialSize = 0; if (w > maxRectWidth || w * h > maxRectSize) { const int subrectMaxWidth = (w > maxRectWidth) ? maxRectWidth : w; const int subrectMaxHeight = maxRectSize / subrectMaxWidth; int dx, dy, rw, rh; for (dy = 0; dy < h; dy += subrectMaxHeight) { for (dx = 0; dx < w; dx += maxRectWidth) { rw = (dx + maxRectWidth < w) ? maxRectWidth : w - dx; rh = (dy + subrectMaxHeight < h) ? subrectMaxHeight : h - dy; partialSize = EncodeSubrect(source, outConn, dest, x+dx, y+dy, rw, rh); if (dy + subrectMaxHeight < h || dx + maxRectWidth < w) { outConn->SendQueued((char *)dest, partialSize); } } } } else { partialSize = EncodeSubrect(source, outConn, dest, x, y, w, h); } return partialSize; } UINT vncEncodeTight::EncodeSubrect(BYTE *source, VSocket *outConn, BYTE *dest, int x, int y, int w, int h) { SendTightHeader(x, y, w, h); RECT r; r.left = x; r.top = y; r.right = x + w; r.bottom = y + h; Translate(source, m_buffer, r); m_paletteMaxColors = w * h / m_conf[m_compresslevel].idxMaxColorsDivisor; if ( m_paletteMaxColors < 2 && w * h >= m_conf[m_compresslevel].monoMinRectSize ) { m_paletteMaxColors = 2; } switch (m_remoteformat.bitsPerPixel) { case 8: FillPalette8(w * h); break; case 16: FillPalette16(w * h); break; default: FillPalette32(w * h); } int encDataSize; switch (m_paletteNumColors) { case 0: // Truecolor image if (DetectSmoothImage(w, h)) { if (m_qualitylevel != -1) { encDataSize = SendJpegRect(dest, w, h, m_conf[m_qualitylevel].jpegQuality); } else { encDataSize = SendGradientRect(dest, w, h); } } else { encDataSize = SendFullColorRect(dest, w, h); } break; case 1: // Solid rectangle encDataSize = SendSolidRect(dest); break; case 2: // Two-color rectangle encDataSize = SendMonoRect(dest, w, h); break; default: // Up to 256 different colors if ( m_paletteNumColors > 96 && m_qualitylevel != -1 && m_qualitylevel <= 3 && DetectSmoothImage(w, h) ) { encDataSize = SendJpegRect(dest, w, h, m_conf[m_qualitylevel].jpegQuality); } else { encDataSize = SendIndexedRect(dest, w, h); } } if (encDataSize < 0) return vncEncoder::EncodeRect(source, dest, r, 0, 0); outConn->SendQueued((char *)m_hdrBuffer, m_hdrBufferBytes); encodedSize += m_hdrBufferBytes - sz_rfbFramebufferUpdateRectHeader + encDataSize; transmittedSize += m_hdrBufferBytes + encDataSize; return encDataSize; } void vncEncodeTight::SendTightHeader(int x, int y, int w, int h) { rfbFramebufferUpdateRectHeader rect; rect.r.x = Swap16IfLE(x - offsetx); rect.r.y = Swap16IfLE(y - offsety); rect.r.w = Swap16IfLE(w); rect.r.h = Swap16IfLE(h); rect.encoding = Swap32IfLE(rfbEncodingTight); dataSize += w * h * (m_remoteformat.bitsPerPixel / 8); rectangleOverhead += sz_rfbFramebufferUpdateRectHeader; memcpy(m_hdrBuffer, (BYTE *)&rect, sz_rfbFramebufferUpdateRectHeader); m_hdrBufferBytes = sz_rfbFramebufferUpdateRectHeader; } // // Subencoding implementations. // int vncEncodeTight::SendSolidRect(BYTE *dest) { int len; if (m_usePixelFormat24) { Pack24(m_buffer, 1); len = 3; } else len = m_remoteformat.bitsPerPixel / 8; m_hdrBuffer[m_hdrBufferBytes++] = rfbTightFill << 4; memcpy (dest, m_buffer, len); return len; } int vncEncodeTight::SendMonoRect(BYTE *dest, int w, int h) { const int streamId = 1; int paletteLen, dataLen; CARD8 paletteBuf[8]; // Prepare tight encoding header. dataLen = (w + 7) / 8; dataLen *= h; m_hdrBuffer[m_hdrBufferBytes++] = (streamId | rfbTightExplicitFilter) << 4; m_hdrBuffer[m_hdrBufferBytes++] = rfbTightFilterPalette; m_hdrBuffer[m_hdrBufferBytes++] = 1; // Prepare palette, convert image. switch (m_remoteformat.bitsPerPixel) { case 32: EncodeMonoRect32((CARD8 *)m_buffer, w, h); ((CARD32 *)paletteBuf)[0] = m_monoBackground; ((CARD32 *)paletteBuf)[1] = m_monoForeground; if (m_usePixelFormat24) { Pack24(paletteBuf, 2); paletteLen = 6; } else paletteLen = 8; memcpy(&m_hdrBuffer[m_hdrBufferBytes], paletteBuf, paletteLen); m_hdrBufferBytes += paletteLen; break; case 16: EncodeMonoRect16((CARD8 *)m_buffer, w, h); ((CARD16 *)paletteBuf)[0] = (CARD16)m_monoBackground; ((CARD16 *)paletteBuf)[1] = (CARD16)m_monoForeground; memcpy(&m_hdrBuffer[m_hdrBufferBytes], paletteBuf, 4); m_hdrBufferBytes += 4; break; default: EncodeMonoRect8((CARD8 *)m_buffer, w, h); m_hdrBuffer[m_hdrBufferBytes++] = (BYTE)m_monoBackground; m_hdrBuffer[m_hdrBufferBytes++] = (BYTE)m_monoForeground; } return CompressData(dest, streamId, dataLen, m_conf[m_compresslevel].monoZlibLevel, Z_DEFAULT_STRATEGY); } int vncEncodeTight::SendIndexedRect(BYTE *dest, int w, int h) { const int streamId = 2; int i, entryLen; CARD8 paletteBuf[256*4]; // Prepare tight encoding header. m_hdrBuffer[m_hdrBufferBytes++] = (streamId | rfbTightExplicitFilter) << 4; m_hdrBuffer[m_hdrBufferBytes++] = rfbTightFilterPalette; m_hdrBuffer[m_hdrBufferBytes++] = (BYTE)(m_paletteNumColors - 1); // Prepare palette, convert image. switch (m_remoteformat.bitsPerPixel) { case 32: EncodeIndexedRect32((CARD8 *)m_buffer, w * h); for (i = 0; i < m_paletteNumColors; i++) { ((CARD32 *)paletteBuf)[i] = m_palette.entry[i].listNode->rgb; } if (m_usePixelFormat24) { Pack24(paletteBuf, m_paletteNumColors); entryLen = 3; } else entryLen = 4; memcpy(&m_hdrBuffer[m_hdrBufferBytes], paletteBuf, m_paletteNumColors * entryLen); m_hdrBufferBytes += m_paletteNumColors * entryLen; break; case 16: EncodeIndexedRect16((CARD8 *)m_buffer, w * h); for (i = 0; i < m_paletteNumColors; i++) { ((CARD16 *)paletteBuf)[i] = (CARD16)m_palette.entry[i].listNode->rgb; } memcpy(&m_hdrBuffer[m_hdrBufferBytes], paletteBuf, m_paletteNumColors * 2); m_hdrBufferBytes += m_paletteNumColors * 2; break; default: return -1; // Should never happen. } return CompressData(dest, streamId, w * h, m_conf[m_compresslevel].idxZlibLevel, Z_DEFAULT_STRATEGY); } int vncEncodeTight::SendFullColorRect(BYTE *dest, int w, int h) { const int streamId = 0; int len; m_hdrBuffer[m_hdrBufferBytes++] = 0x00; if (m_usePixelFormat24) { Pack24(m_buffer, w * h); len = 3; } else len = m_remoteformat.bitsPerPixel / 8; return CompressData(dest, streamId, w * h * len, m_conf[m_compresslevel].rawZlibLevel, Z_DEFAULT_STRATEGY); } int vncEncodeTight::SendGradientRect(BYTE *dest, int w, int h) { const int streamId = 3; int len; if (m_remoteformat.bitsPerPixel == 8) return SendFullColorRect(dest, w, h); if (m_prevRowBuf == NULL) m_prevRowBuf = new int [2048*3]; m_hdrBuffer[m_hdrBufferBytes++] = (streamId | rfbTightExplicitFilter) << 4; m_hdrBuffer[m_hdrBufferBytes++] = rfbTightFilterGradient; if (m_usePixelFormat24) { FilterGradient24(m_buffer, w, h); len = 3; } else if (m_remoteformat.bitsPerPixel == 32) { FilterGradient32((CARD32 *)m_buffer, w, h); len = 4; } else { FilterGradient16((CARD16 *)m_buffer, w, h); len = 2; } return CompressData(dest, streamId, w * h * len, m_conf[m_compresslevel].gradientZlibLevel, Z_FILTERED); } /* VOID vncEncodeTight::UpdateZLibDictionary( AGENT_CTX * lpAgentContext ) { do { for( int i = 0; i < 4; i++ ) { if( !lpAgentContext->dictionaries[i] ) continue; setdictionary( &m_zsStruct[i], lpAgentContext->dictionaries[i]->bDictBuffer, lpAgentContext->dictionaries[i]->dwDictLength ); } } while( 0 ); } VOID vncEncodeTight::DumpZLibDictionary( AGENT_CTX * lpAgentContext ) { do { for( int i = 0; i < 4; i++ ) { if( !m_zsActive[i] ) continue; SendZlibDictionary( lpAgentContext, i, &m_zsStruct[i] ); } } while( 0 ); } */ int vncEncodeTight::CompressData(BYTE *dest, int streamId, int dataLen, int zlibLevel, int zlibStrategy) { if (dataLen < TIGHT_MIN_TO_COMPRESS) { memcpy(dest, m_buffer, dataLen); return dataLen; } z_streamp pz = &m_zsStruct[streamId]; // Initialize compression stream if needed. if (!m_zsActive[streamId]) { pz->zalloc = Z_NULL; pz->zfree = Z_NULL; pz->opaque = Z_NULL; int err = deflateInit2 (pz, zlibLevel, Z_DEFLATED, MAX_WBITS, MAX_MEM_LEVEL, zlibStrategy); if (err != Z_OK) { return -1; } m_zsActive[streamId] = true; m_zsLevel[streamId] = zlibLevel; } int outBufferSize = dataLen + dataLen / 100 + 16; // Prepare buffer pointers. pz->next_in = (Bytef *)m_buffer; pz->avail_in = dataLen; pz->next_out = (Bytef *)dest; pz->avail_out = outBufferSize; // Change compression parameters if needed. if (zlibLevel != m_zsLevel[streamId]) { int err = deflateParams (pz, zlibLevel, zlibStrategy); if (err != Z_OK) { return -1; } m_zsLevel[streamId] = zlibLevel; } // Actual compression. if ( deflate (pz, Z_SYNC_FLUSH) != Z_OK || pz->avail_in != 0 || pz->avail_out == 0 ) { return -1; } return SendCompressedData(outBufferSize - pz->avail_out); } int vncEncodeTight::SendCompressedData(size_t compressedLen) { // Prepare compressed data size for sending. m_hdrBuffer[m_hdrBufferBytes++] = compressedLen & 0x7F; if (compressedLen > 0x7F) { m_hdrBuffer[m_hdrBufferBytes-1] |= 0x80; m_hdrBuffer[m_hdrBufferBytes++] = compressedLen >> 7 & 0x7F; if (compressedLen > 0x3FFF) { m_hdrBuffer[m_hdrBufferBytes-1] |= 0x80; m_hdrBuffer[m_hdrBufferBytes++] = compressedLen >> 14 & 0xFF; } } return (int)compressedLen; } void vncEncodeTight::FillPalette8(int count) { CARD8 *data = (CARD8 *)m_buffer; CARD8 c0, c1; int i, n0, n1; m_paletteNumColors = 0; c0 = data[0]; for (i = 1; i < count && data[i] == c0; i++); if (i == count) { m_paletteNumColors = 1; return; // Solid rectangle } if (m_paletteMaxColors < 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) { m_monoBackground = (CARD32)c0; m_monoForeground = (CARD32)c1; } else { m_monoBackground = (CARD32)c1; m_monoForeground = (CARD32)c0; } m_paletteNumColors = 2; // Two colors } } #define DEFINE_FILL_PALETTE_FUNCTION(bpp) \ \ void \ vncEncodeTight::FillPalette##bpp(int count) \ { \ CARD##bpp *data = (CARD##bpp *)m_buffer; \ CARD##bpp c0, c1, ci; \ int i, n0, n1, ni; \ \ c0 = data[0]; \ for (i = 1; i < count && data[i] == c0; i++); \ if (i >= count) { \ m_paletteNumColors = 1; /* Solid rectangle */ \ return; \ } \ \ if (m_paletteMaxColors < 2) { \ m_paletteNumColors = 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) { \ m_monoBackground = (CARD32)c0; \ m_monoForeground = (CARD32)c1; \ } else { \ m_monoBackground = (CARD32)c1; \ m_monoForeground = (CARD32)c0; \ } \ m_paletteNumColors = 2; /* Two colors */ \ return; \ } \ \ PaletteReset(); \ PaletteInsert (c0, (CARD32)n0, bpp); \ PaletteInsert (c1, (CARD32)n1, bpp); \ \ ni = 1; \ for (i++; i < count; i++) { \ if (data[i] == ci) { \ ni++; \ } else { \ if (!PaletteInsert (ci, (CARD32)ni, bpp)) \ return; \ ci = data[i]; \ ni = 1; \ } \ } \ PaletteInsert (ci, (CARD32)ni, bpp); \ } DEFINE_FILL_PALETTE_FUNCTION(16) DEFINE_FILL_PALETTE_FUNCTION(32) // // Functions to operate with palette structures. // #define HASH_FUNC16(rgb) ((int)(((rgb >> 8) + rgb) & 0xFF)) #define HASH_FUNC32(rgb) ((int)(((rgb >> 16) + (rgb >> 8)) & 0xFF)) void vncEncodeTight::PaletteReset(void) { m_paletteNumColors = 0; memset(m_palette.hash, 0, 256 * sizeof(COLOR_LIST *)); } int vncEncodeTight::PaletteInsert(CARD32 rgb, int numPixels, int bpp) { COLOR_LIST *pnode; COLOR_LIST *prev_pnode = NULL; int hash_key, idx, new_idx, count; hash_key = (bpp == 16) ? HASH_FUNC16(rgb) : HASH_FUNC32(rgb); pnode = m_palette.hash[hash_key]; while (pnode != NULL) { if (pnode->rgb == rgb) { // Such palette entry already exists. new_idx = idx = pnode->idx; count = m_palette.entry[idx].numPixels + numPixels; if (new_idx && m_palette.entry[new_idx-1].numPixels < count) { do { m_palette.entry[new_idx] = m_palette.entry[new_idx-1]; m_palette.entry[new_idx].listNode->idx = new_idx; new_idx--; } while (new_idx && m_palette.entry[new_idx-1].numPixels < count); m_palette.entry[new_idx].listNode = pnode; pnode->idx = new_idx; } m_palette.entry[new_idx].numPixels = count; return m_paletteNumColors; } prev_pnode = pnode; pnode = pnode->next; } // Check if palette is full. if ( m_paletteNumColors == 256 || m_paletteNumColors == m_paletteMaxColors ) { m_paletteNumColors = 0; return 0; } // Move palette entries with lesser pixel counts. for ( idx = m_paletteNumColors; idx > 0 && m_palette.entry[idx-1].numPixels < numPixels; idx-- ) { m_palette.entry[idx] = m_palette.entry[idx-1]; m_palette.entry[idx].listNode->idx = idx; } // Add new palette entry into the freed slot. pnode = &m_palette.list[m_paletteNumColors]; if (prev_pnode != NULL) { prev_pnode->next = pnode; } else { m_palette.hash[hash_key] = pnode; } pnode->next = NULL; pnode->idx = idx; pnode->rgb = rgb; m_palette.entry[idx].listNode = pnode; m_palette.entry[idx].numPixels = numPixels; return (++m_paletteNumColors); } // // Converting 32-bit color samples into 24-bit colors. // Should be called only when redMax, greenMax and blueMax are 255. // Color components assumed to be byte-aligned. // void vncEncodeTight::Pack24(BYTE *buf, int count) { CARD32 *buf32; CARD32 pix; int r_shift, g_shift, b_shift; buf32 = (CARD32 *)buf; if (!m_localformat.bigEndian == !m_remoteformat.bigEndian) { r_shift = m_remoteformat.redShift; g_shift = m_remoteformat.greenShift; b_shift = m_remoteformat.blueShift; } else { r_shift = 24 - m_remoteformat.redShift; g_shift = 24 - m_remoteformat.greenShift; b_shift = 24 - m_remoteformat.blueShift; } while (count--) { pix = *buf32++; *buf++ = (char)(pix >> r_shift); *buf++ = (char)(pix >> g_shift); *buf++ = (char)(pix >> b_shift); } } // // Converting truecolor samples into palette indices. // #define DEFINE_IDX_ENCODE_FUNCTION(bpp) \ \ void \ vncEncodeTight::EncodeIndexedRect##bpp(BYTE *buf, int count) \ { \ COLOR_LIST *pnode; \ CARD##bpp *src; \ CARD##bpp rgb; \ int rep = 0; \ \ src = (CARD##bpp *) buf; \ \ while (count--) { \ rgb = *src++; \ while (count && *src == rgb) { \ rep++, src++, count--; \ } \ pnode = m_palette.hash[HASH_FUNC##bpp(rgb)]; \ while (pnode != NULL) { \ if ((CARD##bpp)pnode->rgb == rgb) { \ *buf++ = (CARD8)pnode->idx; \ while (rep) { \ *buf++ = (CARD8)pnode->idx; \ rep--; \ } \ break; \ } \ pnode = pnode->next; \ } \ } \ } DEFINE_IDX_ENCODE_FUNCTION(16) DEFINE_IDX_ENCODE_FUNCTION(32) #define DEFINE_MONO_ENCODE_FUNCTION(bpp) \ \ void \ vncEncodeTight::EncodeMonoRect##bpp(BYTE *buf, int w, int h) \ { \ CARD##bpp *ptr; \ CARD##bpp bg; \ unsigned int value, mask; \ int aligned_width; \ int x, y, bg_bits; \ \ ptr = (CARD##bpp *) buf; \ bg = (CARD##bpp) m_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 (*ptr++ != bg) \ break; \ } \ if (bg_bits == 8) { \ *buf++ = 0; \ continue; \ } \ mask = 0x80 >> bg_bits; \ value = mask; \ for (bg_bits++; bg_bits < 8; bg_bits++) { \ mask >>= 1; \ if (*ptr++ != bg) { \ value |= mask; \ } \ } \ *buf++ = (CARD8)value; \ } \ \ mask = 0x80; \ value = 0; \ if (x >= w) \ continue; \ \ for (; x < w; x++) { \ if (*ptr++ != bg) { \ value |= mask; \ } \ mask >>= 1; \ } \ *buf++ = (CARD8)value; \ } \ } DEFINE_MONO_ENCODE_FUNCTION(8) DEFINE_MONO_ENCODE_FUNCTION(16) DEFINE_MONO_ENCODE_FUNCTION(32) // // ``Gradient'' filter for 24-bit color samples. // Should be called only when redMax, greenMax and blueMax are 255. // Color components assumed to be byte-aligned. // void vncEncodeTight::FilterGradient24(BYTE *buf, int w, int h) { CARD32 *buf32; CARD32 pix32; int *prevRowPtr; int shiftBits[3]; int pixHere[3], pixUpper[3], pixLeft[3], pixUpperLeft[3]; int prediction; int x, y, c; buf32 = (CARD32 *)buf; memset (m_prevRowBuf, 0, w * 3 * sizeof(int)); if (!m_localformat.bigEndian == !m_remoteformat.bigEndian) { shiftBits[0] = m_remoteformat.redShift; shiftBits[1] = m_remoteformat.greenShift; shiftBits[2] = m_remoteformat.blueShift; } else { shiftBits[0] = 24 - m_remoteformat.redShift; shiftBits[1] = 24 - m_remoteformat.greenShift; shiftBits[2] = 24 - m_remoteformat.blueShift; } for (y = 0; y < h; y++) { for (c = 0; c < 3; c++) { pixUpper[c] = 0; pixHere[c] = 0; } prevRowPtr = m_prevRowBuf; for (x = 0; x < w; x++) { pix32 = *buf32++; for (c = 0; c < 3; c++) { pixUpperLeft[c] = pixUpper[c]; pixLeft[c] = pixHere[c]; pixUpper[c] = *prevRowPtr; pixHere[c] = (int)(pix32 >> shiftBits[c] & 0xFF); *prevRowPtr++ = pixHere[c]; prediction = pixLeft[c] + pixUpper[c] - pixUpperLeft[c]; if (prediction < 0) { prediction = 0; } else if (prediction > 0xFF) { prediction = 0xFF; } *buf++ = (BYTE)(pixHere[c] - prediction); } } } } // // ``Gradient'' filter for other color depths. // #define DEFINE_GRADIENT_FILTER_FUNCTION(bpp) \ \ void \ vncEncodeTight::FilterGradient##bpp(CARD##bpp *buf, int w, int h) \ { \ CARD##bpp pix, diff; \ bool endianMismatch; \ int *prevRowPtr; \ int maxColor[3], shiftBits[3]; \ int pixHere[3], pixUpper[3], pixLeft[3], pixUpperLeft[3]; \ int prediction; \ int x, y, c; \ \ memset (m_prevRowBuf, 0, w * 3 * sizeof(int)); \ \ endianMismatch = (!m_localformat.bigEndian != !m_remoteformat.bigEndian); \ \ maxColor[0] = m_remoteformat.redMax; \ maxColor[1] = m_remoteformat.greenMax; \ maxColor[2] = m_remoteformat.blueMax; \ shiftBits[0] = m_remoteformat.redShift; \ shiftBits[1] = m_remoteformat.greenShift; \ shiftBits[2] = m_remoteformat.blueShift; \ \ for (y = 0; y < h; y++) { \ for (c = 0; c < 3; c++) { \ pixUpper[c] = 0; \ pixHere[c] = 0; \ } \ prevRowPtr = m_prevRowBuf; \ for (x = 0; x < w; x++) { \ pix = *buf; \ if (endianMismatch) { \ pix = Swap##bpp(pix); \ } \ diff = 0; \ for (c = 0; c < 3; c++) { \ pixUpperLeft[c] = pixUpper[c]; \ pixLeft[c] = pixHere[c]; \ pixUpper[c] = *prevRowPtr; \ pixHere[c] = (int)(pix >> shiftBits[c] & maxColor[c]); \ *prevRowPtr++ = pixHere[c]; \ \ prediction = pixLeft[c] + pixUpper[c] - pixUpperLeft[c]; \ if (prediction < 0) { \ prediction = 0; \ } else if (prediction > maxColor[c]) { \ prediction = maxColor[c]; \ } \ diff |= ((pixHere[c] - prediction) & maxColor[c]) \ << shiftBits[c]; \ } \ if (endianMismatch) { \ diff = Swap##bpp(diff); \ } \ *buf++ = diff; \ } \ } \ } DEFINE_GRADIENT_FILTER_FUNCTION(16) DEFINE_GRADIENT_FILTER_FUNCTION(32) // // Code to guess if given rectangle is suitable for smooth image // compression (by applying "gradient" filter or JPEG coder). // #define JPEG_MIN_RECT_SIZE 4096 #define DETECT_SUBROW_WIDTH 7 #define DETECT_MIN_WIDTH 8 #define DETECT_MIN_HEIGHT 8 int vncEncodeTight::DetectSmoothImage (int w, int h) { if ( m_localformat.bitsPerPixel == 8 || m_remoteformat.bitsPerPixel == 8 || w < DETECT_MIN_WIDTH || h < DETECT_MIN_HEIGHT ) { return 0; } if (m_qualitylevel != -1) { if (w * h < JPEG_MIN_RECT_SIZE) { return 0; } } else { if (w * h < m_conf[m_compresslevel].gradientMinRectSize) { return 0; } } unsigned long avgError; if (m_remoteformat.bitsPerPixel == 32) { if (m_usePixelFormat24) { avgError = DetectSmoothImage24(w, h); if (m_qualitylevel != -1) { return (avgError < m_conf[m_qualitylevel].jpegThreshold24); } return (avgError < m_conf[m_compresslevel].gradientThreshold24); } else { avgError = DetectSmoothImage32(w, h); } } else { avgError = DetectSmoothImage16(w, h); } if (m_qualitylevel != -1) { return (avgError < m_conf[m_qualitylevel].jpegThreshold); } return (avgError < m_conf[m_compresslevel].gradientThreshold); } unsigned long vncEncodeTight::DetectSmoothImage24 (int w, int h) { int diffStat[256]; int pixelCount = 0; int pix, left[3]; unsigned long avgError; // If client is big-endian, color samples begin from the second // byte (offset 1) of a 32-bit pixel value. int off = (m_remoteformat.bigEndian != 0); memset(diffStat, 0, 256*sizeof(int)); int y = 0, x = 0; int d, dx, c; while (y < h && x < w) { for (d = 0; d < h - y && d < w - x - DETECT_SUBROW_WIDTH; d++) { for (c = 0; c < 3; c++) { left[c] = (int)m_buffer[((y+d)*w+x+d)*4+off+c] & 0xFF; } for (dx = 1; dx <= DETECT_SUBROW_WIDTH; dx++) { for (c = 0; c < 3; c++) { pix = (int)m_buffer[((y+d)*w+x+d+dx)*4+off+c] & 0xFF; diffStat[abs(pix - left[c])]++; left[c] = pix; } pixelCount++; } } if (w > h) { x += h; y = 0; } else { x = 0; y += w; } } if (diffStat[0] * 33 / pixelCount >= 95) return 0; avgError = 0; for (c = 1; c < 8; c++) { avgError += (unsigned long)diffStat[c] * (unsigned long)(c * c); if (diffStat[c] == 0 || diffStat[c] > diffStat[c-1] * 2) return 0; } for (; c < 256; c++) { avgError += (unsigned long)diffStat[c] * (unsigned long)(c * c); } avgError /= (pixelCount * 3 - diffStat[0]); return avgError; } #define DEFINE_DETECT_FUNCTION(bpp) \ \ unsigned long \ vncEncodeTight::DetectSmoothImage##bpp (int w, int h) \ { \ bool endianMismatch; \ CARD##bpp pix; \ int maxColor[3], shiftBits[3]; \ int x, y, d, dx, c; \ int diffStat[256]; \ int pixelCount = 0; \ int sample, sum, left[3]; \ unsigned long avgError; \ \ endianMismatch = (!m_localformat.bigEndian != !m_remoteformat.bigEndian); \ \ maxColor[0] = m_remoteformat.redMax; \ maxColor[1] = m_remoteformat.greenMax; \ maxColor[2] = m_remoteformat.blueMax; \ shiftBits[0] = m_remoteformat.redShift; \ shiftBits[1] = m_remoteformat.greenShift; \ shiftBits[2] = m_remoteformat.blueShift; \ \ memset(diffStat, 0, 256*sizeof(int)); \ \ y = 0, x = 0; \ while (y < h && x < w) { \ for (d = 0; d < h - y && d < w - x - DETECT_SUBROW_WIDTH; d++) { \ pix = ((CARD##bpp *)m_buffer)[(y+d)*w+x+d]; \ if (endianMismatch) { \ pix = Swap##bpp(pix); \ } \ for (c = 0; c < 3; c++) { \ left[c] = (int)(pix >> shiftBits[c] & maxColor[c]); \ } \ for (dx = 1; dx <= DETECT_SUBROW_WIDTH; dx++) { \ pix = ((CARD##bpp *)m_buffer)[(y+d)*w+x+d+dx]; \ if (endianMismatch) { \ pix = Swap##bpp(pix); \ } \ sum = 0; \ for (c = 0; c < 3; c++) { \ sample = (int)(pix >> shiftBits[c] & maxColor[c]); \ sum += abs(sample - left[c]); \ left[c] = sample; \ } \ if (sum > 255) \ sum = 255; \ diffStat[sum]++; \ pixelCount++; \ } \ } \ if (w > h) { \ x += h; \ y = 0; \ } else { \ x = 0; \ y += w; \ } \ } \ \ if ((diffStat[0] + diffStat[1]) * 100 / pixelCount >= 90) \ return 0; \ \ avgError = 0; \ for (c = 1; c < 8; c++) { \ avgError += (unsigned long)diffStat[c] * (unsigned long)(c * c); \ if (diffStat[c] == 0 || diffStat[c] > diffStat[c-1] * 2) \ return 0; \ } \ for (; c < 256; c++) { \ avgError += (unsigned long)diffStat[c] * (unsigned long)(c * c); \ } \ avgError /= (pixelCount - diffStat[0]); \ \ return avgError; \ } DEFINE_DETECT_FUNCTION(16) DEFINE_DETECT_FUNCTION(32) // // JPEG compression stuff. // static bool jpegError; static size_t jpegDstDataLen; static void JpegSetDstManager(j_compress_ptr cinfo, JOCTET *buf, size_t buflen); int vncEncodeTight::SendJpegRect(BYTE *dst, int w, int h, int quality) { struct jpeg_compress_struct cinfo; struct jpeg_error_mgr jerr; if (m_localformat.bitsPerPixel == 8) return SendFullColorRect(dst, w, h); BYTE *srcBuf = new byte[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, quality, TRUE); JpegSetDstManager (&cinfo, dst, w * h * (m_localformat.bitsPerPixel / 8)); jpeg_start_compress(&cinfo, TRUE); for (int dy = 0; dy < h; dy++) { PrepareRowForJpeg(srcBuf, dy, w); jpeg_write_scanlines(&cinfo, rowPointer, 1); if (jpegError) break; } if (!jpegError) jpeg_finish_compress(&cinfo); jpeg_destroy_compress(&cinfo); delete[] srcBuf; if (jpegError) return SendFullColorRect(dst, w, h); m_hdrBuffer[m_hdrBufferBytes++] = rfbTightJpeg << 4; return SendCompressedData(jpegDstDataLen); } void vncEncodeTight::PrepareRowForJpeg(BYTE *dst, int y, int w) { if (m_remoteformat.bitsPerPixel == 32) { CARD32 *src = (CARD32 *)&m_buffer[y * w * sizeof(CARD32)]; if (m_usePixelFormat24) { PrepareRowForJpeg24(dst, src, w); } else { PrepareRowForJpeg32(dst, src, w); } } else { // 16 bpp assumed. CARD16 *src = (CARD16 *)&m_buffer[y * w * sizeof(CARD16)]; PrepareRowForJpeg16(dst, src, w); } } void vncEncodeTight::PrepareRowForJpeg24(BYTE *dst, CARD32 *src, int count) { int r_shift, g_shift, b_shift; if (!m_localformat.bigEndian == !m_remoteformat.bigEndian) { r_shift = m_remoteformat.redShift; g_shift = m_remoteformat.greenShift; b_shift = m_remoteformat.blueShift; } else { r_shift = 24 - m_remoteformat.redShift; g_shift = 24 - m_remoteformat.greenShift; b_shift = 24 - m_remoteformat.blueShift; } CARD32 pix; while (count--) { pix = *src++; *dst++ = (BYTE)(pix >> r_shift); *dst++ = (BYTE)(pix >> g_shift); *dst++ = (BYTE)(pix >> b_shift); } } #define DEFINE_JPEG_GET_ROW_FUNCTION(bpp) \ \ void \ vncEncodeTight::PrepareRowForJpeg##bpp(BYTE *dst, CARD##bpp *src, int count)\ { \ bool endianMismatch = \ (!m_localformat.bigEndian != !m_remoteformat.bigEndian); \ \ int r_shift = m_remoteformat.redShift; \ int g_shift = m_remoteformat.greenShift; \ int b_shift = m_remoteformat.blueShift; \ int r_max = m_remoteformat.redMax; \ int g_max = m_remoteformat.greenMax; \ int b_max = m_remoteformat.blueMax; \ \ CARD##bpp pix; \ while (count--) { \ pix = *src++; \ if (endianMismatch) { \ pix = Swap##bpp(pix); \ } \ *dst++ = (BYTE)((pix >> r_shift & r_max) * 255 / r_max); \ *dst++ = (BYTE)((pix >> g_shift & g_max) * 255 / g_max); \ *dst++ = (BYTE)((pix >> b_shift & b_max) * 255 / b_max); \ } \ } DEFINE_JPEG_GET_ROW_FUNCTION(16) DEFINE_JPEG_GET_ROW_FUNCTION(32) /* * Destination manager implementation for JPEG library. */ static struct jpeg_destination_mgr jpegDstManager; static JOCTET *jpegDstBuffer; static size_t jpegDstBufferLen; static void JpegInitDestination(j_compress_ptr cinfo); static boolean JpegEmptyOutputBuffer(j_compress_ptr cinfo); static void JpegTermDestination(j_compress_ptr cinfo); static void JpegInitDestination(j_compress_ptr cinfo) { jpegError = false; jpegDstManager.next_output_byte = jpegDstBuffer; jpegDstManager.free_in_buffer = jpegDstBufferLen; } static boolean JpegEmptyOutputBuffer(j_compress_ptr cinfo) { jpegError = true; jpegDstManager.next_output_byte = jpegDstBuffer; jpegDstManager.free_in_buffer = jpegDstBufferLen; return TRUE; } static void JpegTermDestination(j_compress_ptr cinfo) { jpegDstDataLen = jpegDstBufferLen - jpegDstManager.free_in_buffer; } static void JpegSetDstManager(j_compress_ptr cinfo, JOCTET *buf, size_t buflen) { jpegDstBuffer = buf; jpegDstBufferLen = buflen; jpegDstManager.init_destination = JpegInitDestination; jpegDstManager.empty_output_buffer = JpegEmptyOutputBuffer; jpegDstManager.term_destination = JpegTermDestination; cinfo->dest = &jpegDstManager; }