You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
libtdevnc/libvncserver/tight.c

2068 lines
74 KiB

/*
* tight.c
*
* Routines to implement Tight Encoding
*/
/*
* Copyright (C) 2000, 2001 Const Kaplinsky. All Rights Reserved.
* Copyright (C) 1999 AT&T Laboratories Cambridge. 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 <stdio.h>*/
#include <rfb/rfb.h>
#include "private.h"
#ifdef WIN32
#define XMD_H
#undef FAR
#define NEEDFAR_POINTERS
#endif
#ifdef _RPCNDR_H /* This Windows header typedefs 'boolean', jpeglib has to know */
#define HAVE_BOOLEAN
#endif
#ifdef LIBVNCSERVER_HAVE_LIBPNG
#include <png.h>
#endif
#include <jpeglib.h>
/* Note: The following constant should not be changed. */
#define TIGHT_MIN_TO_COMPRESS 12
/* The parameters below may be adjusted. */
#define MIN_SPLIT_RECT_SIZE 4096
#define MIN_SOLID_SUBRECT_SIZE 2048
#define MAX_SPLIT_TILE_SIZE 16
/* May be set to TRUE with "-lazytight" Xvnc option. */
rfbBool rfbTightDisableGradient = FALSE;
/*
* There is so much access of the Tight encoding static data buffers
* that we resort to using thread local storage instead of having
* per-client data.
*/
#if LIBVNCSERVER_HAVE_LIBPTHREAD && LIBVNCSERVER_HAVE_TLS && !defined(TLS) && defined(__linux__)
#define TLS __thread
#endif
#ifndef TLS
#define TLS
#endif
/* This variable is set on every rfbSendRectEncodingTight() call. */
static TLS rfbBool usePixelFormat24 = FALSE;
/* 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). */
typedef struct TIGHT_CONF_s {
int maxRectSize, maxRectWidth;
int monoMinRectSize, gradientMinRectSize;
int idxZlibLevel, monoZlibLevel, rawZlibLevel, gradientZlibLevel;
int gradientThreshold, gradientThreshold24;
int idxMaxColorsDivisor;
int jpegQuality, jpegThreshold, jpegThreshold24;
} TIGHT_CONF;
static TIGHT_CONF tightConf[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 }
};
#ifdef LIBVNCSERVER_HAVE_LIBPNG
typedef struct TIGHT_PNG_CONF_s {
int png_zlib_level, png_filters;
} TIGHT_PNG_CONF;
static TIGHT_PNG_CONF tightPngConf[10] = {
{ 0, PNG_NO_FILTERS },
{ 1, PNG_NO_FILTERS },
{ 2, PNG_NO_FILTERS },
{ 3, PNG_NO_FILTERS },
{ 4, PNG_NO_FILTERS },
{ 5, PNG_ALL_FILTERS },
{ 6, PNG_ALL_FILTERS },
{ 7, PNG_ALL_FILTERS },
{ 8, PNG_ALL_FILTERS },
{ 9, PNG_ALL_FILTERS },
};
#endif
static TLS int compressLevel = 0;
static TLS int qualityLevel = 0;
/* Stuff dealing with palettes. */
typedef struct COLOR_LIST_s {
struct COLOR_LIST_s *next;
int idx;
uint32_t rgb;
} COLOR_LIST;
typedef struct PALETTE_ENTRY_s {
COLOR_LIST *listNode;
int numPixels;
} PALETTE_ENTRY;
typedef struct PALETTE_s {
PALETTE_ENTRY entry[256];
COLOR_LIST *hash[256];
COLOR_LIST list[256];
} PALETTE;
/* TODO: move into rfbScreen struct */
static TLS int paletteNumColors = 0;
static TLS int paletteMaxColors = 0;
static TLS uint32_t monoBackground = 0;
static TLS uint32_t monoForeground = 0;
static TLS PALETTE palette;
/* Pointers to dynamically-allocated buffers. */
static TLS int tightBeforeBufSize = 0;
static TLS char *tightBeforeBuf = NULL;
static TLS int tightAfterBufSize = 0;
static TLS char *tightAfterBuf = NULL;
static TLS int *prevRowBuf = NULL;
void rfbTightCleanup(rfbScreenInfoPtr screen)
{
if(tightBeforeBufSize) {
free(tightBeforeBuf);
tightBeforeBufSize=0;
tightBeforeBuf = NULL;
}
if(tightAfterBufSize) {
free(tightAfterBuf);
tightAfterBufSize=0;
tightAfterBuf = NULL;
}
}
/* Prototypes for static functions. */
static rfbBool SendRectEncodingTight(rfbClientPtr cl, int x, int y,
int w, int h);
static void FindBestSolidArea (rfbClientPtr cl, int x, int y, int w, int h,
uint32_t colorValue, int *w_ptr, int *h_ptr);
static void ExtendSolidArea (rfbClientPtr cl, int x, int y, int w, int h,
uint32_t colorValue,
int *x_ptr, int *y_ptr, int *w_ptr, int *h_ptr);
static rfbBool CheckSolidTile (rfbClientPtr cl, int x, int y, int w, int h,
uint32_t *colorPtr, rfbBool needSameColor);
static rfbBool CheckSolidTile8 (rfbClientPtr cl, int x, int y, int w, int h,
uint32_t *colorPtr, rfbBool needSameColor);
static rfbBool CheckSolidTile16 (rfbClientPtr cl, int x, int y, int w, int h,
uint32_t *colorPtr, rfbBool needSameColor);
static rfbBool CheckSolidTile32 (rfbClientPtr cl, int x, int y, int w, int h,
uint32_t *colorPtr, rfbBool needSameColor);
static rfbBool SendRectSimple (rfbClientPtr cl, int x, int y, int w, int h);
static rfbBool SendSubrect (rfbClientPtr cl, int x, int y, int w, int h);
static rfbBool SendTightHeader (rfbClientPtr cl, int x, int y, int w, int h);
static rfbBool SendSolidRect (rfbClientPtr cl);
static rfbBool SendMonoRect (rfbClientPtr cl, int x, int y, int w, int h);
static rfbBool SendIndexedRect (rfbClientPtr cl, int x, int y, int w, int h);
static rfbBool SendFullColorRect (rfbClientPtr cl, int x, int y, int w, int h);
static rfbBool SendGradientRect (rfbClientPtr cl, int x, int y, int w, int h);
static rfbBool CompressData(rfbClientPtr cl, int streamId, int dataLen,
int zlibLevel, int zlibStrategy);
static rfbBool SendCompressedData(rfbClientPtr cl, int compressedLen);
static void FillPalette8(int count);
static void FillPalette16(int count);
static void FillPalette32(int count);
static void PaletteReset(void);
static int PaletteInsert(uint32_t rgb, int numPixels, int bpp);
static void Pack24(rfbClientPtr cl, char *buf, rfbPixelFormat *fmt, int count);
static void EncodeIndexedRect16(uint8_t *buf, int count);
static void EncodeIndexedRect32(uint8_t *buf, int count);
static void EncodeMonoRect8(uint8_t *buf, int w, int h);
static void EncodeMonoRect16(uint8_t *buf, int w, int h);
static void EncodeMonoRect32(uint8_t *buf, int w, int h);
static void FilterGradient24(rfbClientPtr cl, char *buf, rfbPixelFormat *fmt, int w, int h);
static void FilterGradient16(rfbClientPtr cl, uint16_t *buf, rfbPixelFormat *fmt, int w, int h);
static void FilterGradient32(rfbClientPtr cl, uint32_t *buf, rfbPixelFormat *fmt, int w, int h);
static int DetectSmoothImage(rfbClientPtr cl, rfbPixelFormat *fmt, int w, int h);
static unsigned long DetectSmoothImage24(rfbClientPtr cl, rfbPixelFormat *fmt, int w, int h);
static unsigned long DetectSmoothImage16(rfbClientPtr cl, rfbPixelFormat *fmt, int w, int h);
static unsigned long DetectSmoothImage32(rfbClientPtr cl, rfbPixelFormat *fmt, int w, int h);
static rfbBool SendJpegRect(rfbClientPtr cl, int x, int y, int w, int h,
int quality);
static void PrepareRowForImg(rfbClientPtr cl, uint8_t *dst, int x, int y, int count);
static void PrepareRowForImg24(rfbClientPtr cl, uint8_t *dst, int x, int y, int count);
static void PrepareRowForImg16(rfbClientPtr cl, uint8_t *dst, int x, int y, int count);
static void PrepareRowForImg32(rfbClientPtr cl, uint8_t *dst, int x, int y, int count);
static void JpegInitDestination(j_compress_ptr cinfo);
static boolean JpegEmptyOutputBuffer(j_compress_ptr cinfo);
static void JpegTermDestination(j_compress_ptr cinfo);
static void JpegSetDstManager(j_compress_ptr cinfo);
#ifdef LIBVNCSERVER_HAVE_LIBPNG
static rfbBool SendPngRect(rfbClientPtr cl, int x, int y, int w, int h);
static rfbBool CanSendPngRect(rfbClientPtr cl, int w, int h);
#endif
/*
* Tight encoding implementation.
*/
int
rfbNumCodedRectsTight(rfbClientPtr cl,
int x,
int y,
int w,
int h)
{
int maxRectSize, maxRectWidth;
int subrectMaxWidth, subrectMaxHeight;
/* No matter how many rectangles we will send if LastRect markers
are used to terminate rectangle stream. */
if (cl->enableLastRectEncoding && w * h >= MIN_SPLIT_RECT_SIZE)
return 0;
maxRectSize = tightConf[cl->tightCompressLevel].maxRectSize;
maxRectWidth = tightConf[cl->tightCompressLevel].maxRectWidth;
if (w > maxRectWidth || w * h > maxRectSize) {
subrectMaxWidth = (w > maxRectWidth) ? maxRectWidth : w;
subrectMaxHeight = maxRectSize / subrectMaxWidth;
return (((w - 1) / maxRectWidth + 1) *
((h - 1) / subrectMaxHeight + 1));
} else {
return 1;
}
}
rfbBool
rfbSendRectEncodingTight(rfbClientPtr cl,
int x,
int y,
int w,
int h)
{
cl->tightEncoding = rfbEncodingTight;
return SendRectEncodingTight(cl, x, y, w, h);
}
rfbBool
rfbSendRectEncodingTightPng(rfbClientPtr cl,
int x,
int y,
int w,
int h)
{
cl->tightEncoding = rfbEncodingTightPng;
return SendRectEncodingTight(cl, x, y, w, h);
}
rfbBool
SendRectEncodingTight(rfbClientPtr cl,
int x,
int y,
int w,
int h)
{
int nMaxRows;
uint32_t colorValue;
int dx, dy, dw, dh;
int x_best, y_best, w_best, h_best;
char *fbptr;
rfbSendUpdateBuf(cl);
compressLevel = cl->tightCompressLevel;
qualityLevel = cl->tightQualityLevel;
if ( cl->format.depth == 24 && cl->format.redMax == 0xFF &&
cl->format.greenMax == 0xFF && cl->format.blueMax == 0xFF ) {
usePixelFormat24 = TRUE;
} else {
usePixelFormat24 = FALSE;
}
if (!cl->enableLastRectEncoding || w * h < MIN_SPLIT_RECT_SIZE)
return SendRectSimple(cl, x, y, w, h);
/* Make sure we can write at least one pixel into tightBeforeBuf. */
if (tightBeforeBufSize < 4) {
tightBeforeBufSize = 4;
if (tightBeforeBuf == NULL)
tightBeforeBuf = (char *)malloc(tightBeforeBufSize);
else
tightBeforeBuf = (char *)realloc(tightBeforeBuf,
tightBeforeBufSize);
}
/* Calculate maximum number of rows in one non-solid rectangle. */
{
int maxRectSize, maxRectWidth, nMaxWidth;
maxRectSize = tightConf[compressLevel].maxRectSize;
maxRectWidth = tightConf[compressLevel].maxRectWidth;
nMaxWidth = (w > maxRectWidth) ? maxRectWidth : w;
nMaxRows = maxRectSize / nMaxWidth;
}
/* Try to find large solid-color areas and send them separately. */
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) {
if (!SendRectSimple(cl, x, y, w, nMaxRows))
return 0;
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(cl, dx, dy, dw, dh, &colorValue, FALSE)) {
/* Get dimensions of solid-color area. */
FindBestSolidArea(cl, 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(cl, x, y, w, h, colorValue,
&x_best, &y_best, &w_best, &h_best);
/* Send rectangles at top and left to solid-color area. */
if ( y_best != y &&
!SendRectSimple(cl, x, y, w, y_best-y) )
return FALSE;
if ( x_best != x &&
!SendRectEncodingTight(cl, x, y_best,
x_best-x, h_best) )
return FALSE;
/* Send solid-color rectangle. */
if (!SendTightHeader(cl, x_best, y_best, w_best, h_best))
return FALSE;
fbptr = (cl->scaledScreen->frameBuffer +
(cl->scaledScreen->paddedWidthInBytes * y_best) +
(x_best * (cl->scaledScreen->bitsPerPixel / 8)));
(*cl->translateFn)(cl->translateLookupTable, &cl->screen->serverFormat,
&cl->format, fbptr, tightBeforeBuf,
cl->scaledScreen->paddedWidthInBytes, 1, 1);
if (!SendSolidRect(cl))
return FALSE;
/* Send remaining rectangles (at right and bottom). */
if ( x_best + w_best != x + w &&
!SendRectEncodingTight(cl, x_best+w_best, y_best,
w-(x_best-x)-w_best, h_best) )
return FALSE;
if ( y_best + h_best != y + h &&
!SendRectEncodingTight(cl, x, y_best+h_best,
w, h-(y_best-y)-h_best) )
return FALSE;
/* Return after all recursive calls are done. */
return TRUE;
}
}
}
/* No suitable solid-color rectangles found. */
return SendRectSimple(cl, x, y, w, h);
}
static void
FindBestSolidArea(rfbClientPtr cl,
int x,
int y,
int w,
int h,
uint32_t 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(cl, 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(cl, 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;
}
static void
ExtendSolidArea(rfbClientPtr cl,
int x,
int y,
int w,
int h,
uint32_t 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(cl, *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(cl, *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(cl, 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(cl, cx, *y_ptr, 1, *h_ptr, &colorValue, TRUE);
cx++ );
*w_ptr += cx - (*x_ptr + *w_ptr);
}
/*
* Check if a rectangle is all of the same color. If needSameColor is
* set to non-zero, then also check that its color equals to the
* *colorPtr value. The result is 1 if the test is successfull, and in
* that case new color will be stored in *colorPtr.
*/
static rfbBool CheckSolidTile(rfbClientPtr cl, int x, int y, int w, int h, uint32_t* colorPtr, rfbBool needSameColor)
{
switch(cl->screen->serverFormat.bitsPerPixel) {
case 32:
return CheckSolidTile32(cl, x, y, w, h, colorPtr, needSameColor);
case 16:
return CheckSolidTile16(cl, x, y, w, h, colorPtr, needSameColor);
default:
return CheckSolidTile8(cl, x, y, w, h, colorPtr, needSameColor);
}
}
#define DEFINE_CHECK_SOLID_FUNCTION(bpp) \
\
static rfbBool \
CheckSolidTile##bpp(rfbClientPtr cl, int x, int y, int w, int h, \
uint32_t* colorPtr, rfbBool needSameColor) \
{ \
uint##bpp##_t *fbptr; \
uint##bpp##_t colorValue; \
int dx, dy; \
\
fbptr = (uint##bpp##_t *) \
&cl->scaledScreen->frameBuffer[y * cl->scaledScreen->paddedWidthInBytes + x * (bpp/8)]; \
\
colorValue = *fbptr; \
if (needSameColor && (uint32_t)colorValue != *colorPtr) \
return FALSE; \
\
for (dy = 0; dy < h; dy++) { \
for (dx = 0; dx < w; dx++) { \
if (colorValue != fbptr[dx]) \
return FALSE; \
} \
fbptr = (uint##bpp##_t *)((uint8_t *)fbptr + cl->scaledScreen->paddedWidthInBytes); \
} \
\
*colorPtr = (uint32_t)colorValue; \
return TRUE; \
}
DEFINE_CHECK_SOLID_FUNCTION(8)
DEFINE_CHECK_SOLID_FUNCTION(16)
DEFINE_CHECK_SOLID_FUNCTION(32)
static rfbBool
SendRectSimple(rfbClientPtr cl, int x, int y, int w, int h)
{
int maxBeforeSize, maxAfterSize;
int maxRectSize, maxRectWidth;
int subrectMaxWidth, subrectMaxHeight;
int dx, dy;
int rw, rh;
maxRectSize = tightConf[compressLevel].maxRectSize;
maxRectWidth = tightConf[compressLevel].maxRectWidth;
maxBeforeSize = maxRectSize * (cl->format.bitsPerPixel / 8);
maxAfterSize = maxBeforeSize + (maxBeforeSize + 99) / 100 + 12;
if (tightBeforeBufSize < maxBeforeSize) {
tightBeforeBufSize = maxBeforeSize;
if (tightBeforeBuf == NULL)
tightBeforeBuf = (char *)malloc(tightBeforeBufSize);
else
tightBeforeBuf = (char *)realloc(tightBeforeBuf,
tightBeforeBufSize);
}
if (tightAfterBufSize < maxAfterSize) {
tightAfterBufSize = maxAfterSize;
if (tightAfterBuf == NULL)
tightAfterBuf = (char *)malloc(tightAfterBufSize);
else
tightAfterBuf = (char *)realloc(tightAfterBuf,
tightAfterBufSize);
}
if (w > maxRectWidth || w * h > maxRectSize) {
subrectMaxWidth = (w > maxRectWidth) ? maxRectWidth : w;
subrectMaxHeight = maxRectSize / subrectMaxWidth;
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;
if (!SendSubrect(cl, x+dx, y+dy, rw, rh))
return FALSE;
}
}
} else {
if (!SendSubrect(cl, x, y, w, h))
return FALSE;
}
return TRUE;
}
static rfbBool
SendSubrect(rfbClientPtr cl,
int x,
int y,
int w,
int h)
{
char *fbptr;
rfbBool success = FALSE;
/* Send pending data if there is more than 128 bytes. */
if (cl->ublen > 128) {
if (!rfbSendUpdateBuf(cl))
return FALSE;
}
if (!SendTightHeader(cl, x, y, w, h))
return FALSE;
fbptr = (cl->scaledScreen->frameBuffer + (cl->scaledScreen->paddedWidthInBytes * y)
+ (x * (cl->scaledScreen->bitsPerPixel / 8)));
(*cl->translateFn)(cl->translateLookupTable, &cl->screen->serverFormat,
&cl->format, fbptr, tightBeforeBuf,
cl->scaledScreen->paddedWidthInBytes, w, h);
paletteMaxColors = w * h / tightConf[compressLevel].idxMaxColorsDivisor;
if ( paletteMaxColors < 2 &&
w * h >= tightConf[compressLevel].monoMinRectSize ) {
paletteMaxColors = 2;
}
switch (cl->format.bitsPerPixel) {
case 8:
FillPalette8(w * h);
break;
case 16:
FillPalette16(w * h);
break;
default:
FillPalette32(w * h);
}
switch (paletteNumColors) {
case 0:
/* Truecolor image */
if (DetectSmoothImage(cl, &cl->format, w, h)) {
if (qualityLevel != -1) {
success = SendJpegRect(cl, x, y, w, h,
tightConf[qualityLevel].jpegQuality);
} else {
success = SendGradientRect(cl, x, y, w, h);
}
} else {
success = SendFullColorRect(cl, x, y, w, h);
}
break;
case 1:
/* Solid rectangle */
success = SendSolidRect(cl);
break;
case 2:
/* Two-color rectangle */
success = SendMonoRect(cl, x, y, w, h);
break;
default:
/* Up to 256 different colors */
if ( paletteNumColors > 96 &&
qualityLevel != -1 && qualityLevel <= 3 &&
DetectSmoothImage(cl, &cl->format, w, h) ) {
success = SendJpegRect(cl, x, y, w, h,
tightConf[qualityLevel].jpegQuality);
} else {
success = SendIndexedRect(cl, x, y, w, h);
}
}
return success;
}
static rfbBool
SendTightHeader(rfbClientPtr cl,
int x,
int y,
int w,
int h)
{
rfbFramebufferUpdateRectHeader rect;
if (cl->ublen + sz_rfbFramebufferUpdateRectHeader > UPDATE_BUF_SIZE) {
if (!rfbSendUpdateBuf(cl))
return FALSE;
}
rect.r.x = Swap16IfLE(x);
rect.r.y = Swap16IfLE(y);
rect.r.w = Swap16IfLE(w);
rect.r.h = Swap16IfLE(h);
rect.encoding = Swap32IfLE(cl->tightEncoding);
memcpy(&cl->updateBuf[cl->ublen], (char *)&rect,
sz_rfbFramebufferUpdateRectHeader);
cl->ublen += sz_rfbFramebufferUpdateRectHeader;
rfbStatRecordEncodingSent(cl, cl->tightEncoding, sz_rfbFramebufferUpdateRectHeader,
sz_rfbFramebufferUpdateRectHeader + w * (cl->format.bitsPerPixel / 8) * h);
return TRUE;
}
/*
* Subencoding implementations.
*/
static rfbBool
SendSolidRect(rfbClientPtr cl)
{
int len;
if (usePixelFormat24) {
Pack24(cl, tightBeforeBuf, &cl->format, 1);
len = 3;
} else
len = cl->format.bitsPerPixel / 8;
if (cl->ublen + 1 + len > UPDATE_BUF_SIZE) {
if (!rfbSendUpdateBuf(cl))
return FALSE;
}
cl->updateBuf[cl->ublen++] = (char)(rfbTightFill << 4);
memcpy (&cl->updateBuf[cl->ublen], tightBeforeBuf, len);
cl->ublen += len;
rfbStatRecordEncodingSentAdd(cl, cl->tightEncoding, len+1);
return TRUE;
}
static rfbBool
SendMonoRect(rfbClientPtr cl,
int x,
int y,
int w,
int h)
{
int streamId = 1;
int paletteLen, dataLen;
#ifdef LIBVNCSERVER_HAVE_LIBPNG
if (CanSendPngRect(cl, w, h)) {
/* TODO: setup palette maybe */
return SendPngRect(cl, x, y, w, h);
/* TODO: destroy palette maybe */
}
#endif
if ( cl->ublen + TIGHT_MIN_TO_COMPRESS + 6 +
2 * cl->format.bitsPerPixel / 8 > UPDATE_BUF_SIZE ) {
if (!rfbSendUpdateBuf(cl))
return FALSE;
}
/* Prepare tight encoding header. */
dataLen = (w + 7) / 8;
dataLen *= h;
cl->updateBuf[cl->ublen++] = (streamId | rfbTightExplicitFilter) << 4;
cl->updateBuf[cl->ublen++] = rfbTightFilterPalette;
cl->updateBuf[cl->ublen++] = 1;
/* Prepare palette, convert image. */
switch (cl->format.bitsPerPixel) {
case 32:
EncodeMonoRect32((uint8_t *)tightBeforeBuf, w, h);
((uint32_t *)tightAfterBuf)[0] = monoBackground;
((uint32_t *)tightAfterBuf)[1] = monoForeground;
if (usePixelFormat24) {
Pack24(cl, tightAfterBuf, &cl->format, 2);
paletteLen = 6;
} else
paletteLen = 8;
memcpy(&cl->updateBuf[cl->ublen], tightAfterBuf, paletteLen);
cl->ublen += paletteLen;
rfbStatRecordEncodingSentAdd(cl, cl->tightEncoding, 3 + paletteLen);
break;
case 16:
EncodeMonoRect16((uint8_t *)tightBeforeBuf, w, h);
((uint16_t *)tightAfterBuf)[0] = (uint16_t)monoBackground;
((uint16_t *)tightAfterBuf)[1] = (uint16_t)monoForeground;
memcpy(&cl->updateBuf[cl->ublen], tightAfterBuf, 4);
cl->ublen += 4;
rfbStatRecordEncodingSentAdd(cl, cl->tightEncoding, 7);
break;
default:
EncodeMonoRect8((uint8_t *)tightBeforeBuf, w, h);
cl->updateBuf[cl->ublen++] = (char)monoBackground;
cl->updateBuf[cl->ublen++] = (char)monoForeground;
rfbStatRecordEncodingSentAdd(cl, cl->tightEncoding, 5);
}
return CompressData(cl, streamId, dataLen,
tightConf[compressLevel].monoZlibLevel,
Z_DEFAULT_STRATEGY);
}
static rfbBool
SendIndexedRect(rfbClientPtr cl,
int x,
int y,
int w,
int h)
{
int streamId = 2;
int i, entryLen;
#ifdef LIBVNCSERVER_HAVE_LIBPNG
if (CanSendPngRect(cl, w, h)) {
return SendPngRect(cl, x, y, w, h);
}
#endif
if ( cl->ublen + TIGHT_MIN_TO_COMPRESS + 6 +
paletteNumColors * cl->format.bitsPerPixel / 8 >
UPDATE_BUF_SIZE ) {
if (!rfbSendUpdateBuf(cl))
return FALSE;
}
/* Prepare tight encoding header. */
cl->updateBuf[cl->ublen++] = (streamId | rfbTightExplicitFilter) << 4;
cl->updateBuf[cl->ublen++] = rfbTightFilterPalette;
cl->updateBuf[cl->ublen++] = (char)(paletteNumColors - 1);
/* Prepare palette, convert image. */
switch (cl->format.bitsPerPixel) {
case 32:
EncodeIndexedRect32((uint8_t *)tightBeforeBuf, w * h);
for (i = 0; i < paletteNumColors; i++) {
((uint32_t *)tightAfterBuf)[i] =
palette.entry[i].listNode->rgb;
}
if (usePixelFormat24) {
Pack24(cl, tightAfterBuf, &cl->format, paletteNumColors);
entryLen = 3;
} else
entryLen = 4;
memcpy(&cl->updateBuf[cl->ublen], tightAfterBuf, paletteNumColors * entryLen);
cl->ublen += paletteNumColors * entryLen;
rfbStatRecordEncodingSentAdd(cl, cl->tightEncoding, 3 + paletteNumColors * entryLen);
break;
case 16:
EncodeIndexedRect16((uint8_t *)tightBeforeBuf, w * h);
for (i = 0; i < paletteNumColors; i++) {
((uint16_t *)tightAfterBuf)[i] =
(uint16_t)palette.entry[i].listNode->rgb;
}
memcpy(&cl->updateBuf[cl->ublen], tightAfterBuf, paletteNumColors * 2);
cl->ublen += paletteNumColors * 2;
rfbStatRecordEncodingSentAdd(cl, cl->tightEncoding, 3 + paletteNumColors * 2);
break;
default:
return FALSE; /* Should never happen. */
}
return CompressData(cl, streamId, w * h,
tightConf[compressLevel].idxZlibLevel,
Z_DEFAULT_STRATEGY);
}
static rfbBool
SendFullColorRect(rfbClientPtr cl,
int x,
int y,
int w,
int h)
{
int streamId = 0;
int len;
#ifdef LIBVNCSERVER_HAVE_LIBPNG
if (CanSendPngRect(cl, w, h)) {
return SendPngRect(cl, x, y, w, h);
}
#endif
if (cl->ublen + TIGHT_MIN_TO_COMPRESS + 1 > UPDATE_BUF_SIZE) {
if (!rfbSendUpdateBuf(cl))
return FALSE;
}
cl->updateBuf[cl->ublen++] = 0x00; /* stream id = 0, no flushing, no filter */
rfbStatRecordEncodingSentAdd(cl, cl->tightEncoding, 1);
if (usePixelFormat24) {
Pack24(cl, tightBeforeBuf, &cl->format, w * h);
len = 3;
} else
len = cl->format.bitsPerPixel / 8;
return CompressData(cl, streamId, w * h * len,
tightConf[compressLevel].rawZlibLevel,
Z_DEFAULT_STRATEGY);
}
static rfbBool
SendGradientRect(rfbClientPtr cl,
int x,
int y,
int w,
int h)
{
int streamId = 3;
int len;
#ifdef LIBVNCSERVER_HAVE_LIBPNG
if (CanSendPngRect(cl, w, h)) {
return SendPngRect(cl, x, y, w, h);
}
#endif
if (cl->format.bitsPerPixel == 8)
return SendFullColorRect(cl, x, y, w, h);
if (cl->ublen + TIGHT_MIN_TO_COMPRESS + 2 > UPDATE_BUF_SIZE) {
if (!rfbSendUpdateBuf(cl))
return FALSE;
}
if (prevRowBuf == NULL)
prevRowBuf = (int *)malloc(2048 * 3 * sizeof(int));
cl->updateBuf[cl->ublen++] = (streamId | rfbTightExplicitFilter) << 4;
cl->updateBuf[cl->ublen++] = rfbTightFilterGradient;
rfbStatRecordEncodingSentAdd(cl, cl->tightEncoding, 2);
if (usePixelFormat24) {
FilterGradient24(cl, tightBeforeBuf, &cl->format, w, h);
len = 3;
} else if (cl->format.bitsPerPixel == 32) {
FilterGradient32(cl, (uint32_t *)tightBeforeBuf, &cl->format, w, h);
len = 4;
} else {
FilterGradient16(cl, (uint16_t *)tightBeforeBuf, &cl->format, w, h);
len = 2;
}
return CompressData(cl, streamId, w * h * len,
tightConf[compressLevel].gradientZlibLevel,
Z_FILTERED);
}
static rfbBool
CompressData(rfbClientPtr cl,
int streamId,
int dataLen,
int zlibLevel,
int zlibStrategy)
{
z_streamp pz;
int err;
if (dataLen < TIGHT_MIN_TO_COMPRESS) {
memcpy(&cl->updateBuf[cl->ublen], tightBeforeBuf, dataLen);
cl->ublen += dataLen;
rfbStatRecordEncodingSentAdd(cl, cl->tightEncoding, dataLen);
return TRUE;
}
pz = &cl->zsStruct[streamId];
/* Initialize compression stream if needed. */
if (!cl->zsActive[streamId]) {
pz->zalloc = Z_NULL;
pz->zfree = Z_NULL;
pz->opaque = Z_NULL;
err = deflateInit2 (pz, zlibLevel, Z_DEFLATED, MAX_WBITS,
MAX_MEM_LEVEL, zlibStrategy);
if (err != Z_OK)
return FALSE;
cl->zsActive[streamId] = TRUE;
cl->zsLevel[streamId] = zlibLevel;
}
/* Prepare buffer pointers. */
pz->next_in = (Bytef *)tightBeforeBuf;
pz->avail_in = dataLen;
pz->next_out = (Bytef *)tightAfterBuf;
pz->avail_out = tightAfterBufSize;
/* Change compression parameters if needed. */
if (zlibLevel != cl->zsLevel[streamId]) {
if (deflateParams (pz, zlibLevel, zlibStrategy) != Z_OK) {
return FALSE;
}
cl->zsLevel[streamId] = zlibLevel;
}
/* Actual compression. */
if ( deflate (pz, Z_SYNC_FLUSH) != Z_OK ||
pz->avail_in != 0 || pz->avail_out == 0 ) {
return FALSE;
}
return SendCompressedData(cl, tightAfterBufSize - pz->avail_out);
}
static rfbBool SendCompressedData(rfbClientPtr cl,
int compressedLen)
{
int i, portionLen;
cl->updateBuf[cl->ublen++] = compressedLen & 0x7F;
rfbStatRecordEncodingSentAdd(cl, cl->tightEncoding, 1);
if (compressedLen > 0x7F) {
cl->updateBuf[cl->ublen-1] |= 0x80;
cl->updateBuf[cl->ublen++] = compressedLen >> 7 & 0x7F;
rfbStatRecordEncodingSentAdd(cl, cl->tightEncoding, 1);
if (compressedLen > 0x3FFF) {
cl->updateBuf[cl->ublen-1] |= 0x80;
cl->updateBuf[cl->ublen++] = compressedLen >> 14 & 0xFF;
rfbStatRecordEncodingSentAdd(cl, cl->tightEncoding, 1);
}
}
portionLen = UPDATE_BUF_SIZE;
for (i = 0; i < compressedLen; i += portionLen) {
if (i + portionLen > compressedLen) {
portionLen = compressedLen - i;
}
if (cl->ublen + portionLen > UPDATE_BUF_SIZE) {
if (!rfbSendUpdateBuf(cl))
return FALSE;
}
memcpy(&cl->updateBuf[cl->ublen], &tightAfterBuf[i], portionLen);
cl->ublen += portionLen;
}
rfbStatRecordEncodingSentAdd(cl, cl->tightEncoding, compressedLen);
return TRUE;
}
/*
* Code to determine how many different colors used in rectangle.
*/
static void
FillPalette8(int count)
{
uint8_t *data = (uint8_t *)tightBeforeBuf;
uint8_t c0, c1;
int i, n0, n1;
paletteNumColors = 0;
c0 = data[0];
for (i = 1; i < count && data[i] == c0; i++);
if (i == count) {
paletteNumColors = 1;
return; /* Solid rectangle */
}
if (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) {
monoBackground = (uint32_t)c0;
monoForeground = (uint32_t)c1;
} else {
monoBackground = (uint32_t)c1;
monoForeground = (uint32_t)c0;
}
paletteNumColors = 2; /* Two colors */
}
}
#define DEFINE_FILL_PALETTE_FUNCTION(bpp) \
\
static void \
FillPalette##bpp(int count) { \
uint##bpp##_t *data = (uint##bpp##_t *)tightBeforeBuf; \
uint##bpp##_t c0, c1, ci; \
int i, n0, n1, ni; \
\
c0 = data[0]; \
for (i = 1; i < count && data[i] == c0; i++); \
if (i >= count) { \
paletteNumColors = 1; /* Solid rectangle */ \
return; \
} \
\
if (paletteMaxColors < 2) { \
paletteNumColors = 0; /* Full-color encoding 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) { \
monoBackground = (uint32_t)c0; \
monoForeground = (uint32_t)c1; \
} else { \
monoBackground = (uint32_t)c1; \
monoForeground = (uint32_t)c0; \
} \
paletteNumColors = 2; /* Two colors */ \
return; \
} \
\
PaletteReset(); \
PaletteInsert (c0, (uint32_t)n0, bpp); \
PaletteInsert (c1, (uint32_t)n1, bpp); \
\
ni = 1; \
for (i++; i < count; i++) { \
if (data[i] == ci) { \
ni++; \
} else { \
if (!PaletteInsert (ci, (uint32_t)ni, bpp)) \
return; \
ci = data[i]; \
ni = 1; \
} \
} \
PaletteInsert (ci, (uint32_t)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))
static void
PaletteReset(void)
{
paletteNumColors = 0;
memset(palette.hash, 0, 256 * sizeof(COLOR_LIST *));
}
static int
PaletteInsert(uint32_t 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 = palette.hash[hash_key];
while (pnode != NULL) {
if (pnode->rgb == rgb) {
/* Such palette entry already exists. */
new_idx = idx = pnode->idx;
count = palette.entry[idx].numPixels + numPixels;
if (new_idx && palette.entry[new_idx-1].numPixels < count) {
do {
palette.entry[new_idx] = palette.entry[new_idx-1];
palette.entry[new_idx].listNode->idx = new_idx;
new_idx--;
}
while (new_idx && palette.entry[new_idx-1].numPixels < count);
palette.entry[new_idx].listNode = pnode;
pnode->idx = new_idx;
}
palette.entry[new_idx].numPixels = count;
return paletteNumColors;
}
prev_pnode = pnode;
pnode = pnode->next;
}
/* Check if palette is full. */
if (paletteNumColors == 256 || paletteNumColors == paletteMaxColors) {
paletteNumColors = 0;
return 0;
}
/* Move palette entries with lesser pixel counts. */
for ( idx = paletteNumColors;
idx > 0 && palette.entry[idx-1].numPixels < numPixels;
idx-- ) {
palette.entry[idx] = palette.entry[idx-1];
palette.entry[idx].listNode->idx = idx;
}
/* Add new palette entry into the freed slot. */
pnode = &palette.list[paletteNumColors];
if (prev_pnode != NULL) {
prev_pnode->next = pnode;
} else {
palette.hash[hash_key] = pnode;
}
pnode->next = NULL;
pnode->idx = idx;
pnode->rgb = rgb;
palette.entry[idx].listNode = pnode;
palette.entry[idx].numPixels = numPixels;
return (++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.
*/
static void Pack24(rfbClientPtr cl,
char *buf,
rfbPixelFormat *fmt,
int count)
{
uint32_t *buf32;
uint32_t pix;
int r_shift, g_shift, b_shift;
buf32 = (uint32_t *)buf;
if (!cl->screen->serverFormat.bigEndian == !fmt->bigEndian) {
r_shift = fmt->redShift;
g_shift = fmt->greenShift;
b_shift = fmt->blueShift;
} else {
r_shift = 24 - fmt->redShift;
g_shift = 24 - fmt->greenShift;
b_shift = 24 - fmt->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) \
\
static void \
EncodeIndexedRect##bpp(uint8_t *buf, int count) { \
COLOR_LIST *pnode; \
uint##bpp##_t *src; \
uint##bpp##_t rgb; \
int rep = 0; \
\
src = (uint##bpp##_t *) buf; \
\
while (count--) { \
rgb = *src++; \
while (count && *src == rgb) { \
rep++, src++, count--; \
} \
pnode = palette.hash[HASH_FUNC##bpp(rgb)]; \
while (pnode != NULL) { \
if ((uint##bpp##_t)pnode->rgb == rgb) { \
*buf++ = (uint8_t)pnode->idx; \
while (rep) { \
*buf++ = (uint8_t)pnode->idx; \
rep--; \
} \
break; \
} \
pnode = pnode->next; \
} \
} \
}
DEFINE_IDX_ENCODE_FUNCTION(16)
DEFINE_IDX_ENCODE_FUNCTION(32)
#define DEFINE_MONO_ENCODE_FUNCTION(bpp) \
\
static void \
EncodeMonoRect##bpp(uint8_t *buf, int w, int h) { \
uint##bpp##_t *ptr; \
uint##bpp##_t bg; \
unsigned int value, mask; \
int aligned_width; \
int x, y, bg_bits; \
\
ptr = (uint##bpp##_t *) buf; \
bg = (uint##bpp##_t) 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++ = (uint8_t)value; \
} \
\
mask = 0x80; \
value = 0; \
if (x >= w) \
continue; \
\
for (; x < w; x++) { \
if (*ptr++ != bg) { \
value |= mask; \
} \
mask >>= 1; \
} \
*buf++ = (uint8_t)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.
*/
static void
FilterGradient24(rfbClientPtr cl, char *buf, rfbPixelFormat *fmt, int w, int h)
{
uint32_t *buf32;
uint32_t pix32;
int *prevRowPtr;
int shiftBits[3];
int pixHere[3], pixUpper[3], pixLeft[3], pixUpperLeft[3];
int prediction;
int x, y, c;
buf32 = (uint32_t *)buf;
memset (prevRowBuf, 0, w * 3 * sizeof(int));
if (!cl->screen->serverFormat.bigEndian == !fmt->bigEndian) {
shiftBits[0] = fmt->redShift;
shiftBits[1] = fmt->greenShift;
shiftBits[2] = fmt->blueShift;
} else {
shiftBits[0] = 24 - fmt->redShift;
shiftBits[1] = 24 - fmt->greenShift;
shiftBits[2] = 24 - fmt->blueShift;
}
for (y = 0; y < h; y++) {
for (c = 0; c < 3; c++) {
pixUpper[c] = 0;
pixHere[c] = 0;
}
prevRowPtr = 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++ = (char)(pixHere[c] - prediction);
}
}
}
}
/*
* ``Gradient'' filter for other color depths.
*/
#define DEFINE_GRADIENT_FILTER_FUNCTION(bpp) \
\
static void \
FilterGradient##bpp(rfbClientPtr cl, uint##bpp##_t *buf, \
rfbPixelFormat *fmt, int w, int h) { \
uint##bpp##_t pix, diff; \
rfbBool endianMismatch; \
int *prevRowPtr; \
int maxColor[3], shiftBits[3]; \
int pixHere[3], pixUpper[3], pixLeft[3], pixUpperLeft[3]; \
int prediction; \
int x, y, c; \
\
memset (prevRowBuf, 0, w * 3 * sizeof(int)); \
\
endianMismatch = (!cl->screen->serverFormat.bigEndian != !fmt->bigEndian); \
\
maxColor[0] = fmt->redMax; \
maxColor[1] = fmt->greenMax; \
maxColor[2] = fmt->blueMax; \
shiftBits[0] = fmt->redShift; \
shiftBits[1] = fmt->greenShift; \
shiftBits[2] = fmt->blueShift; \
\
for (y = 0; y < h; y++) { \
for (c = 0; c < 3; c++) { \
pixUpper[c] = 0; \
pixHere[c] = 0; \
} \
prevRowPtr = 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
static int
DetectSmoothImage (rfbClientPtr cl, rfbPixelFormat *fmt, int w, int h)
{
long avgError;
if ( cl->screen->serverFormat.bitsPerPixel == 8 || fmt->bitsPerPixel == 8 ||
w < DETECT_MIN_WIDTH || h < DETECT_MIN_HEIGHT ) {
return 0;
}
if (qualityLevel != -1) {
if (w * h < JPEG_MIN_RECT_SIZE) {
return 0;
}
} else {
if ( rfbTightDisableGradient ||
w * h < tightConf[compressLevel].gradientMinRectSize ) {
return 0;
}
}
if (fmt->bitsPerPixel == 32) {
if (usePixelFormat24) {
avgError = DetectSmoothImage24(cl, fmt, w, h);
if (qualityLevel != -1) {
return (avgError < tightConf[qualityLevel].jpegThreshold24);
}
return (avgError < tightConf[compressLevel].gradientThreshold24);
} else {
avgError = DetectSmoothImage32(cl, fmt, w, h);
}
} else {
avgError = DetectSmoothImage16(cl, fmt, w, h);
}
if (qualityLevel != -1) {
return (avgError < tightConf[qualityLevel].jpegThreshold);
}
return (avgError < tightConf[compressLevel].gradientThreshold);
}
static unsigned long
DetectSmoothImage24 (rfbClientPtr cl,
rfbPixelFormat *fmt,
int w,
int h)
{
int off;
int x, y, d, dx, c;
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. */
off = (fmt->bigEndian != 0);
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++) {
for (c = 0; c < 3; c++) {
left[c] = (int)tightBeforeBuf[((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)tightBeforeBuf[((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) \
\
static unsigned long \
DetectSmoothImage##bpp (rfbClientPtr cl, rfbPixelFormat *fmt, int w, int h) {\
rfbBool endianMismatch; \
uint##bpp##_t 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 = (!cl->screen->serverFormat.bigEndian != !fmt->bigEndian); \
\
maxColor[0] = fmt->redMax; \
maxColor[1] = fmt->greenMax; \
maxColor[2] = fmt->blueMax; \
shiftBits[0] = fmt->redShift; \
shiftBits[1] = fmt->greenShift; \
shiftBits[2] = fmt->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 = ((uint##bpp##_t *)tightBeforeBuf)[(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 = ((uint##bpp##_t *)tightBeforeBuf)[(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 TLS struct jpeg_destination_mgr jpegDstManager;
static TLS rfbBool jpegError = FALSE;
static TLS int jpegDstDataLen = 0;
static rfbBool
SendJpegRect(rfbClientPtr cl, int x, int y, int w, int h, int quality)
{
struct jpeg_compress_struct cinfo;
struct jpeg_error_mgr jerr;
uint8_t *srcBuf;
JSAMPROW rowPointer[1];
int dy;
if (cl->screen->serverFormat.bitsPerPixel == 8)
return SendFullColorRect(cl, x, y, w, h);
srcBuf = (uint8_t *)malloc(w * 3);
if (srcBuf == NULL) {
return SendFullColorRect(cl, x, y, w, h);
}
rowPointer[0] = 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);
jpeg_start_compress(&cinfo, TRUE);
for (dy = 0; dy < h; dy++) {
PrepareRowForImg(cl, srcBuf, x, y + dy, w);
jpeg_write_scanlines(&cinfo, rowPointer, 1);
if (jpegError)
break;
}
if (!jpegError)
jpeg_finish_compress(&cinfo);
jpeg_destroy_compress(&cinfo);
free(srcBuf);
if (jpegError)
return SendFullColorRect(cl, x, y, w, h);
if (cl->ublen + TIGHT_MIN_TO_COMPRESS + 1 > UPDATE_BUF_SIZE) {
if (!rfbSendUpdateBuf(cl))
return FALSE;
}
cl->updateBuf[cl->ublen++] = (char)(rfbTightJpeg << 4);
rfbStatRecordEncodingSentAdd(cl, cl->tightEncoding, 1);
return SendCompressedData(cl, jpegDstDataLen);
}
static void
PrepareRowForImg(rfbClientPtr cl,
uint8_t *dst,
int x,
int y,
int count)
{
if (cl->screen->serverFormat.bitsPerPixel == 32) {
if ( cl->screen->serverFormat.redMax == 0xFF &&
cl->screen->serverFormat.greenMax == 0xFF &&
cl->screen->serverFormat.blueMax == 0xFF ) {
PrepareRowForImg24(cl, dst, x, y, count);
} else {
PrepareRowForImg32(cl, dst, x, y, count);
}
} else {
/* 16 bpp assumed. */
PrepareRowForImg16(cl, dst, x, y, count);
}
}
static void
PrepareRowForImg24(rfbClientPtr cl,
uint8_t *dst,
int x,
int y,
int count)
{
uint32_t *fbptr;
uint32_t pix;
fbptr = (uint32_t *)
&cl->scaledScreen->frameBuffer[y * cl->scaledScreen->paddedWidthInBytes + x * 4];
while (count--) {
pix = *fbptr++;
*dst++ = (uint8_t)(pix >> cl->screen->serverFormat.redShift);
*dst++ = (uint8_t)(pix >> cl->screen->serverFormat.greenShift);
*dst++ = (uint8_t)(pix >> cl->screen->serverFormat.blueShift);
}
}
#define DEFINE_JPEG_GET_ROW_FUNCTION(bpp) \
\
static void \
PrepareRowForImg##bpp(rfbClientPtr cl, uint8_t *dst, int x, int y, int count) { \
uint##bpp##_t *fbptr; \
uint##bpp##_t pix; \
int inRed, inGreen, inBlue; \
\
fbptr = (uint##bpp##_t *) \
&cl->scaledScreen->frameBuffer[y * cl->scaledScreen->paddedWidthInBytes + \
x * (bpp / 8)]; \
\
while (count--) { \
pix = *fbptr++; \
\
inRed = (int) \
(pix >> cl->screen->serverFormat.redShift & cl->screen->serverFormat.redMax); \
inGreen = (int) \
(pix >> cl->screen->serverFormat.greenShift & cl->screen->serverFormat.greenMax); \
inBlue = (int) \
(pix >> cl->screen->serverFormat.blueShift & cl->screen->serverFormat.blueMax); \
\
*dst++ = (uint8_t)((inRed * 255 + cl->screen->serverFormat.redMax / 2) / \
cl->screen->serverFormat.redMax); \
*dst++ = (uint8_t)((inGreen * 255 + cl->screen->serverFormat.greenMax / 2) / \
cl->screen->serverFormat.greenMax); \
*dst++ = (uint8_t)((inBlue * 255 + cl->screen->serverFormat.blueMax / 2) / \
cl->screen->serverFormat.blueMax); \
} \
}
DEFINE_JPEG_GET_ROW_FUNCTION(16)
DEFINE_JPEG_GET_ROW_FUNCTION(32)
/*
* Destination manager implementation for JPEG library.
*/
static void
JpegInitDestination(j_compress_ptr cinfo)
{
jpegError = FALSE;
jpegDstManager.next_output_byte = (JOCTET *)tightAfterBuf;
jpegDstManager.free_in_buffer = (size_t)tightAfterBufSize;
}
static boolean
JpegEmptyOutputBuffer(j_compress_ptr cinfo)
{
jpegError = TRUE;
jpegDstManager.next_output_byte = (JOCTET *)tightAfterBuf;
jpegDstManager.free_in_buffer = (size_t)tightAfterBufSize;
return TRUE;
}
static void
JpegTermDestination(j_compress_ptr cinfo)
{
jpegDstDataLen = tightAfterBufSize - jpegDstManager.free_in_buffer;
}
static void
JpegSetDstManager(j_compress_ptr cinfo)
{
jpegDstManager.init_destination = JpegInitDestination;
jpegDstManager.empty_output_buffer = JpegEmptyOutputBuffer;
jpegDstManager.term_destination = JpegTermDestination;
cinfo->dest = &jpegDstManager;
}
/*
* PNG compression stuff.
*/
#ifdef LIBVNCSERVER_HAVE_LIBPNG
static TLS int pngDstDataLen = 0;
static rfbBool CanSendPngRect(rfbClientPtr cl, int w, int h) {
if (cl->tightEncoding != rfbEncodingTightPng) {
return FALSE;
}
if ( cl->screen->serverFormat.bitsPerPixel == 8 ||
cl->format.bitsPerPixel == 8) {
return FALSE;
}
return TRUE;
}
static void pngWriteData(png_structp png_ptr, png_bytep data,
png_size_t length)
{
#if 0
rfbClientPtr cl = png_get_io_ptr(png_ptr);
buffer_reserve(&vs->tight.png, vs->tight.png.offset + length);
memcpy(vs->tight.png.buffer + vs->tight.png.offset, data, length);
#endif
memcpy(tightAfterBuf + pngDstDataLen, data, length);
pngDstDataLen += length;
}
static void pngFlushData(png_structp png_ptr)
{
}
static void *pngMalloc(png_structp png_ptr, png_size_t size)
{
return malloc(size);
}
static void pngFree(png_structp png_ptr, png_voidp ptr)
{
free(ptr);
}
static rfbBool SendPngRect(rfbClientPtr cl, int x, int y, int w, int h) {
/* rfbLog(">> SendPngRect x:%d, y:%d, w:%d, h:%d\n", x, y, w, h); */
png_byte color_type;
png_structp png_ptr;
png_infop info_ptr;
png_colorp png_palette = NULL;
int level = tightPngConf[cl->tightCompressLevel].png_zlib_level;
int filters = tightPngConf[cl->tightCompressLevel].png_filters;
uint8_t *buf;
int dy;
pngDstDataLen = 0;
png_ptr = png_create_write_struct_2(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL,
NULL, pngMalloc, pngFree);
if (png_ptr == NULL)
return FALSE;
info_ptr = png_create_info_struct(png_ptr);
if (info_ptr == NULL) {
png_destroy_write_struct(&png_ptr, NULL);
return FALSE;
}
png_set_write_fn(png_ptr, (void *) cl, pngWriteData, pngFlushData);
png_set_compression_level(png_ptr, level);
png_set_filter(png_ptr, PNG_FILTER_TYPE_DEFAULT, filters);
#if 0
/* TODO: */
if (palette) {
color_type = PNG_COLOR_TYPE_PALETTE;
} else {
color_type = PNG_COLOR_TYPE_RGB;
}
#else
color_type = PNG_COLOR_TYPE_RGB;
#endif
png_set_IHDR(png_ptr, info_ptr, w, h,
8, color_type, PNG_INTERLACE_NONE,
PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
#if 0
if (color_type == PNG_COLOR_TYPE_PALETTE) {
struct palette_cb_priv priv;
png_palette = pngMalloc(png_ptr, sizeof(*png_palette) *
palette_size(palette));
priv.vs = vs;
priv.png_palette = png_palette;
palette_iter(palette, write_png_palette, &priv);
png_set_PLTE(png_ptr, info_ptr, png_palette, palette_size(palette));
offset = vs->tight.tight.offset;
if (vs->clientds.pf.bytes_per_pixel == 4) {
tight_encode_indexed_rect32(vs->tight.tight.buffer, w * h, palette);
} else {
tight_encode_indexed_rect16(vs->tight.tight.buffer, w * h, palette);
}
}
buffer_reserve(&vs->tight.png, 2048);
#endif
png_write_info(png_ptr, info_ptr);
buf = malloc(w * 3);
for (dy = 0; dy < h; dy++)
{
#if 0
if (color_type == PNG_COLOR_TYPE_PALETTE) {
memcpy(buf, vs->tight.tight.buffer + (dy * w), w);
} else {
PrepareRowForImg(cl, buf, x, y + dy, w);
}
#else
PrepareRowForImg(cl, buf, x, y + dy, w);
#endif
png_write_row(png_ptr, buf);
}
free(buf);
png_write_end(png_ptr, NULL);
if (color_type == PNG_COLOR_TYPE_PALETTE) {
pngFree(png_ptr, png_palette);
}
png_destroy_write_struct(&png_ptr, &info_ptr);
/* done v */
if (cl->ublen + TIGHT_MIN_TO_COMPRESS + 1 > UPDATE_BUF_SIZE) {
if (!rfbSendUpdateBuf(cl))
return FALSE;
}
cl->updateBuf[cl->ublen++] = (char)(rfbTightPng << 4);
rfbStatRecordEncodingSentAdd(cl, cl->tightEncoding, 1);
/* rfbLog("<< SendPngRect\n"); */
return SendCompressedData(cl, pngDstDataLen);
}
#endif