From 97001a7e7bc80b45ac7c86413afa69f2a0300e7f Mon Sep 17 00:00:00 2001 From: DRC Date: Sat, 10 Mar 2012 11:27:18 -0600 Subject: [PATCH 01/11] Add TurboVNC encoding support. TurboVNC is a variant of TightVNC that uses the same client/server protocol (RFB version 3.8t), and thus it is fully cross-compatible with TightVNC and TigerVNC (with one exception, which is noted below.) Both the TightVNC and TurboVNC encoders analyze each rectangle, pick out regions of solid color to send separately, and send the remaining subrectangles using mono, indexed color, JPEG, or raw encoding, depending on the number of colors in the subrectangle. However, TurboVNC uses a fundamentally different selection algorithm to determine the appropriate subencoding to use for each subrectangle. Thus, while it sends a protocol stream that can be decoded by any TightVNC-compatible viewer, the mix of subencoding types in this protocol stream will be different from those generated by a TightVNC server. The research that led to TurboVNC is described in the following report: http://www.virtualgl.org/pmwiki/uploads/About/tighttoturbo.pdf. In summary: 20 RFB captures, representing "common" 2D and 3D application workloads (the 3D workloads were run using VirtualGL), were studied using the TightVNC encoder in isolation. Some of the analysis features in the TightVNC encoder, such as smoothness detection, were found to generate a lot of CPU usage with little or no benefit in compression, so those features were disabled. JPEG encoding was accelerated using libjpeg-turbo (which achieves a 2-4x speedup over plain libjpeg on modern x86 or ARM processors.) Finally, the "palette threshold" (minimum number of colors that the subrectangle must have before it is compressed using JPEG or raw) was adjusted to account for the fact that JPEG encoding is now quite a bit faster (meaning that we can now use it more without a CPU penalty.) TurboVNC has additional optimizations, such as the ability to count colors and encode JPEG images directly from the framebuffer without first translating the pixels into RGB. The TurboVNC encoder compares quite favorably in terms of compression ratio with TightVNC and generally encodes a great deal faster (often an order of magnitude or more.) The version of the TurboVNC encoder included in this patch is roughly equivalent to the one found in version 0.6 of the Unix TurboVNC Server, with a few minor patches integrated from TurboVNC 1.1. TurboVNC 1.0 added multi-threading capabilities, which can be added in later if desired (at the expense of making libvncserver depend on libpthread.) Because TurboVNC uses a fundamentally different mix of subencodings than TightVNC, because it uses the identical protocol (and thus a viewer really has no idea whether it's talking to a TightVNC or TurboVNC server), and because it doesn't support rfbTightPng (and in fact conflicts with it-- see below), the TurboVNC and TightVNC encoders cannot be enabled simultaneously. Compatibility: In *most* cases, a TurboVNC-enabled viewer is fully compatible with a TightVNC server, and vice versa. TurboVNC supports pseudo-encodings for specifying a fine-grained (1-100) quality scale and specifying chrominance subsampling. If a TurboVNC viewer sends those to a TightVNC server, then the TightVNC server ignores them, so the TurboVNC viewer also sends the quality on a 0-9 scale that the TightVNC server can understand. Similarly, the TurboVNC server checks first for fine-grained quality and subsampling pseudo-encodings from the viewer, and failing to receive those, it then checks for the TightVNC 0-9 quality pseudo-encoding. There is one case in which the two systems are not compatible, and that is when a TightVNC or TigerVNC viewer requests compression level 0 without JPEG from a TurboVNC server. For performance reasons, this causes the TurboVNC server to send images directly to the viewer, bypassing Zlib. When the TurboVNC server does this, it also sets bits 7-4 in the compression control byte to rfbTightNoZlib (0x0A), which is unfortunately the same value as rfbTightPng. Older TightVNC viewers that don't handle PNG will assume that the stream is uncompressed but still encapsulated in a Zlib structure, whereas newer PNG-supporting TightVNC viewers will assume that the stream is PNG. In either case, the viewer will probably crash. Since most VNC viewers don't expose compression level 0 in the GUI, this is a relatively rare situation. Description of changes: configure.ac -- Added support for libjpeg-turbo. If passed an argument of --with-turbovnc, configure will now run (or, if cross-compiling, just link) a test program that determines whether the libjpeg library being used is libjpeg-turbo. libjpeg-turbo must be used when building the TurboVNC encoder, because the TurboVNC encoder relies on the libjpeg-turbo colorspace extensions in order to compress images directly out of the framebuffer (which may be, for instance, BGRA rather than RGB.) libjpeg-turbo can optionally be used with the TightVNC encoder as well, but the speedup will only be marginal (the report linked above explains why in more detail, but basically it's because of Amdahl's Law. The TightVNC encoder was designed with the assumption that JPEG had a very high CPU cost, and thus JPEG is used only sparingly.) -- Added a new configure variable, JPEG_LDFLAGS. This is necessitated by the fact that libjpeg-turbo often distributes libjpeg.a and libjpeg.so in /opt/libjpeg-turbo/lib32 or /opt/libjpeg-turbo/lib64, and many people prefer to statically link with it. Thus, more flexibility is needed than is provided by --with-jpeg. If JPEG_LDFLAGS is specified, then it overrides the changes to LDFLAGS enacted by --with-jpeg (but --with-jpeg is still used to set the include path.) The addition of JPEG_LDFLAGS necessitated replacing AC_CHECK_LIB with AC_LINK_IFELSE (because AC_CHECK_LIB automatically sets LIBS to -ljpeg, which is not what we want if we're, for instance, linking statically with libjpeg-turbo.) -- configure does not check for PNG support if TurboVNC encoding is enabled. This prevents the rfbSendRectEncodingTightPng() function from being compiled in, since the TurboVNC encoder doesn't (and can't) support it. common/turbojpeg.c, common/turbojpeg.h -- TurboJPEG is a simple API used to compress and decompress JPEG images in memory. It was originally implemented because it was desirable to use different types of underlying technologies to compress JPEG on different platforms (mediaLib on SPARC, Quicktime on PPC Macs, Intel Performance Primitives, etc.) These days, however, libjpeg-turbo is the only underlying technology used by TurboVNC, so TurboJPEG's purpose is largely just code simplicity and flexibility. Thus, since there is no real need for libvncserver to use any technology other than libjpeg-turbo for compressing JPEG, the TurboJPEG wrapper for libjpeg-turbo has been included in-tree so that libvncserver can be directly linked with libjpeg-turbo. This is convenient because many modern Linux distros (Fedora, Ubuntu, etc.) now ship libjpeg-turbo as their default libjpeg library. libvncserver/rfbserver.c -- Added logic to check for the TurboVNC fine-grained quality level and subsampling encodings and to map Tight (0-9) quality levels to appropriate fine-grained quality level and subsampling values if communicating with a TightVNC/TigerVNC viewer. libvncserver/turbo.c -- TurboVNC encoder (compiled instead of libvncserver/tight.c) rfb/rfb.h -- Added support for the TurboVNC subsampling level rfb/rfbproto.h -- Added constants for the TurboVNC fine quality level and subsampling encodings as well as the rfbTightNoZlib constant and notes on its usage. --- common/turbojpeg.c | 424 +++++++++++ common/turbojpeg.h | 255 +++++++ configure.ac | 74 +- libvncserver/Makefile.am | 4 + libvncserver/rfbserver.c | 38 + libvncserver/turbo.c | 1566 ++++++++++++++++++++++++++++++++++++++ rfb/rfb.h | 8 + rfb/rfbproto.h | 34 +- 8 files changed, 2395 insertions(+), 8 deletions(-) create mode 100644 common/turbojpeg.c create mode 100644 common/turbojpeg.h create mode 100644 libvncserver/turbo.c diff --git a/common/turbojpeg.c b/common/turbojpeg.c new file mode 100644 index 0000000..497ec59 --- /dev/null +++ b/common/turbojpeg.c @@ -0,0 +1,424 @@ +/* Copyright (C)2004 Landmark Graphics Corporation + * Copyright (C)2005 Sun Microsystems, Inc. + * Copyright (C)2009-2011 D. R. Commander + * + * This library is free software and may be redistributed and/or modified under + * the terms of the wxWindows Library License, Version 3.1 or (at your option) + * any later version. The full license is in the LICENSE.txt file included + * with this distribution. + * + * This library 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 + * wxWindows Library License for more details. + */ + +// This implements a JPEG compressor/decompressor using the libjpeg API + +#include +#include +#include +#include +#include +#include +#include "./turbojpeg.h" + + +#define CSTATE_START 100 +#define DSTATE_START 200 + + +// Error handling + +static char lasterror[JMSG_LENGTH_MAX]="No error"; + +typedef struct _error_mgr +{ + struct jpeg_error_mgr pub; + jmp_buf jb; +} error_mgr; + +static void my_error_exit(j_common_ptr cinfo) +{ + error_mgr *myerr = (error_mgr *)cinfo->err; + (*cinfo->err->output_message)(cinfo); + longjmp(myerr->jb, 1); +} + +static void my_output_message(j_common_ptr cinfo) +{ + (*cinfo->err->format_message)(cinfo, lasterror); +} + + +// Global structures, macros, etc. + +typedef struct _jpgstruct +{ + struct jpeg_compress_struct cinfo; + struct jpeg_decompress_struct dinfo; + struct jpeg_destination_mgr jdms; + struct jpeg_source_mgr jsms; + error_mgr jerr; + int initc, initd; +} jpgstruct; + +static const int hsampfactor[NUMSUBOPT]={1, 2, 2, 1}; +static const int vsampfactor[NUMSUBOPT]={1, 1, 2, 1}; +static const int pixelsize[NUMSUBOPT]={3, 3, 3, 1}; + +#define _throw(c) {sprintf(lasterror, "%s", c); retval=-1; goto bailout;} +#define checkhandle(h) jpgstruct *j=(jpgstruct *)h; \ + if(!j) {sprintf(lasterror, "Invalid handle"); return -1;} + + +// CO + +static boolean empty_output_buffer(struct jpeg_compress_struct *cinfo) +{ + ERREXIT(cinfo, JERR_BUFFER_SIZE); + return TRUE; +} + +static void destination_noop(struct jpeg_compress_struct *cinfo) +{ +} + +DLLEXPORT tjhandle DLLCALL tjInitCompress(void) +{ + jpgstruct *j=NULL; + if((j=(jpgstruct *)malloc(sizeof(jpgstruct)))==NULL) + {sprintf(lasterror, "Memory allocation failure"); return NULL;} + memset(j, 0, sizeof(jpgstruct)); + j->cinfo.err=jpeg_std_error(&j->jerr.pub); + j->jerr.pub.error_exit=my_error_exit; + j->jerr.pub.output_message=my_output_message; + + if(setjmp(j->jerr.jb)) + { // this will execute if LIBJPEG has an error + if(j) free(j); return NULL; + } + + jpeg_create_compress(&j->cinfo); + j->cinfo.dest=&j->jdms; + j->jdms.init_destination=destination_noop; + j->jdms.empty_output_buffer=empty_output_buffer; + j->jdms.term_destination=destination_noop; + + j->initc=1; + return (tjhandle)j; +} + + +DLLEXPORT unsigned long DLLCALL TJBUFSIZE(int width, int height) +{ + unsigned long retval=0; + if(width<1 || height<1) + _throw("Invalid argument in TJBUFSIZE()"); + + // This allows for rare corner cases in which a JPEG image can actually be + // larger than the uncompressed input (we wouldn't mention it if it hadn't + // happened before.) + retval=((width+15)&(~15)) * ((height+15)&(~15)) * 6 + 2048; + + bailout: + return retval; +} + + +DLLEXPORT int DLLCALL tjCompress(tjhandle h, + unsigned char *srcbuf, int width, int pitch, int height, int ps, + unsigned char *dstbuf, unsigned long *size, + int jpegsub, int qual, int flags) +{ + int i, retval=0; JSAMPROW *row_pointer=NULL; + + checkhandle(h); + + if(srcbuf==NULL || width<=0 || pitch<0 || height<=0 + || dstbuf==NULL || size==NULL + || jpegsub<0 || jpegsub>=NUMSUBOPT || qual<0 || qual>100) + _throw("Invalid argument in tjCompress()"); + if(ps!=3 && ps!=4 && ps!=1) + _throw("This compressor can only handle 24-bit and 32-bit RGB or 8-bit grayscale input"); + if(!j->initc) _throw("Instance has not been initialized for compression"); + + if(pitch==0) pitch=width*ps; + + j->cinfo.image_width = width; + j->cinfo.image_height = height; + j->cinfo.input_components = ps; + + if(ps==1) j->cinfo.in_color_space = JCS_GRAYSCALE; + #if JCS_EXTENSIONS==1 + else j->cinfo.in_color_space = JCS_EXT_RGB; + if(ps==3 && (flags&TJ_BGR)) + j->cinfo.in_color_space = JCS_EXT_BGR; + else if(ps==4 && !(flags&TJ_BGR) && !(flags&TJ_ALPHAFIRST)) + j->cinfo.in_color_space = JCS_EXT_RGBX; + else if(ps==4 && (flags&TJ_BGR) && !(flags&TJ_ALPHAFIRST)) + j->cinfo.in_color_space = JCS_EXT_BGRX; + else if(ps==4 && (flags&TJ_BGR) && (flags&TJ_ALPHAFIRST)) + j->cinfo.in_color_space = JCS_EXT_XBGR; + else if(ps==4 && !(flags&TJ_BGR) && (flags&TJ_ALPHAFIRST)) + j->cinfo.in_color_space = JCS_EXT_XRGB; + #else + #error "TurboJPEG requires JPEG colorspace extensions" + #endif + + if(flags&TJ_FORCEMMX) putenv("JSIMD_FORCEMMX=1"); + else if(flags&TJ_FORCESSE) putenv("JSIMD_FORCESSE=1"); + else if(flags&TJ_FORCESSE2) putenv("JSIMD_FORCESSE2=1"); + + if(setjmp(j->jerr.jb)) + { // this will execute if LIBJPEG has an error + retval=-1; + goto bailout; + } + + jpeg_set_defaults(&j->cinfo); + + jpeg_set_quality(&j->cinfo, qual, TRUE); + if(jpegsub==TJ_GRAYSCALE) + jpeg_set_colorspace(&j->cinfo, JCS_GRAYSCALE); + else + jpeg_set_colorspace(&j->cinfo, JCS_YCbCr); + if(qual>=96) j->cinfo.dct_method=JDCT_ISLOW; + else j->cinfo.dct_method=JDCT_FASTEST; + + j->cinfo.comp_info[0].h_samp_factor=hsampfactor[jpegsub]; + j->cinfo.comp_info[1].h_samp_factor=1; + j->cinfo.comp_info[2].h_samp_factor=1; + j->cinfo.comp_info[0].v_samp_factor=vsampfactor[jpegsub]; + j->cinfo.comp_info[1].v_samp_factor=1; + j->cinfo.comp_info[2].v_samp_factor=1; + + j->jdms.next_output_byte = dstbuf; + j->jdms.free_in_buffer = TJBUFSIZE(j->cinfo.image_width, j->cinfo.image_height); + + jpeg_start_compress(&j->cinfo, TRUE); + if((row_pointer=(JSAMPROW *)malloc(sizeof(JSAMPROW)*height))==NULL) + _throw("Memory allocation failed in tjCompress()"); + for(i=0; icinfo.next_scanlinecinfo.image_height) + { + jpeg_write_scanlines(&j->cinfo, &row_pointer[j->cinfo.next_scanline], + j->cinfo.image_height-j->cinfo.next_scanline); + } + jpeg_finish_compress(&j->cinfo); + *size=TJBUFSIZE(j->cinfo.image_width, j->cinfo.image_height) + -(unsigned long)(j->jdms.free_in_buffer); + + bailout: + if(j->cinfo.global_state>CSTATE_START) jpeg_abort_compress(&j->cinfo); + if(row_pointer) free(row_pointer); + return retval; +} + + +// DEC + +static boolean fill_input_buffer (struct jpeg_decompress_struct *dinfo) +{ + ERREXIT(dinfo, JERR_BUFFER_SIZE); + return TRUE; +} + +static void skip_input_data (struct jpeg_decompress_struct *dinfo, long num_bytes) +{ + dinfo->src->next_input_byte += (size_t) num_bytes; + dinfo->src->bytes_in_buffer -= (size_t) num_bytes; +} + +static void source_noop (struct jpeg_decompress_struct *dinfo) +{ +} + +DLLEXPORT tjhandle DLLCALL tjInitDecompress(void) +{ + jpgstruct *j; + if((j=(jpgstruct *)malloc(sizeof(jpgstruct)))==NULL) + {sprintf(lasterror, "Memory allocation failure"); return NULL;} + memset(j, 0, sizeof(jpgstruct)); + j->dinfo.err=jpeg_std_error(&j->jerr.pub); + j->jerr.pub.error_exit=my_error_exit; + j->jerr.pub.output_message=my_output_message; + + if(setjmp(j->jerr.jb)) + { // this will execute if LIBJPEG has an error + free(j); return NULL; + } + + jpeg_create_decompress(&j->dinfo); + j->dinfo.src=&j->jsms; + j->jsms.init_source=source_noop; + j->jsms.fill_input_buffer = fill_input_buffer; + j->jsms.skip_input_data = skip_input_data; + j->jsms.resync_to_restart = jpeg_resync_to_restart; + j->jsms.term_source = source_noop; + + j->initd=1; + return (tjhandle)j; +} + + +DLLEXPORT int DLLCALL tjDecompressHeader2(tjhandle h, + unsigned char *srcbuf, unsigned long size, + int *width, int *height, int *jpegsub) +{ + int i, k, retval=0; + + checkhandle(h); + + if(srcbuf==NULL || size<=0 || width==NULL || height==NULL || jpegsub==NULL) + _throw("Invalid argument in tjDecompressHeader2()"); + if(!j->initd) _throw("Instance has not been initialized for decompression"); + + if(setjmp(j->jerr.jb)) + { // this will execute if LIBJPEG has an error + return -1; + } + + j->jsms.bytes_in_buffer = size; + j->jsms.next_input_byte = srcbuf; + + jpeg_read_header(&j->dinfo, TRUE); + + *width=j->dinfo.image_width; *height=j->dinfo.image_height; + *jpegsub=-1; + for(i=0; idinfo.num_components==pixelsize[i]) + { + if(j->dinfo.comp_info[0].h_samp_factor==hsampfactor[i] + && j->dinfo.comp_info[0].v_samp_factor==vsampfactor[i]) + { + int match=0; + for(k=1; kdinfo.num_components; k++) + { + if(j->dinfo.comp_info[k].h_samp_factor==1 + && j->dinfo.comp_info[k].v_samp_factor==1) + match++; + } + if(match==j->dinfo.num_components-1) + { + *jpegsub=i; break; + } + } + } + } + + jpeg_abort_decompress(&j->dinfo); + + if(*jpegsub<0) _throw("Could not determine subsampling type for JPEG image"); + if(*width<1 || *height<1) _throw("Invalid data returned in header"); + + bailout: + return retval; +} + + +DLLEXPORT int DLLCALL tjDecompressHeader(tjhandle h, + unsigned char *srcbuf, unsigned long size, + int *width, int *height) +{ + int jpegsub; + return tjDecompressHeader2(h, srcbuf, size, width, height, &jpegsub); +} + + +DLLEXPORT int DLLCALL tjDecompress(tjhandle h, + unsigned char *srcbuf, unsigned long size, + unsigned char *dstbuf, int width, int pitch, int height, int ps, + int flags) +{ + int i, retval=0; JSAMPROW *row_pointer=NULL; + + checkhandle(h); + + if(srcbuf==NULL || size<=0 + || dstbuf==NULL || width<=0 || pitch<0 || height<=0) + _throw("Invalid argument in tjDecompress()"); + if(ps!=3 && ps!=4 && ps!=1) + _throw("This decompressor can only handle 24-bit and 32-bit RGB or 8-bit grayscale output"); + if(!j->initd) _throw("Instance has not been initialized for decompression"); + + if(pitch==0) pitch=width*ps; + + if(flags&TJ_FORCEMMX) putenv("JSIMD_FORCEMMX=1"); + else if(flags&TJ_FORCESSE) putenv("JSIMD_FORCESSE=1"); + else if(flags&TJ_FORCESSE2) putenv("JSIMD_FORCESSE2=1"); + + if(setjmp(j->jerr.jb)) + { // this will execute if LIBJPEG has an error + retval=-1; + goto bailout; + } + + j->jsms.bytes_in_buffer = size; + j->jsms.next_input_byte = srcbuf; + + jpeg_read_header(&j->dinfo, TRUE); + + if((row_pointer=(JSAMPROW *)malloc(sizeof(JSAMPROW)*height))==NULL) + _throw("Memory allocation failed in tjDecompress()"); + for(i=0; idinfo.out_color_space = JCS_GRAYSCALE; + #if JCS_EXTENSIONS==1 + else j->dinfo.out_color_space = JCS_EXT_RGB; + if(ps==3 && (flags&TJ_BGR)) + j->dinfo.out_color_space = JCS_EXT_BGR; + else if(ps==4 && !(flags&TJ_BGR) && !(flags&TJ_ALPHAFIRST)) + j->dinfo.out_color_space = JCS_EXT_RGBX; + else if(ps==4 && (flags&TJ_BGR) && !(flags&TJ_ALPHAFIRST)) + j->dinfo.out_color_space = JCS_EXT_BGRX; + else if(ps==4 && (flags&TJ_BGR) && (flags&TJ_ALPHAFIRST)) + j->dinfo.out_color_space = JCS_EXT_XBGR; + else if(ps==4 && !(flags&TJ_BGR) && (flags&TJ_ALPHAFIRST)) + j->dinfo.out_color_space = JCS_EXT_XRGB; + #else + #error "TurboJPEG requires JPEG colorspace extensions" + #endif + + if(flags&TJ_FASTUPSAMPLE) j->dinfo.do_fancy_upsampling=FALSE; + + jpeg_start_decompress(&j->dinfo); + while(j->dinfo.output_scanlinedinfo.output_height) + { + jpeg_read_scanlines(&j->dinfo, &row_pointer[j->dinfo.output_scanline], + j->dinfo.output_height-j->dinfo.output_scanline); + } + jpeg_finish_decompress(&j->dinfo); + + bailout: + if(j->dinfo.global_state>DSTATE_START) jpeg_abort_decompress(&j->dinfo); + if(row_pointer) free(row_pointer); + return retval; +} + + +// General + +DLLEXPORT char* DLLCALL tjGetErrorStr(void) +{ + return lasterror; +} + +DLLEXPORT int DLLCALL tjDestroy(tjhandle h) +{ + checkhandle(h); + if(setjmp(j->jerr.jb)) return -1; + if(j->initc) jpeg_destroy_compress(&j->cinfo); + if(j->initd) jpeg_destroy_decompress(&j->dinfo); + free(j); + return 0; +} diff --git a/common/turbojpeg.h b/common/turbojpeg.h new file mode 100644 index 0000000..6e3e259 --- /dev/null +++ b/common/turbojpeg.h @@ -0,0 +1,255 @@ +/* Copyright (C)2004 Landmark Graphics Corporation + * Copyright (C)2005, 2006 Sun Microsystems, Inc. + * Copyright (C)2009-2011 D. R. Commander + * + * This library is free software and may be redistributed and/or modified under + * the terms of the wxWindows Library License, Version 3.1 or (at your option) + * any later version. The full license is in the LICENSE.txt file included + * with this distribution. + * + * This library 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 + * wxWindows Library License for more details. + */ + +#if (defined(_MSC_VER) || defined(__CYGWIN__) || defined(__MINGW32__)) \ + && defined(_WIN32) && defined(DLLDEFINE) +#define DLLEXPORT __declspec(dllexport) +#else +#define DLLEXPORT +#endif + +#define DLLCALL + + +/* Subsampling */ +#define NUMSUBOPT 4 + +enum {TJ_444=0, TJ_422, TJ_420, TJ_GRAYSCALE}; +#define TJ_411 TJ_420 /* for backward compatibility with VirtualGL <= 2.1.x, + TurboVNC <= 0.6, and TurboJPEG/IPP */ + + +/* Flags */ +#define TJ_BGR 1 + /* The components of each pixel in the source/destination bitmap are stored + in B,G,R order, not R,G,B */ +#define TJ_BOTTOMUP 2 + /* The source/destination bitmap is stored in bottom-up (Windows, OpenGL) + order, not top-down (X11) order */ +#define TJ_FORCEMMX 8 + /* Turn off CPU auto-detection and force TurboJPEG to use MMX code + (IPP and 32-bit libjpeg-turbo versions only) */ +#define TJ_FORCESSE 16 + /* Turn off CPU auto-detection and force TurboJPEG to use SSE code + (32-bit IPP and 32-bit libjpeg-turbo versions only) */ +#define TJ_FORCESSE2 32 + /* Turn off CPU auto-detection and force TurboJPEG to use SSE2 code + (32-bit IPP and 32-bit libjpeg-turbo versions only) */ +#define TJ_ALPHAFIRST 64 + /* If the source/destination bitmap is 32 bpp, assume that each pixel is + ARGB/XRGB (or ABGR/XBGR if TJ_BGR is also specified) */ +#define TJ_FORCESSE3 128 + /* Turn off CPU auto-detection and force TurboJPEG to use SSE3 code + (64-bit IPP version only) */ +#define TJ_FASTUPSAMPLE 256 + /* Use fast, inaccurate 4:2:2 and 4:2:0 YUV upsampling routines + (libjpeg and libjpeg-turbo versions only) */ + + +typedef void* tjhandle; + +#define TJPAD(p) (((p)+3)&(~3)) +#ifndef max + #define max(a,b) ((a)>(b)?(a):(b)) +#endif + + +#ifdef __cplusplus +extern "C" { +#endif + +/* API follows */ + + +/* + tjhandle tjInitCompress(void) + + Creates a new JPEG compressor instance, allocates memory for the structures, + and returns a handle to the instance. Most applications will only + need to call this once at the beginning of the program or once for each + concurrent thread. Don't try to create a new instance every time you + compress an image, because this may cause performance to suffer in some + TurboJPEG implementations. + + RETURNS: NULL on error +*/ +DLLEXPORT tjhandle DLLCALL tjInitCompress(void); + + +/* + int tjCompress(tjhandle j, + unsigned char *srcbuf, int width, int pitch, int height, int pixelsize, + unsigned char *dstbuf, unsigned long *size, + int jpegsubsamp, int jpegqual, int flags) + + [INPUT] j = instance handle previously returned from a call to + tjInitCompress() + [INPUT] srcbuf = pointer to user-allocated image buffer containing RGB or + grayscale pixels to be compressed + [INPUT] width = width (in pixels) of the source image + [INPUT] pitch = bytes per line of the source image (width*pixelsize if the + bitmap is unpadded, else TJPAD(width*pixelsize) if each line of the bitmap + is padded to the nearest 32-bit boundary, such as is the case for Windows + bitmaps. You can also be clever and use this parameter to skip lines, + etc. Setting this parameter to 0 is the equivalent of setting it to + width*pixelsize. + [INPUT] height = height (in pixels) of the source image + [INPUT] pixelsize = size (in bytes) of each pixel in the source image + RGBX/BGRX/XRGB/XBGR: 4, RGB/BGR: 3, Grayscale: 1 + [INPUT] dstbuf = pointer to user-allocated image buffer that will receive + the JPEG image. Use the TJBUFSIZE(width, height) function to determine + the appropriate size for this buffer based on the image width and height. + [OUTPUT] size = pointer to unsigned long that receives the size (in bytes) + of the compressed image + [INPUT] jpegsubsamp = Specifies either 4:2:0, 4:2:2, 4:4:4, or grayscale + subsampling. When the image is converted from the RGB to YCbCr colorspace + as part of the JPEG compression process, every other Cb and Cr + (chrominance) pixel can be discarded to produce a smaller image with + little perceptible loss of image clarity (the human eye is more sensitive + to small changes in brightness than small changes in color.) + + TJ_420: 4:2:0 subsampling. Discards every other Cb, Cr pixel in both + horizontal and vertical directions + TJ_422: 4:2:2 subsampling. Discards every other Cb, Cr pixel only in + the horizontal direction + TJ_444: no subsampling + TJ_GRAYSCALE: Generate grayscale JPEG image + + [INPUT] jpegqual = JPEG quality (an integer between 0 and 100 inclusive) + [INPUT] flags = the bitwise OR of one or more of the flags described in the + "Flags" section above + + RETURNS: 0 on success, -1 on error +*/ +DLLEXPORT int DLLCALL tjCompress(tjhandle j, + unsigned char *srcbuf, int width, int pitch, int height, int pixelsize, + unsigned char *dstbuf, unsigned long *size, + int jpegsubsamp, int jpegqual, int flags); + + +/* + unsigned long TJBUFSIZE(int width, int height) + + Convenience function that returns the maximum size of the buffer required to + hold a JPEG image with the given width and height + + RETURNS: -1 if arguments are out of bounds +*/ +DLLEXPORT unsigned long DLLCALL TJBUFSIZE(int width, int height); + + +/* + tjhandle tjInitDecompress(void) + + Creates a new JPEG decompressor instance, allocates memory for the + structures, and returns a handle to the instance. Most applications will + only need to call this once at the beginning of the program or once for each + concurrent thread. Don't try to create a new instance every time you + decompress an image, because this may cause performance to suffer in some + TurboJPEG implementations. + + RETURNS: NULL on error +*/ +DLLEXPORT tjhandle DLLCALL tjInitDecompress(void); + + +/* + int tjDecompressHeader2(tjhandle j, + unsigned char *srcbuf, unsigned long size, + int *width, int *height, int *jpegsubsamp) + + [INPUT] j = instance handle previously returned from a call to + tjInitDecompress() + [INPUT] srcbuf = pointer to a user-allocated buffer containing a JPEG image + [INPUT] size = size of the JPEG image buffer (in bytes) + [OUTPUT] width = width (in pixels) of the JPEG image + [OUTPUT] height = height (in pixels) of the JPEG image + [OUTPUT] jpegsubsamp = type of chrominance subsampling used when compressing + the JPEG image + + RETURNS: 0 on success, -1 on error +*/ +DLLEXPORT int DLLCALL tjDecompressHeader2(tjhandle j, + unsigned char *srcbuf, unsigned long size, + int *width, int *height, int *jpegsubsamp); + +/* + Legacy version of the above function +*/ +DLLEXPORT int DLLCALL tjDecompressHeader(tjhandle j, + unsigned char *srcbuf, unsigned long size, + int *width, int *height); + + +/* + int tjDecompress(tjhandle j, + unsigned char *srcbuf, unsigned long size, + unsigned char *dstbuf, int width, int pitch, int height, int pixelsize, + int flags) + + [INPUT] j = instance handle previously returned from a call to + tjInitDecompress() + [INPUT] srcbuf = pointer to a user-allocated buffer containing the JPEG image + to decompress + [INPUT] size = size of the JPEG image buffer (in bytes) + [INPUT] dstbuf = pointer to user-allocated image buffer that will receive + the bitmap image. This buffer should normally be pitch*height + bytes in size, although this pointer may also be used to decompress into + a specific region of a larger buffer. + [INPUT] width = width (in pixels) of the destination image + [INPUT] pitch = bytes per line of the destination image (width*pixelsize if + the bitmap is unpadded, else TJPAD(width*pixelsize) if each line of the + bitmap is padded to the nearest 32-bit boundary, such as is the case for + Windows bitmaps. You can also be clever and use this parameter to skip + lines, etc. Setting this parameter to 0 is the equivalent of setting it + to width*pixelsize. + [INPUT] height = height (in pixels) of the destination image + [INPUT] pixelsize = size (in bytes) of each pixel in the destination image + RGBX/BGRX/XRGB/XBGR: 4, RGB/BGR: 3, Grayscale: 1 + [INPUT] flags = the bitwise OR of one or more of the flags described in the + "Flags" section above. + + RETURNS: 0 on success, -1 on error +*/ +DLLEXPORT int DLLCALL tjDecompress(tjhandle j, + unsigned char *srcbuf, unsigned long size, + unsigned char *dstbuf, int width, int pitch, int height, int pixelsize, + int flags); + + +/* + int tjDestroy(tjhandle h) + + Frees structures associated with a compression or decompression instance + + [INPUT] h = instance handle (returned from a previous call to + tjInitCompress() or tjInitDecompress() + + RETURNS: 0 on success, -1 on error +*/ +DLLEXPORT int DLLCALL tjDestroy(tjhandle h); + + +/* + char *tjGetErrorStr(void) + + Returns a descriptive error message explaining why the last command failed +*/ +DLLEXPORT char* DLLCALL tjGetErrorStr(void); + + +#ifdef __cplusplus +} +#endif diff --git a/configure.ac b/configure.ac index c5578fa..211cad6 100644 --- a/configure.ac +++ b/configure.ac @@ -528,7 +528,11 @@ AC_ARG_WITH(jpeg, # -without-jpeg with_jpeg="no" # -with-jpeg=/foo/dir with_jpeg="/foo/dir" +HAVE_LIBJPEG_TURBO="false" + if test "x$with_jpeg" != "xno"; then + AC_ARG_VAR(JPEG_LDFLAGS, + [Linker flags to use when linking with libjpeg, e.g. -L/foo/dir/lib -Wl,-static -ljpeg -Wl,-shared. This overrides the linker flags set by --with-jpeg.]) if test ! -z "$with_jpeg" -a "x$with_jpeg" != "xyes"; then # add user supplied directory to flags: saved_CPPFLAGS="$CPPFLAGS" @@ -544,9 +548,19 @@ if test "x$with_jpeg" != "xno"; then LDFLAGS="$LDFLAGS -R$with_jpeg/lib" fi fi + if test "x$JPEG_LDFLAGS" != "x"; then + LDFLAGS="$saved_LDFLAGS" + LIBS="$LIBS $JPEG_LDFLAGS" + else + LIBS="-ljpeg" + fi AC_CHECK_HEADER(jpeglib.h, HAVE_JPEGLIB_H="true") + AC_MSG_CHECKING(for jpeg_CreateCompress in libjpeg) if test "x$HAVE_JPEGLIB_H" = "xtrue"; then - AC_CHECK_LIB(jpeg, jpeg_CreateCompress, , HAVE_JPEGLIB_H="") + AC_LINK_IFELSE([AC_LANG_CALL([], [jpeg_CreateCompress])], + [AC_MSG_RESULT(yes); + AC_DEFINE(HAVE_LIBJPEG, 1, libjpeg support enabled)], + [AC_MSG_RESULT(no); HAVE_JPEGLIB_H=""]) fi if test ! -z "$with_jpeg" -a "x$with_jpeg" != "xyes"; then if test "x$HAVE_JPEGLIB_H" != "xtrue"; then @@ -563,15 +577,67 @@ if test "x$with_jpeg" != "xno"; then This may lead to reduced performance, especially over slow links. If libjpeg is in a non-standard location use --with-jpeg=DIR to indicate the header file is in DIR/include/jpeglib.h and the library -in DIR/lib/libjpeg.a. A copy of libjpeg may be obtained from: -ftp://ftp.uu.net/graphics/jpeg/ +in DIR/lib/libjpeg.a. You can also set the JPEG_LDFLAGS variable to +specify more detailed linker flags. A copy of libjpeg-turbo may be +obtained from: https://sourceforge.net/projects/libjpeg-turbo/files/ +A copy of libjpeg may be obtained from: http://ijg.org/files/ ========================================================================== ]) sleep 5 fi fi + + if test "x$HAVE_JPEGLIB_H" = "xtrue"; then + AC_MSG_CHECKING(whether JPEG library is libjpeg-turbo) + m4_define([LJT_TEST], + [AC_LANG_PROGRAM([#include + #include ], + [struct jpeg_compress_struct cinfo; + struct jpeg_error_mgr jerr; + cinfo.err=jpeg_std_error(&jerr); + jpeg_create_compress(&cinfo); + cinfo.input_components = 3; + jpeg_set_defaults(&cinfo); + cinfo.in_color_space = JCS_EXT_RGB; + jpeg_default_colorspace(&cinfo); + return 0;])] + ) + if test "x$cross_compiling" != "xyes"; then + AC_RUN_IFELSE([LJT_TEST], + [HAVE_LIBJPEG_TURBO="true"; AC_MSG_RESULT(yes)], + [AC_MSG_RESULT(no)]) + else + AC_LINK_IFELSE([LJT_TEST], + [HAVE_LIBJPEG_TURBO="true"; AC_MSG_RESULT(yes)], + [AC_MSG_RESULT(no)]) + fi + fi fi +AC_ARG_WITH(turbovnc, +[ --with-turbovnc use TurboVNC encoder instead of TightVNC encoder],,) + +AC_MSG_CHECKING(whether to enable TurboVNC encoder) +if test "x$with_turbovnc" = "xyes"; then + if test "x$HAVE_LIBJPEG_TURBO" != "xtrue"; then + AC_MSG_ERROR([ +========================================================================== +*** The TurboVNC encoder requires libjpeg-turbo, which was not detected. +You can obtain libjpeg-turbo from: +https://sourceforge.net/projects/libjpeg-turbo/files/ +Optionally, you can pass --without-turbovnc to configure to use the +TightVNC encoder instead. *** +========================================================================== +]) + fi + AC_DEFINE(HAVE_TURBOVNC, 1, TurboVNC support enabled) + AC_MSG_RESULT(yes) +else + AC_MSG_RESULT(no) +fi + +AM_CONDITIONAL(HAVE_TURBOVNC, test "x$with_turbovnc" = "xyes" ) + AC_ARG_WITH(png, [ --without-png disable support for png] [ --with-png=DIR use png include/library files in DIR],,) @@ -582,7 +648,7 @@ AC_ARG_WITH(png, # -without-png with_png="no" # -with-png=/foo/dir with_png="/foo/dir" -if test "x$with_png" != "xno"; then +if test "x$with_png" != "xno" -a "x$with_turbovnc" != "xyes"; then if test ! -z "$with_png" -a "x$with_png" != "xyes"; then # add user supplied directory to flags: saved_CPPFLAGS="$CPPFLAGS" diff --git a/libvncserver/Makefile.am b/libvncserver/Makefile.am index fce398d..6a29b84 100644 --- a/libvncserver/Makefile.am +++ b/libvncserver/Makefile.am @@ -46,9 +46,13 @@ EXTRA_DIST=tableinit24.c tableinittctemplate.c tabletranstemplate.c \ if HAVE_LIBZ ZLIBSRCS = zlib.c zrle.c zrleoutstream.c zrlepalettehelper.c ../common/zywrletemplate.c if HAVE_LIBJPEG +if HAVE_TURBOVNC +TIGHTSRCS = turbo.c ../common/turbojpeg.c +else TIGHTSRCS = tight.c endif endif +endif LIB_SRCS = main.c rfbserver.c rfbregion.c auth.c sockets.c $(WEBSOCKETSSRCS) \ stats.c corre.c hextile.c rre.c translate.c cutpaste.c \ diff --git a/libvncserver/rfbserver.c b/libvncserver/rfbserver.c index 9be255f..04231f2 100644 --- a/libvncserver/rfbserver.c +++ b/libvncserver/rfbserver.c @@ -3,6 +3,7 @@ */ /* + * Copyright (C) 2011 D. R. Commander * Copyright (C) 2005 Rohit Kumar, Johannes E. Schindelin * Copyright (C) 2002 RealVNC Ltd. * OSXvnc Copyright (C) 2001 Dan McGuirk . @@ -85,6 +86,21 @@ static int compat_mkdir(const char *path, int mode) #define mkdir compat_mkdir #endif +#ifdef LIBVNCSERVER_HAVE_TURBOVNC +/* + * Map of quality levels to provide compatibility with TightVNC/TigerVNC + * clients + */ + +static const int tight2turbo_qual[10] = { + 15, 29, 41, 42, 62, 77, 79, 86, 92, 100 +}; + +static const int tight2turbo_subsamp[10] = { + 1, 1, 1, 2, 2, 2, 0, 0, 0, 0 +}; +#endif + static void rfbProcessClientProtocolVersion(rfbClientPtr cl); static void rfbProcessClientNormalMessage(rfbClientPtr cl); static void rfbProcessClientInitMessage(rfbClientPtr cl); @@ -367,6 +383,9 @@ rfbNewTCPOrUDPClient(rfbScreenInfoPtr rfbScreen, #if defined(LIBVNCSERVER_HAVE_LIBJPEG) || defined(LIBVNCSERVER_HAVE_LIBPNG) cl->tightCompressLevel = TIGHT_DEFAULT_COMPRESSION; #endif +#ifdef LIBVNCSERVER_HAVE_TURBOVNC + cl->tightSubsampLevel = TIGHT_DEFAULT_SUBSAMP; +#endif #ifdef LIBVNCSERVER_HAVE_LIBJPEG { int i; @@ -2077,11 +2096,30 @@ rfbProcessClientNormalMessage(rfbClientPtr cl) rfbLog("Using compression level %d for client %s\n", cl->tightCompressLevel, cl->host); #endif +#ifdef LIBVNCSERVER_HAVE_TURBOVNC + } else if ( enc >= (uint32_t)rfbEncodingSubsamp1X && + enc <= (uint32_t)rfbEncodingSubsampGray ) { + cl->tightSubsampLevel = enc & 0xFF; + rfbLog("Using JPEG subsampling %d for client %s\n", + cl->tightSubsampLevel, cl->host); + } else if ( enc >= (uint32_t)rfbEncodingQualityLevel0 && + enc <= (uint32_t)rfbEncodingQualityLevel9 ) { + cl->tightQualityLevel = tight2turbo_qual[enc & 0x0F]; + cl->tightSubsampLevel = tight2turbo_subsamp[enc & 0x0F]; + rfbLog("Using JPEG subsampling %d, Q%d for client %s\n", + cl->tightSubsampLevel, cl->tightQualityLevel, cl->host); + } else if ( enc >= (uint32_t)rfbEncodingFineQualityLevel0 + 1 && + enc <= (uint32_t)rfbEncodingFineQualityLevel100 ) { + cl->tightQualityLevel = enc & 0xFF; + rfbLog("Using image quality level %d for client %s\n", + cl->tightQualityLevel, cl->host); +#else } else if ( enc >= (uint32_t)rfbEncodingQualityLevel0 && enc <= (uint32_t)rfbEncodingQualityLevel9 ) { cl->tightQualityLevel = enc & 0x0F; rfbLog("Using image quality level %d for client %s\n", cl->tightQualityLevel, cl->host); +#endif } else #endif { diff --git a/libvncserver/turbo.c b/libvncserver/turbo.c new file mode 100644 index 0000000..f205b6d --- /dev/null +++ b/libvncserver/turbo.c @@ -0,0 +1,1566 @@ +/* + * turbo.c + * + * Routines to implement TurboVNC Encoding + */ + +/* + * Copyright (C) 2010-2012 D. R. Commander. All Rights Reserved. + * Copyright (C) 2005-2008 Sun Microsystems, Inc. All Rights Reserved. + * Copyright (C) 2004 Landmark Graphics Corporation. All Rights Reserved. + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ + +#include +#include "private.h" + +#include "turbojpeg.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 + +/* + * 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; + int idxZlibLevel, monoZlibLevel, rawZlibLevel; + int idxMaxColorsDivisor; + int palMaxColorsWithJPEG; +} TIGHT_CONF; + +static TIGHT_CONF tightConf[3] = { + { 65536, 2048, 6, 0, 0, 0, 4, 24 }, + { 65536, 2048, 32, 1, 1, 1, 96, 24 }, + { 65536, 2048, 32, 3, 3, 2, 96, 96 } +}; + +static TLS int compressLevel = 1; +static TLS int qualityLevel = 95; +static TLS int subsampLevel = TJ_444; + +static const int subsampLevel2tjsubsamp[4] = { + TJ_444, TJ_420, TJ_422, TJ_GRAYSCALE +}; + + +/* 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 tjhandle j = NULL; + +void rfbTightCleanup (rfbScreenInfoPtr screen) +{ + if (tightBeforeBufSize) { + free (tightBeforeBuf); + tightBeforeBufSize = 0; + tightBeforeBuf = NULL; + } + if (tightAfterBufSize) { + free (tightAfterBuf); + tightAfterBufSize = 0; + tightAfterBuf = NULL; + } + if (j) tjDestroy(j); +} + + +/* Prototypes for static functions. */ + +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 w, int h); +static rfbBool SendIndexedRect (rfbClientPtr cl, int w, int h); +static rfbBool SendFullColorRect (rfbClientPtr cl, int w, int h); + +static rfbBool CompressData (rfbClientPtr cl, int streamId, int dataLen, + int zlibLevel, int zlibStrategy); +static rfbBool SendCompressedData (rfbClientPtr cl, char *buf, + int compressedLen); + +static void FillPalette8 (int count); +static void FillPalette16 (int count); +static void FillPalette32 (int count); +static void FastFillPalette16 (rfbClientPtr cl, uint16_t *data, int w, + int pitch, int h); +static void FastFillPalette32 (rfbClientPtr cl, uint32_t *data, int w, + int pitch, int h); + +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 rfbBool SendJpegRect (rfbClientPtr cl, int x, int y, int w, int h, + int quality); + +/* + * 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[compressLevel].maxRectSize; + maxRectWidth = tightConf[compressLevel].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) +{ + 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; + subsampLevel = cl->tightSubsampLevel; + + /* We only allow compression levels that have a demonstrable performance + benefit. CL 0 with JPEG reduces CPU usage for workloads that have low + numbers of unique colors, but the same thing can be accomplished by + using CL 0 without JPEG (AKA "Lossless Tight.") CL 2 is a mixed bag. + It can be shown to reduce bandwidth (and commensurately increase CPU + usage) by typically 30-40% relative to CL 1, but only when it is used in + conjunction with high-quality JPEG, and only on workloads that have low + numbers of unique colors. Increasing the amount of Zlib compression + beyond CL 2 cannot be shown to provide any significant bandwidth savings + except in very rare corner cases that are not performance-critical to + begin with, and higher Zlib levels increase CPU usage exponentially. */ + if (qualityLevel != -1) { + if (compressLevel < 1) compressLevel = 1; + if (compressLevel > 2) compressLevel = 2; + } + + /* With JPEG disabled, increasing the Zlib compression level beyond CL 1 + offers no significant bandwidth savings, and the CPU usage starts to + increase exponentially. */ + else if (compressLevel > 1) compressLevel = 1; + + 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)) { + + if (subsampLevel == TJ_GRAYSCALE && qualityLevel != -1) { + uint32_t r = (colorValue >> 16) & 0xFF; + uint32_t g = (colorValue >> 8) & 0xFF; + uint32_t b = (colorValue) & 0xFF; + double y = (0.257 * (double)r) + (0.504 * (double)g) + + (0.098 * (double)b) + 16.; + colorValue = (int)y + (((int)y) << 8) + (((int)y) << 16); + } + + /* 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 && + !rfbSendRectEncodingTight(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 && + !rfbSendRectEncodingTight(cl, x_best + w_best, y_best, + w - (x_best-x) - w_best, h_best) ) + return FALSE; + if ( y_best + h_best != y + h && + !rfbSendRectEncodingTight(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))); + + if (subsampLevel == TJ_GRAYSCALE && qualityLevel != -1) + return SendJpegRect(cl, x, y, w, h, qualityLevel); + + paletteMaxColors = w * h / tightConf[compressLevel].idxMaxColorsDivisor; + if(qualityLevel != -1) + paletteMaxColors = tightConf[compressLevel].palMaxColorsWithJPEG; + if ( paletteMaxColors < 2 && + w * h >= tightConf[compressLevel].monoMinRectSize ) { + paletteMaxColors = 2; + } + + if (cl->format.bitsPerPixel == cl->screen->serverFormat.bitsPerPixel && + cl->format.redMax == cl->screen->serverFormat.redMax && + cl->format.greenMax == cl->screen->serverFormat.greenMax && + cl->format.blueMax == cl->screen->serverFormat.blueMax && + cl->format.bitsPerPixel >= 16) { + + /* This is so we can avoid translating the pixels when compressing + with JPEG, since it is unnecessary */ + switch (cl->format.bitsPerPixel) { + case 16: + FastFillPalette16(cl, (uint16_t *)fbptr, w, + cl->scaledScreen->paddedWidthInBytes / 2, h); + break; + default: + FastFillPalette32(cl, (uint32_t *)fbptr, w, + cl->scaledScreen->paddedWidthInBytes / 4, h); + } + + if(paletteNumColors != 0 || qualityLevel == -1) { + (*cl->translateFn)(cl->translateLookupTable, + &cl->screen->serverFormat, &cl->format, fbptr, + tightBeforeBuf, + cl->scaledScreen->paddedWidthInBytes, w, h); + } + } + else { + (*cl->translateFn)(cl->translateLookupTable, &cl->screen->serverFormat, + &cl->format, fbptr, tightBeforeBuf, + cl->scaledScreen->paddedWidthInBytes, w, h); + + 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 (qualityLevel != -1) { + success = SendJpegRect(cl, x, y, w, h, qualityLevel); + } else { + success = SendFullColorRect(cl, w, h); + } + break; + case 1: + /* Solid rectangle */ + success = SendSolidRect(cl); + break; + case 2: + /* Two-color rectangle */ + success = SendMonoRect(cl, w, h); + break; + default: + /* Up to 256 different colors */ + success = SendIndexedRect(cl, 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(rfbEncodingTight); + + memcpy(&cl->updateBuf[cl->ublen], (char *)&rect, + sz_rfbFramebufferUpdateRectHeader); + cl->ublen += sz_rfbFramebufferUpdateRectHeader; + + rfbStatRecordEncodingSent(cl, rfbEncodingTight, + 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, rfbEncodingTight, len + 1); + + return TRUE; +} + +static rfbBool +SendMonoRect(rfbClientPtr cl, + int w, + int h) +{ + int streamId = 1; + int paletteLen, dataLen; + + 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; + + if (tightConf[compressLevel].monoZlibLevel == 0) + cl->updateBuf[cl->ublen++] = + (char)((rfbTightNoZlib | rfbTightExplicitFilter) << 4); + else + 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, rfbEncodingTight, 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, rfbEncodingTight, 7); + break; + + default: + EncodeMonoRect8((uint8_t *)tightBeforeBuf, w, h); + + cl->updateBuf[cl->ublen++] = (char)monoBackground; + cl->updateBuf[cl->ublen++] = (char)monoForeground; + rfbStatRecordEncodingSentAdd(cl, rfbEncodingTight, 5); + } + + return CompressData(cl, streamId, dataLen, + tightConf[compressLevel].monoZlibLevel, + Z_DEFAULT_STRATEGY); +} + +static rfbBool +SendIndexedRect(rfbClientPtr cl, + int w, + int h) +{ + int streamId = 2; + int i, entryLen; + + 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. */ + if (tightConf[compressLevel].idxZlibLevel == 0) + cl->updateBuf[cl->ublen++] = + (char)((rfbTightNoZlib | rfbTightExplicitFilter) << 4); + else + 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, rfbEncodingTight, + 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, rfbEncodingTight, + 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 w, + int h) +{ + int streamId = 0; + int len; + + if (cl->ublen + TIGHT_MIN_TO_COMPRESS + 1 > UPDATE_BUF_SIZE) { + if (!rfbSendUpdateBuf(cl)) + return FALSE; + } + + if (tightConf[compressLevel].rawZlibLevel == 0) + cl->updateBuf[cl->ublen++] = (char)(rfbTightNoZlib << 4); + else + cl->updateBuf[cl->ublen++] = 0x00; /* stream id = 0, no flushing, no filter */ + rfbStatRecordEncodingSentAdd(cl, rfbEncodingTight, 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 +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, rfbEncodingTight, dataLen); + return TRUE; + } + + if (zlibLevel == 0) + return SendCompressedData (cl, tightBeforeBuf, dataLen); + + 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, tightAfterBuf, + tightAfterBufSize - pz->avail_out); +} + +static rfbBool SendCompressedData(rfbClientPtr cl, char *buf, + int compressedLen) +{ + int i, portionLen; + + cl->updateBuf[cl->ublen++] = compressedLen & 0x7F; + rfbStatRecordEncodingSentAdd(cl, rfbEncodingTight, 1); + if (compressedLen > 0x7F) { + cl->updateBuf[cl->ublen-1] |= 0x80; + cl->updateBuf[cl->ublen++] = compressedLen >> 7 & 0x7F; + rfbStatRecordEncodingSentAdd(cl, rfbEncodingTight, 1); + if (compressedLen > 0x3FFF) { + cl->updateBuf[cl->ublen-1] |= 0x80; + cl->updateBuf[cl->ublen++] = compressedLen >> 14 & 0xFF; + rfbStatRecordEncodingSentAdd(cl, rfbEncodingTight, 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], &buf[i], portionLen); + cl->ublen += portionLen; + } + rfbStatRecordEncodingSentAdd(cl, rfbEncodingTight, 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) + +#define DEFINE_FAST_FILL_PALETTE_FUNCTION(bpp) \ + \ +static void \ +FastFillPalette##bpp(rfbClientPtr cl, uint##bpp##_t *data, int w, \ + int pitch, int h) \ +{ \ + uint##bpp##_t c0, c1, ci, mask, c0t, c1t, cit; \ + int i, j, i2 = 0, j2, n0, n1, ni; \ + \ + if (cl->translateFn != rfbTranslateNone) { \ + mask = cl->screen->serverFormat.redMax \ + << cl->screen->serverFormat.redShift; \ + mask |= cl->screen->serverFormat.greenMax \ + << cl->screen->serverFormat.greenShift; \ + mask |= cl->screen->serverFormat.blueMax \ + << cl->screen->serverFormat.blueShift; \ + } else mask = ~0; \ + \ + c0 = data[0] & mask; \ + for (j = 0; j < h; j++) { \ + for (i = 0; i < w; i++) { \ + if ((data[j * pitch + i] & mask) != c0) \ + goto done; \ + } \ + } \ + done: \ + if (j >= h) { \ + paletteNumColors = 1; /* Solid rectangle */ \ + return; \ + } \ + if (paletteMaxColors < 2) { \ + paletteNumColors = 0; /* Full-color encoding preferred */ \ + return; \ + } \ + \ + n0 = j * w + i; \ + c1 = data[j * pitch + i] & mask; \ + n1 = 0; \ + i++; if (i >= w) {i = 0; j++;} \ + for (j2 = j; j2 < h; j2++) { \ + for (i2 = i; i2 < w; i2++) { \ + ci = data[j2 * pitch + i2] & mask; \ + if (ci == c0) { \ + n0++; \ + } else if (ci == c1) { \ + n1++; \ + } else \ + goto done2; \ + } \ + i = 0; \ + } \ + done2: \ + (*cl->translateFn)(cl->translateLookupTable, \ + &cl->screen->serverFormat, &cl->format, \ + (char *)&c0, (char *)&c0t, bpp/8, 1, 1); \ + (*cl->translateFn)(cl->translateLookupTable, \ + &cl->screen->serverFormat, &cl->format, \ + (char *)&c1, (char *)&c1t, bpp/8, 1, 1); \ + if (j2 >= h) { \ + if (n0 > n1) { \ + monoBackground = (uint32_t)c0t; \ + monoForeground = (uint32_t)c1t; \ + } else { \ + monoBackground = (uint32_t)c1t; \ + monoForeground = (uint32_t)c0t; \ + } \ + paletteNumColors = 2; /* Two colors */ \ + return; \ + } \ + \ + PaletteReset(); \ + PaletteInsert (c0t, (uint32_t)n0, bpp); \ + PaletteInsert (c1t, (uint32_t)n1, bpp); \ + \ + ni = 1; \ + i2++; if (i2 >= w) {i2 = 0; j2++;} \ + for (j = j2; j < h; j++) { \ + for (i = i2; i < w; i++) { \ + if ((data[j * pitch + i] & mask) == ci) { \ + ni++; \ + } else { \ + (*cl->translateFn)(cl->translateLookupTable, \ + &cl->screen->serverFormat, \ + &cl->format, (char *)&ci, \ + (char *)&cit, bpp/8, 1, 1); \ + if (!PaletteInsert (cit, (uint32_t)ni, bpp)) \ + return; \ + ci = data[j * pitch + i] & mask; \ + ni = 1; \ + } \ + } \ + i2 = 0; \ + } \ + \ + (*cl->translateFn)(cl->translateLookupTable, \ + &cl->screen->serverFormat, &cl->format, \ + (char *)&ci, (char *)&cit, bpp/8, 1, 1); \ + PaletteInsert (cit, (uint32_t)ni, bpp); \ +} + +DEFINE_FAST_FILL_PALETTE_FUNCTION(16) +DEFINE_FAST_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) + + +/* + * JPEG compression stuff. + */ + +static rfbBool +SendJpegRect(rfbClientPtr cl, int x, int y, int w, int h, int quality) +{ + unsigned char *srcbuf; + int ps = cl->screen->serverFormat.bitsPerPixel / 8; + int subsamp = subsampLevel2tjsubsamp[subsampLevel]; + unsigned long size = 0; + int flags = 0, pitch; + unsigned char *tmpbuf = NULL; + + if (cl->screen->serverFormat.bitsPerPixel == 8) + return SendFullColorRect(cl, w, h); + + if (ps < 2) { + rfbLog("Error: JPEG requires 16-bit, 24-bit, or 32-bit pixel format.\n"); + return 0; + } + if (!j) { + if ((j = tjInitCompress()) == NULL) { + rfbLog("JPEG Error: %s\n", tjGetErrorStr()); + return 0; + } + } + + if (tightAfterBufSize < TJBUFSIZE(w, h)) { + if (tightAfterBuf == NULL) + tightAfterBuf = (char *)malloc(TJBUFSIZE(w, h)); + else + tightAfterBuf = (char *)realloc(tightAfterBuf, + TJBUFSIZE(w, h)); + if (!tightAfterBuf) { + rfbLog("Memory allocation failure!\n"); + return 0; + } + tightAfterBufSize = TJBUFSIZE(w, h); + } + + if (ps == 2) { + uint16_t *srcptr, pix; + unsigned char *dst; + int inRed, inGreen, inBlue, i, j; + + if((tmpbuf = (unsigned char *)malloc(w * h * 3)) == NULL) + rfbLog("Memory allocation failure!\n"); + srcptr = (uint16_t *)&cl->scaledScreen->frameBuffer + [y * cl->scaledScreen->paddedWidthInBytes + x * ps]; + dst = tmpbuf; + for(j = 0; j < h; j++) { + uint16_t *srcptr2 = srcptr; + unsigned char *dst2 = dst; + for (i = 0; i < w; i++) { + pix = *srcptr2++; + 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); + *dst2++ = (uint8_t)((inRed * 255 + + cl->screen->serverFormat.redMax / 2) + / cl->screen->serverFormat.redMax); + *dst2++ = (uint8_t)((inGreen * 255 + + cl->screen->serverFormat.greenMax / 2) + / cl->screen->serverFormat.greenMax); + *dst2++ = (uint8_t)((inBlue * 255 + + cl->screen->serverFormat.blueMax / 2) + / cl->screen->serverFormat.blueMax); + } + srcptr += cl->scaledScreen->paddedWidthInBytes / ps; + dst += w * 3; + } + srcbuf = tmpbuf; + pitch = w * 3; + ps = 3; + } else { + if (cl->screen->serverFormat.bigEndian && ps == 4) + flags |= TJ_ALPHAFIRST; + if (cl->screen->serverFormat.redShift == 16 + && cl->screen->serverFormat.blueShift == 0) + flags |= TJ_BGR; + if (cl->screen->serverFormat.bigEndian) + flags ^= TJ_BGR; + pitch = cl->scaledScreen->paddedWidthInBytes; + srcbuf = (unsigned char *)&cl->scaledScreen->frameBuffer + [y * pitch + x * ps]; + } + + if (tjCompress(j, srcbuf, w, pitch, h, ps, (unsigned char *)tightAfterBuf, + &size, subsamp, quality, flags) == -1) { + rfbLog("JPEG Error: %s\n", tjGetErrorStr()); + if (tmpbuf) { + free(tmpbuf); + tmpbuf = NULL; + } + return 0; + } + + if (tmpbuf) { + free(tmpbuf); + tmpbuf = NULL; + } + + 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, rfbEncodingTight, 1); + + return SendCompressedData(cl, tightAfterBuf, (int)size); +} diff --git a/rfb/rfb.h b/rfb/rfb.h index e068e76..0d11357 100644 --- a/rfb/rfb.h +++ b/rfb/rfb.h @@ -599,6 +599,10 @@ typedef struct _rfbClientRec { #if defined(LIBVNCSERVER_HAVE_LIBJPEG) || defined(LIBVNCSERVER_HAVE_LIBPNG) int tightCompressLevel; #endif +#ifdef LIBVNCSERVER_HAVE_TURBOVNC + /* TurboVNC Encoding support (extends TightVNC) */ + int tightSubsampLevel; +#endif #endif /* Ultra Encoding support */ @@ -871,6 +875,10 @@ extern rfbBool rfbSendRectEncodingZlib(rfbClientPtr cl, int x, int y, int w, #define TIGHT_DEFAULT_COMPRESSION 6 +#ifdef LIBVNCSERVER_HAVE_TURBOVNC +#define TIGHT_DEFAULT_SUBSAMP 0 +#endif + extern rfbBool rfbTightDisableGradient; extern int rfbNumCodedRectsTight(rfbClientPtr cl, int x,int y,int w,int h); diff --git a/rfb/rfbproto.h b/rfb/rfbproto.h index c6dfd2c..d99fbe5 100644 --- a/rfb/rfbproto.h +++ b/rfb/rfbproto.h @@ -13,7 +13,9 @@ */ /* + * Copyright (C) 2009-2010 D. R. Commander. All Rights Reserved. * Copyright (C) 2005 Rohit Kumar, Johannes E. Schindelin + * Copyright (C) 2004-2008 Sun Microsystems, Inc. All Rights Reserved. * Copyright (C) 2000-2002 Constantin Kaplinsky. All Rights Reserved. * Copyright (C) 2000 Tridia Corporation. All Rights Reserved. * Copyright (C) 1999 AT&T Laboratories Cambridge. All Rights Reserved. @@ -457,6 +459,8 @@ typedef struct { /* * Special encoding numbers: + * 0xFFFFFD00 .. 0xFFFFFD05 -- subsampling level + * 0xFFFFFE00 .. 0xFFFFFE64 -- fine-grained quality level (0-100 scale) * 0xFFFFFF00 .. 0xFFFFFF0F -- encoding-specific compression levels; * 0xFFFFFF10 .. 0xFFFFFF1F -- mouse cursor shape data; * 0xFFFFFF20 .. 0xFFFFFF2F -- various protocol extensions; @@ -465,6 +469,17 @@ typedef struct { * 0xFFFFFFF0 .. 0xFFFFFFFF -- cross-encoding compression levels. */ +#ifdef LIBVNCSERVER_HAVE_TURBOVNC +#define rfbEncodingFineQualityLevel0 0xFFFFFE00 +#define rfbEncodingFineQualityLevel100 0xFFFFFE64 +#define rfbEncodingSubsamp1X 0xFFFFFD00 +#define rfbEncodingSubsamp4X 0xFFFFFD01 +#define rfbEncodingSubsamp2X 0xFFFFFD02 +#define rfbEncodingSubsampGray 0xFFFFFD03 +#define rfbEncodingSubsamp8X 0xFFFFFD04 +#define rfbEncodingSubsamp16X 0xFFFFFD05 +#endif + #define rfbEncodingCompressLevel0 0xFFFFFF00 #define rfbEncodingCompressLevel1 0xFFFFFF01 #define rfbEncodingCompressLevel2 0xFFFFFF02 @@ -719,12 +734,19 @@ typedef struct { * bit 3: if 1, then compression stream 3 should be reset; * bits 7-4: if 1000 (0x08), then the compression type is "fill", * if 1001 (0x09), then the compression type is "jpeg", - * if 1001 (0x0A), then the compression type is "png", - * if 0xxx, then the compression type is "basic", + * (TurboVNC) if 1010 (0x0A), then the compression type is "basic" + * and no Zlib compression was used, + * (TurboVNC) if 1110 (0x0E), then the compression type is "basic", + * no Zlib compression was used, and a "filter id" byte follows + * this byte, + * (TightVNC) if 1010 (0x0A), then the compression type is "png", + * if 0xxx, then the compression type is "basic" and Zlib + * compression was used, * values greater than 1010 are not valid. * - * If the compression type is "basic", then bits 6..4 of the - * compression control byte (those xxx in 0xxx) specify the following: + * If the compression type is "basic" and Zlib compression was used, then bits + * 6..4 of the compression control byte (those xxx in 0xxx) specify the + * following: * * bits 5-4: decimal representation is the index of a particular zlib * stream which should be used for decompressing the data; @@ -836,7 +858,11 @@ typedef struct { #define rfbTightExplicitFilter 0x04 #define rfbTightFill 0x08 #define rfbTightJpeg 0x09 +#ifdef LIBVNCSERVER_HAVE_TURBOVNC +#define rfbTightNoZlib 0x0A +#else #define rfbTightPng 0x0A +#endif #define rfbTightMaxSubencoding 0x0A /* Filters to improve compression efficiency */ From 503dd6bb6960ef0e35d647c6a51f013f7b62cb45 Mon Sep 17 00:00:00 2001 From: DRC Date: Sat, 10 Mar 2012 11:35:51 -0600 Subject: [PATCH 02/11] Fix an issue that affects the existing Tight encoder as well as the newly-implemented Turbo encoder. The issue is that, when using the current libvncserver source, it is impossible to disable Tight JPEG encoding. The way Tight/Turbo viewers disable JPEG encoding is by simply not sending the Tight quality value, causing the server to use the default value of -1. Thus, cl->tightQualityLevel has to be set to -1 prior to processing the encodings message for this mechanism to work. Similarly, it is not guaranteed that the compress level will be set in the encodings message, so it is set to a default value prior to processing the message. --- libvncserver/rfbserver.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/libvncserver/rfbserver.c b/libvncserver/rfbserver.c index 04231f2..3f4b896 100644 --- a/libvncserver/rfbserver.c +++ b/libvncserver/rfbserver.c @@ -1967,6 +1967,15 @@ rfbProcessClientNormalMessage(rfbClientPtr cl) cl->enableSupportedMessages = FALSE; cl->enableSupportedEncodings = FALSE; cl->enableServerIdentity = FALSE; +#if defined(LIBVNCSERVER_HAVE_LIBZ) || defined(LIBVNCSERVER_HAVE_LIBPNG) + cl->tightQualityLevel = -1; +#if defined(LIBVNCSERVER_HAVE_LIBJPEG) || defined(LIBVNCSERVER_HAVE_TURBOVNC) || defined(LIBVNCSERVER_HAVE_LIBPNG) + cl->tightCompressLevel = TIGHT_DEFAULT_COMPRESSION; +#endif +#ifdef LIBVNCSERVER_HAVE_TURBOVNC + cl->tightSubsampLevel = TIGHT_DEFAULT_SUBSAMP; +#endif +#endif for (i = 0; i < msg.se.nEncodings; i++) { From 2ebf2d06f3c3b57d8882bc96b907600887590d69 Mon Sep 17 00:00:00 2001 From: DRC Date: Sat, 10 Mar 2012 11:38:07 -0600 Subject: [PATCH 03/11] Fix the build of x11vnc when an out-of-tree build directory is used --- x11vnc/Makefile.am | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x11vnc/Makefile.am b/x11vnc/Makefile.am index cef82f3..12ca9f1 100644 --- a/x11vnc/Makefile.am +++ b/x11vnc/Makefile.am @@ -36,7 +36,7 @@ x11vnc_SOURCES = 8to24.c appshare.c avahi.c cleanup.c connections.c cursor.c gui if HAVE_SYSTEM_LIBVNCSERVER INCLUDES_LIBVNCSERVER = @SYSTEM_LIBVNCSERVER_CFLAGS@ else -INCLUDES_LIBVNCSERVER = +INCLUDES_LIBVNCSERVER = -I${top_srcdir} endif INCLUDES = $(INCLUDES_LIBVNCSERVER) @X_CFLAGS@ @AVAHI_CFLAGS@ From 4c7e185a971bbc93ed4469c0442dfaeeda513982 Mon Sep 17 00:00:00 2001 From: Christian Beier Date: Thu, 15 Mar 2012 19:46:57 +0100 Subject: [PATCH 04/11] Move tightsubsamplevel member to the end of rfbClient struct. Try to not break ABI between releases. Even if the code gets ugly... --- rfb/rfb.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/rfb/rfb.h b/rfb/rfb.h index 0d11357..91dea83 100644 --- a/rfb/rfb.h +++ b/rfb/rfb.h @@ -599,10 +599,6 @@ typedef struct _rfbClientRec { #if defined(LIBVNCSERVER_HAVE_LIBJPEG) || defined(LIBVNCSERVER_HAVE_LIBPNG) int tightCompressLevel; #endif -#ifdef LIBVNCSERVER_HAVE_TURBOVNC - /* TurboVNC Encoding support (extends TightVNC) */ - int tightSubsampLevel; -#endif #endif /* Ultra Encoding support */ @@ -677,6 +673,10 @@ typedef struct _rfbClientRec { int afterEncBufLen; #if defined(LIBVNCSERVER_HAVE_LIBZ) || defined(LIBVNCSERVER_HAVE_LIBPNG) uint32_t tightEncoding; /* rfbEncodingTight or rfbEncodingTightPng */ +#ifdef LIBVNCSERVER_HAVE_TURBOVNC + /* TurboVNC Encoding support (extends TightVNC) */ + int tightSubsampLevel; +#endif #endif #ifdef LIBVNCSERVER_WITH_WEBSOCKETS From 5f2794f31b8744b4ae73b726d59580175ea55146 Mon Sep 17 00:00:00 2001 From: Christian Beier Date: Thu, 15 Mar 2012 20:06:41 +0100 Subject: [PATCH 05/11] Add DRC to AUTHORS. --- AUTHORS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AUTHORS b/AUTHORS index 4f2a1fc..dc3e8b3 100644 --- a/AUTHORS +++ b/AUTHORS @@ -35,7 +35,7 @@ Noriaki Yamazaki, Ben Klopfenstein, Vic Lee, Christian Beier, Alexander Dorokhine, Corentin Chary, Wouter Van Meir, George Kiagiadakis, Joel Martin, Gernot Tenchio, William Roberts, Cristian Rodríguez, George Fleury, Kan-Ru Chen, Steve Guo, Luca Stauble, Peter Watkins, -Kyle J. McKay and Mateus Cesar Groess. +Kyle J. McKay, Mateus Cesar Groess and D. R. Commander. Probably I forgot quite a few people sending a patch here and there, which really made a difference. Without those, some obscure bugs still would From 7124b5fbcf0df8db4d3f73023d77af6ea56409e7 Mon Sep 17 00:00:00 2001 From: DRC Date: Sun, 25 Mar 2012 11:34:22 -0500 Subject: [PATCH 06/11] Replace TightVNC encoder with TurboVNC encoder. This patch is the result of further research and discussion that revealed the following: -- TightPng encoding and the rfbTightNoZlib extension need not conflict. Since TightPng is a separate encoding type, not supported by TurboVNC-compatible viewers, then the rfbTightNoZlib extension can be used solely whenever the encoding type is Tight and disabled with the encoding type is TightPng. -- In the TightVNC encoder, compression levels above 5 are basically useless. On the set of 20 low-level datasets that were used to design the TurboVNC encoder (these include the eight 2D application captures that were also used when designing the TightVNC encoder, as well as 12 3D application captures provided by the VirtualGL Project-- see http://www.virtualgl.org/pmwiki/uploads/About/tighttoturbo.pdf), moving from Compression Level (CL) 5 to CL 9 in the TightVNC encoder did not increase the compression ratio of any datasets more than 10%, and the compression ratio only increased by more than 5% on four of them. The compression ratio actually decreased a few percent on five of them. In exchange for this paltry increase in compression ratio, the CPU usage, on average, went up by a factor of 5. Thus, for all intents and purposes, TightVNC CL 5 provides the "best useful compression" for that encoder. -- TurboVNC's best compression level (CL 2) compresses 3D and video workloads significantly more "tightly" than TightVNC CL 5 (~70% better, in the aggregate) but does not quite achieve the same level of compression with 2D workloads (~20% worse, in the aggregate.) This decrease in compression ratio may or may not be noticeable, since many of the datasets it affects are not performance-critical (such as the console output of a compilation, etc.) However, for peace of mind, it was still desirable to have a mode that compressed with equal "tightness" to TightVNC CL 5, since we proposed to replace that encoder entirely. -- A new mode was discovered in the TurboVNC encoder that produces, in the aggregate, similar compression ratios on 2D datasets as TightVNC CL 5. That new mode involves using Zlib level 7 (the same level used by TightVNC CL 5) but setting the "palette threshold" to 256, so that indexed color encoding is used whenever possible. This mode reduces bandwidth only marginally (typically 10-20%) relative to TurboVNC CL 2 on low-color workloads, in exchange for nearly doubling CPU usage, and it does not benefit high-color workloads at all (since those are usually encoded with JPEG.) However, it provides a means of reproducing the same "tightness" as the TightVNC encoder on 2D workloads without sacrificing any compression for 3D/video workloads, and without using any more CPU time than necessary. -- The TurboVNC encoder still performs as well or better than the TightVNC encoder when plain libjpeg is used instead of libjpeg-turbo. Specific notes follow: common/turbojpeg.c common/turbojpeg.h: Added code to emulate the libjpeg-turbo colorspace extensions, so that the TurboJPEG wrapper can be used with plain libjpeg as well. This required updating the TurboJPEG wrapper to the latest code from libjpeg-turbo 1.2.0, mainly because the TurboJPEG 1.2 API handles pixel formats in a much cleaner way, which made the conversion code easier to write. It also eases the maintenance to have the wrapper synced as much as possible with the upstream code base (so I can merge any relevant bug fixes that are discovered upstream.) The libvncserver version of the TurboJPEG wrapper is a "lite" version, containing only the JPEG compression/decompression code and not the lossless transform, YUV encoding/decoding, and dynamic buffer allocation features from TurboJPEG 1.2. configure.ac: Removed the --with-turbovnc option. configure still checks for the presence of libjpeg-turbo, but only for the purposes of printing a performance warning if it isn't available. rfb/rfb.h: Fix a bug introduced with the initial TurboVNC encoder patch. We cannot use tightQualityLevel for the TurboVNC 1-100 quality level, because tightQualityLevel is also used by ZRLE. Thus, a new parameter (turboQualityLevel) was created. rfb/rfbproto.h: Remove TurboVNC-specific #ifdefs and language libvncserver/rfbserver.c: Remove TurboVNC-specific #ifdefs. Fix afore-mentioned tightQualityLevel bug. libvncserver/tight.c: Replaced the TightVNC encoder with the TurboVNC encoder. Relative to the initial TurboVNC encoder patch, this patch also: -- Adds TightPng support to the TurboVNC encoder -- Adds the afore-mentioned low-bandwidth mode, which is mapped externally to Compression Level 9 test/*: Included TJUnitTest (a regression test for the TurboJPEG wrapper) as well as TJBench (a benchmark for same.) These are useful for ensuring that the wrapper still functions correctly and performantly if it needs to be modified for whatever reason. Both of these programs are derived from libjpeg-turbo 1.2.0. As with the TurboJPEG wrapper, they do not contain the more advanced features of TurboJPEG 1.2, such as YUV encoding/decoding and lossless transforms. --- common/turbojpeg.c | 944 ++++++++++++++++------- common/turbojpeg.h | 680 ++++++++++++----- configure.ac | 27 +- libvncserver/Makefile.am | 6 +- libvncserver/rfbserver.c | 50 +- libvncserver/tight.c | 999 ++++++++++-------------- libvncserver/turbo.c | 1566 -------------------------------------- rfb/rfb.h | 9 +- rfb/rfbproto.h | 18 +- test/Makefile.am | 11 + test/bmp.c | 389 ++++++++++ test/bmp.h | 49 ++ test/tjbench.c | 658 ++++++++++++++++ test/tjunittest.c | 461 +++++++++++ test/tjutil.c | 66 ++ test/tjutil.h | 47 ++ 16 files changed, 3302 insertions(+), 2678 deletions(-) delete mode 100644 libvncserver/turbo.c create mode 100644 test/bmp.c create mode 100644 test/bmp.h create mode 100644 test/tjbench.c create mode 100644 test/tjunittest.c create mode 100644 test/tjutil.c create mode 100644 test/tjutil.h diff --git a/common/turbojpeg.c b/common/turbojpeg.c index 497ec59..c145338 100644 --- a/common/turbojpeg.c +++ b/common/turbojpeg.c @@ -1,112 +1,517 @@ -/* Copyright (C)2004 Landmark Graphics Corporation - * Copyright (C)2005 Sun Microsystems, Inc. - * Copyright (C)2009-2011 D. R. Commander +/* + * Copyright (C)2009-2012 D. R. Commander. All Rights Reserved. * - * This library is free software and may be redistributed and/or modified under - * the terms of the wxWindows Library License, Version 3.1 or (at your option) - * any later version. The full license is in the LICENSE.txt file included - * with this distribution. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: * - * This library 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 - * wxWindows Library License for more details. + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of the libjpeg-turbo Project nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS", + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. */ -// This implements a JPEG compressor/decompressor using the libjpeg API +/* TurboJPEG/OSS: this implements the TurboJPEG API using libjpeg-turbo */ #include #include #include #include #include +#ifndef JCS_EXTENSIONS +#define JPEG_INTERNALS +#include +#endif #include #include "./turbojpeg.h" +#define PAD(v, p) ((v+(p)-1)&(~((p)-1))) #define CSTATE_START 100 #define DSTATE_START 200 +#define MEMZERO(ptr, size) memset(ptr, 0, size) + +#ifndef min + #define min(a,b) ((a)<(b)?(a):(b)) +#endif + +#ifndef max + #define max(a,b) ((a)>(b)?(a):(b)) +#endif -// Error handling +/* Error handling (based on example in example.c) */ -static char lasterror[JMSG_LENGTH_MAX]="No error"; +static char errStr[JMSG_LENGTH_MAX]="No error"; -typedef struct _error_mgr +struct my_error_mgr { struct jpeg_error_mgr pub; - jmp_buf jb; -} error_mgr; + jmp_buf setjmp_buffer; +}; +typedef struct my_error_mgr *my_error_ptr; static void my_error_exit(j_common_ptr cinfo) { - error_mgr *myerr = (error_mgr *)cinfo->err; + my_error_ptr myerr=(my_error_ptr)cinfo->err; (*cinfo->err->output_message)(cinfo); - longjmp(myerr->jb, 1); + longjmp(myerr->setjmp_buffer, 1); } +/* Based on output_message() in jerror.c */ + static void my_output_message(j_common_ptr cinfo) { - (*cinfo->err->format_message)(cinfo, lasterror); + (*cinfo->err->format_message)(cinfo, errStr); } -// Global structures, macros, etc. +/* Global structures, macros, etc. */ + +enum {COMPRESS=1, DECOMPRESS=2}; -typedef struct _jpgstruct +typedef struct _tjinstance { struct jpeg_compress_struct cinfo; struct jpeg_decompress_struct dinfo; - struct jpeg_destination_mgr jdms; - struct jpeg_source_mgr jsms; - error_mgr jerr; - int initc, initd; -} jpgstruct; + struct jpeg_destination_mgr jdst; + struct jpeg_source_mgr jsrc; + struct my_error_mgr jerr; + int init; +} tjinstance; + +static const int pixelsize[TJ_NUMSAMP]={3, 3, 3, 1, 3}; + +#define NUMSF 4 +static const tjscalingfactor sf[NUMSF]={ + {1, 1}, + {1, 2}, + {1, 4}, + {1, 8} +}; + +#define _throw(m) {snprintf(errStr, JMSG_LENGTH_MAX, "%s", m); \ + retval=-1; goto bailout;} +#define getinstance(handle) tjinstance *this=(tjinstance *)handle; \ + j_compress_ptr cinfo=NULL; j_decompress_ptr dinfo=NULL; \ + if(!this) {snprintf(errStr, JMSG_LENGTH_MAX, "Invalid handle"); \ + return -1;} \ + cinfo=&this->cinfo; dinfo=&this->dinfo; + +static int getPixelFormat(int pixelSize, int flags) +{ + if(pixelSize==1) return TJPF_GRAY; + if(pixelSize==3) + { + if(flags&TJ_BGR) return TJPF_BGR; + else return TJPF_RGB; + } + if(pixelSize==4) + { + if(flags&TJ_ALPHAFIRST) + { + if(flags&TJ_BGR) return TJPF_XBGR; + else return TJPF_XRGB; + } + else + { + if(flags&TJ_BGR) return TJPF_BGRX; + else return TJPF_RGBX; + } + } + return -1; +} + +static int setCompDefaults(struct jpeg_compress_struct *cinfo, + int pixelFormat, int subsamp, int jpegQual) +{ + int retval=0; + + switch(pixelFormat) + { + case TJPF_GRAY: + cinfo->in_color_space=JCS_GRAYSCALE; break; + #if JCS_EXTENSIONS==1 + case TJPF_RGB: + cinfo->in_color_space=JCS_EXT_RGB; break; + case TJPF_BGR: + cinfo->in_color_space=JCS_EXT_BGR; break; + case TJPF_RGBX: + case TJPF_RGBA: + cinfo->in_color_space=JCS_EXT_RGBX; break; + case TJPF_BGRX: + case TJPF_BGRA: + cinfo->in_color_space=JCS_EXT_BGRX; break; + case TJPF_XRGB: + case TJPF_ARGB: + cinfo->in_color_space=JCS_EXT_XRGB; break; + case TJPF_XBGR: + case TJPF_ABGR: + cinfo->in_color_space=JCS_EXT_XBGR; break; + #else + case TJPF_RGB: + case TJPF_BGR: + case TJPF_RGBX: + case TJPF_BGRX: + case TJPF_XRGB: + case TJPF_XBGR: + case TJPF_RGBA: + case TJPF_BGRA: + case TJPF_ARGB: + case TJPF_ABGR: + cinfo->in_color_space=JCS_RGB; pixelFormat=TJPF_RGB; + break; + #endif + } + + cinfo->input_components=tjPixelSize[pixelFormat]; + jpeg_set_defaults(cinfo); + if(jpegQual>=0) + { + jpeg_set_quality(cinfo, jpegQual, TRUE); + if(jpegQual>=96) cinfo->dct_method=JDCT_ISLOW; + else cinfo->dct_method=JDCT_FASTEST; + } + if(subsamp==TJSAMP_GRAY) + jpeg_set_colorspace(cinfo, JCS_GRAYSCALE); + else + jpeg_set_colorspace(cinfo, JCS_YCbCr); + + cinfo->comp_info[0].h_samp_factor=tjMCUWidth[subsamp]/8; + cinfo->comp_info[1].h_samp_factor=1; + cinfo->comp_info[2].h_samp_factor=1; + cinfo->comp_info[0].v_samp_factor=tjMCUHeight[subsamp]/8; + cinfo->comp_info[1].v_samp_factor=1; + cinfo->comp_info[2].v_samp_factor=1; + + return retval; +} + +static int setDecompDefaults(struct jpeg_decompress_struct *dinfo, + int pixelFormat) +{ + int retval=0; + + switch(pixelFormat) + { + case TJPF_GRAY: + dinfo->out_color_space=JCS_GRAYSCALE; break; + #if JCS_EXTENSIONS==1 + case TJPF_RGB: + dinfo->out_color_space=JCS_EXT_RGB; break; + case TJPF_BGR: + dinfo->out_color_space=JCS_EXT_BGR; break; + case TJPF_RGBX: + dinfo->out_color_space=JCS_EXT_RGBX; break; + case TJPF_BGRX: + dinfo->out_color_space=JCS_EXT_BGRX; break; + case TJPF_XRGB: + dinfo->out_color_space=JCS_EXT_XRGB; break; + case TJPF_XBGR: + dinfo->out_color_space=JCS_EXT_XBGR; break; + #if JCS_ALPHA_EXTENSIONS==1 + case TJPF_RGBA: + dinfo->out_color_space=JCS_EXT_RGBA; break; + case TJPF_BGRA: + dinfo->out_color_space=JCS_EXT_BGRA; break; + case TJPF_ARGB: + dinfo->out_color_space=JCS_EXT_ARGB; break; + case TJPF_ABGR: + dinfo->out_color_space=JCS_EXT_ABGR; break; + #endif + #else + case TJPF_RGB: + case TJPF_BGR: + case TJPF_RGBX: + case TJPF_BGRX: + case TJPF_XRGB: + case TJPF_XBGR: + case TJPF_RGBA: + case TJPF_BGRA: + case TJPF_ARGB: + case TJPF_ABGR: + dinfo->out_color_space=JCS_RGB; break; + #endif + default: + _throw("Unsupported pixel format"); + } + + bailout: + return retval; +} + + +static int getSubsamp(j_decompress_ptr dinfo) +{ + int retval=-1, i, k; + for(i=0; inum_components==pixelsize[i]) + { + if(dinfo->comp_info[0].h_samp_factor==tjMCUWidth[i]/8 + && dinfo->comp_info[0].v_samp_factor==tjMCUHeight[i]/8) + { + int match=0; + for(k=1; knum_components; k++) + { + if(dinfo->comp_info[k].h_samp_factor==1 + && dinfo->comp_info[k].v_samp_factor==1) + match++; + } + if(match==dinfo->num_components-1) + { + retval=i; break; + } + } + } + } + return retval; +} + + +#ifndef JCS_EXTENSIONS + +/* Conversion functions to emulate the colorspace extensions. This allows the + TurboJPEG wrapper to be used with libjpeg */ + +#define TORGB(PS, ROFFSET, GOFFSET, BOFFSET) { \ + int rowPad=pitch-width*PS; \ + while(height--) \ + { \ + unsigned char *endOfRow=src+width*PS; \ + while(srcjerr.setjmp_buffer)) return -1; + if(this->init&COMPRESS) jpeg_destroy_compress(cinfo); + if(this->init&DECOMPRESS) jpeg_destroy_decompress(dinfo); + free(this); + return 0; +} + + +/* Compressor */ + +static boolean empty_output_buffer(j_compress_ptr cinfo) { ERREXIT(cinfo, JERR_BUFFER_SIZE); return TRUE; } -static void destination_noop(struct jpeg_compress_struct *cinfo) +static void dst_noop(j_compress_ptr cinfo) { } +static tjhandle _tjInitCompress(tjinstance *this) +{ + /* This is also straight out of example.c */ + this->cinfo.err=jpeg_std_error(&this->jerr.pub); + this->jerr.pub.error_exit=my_error_exit; + this->jerr.pub.output_message=my_output_message; + + if(setjmp(this->jerr.setjmp_buffer)) + { + /* If we get here, the JPEG code has signaled an error. */ + if(this) free(this); return NULL; + } + + jpeg_create_compress(&this->cinfo); + this->cinfo.dest=&this->jdst; + this->jdst.init_destination=dst_noop; + this->jdst.empty_output_buffer=empty_output_buffer; + this->jdst.term_destination=dst_noop; + + this->init|=COMPRESS; + return (tjhandle)this; +} + DLLEXPORT tjhandle DLLCALL tjInitCompress(void) { - jpgstruct *j=NULL; - if((j=(jpgstruct *)malloc(sizeof(jpgstruct)))==NULL) - {sprintf(lasterror, "Memory allocation failure"); return NULL;} - memset(j, 0, sizeof(jpgstruct)); - j->cinfo.err=jpeg_std_error(&j->jerr.pub); - j->jerr.pub.error_exit=my_error_exit; - j->jerr.pub.output_message=my_output_message; - - if(setjmp(j->jerr.jb)) - { // this will execute if LIBJPEG has an error - if(j) free(j); return NULL; + tjinstance *this=NULL; + if((this=(tjinstance *)malloc(sizeof(tjinstance)))==NULL) + { + snprintf(errStr, JMSG_LENGTH_MAX, + "tjInitCompress(): Memory allocation failure"); + return NULL; } + MEMZERO(this, sizeof(tjinstance)); + return _tjInitCompress(this); +} + - jpeg_create_compress(&j->cinfo); - j->cinfo.dest=&j->jdms; - j->jdms.init_destination=destination_noop; - j->jdms.empty_output_buffer=empty_output_buffer; - j->jdms.term_destination=destination_noop; +DLLEXPORT unsigned long DLLCALL tjBufSize(int width, int height, + int jpegSubsamp) +{ + unsigned long retval=0; int mcuw, mcuh, chromasf; + if(width<1 || height<1 || jpegSubsamp<0 || jpegSubsamp>=NUMSUBOPT) + _throw("tjBufSize(): Invalid argument"); - j->initc=1; - return (tjhandle)j; + // This allows for rare corner cases in which a JPEG image can actually be + // larger than the uncompressed input (we wouldn't mention it if it hadn't + // happened before.) + mcuw=tjMCUWidth[jpegSubsamp]; + mcuh=tjMCUHeight[jpegSubsamp]; + chromasf=jpegSubsamp==TJSAMP_GRAY? 0: 4*64/(mcuw*mcuh); + retval=PAD(width, mcuw) * PAD(height, mcuh) * (2 + chromasf) + 2048; + + bailout: + return retval; } @@ -114,311 +519,332 @@ DLLEXPORT unsigned long DLLCALL TJBUFSIZE(int width, int height) { unsigned long retval=0; if(width<1 || height<1) - _throw("Invalid argument in TJBUFSIZE()"); + _throw("TJBUFSIZE(): Invalid argument"); // This allows for rare corner cases in which a JPEG image can actually be // larger than the uncompressed input (we wouldn't mention it if it hadn't // happened before.) - retval=((width+15)&(~15)) * ((height+15)&(~15)) * 6 + 2048; + retval=PAD(width, 16) * PAD(height, 16) * 6 + 2048; bailout: return retval; } -DLLEXPORT int DLLCALL tjCompress(tjhandle h, - unsigned char *srcbuf, int width, int pitch, int height, int ps, - unsigned char *dstbuf, unsigned long *size, - int jpegsub, int qual, int flags) +DLLEXPORT int DLLCALL tjCompress2(tjhandle handle, unsigned char *srcBuf, + int width, int pitch, int height, int pixelFormat, unsigned char **jpegBuf, + unsigned long *jpegSize, int jpegSubsamp, int jpegQual, int flags) { int i, retval=0; JSAMPROW *row_pointer=NULL; - - checkhandle(h); - - if(srcbuf==NULL || width<=0 || pitch<0 || height<=0 - || dstbuf==NULL || size==NULL - || jpegsub<0 || jpegsub>=NUMSUBOPT || qual<0 || qual>100) - _throw("Invalid argument in tjCompress()"); - if(ps!=3 && ps!=4 && ps!=1) - _throw("This compressor can only handle 24-bit and 32-bit RGB or 8-bit grayscale input"); - if(!j->initc) _throw("Instance has not been initialized for compression"); - - if(pitch==0) pitch=width*ps; - - j->cinfo.image_width = width; - j->cinfo.image_height = height; - j->cinfo.input_components = ps; - - if(ps==1) j->cinfo.in_color_space = JCS_GRAYSCALE; - #if JCS_EXTENSIONS==1 - else j->cinfo.in_color_space = JCS_EXT_RGB; - if(ps==3 && (flags&TJ_BGR)) - j->cinfo.in_color_space = JCS_EXT_BGR; - else if(ps==4 && !(flags&TJ_BGR) && !(flags&TJ_ALPHAFIRST)) - j->cinfo.in_color_space = JCS_EXT_RGBX; - else if(ps==4 && (flags&TJ_BGR) && !(flags&TJ_ALPHAFIRST)) - j->cinfo.in_color_space = JCS_EXT_BGRX; - else if(ps==4 && (flags&TJ_BGR) && (flags&TJ_ALPHAFIRST)) - j->cinfo.in_color_space = JCS_EXT_XBGR; - else if(ps==4 && !(flags&TJ_BGR) && (flags&TJ_ALPHAFIRST)) - j->cinfo.in_color_space = JCS_EXT_XRGB; - #else - #error "TurboJPEG requires JPEG colorspace extensions" + #ifndef JCS_EXTENSIONS + unsigned char *rgbBuf=NULL; #endif - if(flags&TJ_FORCEMMX) putenv("JSIMD_FORCEMMX=1"); - else if(flags&TJ_FORCESSE) putenv("JSIMD_FORCESSE=1"); - else if(flags&TJ_FORCESSE2) putenv("JSIMD_FORCESSE2=1"); + getinstance(handle) + if((this->init&COMPRESS)==0) + _throw("tjCompress2(): Instance has not been initialized for compression"); - if(setjmp(j->jerr.jb)) - { // this will execute if LIBJPEG has an error + if(srcBuf==NULL || width<=0 || pitch<0 || height<=0 || pixelFormat<0 + || pixelFormat>=TJ_NUMPF || jpegBuf==NULL || jpegSize==NULL + || jpegSubsamp<0 || jpegSubsamp>=NUMSUBOPT || jpegQual<0 || jpegQual>100) + _throw("tjCompress2(): Invalid argument"); + + if(setjmp(this->jerr.setjmp_buffer)) + { + /* If we get here, the JPEG code has signaled an error. */ retval=-1; goto bailout; } - jpeg_set_defaults(&j->cinfo); + if(pitch==0) pitch=width*tjPixelSize[pixelFormat]; - jpeg_set_quality(&j->cinfo, qual, TRUE); - if(jpegsub==TJ_GRAYSCALE) - jpeg_set_colorspace(&j->cinfo, JCS_GRAYSCALE); - else - jpeg_set_colorspace(&j->cinfo, JCS_YCbCr); - if(qual>=96) j->cinfo.dct_method=JDCT_ISLOW; - else j->cinfo.dct_method=JDCT_FASTEST; + #ifndef JCS_EXTENSIONS + if(pixelFormat!=TJPF_GRAY) + { + rgbBuf=(unsigned char *)malloc(width*height*RGB_PIXELSIZE); + if(!rgbBuf) _throw("tjCompress2(): Memory allocation failure"); + srcBuf=toRGB(srcBuf, width, pitch, height, pixelFormat, rgbBuf); + pitch=width*RGB_PIXELSIZE; + } + #endif + + cinfo->image_width=width; + cinfo->image_height=height; + + if(flags&TJFLAG_FORCEMMX) putenv("JSIMD_FORCEMMX=1"); + else if(flags&TJFLAG_FORCESSE) putenv("JSIMD_FORCESSE=1"); + else if(flags&TJFLAG_FORCESSE2) putenv("JSIMD_FORCESSE2=1"); - j->cinfo.comp_info[0].h_samp_factor=hsampfactor[jpegsub]; - j->cinfo.comp_info[1].h_samp_factor=1; - j->cinfo.comp_info[2].h_samp_factor=1; - j->cinfo.comp_info[0].v_samp_factor=vsampfactor[jpegsub]; - j->cinfo.comp_info[1].v_samp_factor=1; - j->cinfo.comp_info[2].v_samp_factor=1; + if(setCompDefaults(cinfo, pixelFormat, jpegSubsamp, jpegQual)==-1) + return -1; - j->jdms.next_output_byte = dstbuf; - j->jdms.free_in_buffer = TJBUFSIZE(j->cinfo.image_width, j->cinfo.image_height); + this->jdst.next_output_byte=*jpegBuf; + this->jdst.free_in_buffer=tjBufSize(width, height, jpegSubsamp); - jpeg_start_compress(&j->cinfo, TRUE); + jpeg_start_compress(cinfo, TRUE); if((row_pointer=(JSAMPROW *)malloc(sizeof(JSAMPROW)*height))==NULL) - _throw("Memory allocation failed in tjCompress()"); + _throw("tjCompress2(): Memory allocation failure"); for(i=0; icinfo.next_scanlinecinfo.image_height) + while(cinfo->next_scanlineimage_height) { - jpeg_write_scanlines(&j->cinfo, &row_pointer[j->cinfo.next_scanline], - j->cinfo.image_height-j->cinfo.next_scanline); + jpeg_write_scanlines(cinfo, &row_pointer[cinfo->next_scanline], + cinfo->image_height-cinfo->next_scanline); } - jpeg_finish_compress(&j->cinfo); - *size=TJBUFSIZE(j->cinfo.image_width, j->cinfo.image_height) - -(unsigned long)(j->jdms.free_in_buffer); + jpeg_finish_compress(cinfo); + *jpegSize=tjBufSize(width, height, jpegSubsamp) + -(unsigned long)(this->jdst.free_in_buffer); bailout: - if(j->cinfo.global_state>CSTATE_START) jpeg_abort_compress(&j->cinfo); + if(cinfo->global_state>CSTATE_START) jpeg_abort_compress(cinfo); + #ifndef JCS_EXTENSIONS + if(rgbBuf && rgbBuf!=srcBuf) free(rgbBuf); + #endif if(row_pointer) free(row_pointer); return retval; } +DLLEXPORT int DLLCALL tjCompress(tjhandle handle, unsigned char *srcBuf, + int width, int pitch, int height, int pixelSize, unsigned char *jpegBuf, + unsigned long *jpegSize, int jpegSubsamp, int jpegQual, int flags) +{ + int retval=0; unsigned long size; + retval=tjCompress2(handle, srcBuf, width, pitch, height, + getPixelFormat(pixelSize, flags), &jpegBuf, &size, jpegSubsamp, jpegQual, + flags); + *jpegSize=size; + return retval; +} + -// DEC +/* Decompressor */ -static boolean fill_input_buffer (struct jpeg_decompress_struct *dinfo) +static boolean fill_input_buffer(j_decompress_ptr dinfo) { ERREXIT(dinfo, JERR_BUFFER_SIZE); return TRUE; } -static void skip_input_data (struct jpeg_decompress_struct *dinfo, long num_bytes) +static void skip_input_data(j_decompress_ptr dinfo, long num_bytes) { dinfo->src->next_input_byte += (size_t) num_bytes; dinfo->src->bytes_in_buffer -= (size_t) num_bytes; } -static void source_noop (struct jpeg_decompress_struct *dinfo) +static void src_noop(j_decompress_ptr dinfo) { } -DLLEXPORT tjhandle DLLCALL tjInitDecompress(void) +static tjhandle _tjInitDecompress(tjinstance *this) { - jpgstruct *j; - if((j=(jpgstruct *)malloc(sizeof(jpgstruct)))==NULL) - {sprintf(lasterror, "Memory allocation failure"); return NULL;} - memset(j, 0, sizeof(jpgstruct)); - j->dinfo.err=jpeg_std_error(&j->jerr.pub); - j->jerr.pub.error_exit=my_error_exit; - j->jerr.pub.output_message=my_output_message; - - if(setjmp(j->jerr.jb)) - { // this will execute if LIBJPEG has an error - free(j); return NULL; + /* This is also straight out of example.c */ + this->dinfo.err=jpeg_std_error(&this->jerr.pub); + this->jerr.pub.error_exit=my_error_exit; + this->jerr.pub.output_message=my_output_message; + + if(setjmp(this->jerr.setjmp_buffer)) + { + /* If we get here, the JPEG code has signaled an error. */ + if(this) free(this); return NULL; } - jpeg_create_decompress(&j->dinfo); - j->dinfo.src=&j->jsms; - j->jsms.init_source=source_noop; - j->jsms.fill_input_buffer = fill_input_buffer; - j->jsms.skip_input_data = skip_input_data; - j->jsms.resync_to_restart = jpeg_resync_to_restart; - j->jsms.term_source = source_noop; + jpeg_create_decompress(&this->dinfo); + this->dinfo.src=&this->jsrc; + this->jsrc.init_source=src_noop; + this->jsrc.fill_input_buffer=fill_input_buffer; + this->jsrc.skip_input_data=skip_input_data; + this->jsrc.resync_to_restart=jpeg_resync_to_restart; + this->jsrc.term_source=src_noop; + + this->init|=DECOMPRESS; + return (tjhandle)this; +} - j->initd=1; - return (tjhandle)j; +DLLEXPORT tjhandle DLLCALL tjInitDecompress(void) +{ + tjinstance *this; + if((this=(tjinstance *)malloc(sizeof(tjinstance)))==NULL) + { + snprintf(errStr, JMSG_LENGTH_MAX, + "tjInitDecompress(): Memory allocation failure"); + return NULL; + } + MEMZERO(this, sizeof(tjinstance)); + return _tjInitDecompress(this); } -DLLEXPORT int DLLCALL tjDecompressHeader2(tjhandle h, - unsigned char *srcbuf, unsigned long size, - int *width, int *height, int *jpegsub) +DLLEXPORT int DLLCALL tjDecompressHeader2(tjhandle handle, + unsigned char *jpegBuf, unsigned long jpegSize, int *width, int *height, + int *jpegSubsamp) { - int i, k, retval=0; + int retval=0; - checkhandle(h); + getinstance(handle); + if((this->init&DECOMPRESS)==0) + _throw("tjDecompressHeader2(): Instance has not been initialized for decompression"); - if(srcbuf==NULL || size<=0 || width==NULL || height==NULL || jpegsub==NULL) - _throw("Invalid argument in tjDecompressHeader2()"); - if(!j->initd) _throw("Instance has not been initialized for decompression"); + if(jpegBuf==NULL || jpegSize<=0 || width==NULL || height==NULL + || jpegSubsamp==NULL) + _throw("tjDecompressHeader2(): Invalid argument"); - if(setjmp(j->jerr.jb)) - { // this will execute if LIBJPEG has an error + if(setjmp(this->jerr.setjmp_buffer)) + { + /* If we get here, the JPEG code has signaled an error. */ return -1; } - j->jsms.bytes_in_buffer = size; - j->jsms.next_input_byte = srcbuf; + this->jsrc.bytes_in_buffer=jpegSize; + this->jsrc.next_input_byte=jpegBuf; + jpeg_read_header(dinfo, TRUE); - jpeg_read_header(&j->dinfo, TRUE); + *width=dinfo->image_width; + *height=dinfo->image_height; + *jpegSubsamp=getSubsamp(dinfo); - *width=j->dinfo.image_width; *height=j->dinfo.image_height; - *jpegsub=-1; - for(i=0; idinfo.num_components==pixelsize[i]) - { - if(j->dinfo.comp_info[0].h_samp_factor==hsampfactor[i] - && j->dinfo.comp_info[0].v_samp_factor==vsampfactor[i]) - { - int match=0; - for(k=1; kdinfo.num_components; k++) - { - if(j->dinfo.comp_info[k].h_samp_factor==1 - && j->dinfo.comp_info[k].v_samp_factor==1) - match++; - } - if(match==j->dinfo.num_components-1) - { - *jpegsub=i; break; - } - } - } - } + jpeg_abort_decompress(dinfo); - jpeg_abort_decompress(&j->dinfo); - - if(*jpegsub<0) _throw("Could not determine subsampling type for JPEG image"); - if(*width<1 || *height<1) _throw("Invalid data returned in header"); + if(*jpegSubsamp<0) + _throw("tjDecompressHeader2(): Could not determine subsampling type for JPEG image"); + if(*width<1 || *height<1) + _throw("tjDecompressHeader2(): Invalid data returned in header"); bailout: return retval; } +DLLEXPORT int DLLCALL tjDecompressHeader(tjhandle handle, + unsigned char *jpegBuf, unsigned long jpegSize, int *width, int *height) +{ + int jpegSubsamp; + return tjDecompressHeader2(handle, jpegBuf, jpegSize, width, height, + &jpegSubsamp); +} -DLLEXPORT int DLLCALL tjDecompressHeader(tjhandle h, - unsigned char *srcbuf, unsigned long size, - int *width, int *height) + +DLLEXPORT tjscalingfactor* DLLCALL tjGetScalingFactors(int *numscalingfactors) { - int jpegsub; - return tjDecompressHeader2(h, srcbuf, size, width, height, &jpegsub); + if(numscalingfactors==NULL) + { + snprintf(errStr, JMSG_LENGTH_MAX, + "tjGetScalingFactors(): Invalid argument"); + return NULL; + } + + *numscalingfactors=NUMSF; + return (tjscalingfactor *)sf; } -DLLEXPORT int DLLCALL tjDecompress(tjhandle h, - unsigned char *srcbuf, unsigned long size, - unsigned char *dstbuf, int width, int pitch, int height, int ps, - int flags) +DLLEXPORT int DLLCALL tjDecompress2(tjhandle handle, unsigned char *jpegBuf, + unsigned long jpegSize, unsigned char *dstBuf, int width, int pitch, + int height, int pixelFormat, int flags) { int i, retval=0; JSAMPROW *row_pointer=NULL; + int jpegwidth, jpegheight, scaledw, scaledh; + #ifndef JCS_EXTENSIONS + unsigned char *rgbBuf=NULL; + unsigned char *_dstBuf=NULL; int _pitch=0; + #endif - checkhandle(h); - - if(srcbuf==NULL || size<=0 - || dstbuf==NULL || width<=0 || pitch<0 || height<=0) - _throw("Invalid argument in tjDecompress()"); - if(ps!=3 && ps!=4 && ps!=1) - _throw("This decompressor can only handle 24-bit and 32-bit RGB or 8-bit grayscale output"); - if(!j->initd) _throw("Instance has not been initialized for decompression"); + getinstance(handle); + if((this->init&DECOMPRESS)==0) + _throw("tjDecompress2(): Instance has not been initialized for decompression"); - if(pitch==0) pitch=width*ps; + if(jpegBuf==NULL || jpegSize<=0 || dstBuf==NULL || width<0 || pitch<0 + || height<0 || pixelFormat<0 || pixelFormat>=TJ_NUMPF) + _throw("tjDecompress2(): Invalid argument"); - if(flags&TJ_FORCEMMX) putenv("JSIMD_FORCEMMX=1"); - else if(flags&TJ_FORCESSE) putenv("JSIMD_FORCESSE=1"); - else if(flags&TJ_FORCESSE2) putenv("JSIMD_FORCESSE2=1"); + if(flags&TJFLAG_FORCEMMX) putenv("JSIMD_FORCEMMX=1"); + else if(flags&TJFLAG_FORCESSE) putenv("JSIMD_FORCESSE=1"); + else if(flags&TJFLAG_FORCESSE2) putenv("JSIMD_FORCESSE2=1"); - if(setjmp(j->jerr.jb)) - { // this will execute if LIBJPEG has an error + if(setjmp(this->jerr.setjmp_buffer)) + { + /* If we get here, the JPEG code has signaled an error. */ retval=-1; goto bailout; } - j->jsms.bytes_in_buffer = size; - j->jsms.next_input_byte = srcbuf; + this->jsrc.bytes_in_buffer=jpegSize; + this->jsrc.next_input_byte=jpegBuf; + jpeg_read_header(dinfo, TRUE); + if(setDecompDefaults(dinfo, pixelFormat)==-1) + { + retval=-1; goto bailout; + } - jpeg_read_header(&j->dinfo, TRUE); + if(flags&TJFLAG_FASTUPSAMPLE) dinfo->do_fancy_upsampling=FALSE; - if((row_pointer=(JSAMPROW *)malloc(sizeof(JSAMPROW)*height))==NULL) - _throw("Memory allocation failed in tjDecompress()"); - for(i=0; iimage_width; jpegheight=dinfo->image_height; + if(width==0) width=jpegwidth; + if(height==0) height=jpegheight; + for(i=0; iwidth || scaledh>height) + _throw("tjDecompress2(): Could not scale down to desired image dimensions"); + width=scaledw; height=scaledh; + dinfo->scale_num=sf[i].num; + dinfo->scale_denom=sf[i].denom; + + jpeg_start_decompress(dinfo); + if(pitch==0) pitch=dinfo->output_width*tjPixelSize[pixelFormat]; + + #ifndef JCS_EXTENSIONS + if(pixelFormat!=TJPF_GRAY && + (RGB_RED!=tjRedOffset[pixelFormat] || + RGB_GREEN!=tjGreenOffset[pixelFormat] || + RGB_BLUE!=tjBlueOffset[pixelFormat] || + RGB_PIXELSIZE!=tjPixelSize[pixelFormat])) + { + rgbBuf=(unsigned char *)malloc(width*height*3); + if(!rgbBuf) _throw("tjDecompress2(): Memory allocation failure"); + _pitch=pitch; pitch=width*3; + _dstBuf=dstBuf; dstBuf=rgbBuf; } - - if(ps==1) j->dinfo.out_color_space = JCS_GRAYSCALE; - #if JCS_EXTENSIONS==1 - else j->dinfo.out_color_space = JCS_EXT_RGB; - if(ps==3 && (flags&TJ_BGR)) - j->dinfo.out_color_space = JCS_EXT_BGR; - else if(ps==4 && !(flags&TJ_BGR) && !(flags&TJ_ALPHAFIRST)) - j->dinfo.out_color_space = JCS_EXT_RGBX; - else if(ps==4 && (flags&TJ_BGR) && !(flags&TJ_ALPHAFIRST)) - j->dinfo.out_color_space = JCS_EXT_BGRX; - else if(ps==4 && (flags&TJ_BGR) && (flags&TJ_ALPHAFIRST)) - j->dinfo.out_color_space = JCS_EXT_XBGR; - else if(ps==4 && !(flags&TJ_BGR) && (flags&TJ_ALPHAFIRST)) - j->dinfo.out_color_space = JCS_EXT_XRGB; - #else - #error "TurboJPEG requires JPEG colorspace extensions" #endif - if(flags&TJ_FASTUPSAMPLE) j->dinfo.do_fancy_upsampling=FALSE; - - jpeg_start_decompress(&j->dinfo); - while(j->dinfo.output_scanlinedinfo.output_height) + if((row_pointer=(JSAMPROW *)malloc(sizeof(JSAMPROW) + *dinfo->output_height))==NULL) + _throw("tjDecompress2(): Memory allocation failure"); + for(i=0; i<(int)dinfo->output_height; i++) + { + if(flags&TJFLAG_BOTTOMUP) + row_pointer[i]=&dstBuf[(dinfo->output_height-i-1)*pitch]; + else row_pointer[i]=&dstBuf[i*pitch]; + } + while(dinfo->output_scanlineoutput_height) { - jpeg_read_scanlines(&j->dinfo, &row_pointer[j->dinfo.output_scanline], - j->dinfo.output_height-j->dinfo.output_scanline); + jpeg_read_scanlines(dinfo, &row_pointer[dinfo->output_scanline], + dinfo->output_height-dinfo->output_scanline); } - jpeg_finish_decompress(&j->dinfo); + jpeg_finish_decompress(dinfo); + + #ifndef JCS_EXTENSIONS + fromRGB(rgbBuf, _dstBuf, width, _pitch, height, pixelFormat); + #endif bailout: - if(j->dinfo.global_state>DSTATE_START) jpeg_abort_decompress(&j->dinfo); + if(dinfo->global_state>DSTATE_START) jpeg_abort_decompress(dinfo); + #ifndef JCS_EXTENSIONS + if(rgbBuf && rgbBuf!=dstBuf) free(rgbBuf); + #endif if(row_pointer) free(row_pointer); return retval; } - -// General - -DLLEXPORT char* DLLCALL tjGetErrorStr(void) -{ - return lasterror; -} - -DLLEXPORT int DLLCALL tjDestroy(tjhandle h) +DLLEXPORT int DLLCALL tjDecompress(tjhandle handle, unsigned char *jpegBuf, + unsigned long jpegSize, unsigned char *dstBuf, int width, int pitch, + int height, int pixelSize, int flags) { - checkhandle(h); - if(setjmp(j->jerr.jb)) return -1; - if(j->initc) jpeg_destroy_compress(&j->cinfo); - if(j->initd) jpeg_destroy_decompress(&j->dinfo); - free(j); - return 0; + return tjDecompress2(handle, jpegBuf, jpegSize, dstBuf, width, pitch, + height, getPixelFormat(pixelSize, flags), flags); } diff --git a/common/turbojpeg.h b/common/turbojpeg.h index 6e3e259..ab8adda 100644 --- a/common/turbojpeg.h +++ b/common/turbojpeg.h @@ -1,255 +1,529 @@ -/* Copyright (C)2004 Landmark Graphics Corporation - * Copyright (C)2005, 2006 Sun Microsystems, Inc. - * Copyright (C)2009-2011 D. R. Commander +/* + * Copyright (C)2009-2012 D. R. Commander. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: * - * This library is free software and may be redistributed and/or modified under - * the terms of the wxWindows Library License, Version 3.1 or (at your option) - * any later version. The full license is in the LICENSE.txt file included - * with this distribution. + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of the libjpeg-turbo Project nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. * - * This library 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 - * wxWindows Library License for more details. + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS", + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. */ -#if (defined(_MSC_VER) || defined(__CYGWIN__) || defined(__MINGW32__)) \ - && defined(_WIN32) && defined(DLLDEFINE) +#ifndef __TURBOJPEG_H__ +#define __TURBOJPEG_H__ + +#if defined(_WIN32) && defined(DLLDEFINE) #define DLLEXPORT __declspec(dllexport) #else #define DLLEXPORT #endif - #define DLLCALL -/* Subsampling */ -#define NUMSUBOPT 4 - -enum {TJ_444=0, TJ_422, TJ_420, TJ_GRAYSCALE}; -#define TJ_411 TJ_420 /* for backward compatibility with VirtualGL <= 2.1.x, - TurboVNC <= 0.6, and TurboJPEG/IPP */ - - -/* Flags */ -#define TJ_BGR 1 - /* The components of each pixel in the source/destination bitmap are stored - in B,G,R order, not R,G,B */ -#define TJ_BOTTOMUP 2 - /* The source/destination bitmap is stored in bottom-up (Windows, OpenGL) - order, not top-down (X11) order */ -#define TJ_FORCEMMX 8 - /* Turn off CPU auto-detection and force TurboJPEG to use MMX code - (IPP and 32-bit libjpeg-turbo versions only) */ -#define TJ_FORCESSE 16 - /* Turn off CPU auto-detection and force TurboJPEG to use SSE code - (32-bit IPP and 32-bit libjpeg-turbo versions only) */ -#define TJ_FORCESSE2 32 - /* Turn off CPU auto-detection and force TurboJPEG to use SSE2 code - (32-bit IPP and 32-bit libjpeg-turbo versions only) */ -#define TJ_ALPHAFIRST 64 - /* If the source/destination bitmap is 32 bpp, assume that each pixel is - ARGB/XRGB (or ABGR/XBGR if TJ_BGR is also specified) */ -#define TJ_FORCESSE3 128 - /* Turn off CPU auto-detection and force TurboJPEG to use SSE3 code - (64-bit IPP version only) */ -#define TJ_FASTUPSAMPLE 256 - /* Use fast, inaccurate 4:2:2 and 4:2:0 YUV upsampling routines - (libjpeg and libjpeg-turbo versions only) */ +/** + * @addtogroup TurboJPEG Lite + * TurboJPEG API. This API provides an interface for generating and decoding + * JPEG images in memory. + * + * @{ + */ + + +/** + * The number of chrominance subsampling options + */ +#define TJ_NUMSAMP 5 + +/** + * Chrominance subsampling options. + * When an image is converted from the RGB to the YCbCr colorspace as part of + * the JPEG compression process, some of the Cb and Cr (chrominance) components + * can be discarded or averaged together to produce a smaller image with little + * perceptible loss of image clarity (the human eye is more sensitive to small + * changes in brightness than small changes in color.) This is called + * "chrominance subsampling". + */ +enum TJSAMP +{ + /** + * 4:4:4 chrominance subsampling (no chrominance subsampling). The JPEG or + * YUV image will contain one chrominance component for every pixel in the + * source image. + */ + TJSAMP_444=0, + /** + * 4:2:2 chrominance subsampling. The JPEG or YUV image will contain one + * chrominance component for every 2x1 block of pixels in the source image. + */ + TJSAMP_422, + /** + * 4:2:0 chrominance subsampling. The JPEG or YUV image will contain one + * chrominance component for every 2x2 block of pixels in the source image. + */ + TJSAMP_420, + /** + * Grayscale. The JPEG or YUV image will contain no chrominance components. + */ + TJSAMP_GRAY, + /** + * 4:4:0 chrominance subsampling. The JPEG or YUV image will contain one + * chrominance component for every 1x2 block of pixels in the source image. + */ + TJSAMP_440 +}; + +/** + * MCU block width (in pixels) for a given level of chrominance subsampling. + * MCU block sizes: + * - 8x8 for no subsampling or grayscale + * - 16x8 for 4:2:2 + * - 8x16 for 4:4:0 + * - 16x16 for 4:2:0 + */ +static const int tjMCUWidth[TJ_NUMSAMP] = {8, 16, 16, 8, 8}; + +/** + * MCU block height (in pixels) for a given level of chrominance subsampling. + * MCU block sizes: + * - 8x8 for no subsampling or grayscale + * - 16x8 for 4:2:2 + * - 8x16 for 4:4:0 + * - 16x16 for 4:2:0 + */ +static const int tjMCUHeight[TJ_NUMSAMP] = {8, 8, 16, 8, 16}; + + +/** + * The number of pixel formats + */ +#define TJ_NUMPF 11 + +/** + * Pixel formats + */ +enum TJPF +{ + /** + * RGB pixel format. The red, green, and blue components in the image are + * stored in 3-byte pixels in the order R, G, B from lowest to highest byte + * address within each pixel. + */ + TJPF_RGB=0, + /** + * BGR pixel format. The red, green, and blue components in the image are + * stored in 3-byte pixels in the order B, G, R from lowest to highest byte + * address within each pixel. + */ + TJPF_BGR, + /** + * RGBX pixel format. The red, green, and blue components in the image are + * stored in 4-byte pixels in the order R, G, B from lowest to highest byte + * address within each pixel. The X component is ignored when compressing + * and undefined when decompressing. + */ + TJPF_RGBX, + /** + * BGRX pixel format. The red, green, and blue components in the image are + * stored in 4-byte pixels in the order B, G, R from lowest to highest byte + * address within each pixel. The X component is ignored when compressing + * and undefined when decompressing. + */ + TJPF_BGRX, + /** + * XBGR pixel format. The red, green, and blue components in the image are + * stored in 4-byte pixels in the order R, G, B from highest to lowest byte + * address within each pixel. The X component is ignored when compressing + * and undefined when decompressing. + */ + TJPF_XBGR, + /** + * XRGB pixel format. The red, green, and blue components in the image are + * stored in 4-byte pixels in the order B, G, R from highest to lowest byte + * address within each pixel. The X component is ignored when compressing + * and undefined when decompressing. + */ + TJPF_XRGB, + /** + * Grayscale pixel format. Each 1-byte pixel represents a luminance + * (brightness) level from 0 to 255. + */ + TJPF_GRAY, + /** + * RGBA pixel format. This is the same as @ref TJPF_RGBX, except that when + * decompressing, the X component is guaranteed to be 0xFF, which can be + * interpreted as an opaque alpha channel. + */ + TJPF_RGBA, + /** + * BGRA pixel format. This is the same as @ref TJPF_BGRX, except that when + * decompressing, the X component is guaranteed to be 0xFF, which can be + * interpreted as an opaque alpha channel. + */ + TJPF_BGRA, + /** + * ABGR pixel format. This is the same as @ref TJPF_XBGR, except that when + * decompressing, the X component is guaranteed to be 0xFF, which can be + * interpreted as an opaque alpha channel. + */ + TJPF_ABGR, + /** + * ARGB pixel format. This is the same as @ref TJPF_XRGB, except that when + * decompressing, the X component is guaranteed to be 0xFF, which can be + * interpreted as an opaque alpha channel. + */ + TJPF_ARGB +}; + +/** + * Red offset (in bytes) for a given pixel format. This specifies the number + * of bytes that the red component is offset from the start of the pixel. For + * instance, if a pixel of format TJ_BGRX is stored in char pixel[], + * then the red component will be pixel[tjRedOffset[TJ_BGRX]]. + */ +static const int tjRedOffset[TJ_NUMPF] = {0, 2, 0, 2, 3, 1, 0, 0, 2, 3, 1}; +/** + * Green offset (in bytes) for a given pixel format. This specifies the number + * of bytes that the green component is offset from the start of the pixel. + * For instance, if a pixel of format TJ_BGRX is stored in + * char pixel[], then the green component will be + * pixel[tjGreenOffset[TJ_BGRX]]. + */ +static const int tjGreenOffset[TJ_NUMPF] = {1, 1, 1, 1, 2, 2, 0, 1, 1, 2, 2}; +/** + * Blue offset (in bytes) for a given pixel format. This specifies the number + * of bytes that the Blue component is offset from the start of the pixel. For + * instance, if a pixel of format TJ_BGRX is stored in char pixel[], + * then the blue component will be pixel[tjBlueOffset[TJ_BGRX]]. + */ +static const int tjBlueOffset[TJ_NUMPF] = {2, 0, 2, 0, 1, 3, 0, 2, 0, 1, 3}; + +/** + * Pixel size (in bytes) for a given pixel format. + */ +static const int tjPixelSize[TJ_NUMPF] = {3, 3, 4, 4, 4, 4, 1, 4, 4, 4, 4}; +/** + * The uncompressed source/destination image is stored in bottom-up (Windows, + * OpenGL) order, not top-down (X11) order. + */ +#define TJFLAG_BOTTOMUP 2 +/** + * Turn off CPU auto-detection and force TurboJPEG to use MMX code (IPP and + * 32-bit libjpeg-turbo versions only.) + */ +#define TJFLAG_FORCEMMX 8 +/** + * Turn off CPU auto-detection and force TurboJPEG to use SSE code (32-bit IPP + * and 32-bit libjpeg-turbo versions only) + */ +#define TJFLAG_FORCESSE 16 +/** + * Turn off CPU auto-detection and force TurboJPEG to use SSE2 code (32-bit IPP + * and 32-bit libjpeg-turbo versions only) + */ +#define TJFLAG_FORCESSE2 32 +/** + * Turn off CPU auto-detection and force TurboJPEG to use SSE3 code (64-bit IPP + * version only) + */ +#define TJFLAG_FORCESSE3 128 +/** + * Use fast, inaccurate chrominance upsampling routines in the JPEG + * decompressor (libjpeg and libjpeg-turbo versions only) + */ +#define TJFLAG_FASTUPSAMPLE 256 + + +/** + * Scaling factor + */ +typedef struct +{ + /** + * Numerator + */ + int num; + /** + * Denominator + */ + int denom; +} tjscalingfactor; + + +/** + * TurboJPEG instance handle + */ typedef void* tjhandle; -#define TJPAD(p) (((p)+3)&(~3)) -#ifndef max - #define max(a,b) ((a)>(b)?(a):(b)) -#endif + +/** + * Pad the given width to the nearest 32-bit boundary + */ +#define TJPAD(width) (((width)+3)&(~3)) + +/** + * Compute the scaled value of dimension using the given scaling + * factor. This macro performs the integer equivalent of ceil(dimension * + * scalingFactor). + */ +#define TJSCALED(dimension, scalingFactor) ((dimension * scalingFactor.num \ + + scalingFactor.denom - 1) / scalingFactor.denom) #ifdef __cplusplus extern "C" { #endif -/* API follows */ - -/* - tjhandle tjInitCompress(void) +/** + * Create a TurboJPEG compressor instance. + * + * @return a handle to the newly-created instance, or NULL if an error + * occurred (see #tjGetErrorStr().) + */ +DLLEXPORT tjhandle DLLCALL tjInitCompress(void); - Creates a new JPEG compressor instance, allocates memory for the structures, - and returns a handle to the instance. Most applications will only - need to call this once at the beginning of the program or once for each - concurrent thread. Don't try to create a new instance every time you - compress an image, because this may cause performance to suffer in some - TurboJPEG implementations. - RETURNS: NULL on error +/** + * Compress an RGB or grayscale image into a JPEG image. + * + * @param handle a handle to a TurboJPEG compressor or transformer instance + * @param srcBuf pointer to an image buffer containing RGB or grayscale pixels + * to be compressed + * @param width width (in pixels) of the source image + * @param pitch bytes per line of the source image. Normally, this should be + * width * #tjPixelSize[pixelFormat] if the image is unpadded, + * or #TJPAD(width * #tjPixelSize[pixelFormat]) if each line of + * the image is padded to the nearest 32-bit boundary, as is the case + * for Windows bitmaps. You can also be clever and use this parameter + * to skip lines, etc. Setting this parameter to 0 is the equivalent of + * setting it to width * #tjPixelSize[pixelFormat]. + * @param height height (in pixels) of the source image + * @param pixelFormat pixel format of the source image (see @ref TJPF + * "Pixel formats".) + * @param jpegBuf address of a pointer to an image buffer that will receive the + * JPEG image. TurboJPEG has the ability to reallocate the JPEG buffer + * to accommodate the size of the JPEG image. Thus, you can choose to: + * -# pre-allocate the JPEG buffer with an arbitrary size using + * #tjAlloc() and let TurboJPEG grow the buffer as needed, + * -# set *jpegBuf to NULL to tell TurboJPEG to allocate the + * buffer for you, or + * -# pre-allocate the buffer to a "worst case" size determined by + * calling #tjBufSize(). This should ensure that the buffer never has + * to be re-allocated (setting #TJFLAG_NOREALLOC guarantees this.) + * . + * If you choose option 1, *jpegSize should be set to the + * size of your pre-allocated buffer. In any case, unless you have + * set #TJFLAG_NOREALLOC, you should always check *jpegBuf upon + * return from this function, as it may have changed. + * @param jpegSize pointer to an unsigned long variable that holds the size of + * the JPEG image buffer. If *jpegBuf points to a + * pre-allocated buffer, then *jpegSize should be set to the + * size of the buffer. Upon return, *jpegSize will contain the + * size of the JPEG image (in bytes.) + * @param jpegSubsamp the level of chrominance subsampling to be used when + * generating the JPEG image (see @ref TJSAMP + * "Chrominance subsampling options".) + * @param jpegQual the image quality of the generated JPEG image (1 = worst, + 100 = best) + * @param flags the bitwise OR of one or more of the @ref TJFLAG_BOTTOMUP + * "flags". + * + * @return 0 if successful, or -1 if an error occurred (see #tjGetErrorStr().) */ -DLLEXPORT tjhandle DLLCALL tjInitCompress(void); +DLLEXPORT int DLLCALL tjCompress2(tjhandle handle, unsigned char *srcBuf, + int width, int pitch, int height, int pixelFormat, unsigned char **jpegBuf, + unsigned long *jpegSize, int jpegSubsamp, int jpegQual, int flags); + + +/** + * The maximum size of the buffer (in bytes) required to hold a JPEG image with + * the given parameters. The number of bytes returned by this function is + * larger than the size of the uncompressed source image. The reason for this + * is that the JPEG format uses 16-bit coefficients, and it is thus possible + * for a very high-quality JPEG image with very high frequency content to + * expand rather than compress when converted to the JPEG format. Such images + * represent a very rare corner case, but since there is no way to predict the + * size of a JPEG image prior to compression, the corner case has to be + * handled. + * + * @param width width of the image (in pixels) + * @param height height of the image (in pixels) + * @param jpegSubsamp the level of chrominance subsampling to be used when + * generating the JPEG image (see @ref TJSAMP + * "Chrominance subsampling options".) + * + * @return the maximum size of the buffer (in bytes) required to hold the + * image, or -1 if the arguments are out of bounds. + */ +DLLEXPORT unsigned long DLLCALL tjBufSize(int width, int height, + int jpegSubsamp); -/* - int tjCompress(tjhandle j, - unsigned char *srcbuf, int width, int pitch, int height, int pixelsize, - unsigned char *dstbuf, unsigned long *size, - int jpegsubsamp, int jpegqual, int flags) - - [INPUT] j = instance handle previously returned from a call to - tjInitCompress() - [INPUT] srcbuf = pointer to user-allocated image buffer containing RGB or - grayscale pixels to be compressed - [INPUT] width = width (in pixels) of the source image - [INPUT] pitch = bytes per line of the source image (width*pixelsize if the - bitmap is unpadded, else TJPAD(width*pixelsize) if each line of the bitmap - is padded to the nearest 32-bit boundary, such as is the case for Windows - bitmaps. You can also be clever and use this parameter to skip lines, - etc. Setting this parameter to 0 is the equivalent of setting it to - width*pixelsize. - [INPUT] height = height (in pixels) of the source image - [INPUT] pixelsize = size (in bytes) of each pixel in the source image - RGBX/BGRX/XRGB/XBGR: 4, RGB/BGR: 3, Grayscale: 1 - [INPUT] dstbuf = pointer to user-allocated image buffer that will receive - the JPEG image. Use the TJBUFSIZE(width, height) function to determine - the appropriate size for this buffer based on the image width and height. - [OUTPUT] size = pointer to unsigned long that receives the size (in bytes) - of the compressed image - [INPUT] jpegsubsamp = Specifies either 4:2:0, 4:2:2, 4:4:4, or grayscale - subsampling. When the image is converted from the RGB to YCbCr colorspace - as part of the JPEG compression process, every other Cb and Cr - (chrominance) pixel can be discarded to produce a smaller image with - little perceptible loss of image clarity (the human eye is more sensitive - to small changes in brightness than small changes in color.) - - TJ_420: 4:2:0 subsampling. Discards every other Cb, Cr pixel in both - horizontal and vertical directions - TJ_422: 4:2:2 subsampling. Discards every other Cb, Cr pixel only in - the horizontal direction - TJ_444: no subsampling - TJ_GRAYSCALE: Generate grayscale JPEG image - - [INPUT] jpegqual = JPEG quality (an integer between 0 and 100 inclusive) - [INPUT] flags = the bitwise OR of one or more of the flags described in the - "Flags" section above - - RETURNS: 0 on success, -1 on error +/** + * Create a TurboJPEG decompressor instance. + * + * @return a handle to the newly-created instance, or NULL if an error + * occurred (see #tjGetErrorStr().) */ -DLLEXPORT int DLLCALL tjCompress(tjhandle j, - unsigned char *srcbuf, int width, int pitch, int height, int pixelsize, - unsigned char *dstbuf, unsigned long *size, - int jpegsubsamp, int jpegqual, int flags); +DLLEXPORT tjhandle DLLCALL tjInitDecompress(void); -/* - unsigned long TJBUFSIZE(int width, int height) +/** + * Retrieve information about a JPEG image without decompressing it. + * + * @param handle a handle to a TurboJPEG decompressor or transformer instance + * @param jpegBuf pointer to a buffer containing a JPEG image + * @param jpegSize size of the JPEG image (in bytes) + * @param width pointer to an integer variable that will receive the width (in + * pixels) of the JPEG image + * @param height pointer to an integer variable that will receive the height + * (in pixels) of the JPEG image + * @param jpegSubsamp pointer to an integer variable that will receive the + * level of chrominance subsampling used when compressing the JPEG image + * (see @ref TJSAMP "Chrominance subsampling options".) + * + * @return 0 if successful, or -1 if an error occurred (see #tjGetErrorStr().) +*/ +DLLEXPORT int DLLCALL tjDecompressHeader2(tjhandle handle, + unsigned char *jpegBuf, unsigned long jpegSize, int *width, int *height, + int *jpegSubsamp); - Convenience function that returns the maximum size of the buffer required to - hold a JPEG image with the given width and height - RETURNS: -1 if arguments are out of bounds +/** + * Returns a list of fractional scaling factors that the JPEG decompressor in + * this implementation of TurboJPEG supports. + * + * @param numscalingfactors pointer to an integer variable that will receive + * the number of elements in the list + * + * @return a pointer to a list of fractional scaling factors, or NULL if an + * error is encountered (see #tjGetErrorStr().) */ -DLLEXPORT unsigned long DLLCALL TJBUFSIZE(int width, int height); - +DLLEXPORT tjscalingfactor* DLLCALL tjGetScalingFactors(int *numscalingfactors); -/* - tjhandle tjInitDecompress(void) - Creates a new JPEG decompressor instance, allocates memory for the - structures, and returns a handle to the instance. Most applications will - only need to call this once at the beginning of the program or once for each - concurrent thread. Don't try to create a new instance every time you - decompress an image, because this may cause performance to suffer in some - TurboJPEG implementations. +/** + * Decompress a JPEG image to an RGB or grayscale image. + * + * @param handle a handle to a TurboJPEG decompressor or transformer instance + * @param jpegBuf pointer to a buffer containing the JPEG image to decompress + * @param jpegSize size of the JPEG image (in bytes) + * @param dstBuf pointer to an image buffer that will receive the decompressed + * image. This buffer should normally be pitch * scaledHeight + * bytes in size, where scaledHeight can be determined by + * calling #TJSCALED() with the JPEG image height and one of the scaling + * factors returned by #tjGetScalingFactors(). The dstBuf pointer may + * also be used to decompress into a specific region of a larger buffer. + * @param width desired width (in pixels) of the destination image. If this is + * smaller than the width of the JPEG image being decompressed, then + * TurboJPEG will use scaling in the JPEG decompressor to generate the + * largest possible image that will fit within the desired width. If + * width is set to 0, then only the height will be considered when + * determining the scaled image size. + * @param pitch bytes per line of the destination image. Normally, this is + * scaledWidth * #tjPixelSize[pixelFormat] if the decompressed + * image is unpadded, else #TJPAD(scaledWidth * + * #tjPixelSize[pixelFormat]) if each line of the decompressed + * image is padded to the nearest 32-bit boundary, as is the case for + * Windows bitmaps. (NOTE: scaledWidth can be determined by + * calling #TJSCALED() with the JPEG image width and one of the scaling + * factors returned by #tjGetScalingFactors().) You can also be clever + * and use the pitch parameter to skip lines, etc. Setting this + * parameter to 0 is the equivalent of setting it to scaledWidth + * * #tjPixelSize[pixelFormat]. + * @param height desired height (in pixels) of the destination image. If this + * is smaller than the height of the JPEG image being decompressed, then + * TurboJPEG will use scaling in the JPEG decompressor to generate the + * largest possible image that will fit within the desired height. If + * height is set to 0, then only the width will be considered when + * determining the scaled image size. + * @param pixelFormat pixel format of the destination image (see @ref + * TJPF "Pixel formats".) + * @param flags the bitwise OR of one or more of the @ref TJFLAG_BOTTOMUP + * "flags". + * + * @return 0 if successful, or -1 if an error occurred (see #tjGetErrorStr().) + */ +DLLEXPORT int DLLCALL tjDecompress2(tjhandle handle, + unsigned char *jpegBuf, unsigned long jpegSize, unsigned char *dstBuf, + int width, int pitch, int height, int pixelFormat, int flags); - RETURNS: NULL on error -*/ -DLLEXPORT tjhandle DLLCALL tjInitDecompress(void); +/** + * Destroy a TurboJPEG compressor, decompressor, or transformer instance. + * + * @param handle a handle to a TurboJPEG compressor, decompressor or + * transformer instance + * + * @return 0 if successful, or -1 if an error occurred (see #tjGetErrorStr().) + */ +DLLEXPORT int DLLCALL tjDestroy(tjhandle handle); -/* - int tjDecompressHeader2(tjhandle j, - unsigned char *srcbuf, unsigned long size, - int *width, int *height, int *jpegsubsamp) - - [INPUT] j = instance handle previously returned from a call to - tjInitDecompress() - [INPUT] srcbuf = pointer to a user-allocated buffer containing a JPEG image - [INPUT] size = size of the JPEG image buffer (in bytes) - [OUTPUT] width = width (in pixels) of the JPEG image - [OUTPUT] height = height (in pixels) of the JPEG image - [OUTPUT] jpegsubsamp = type of chrominance subsampling used when compressing - the JPEG image - - RETURNS: 0 on success, -1 on error -*/ -DLLEXPORT int DLLCALL tjDecompressHeader2(tjhandle j, - unsigned char *srcbuf, unsigned long size, - int *width, int *height, int *jpegsubsamp); -/* - Legacy version of the above function -*/ -DLLEXPORT int DLLCALL tjDecompressHeader(tjhandle j, - unsigned char *srcbuf, unsigned long size, - int *width, int *height); +/** + * Returns a descriptive error message explaining why the last command failed. + * + * @return a descriptive error message explaining why the last command failed. + */ +DLLEXPORT char* DLLCALL tjGetErrorStr(void); -/* - int tjDecompress(tjhandle j, - unsigned char *srcbuf, unsigned long size, - unsigned char *dstbuf, int width, int pitch, int height, int pixelsize, - int flags) - - [INPUT] j = instance handle previously returned from a call to - tjInitDecompress() - [INPUT] srcbuf = pointer to a user-allocated buffer containing the JPEG image - to decompress - [INPUT] size = size of the JPEG image buffer (in bytes) - [INPUT] dstbuf = pointer to user-allocated image buffer that will receive - the bitmap image. This buffer should normally be pitch*height - bytes in size, although this pointer may also be used to decompress into - a specific region of a larger buffer. - [INPUT] width = width (in pixels) of the destination image - [INPUT] pitch = bytes per line of the destination image (width*pixelsize if - the bitmap is unpadded, else TJPAD(width*pixelsize) if each line of the - bitmap is padded to the nearest 32-bit boundary, such as is the case for - Windows bitmaps. You can also be clever and use this parameter to skip - lines, etc. Setting this parameter to 0 is the equivalent of setting it - to width*pixelsize. - [INPUT] height = height (in pixels) of the destination image - [INPUT] pixelsize = size (in bytes) of each pixel in the destination image - RGBX/BGRX/XRGB/XBGR: 4, RGB/BGR: 3, Grayscale: 1 - [INPUT] flags = the bitwise OR of one or more of the flags described in the - "Flags" section above. - - RETURNS: 0 on success, -1 on error -*/ -DLLEXPORT int DLLCALL tjDecompress(tjhandle j, - unsigned char *srcbuf, unsigned long size, - unsigned char *dstbuf, int width, int pitch, int height, int pixelsize, - int flags); +/* Backward compatibility functions and macros (nothing to see here) */ +#define NUMSUBOPT TJ_NUMSAMP +#define TJ_444 TJSAMP_444 +#define TJ_422 TJSAMP_422 +#define TJ_420 TJSAMP_420 +#define TJ_411 TJSAMP_420 +#define TJ_GRAYSCALE TJSAMP_GRAY +#define TJ_BGR 1 +#define TJ_BOTTOMUP TJFLAG_BOTTOMUP +#define TJ_FORCEMMX TJFLAG_FORCEMMX +#define TJ_FORCESSE TJFLAG_FORCESSE +#define TJ_FORCESSE2 TJFLAG_FORCESSE2 +#define TJ_ALPHAFIRST 64 +#define TJ_FORCESSE3 TJFLAG_FORCESSE3 +#define TJ_FASTUPSAMPLE TJFLAG_FASTUPSAMPLE -/* - int tjDestroy(tjhandle h) +DLLEXPORT unsigned long DLLCALL TJBUFSIZE(int width, int height); - Frees structures associated with a compression or decompression instance - - [INPUT] h = instance handle (returned from a previous call to - tjInitCompress() or tjInitDecompress() +DLLEXPORT int DLLCALL tjCompress(tjhandle handle, unsigned char *srcBuf, + int width, int pitch, int height, int pixelSize, unsigned char *dstBuf, + unsigned long *compressedSize, int jpegSubsamp, int jpegQual, int flags); - RETURNS: 0 on success, -1 on error -*/ -DLLEXPORT int DLLCALL tjDestroy(tjhandle h); +DLLEXPORT int DLLCALL tjDecompressHeader(tjhandle handle, + unsigned char *jpegBuf, unsigned long jpegSize, int *width, int *height); +DLLEXPORT int DLLCALL tjDecompress(tjhandle handle, + unsigned char *jpegBuf, unsigned long jpegSize, unsigned char *dstBuf, + int width, int pitch, int height, int pixelSize, int flags); -/* - char *tjGetErrorStr(void) - - Returns a descriptive error message explaining why the last command failed -*/ -DLLEXPORT char* DLLCALL tjGetErrorStr(void); +/** + * @} + */ #ifdef __cplusplus } #endif + +#endif diff --git a/configure.ac b/configure.ac index 211cad6..ddfe5e0 100644 --- a/configure.ac +++ b/configure.ac @@ -612,31 +612,18 @@ A copy of libjpeg may be obtained from: http://ijg.org/files/ [AC_MSG_RESULT(no)]) fi fi -fi - -AC_ARG_WITH(turbovnc, -[ --with-turbovnc use TurboVNC encoder instead of TightVNC encoder],,) -AC_MSG_CHECKING(whether to enable TurboVNC encoder) -if test "x$with_turbovnc" = "xyes"; then if test "x$HAVE_LIBJPEG_TURBO" != "xtrue"; then - AC_MSG_ERROR([ + AC_MSG_WARN([ ========================================================================== -*** The TurboVNC encoder requires libjpeg-turbo, which was not detected. -You can obtain libjpeg-turbo from: -https://sourceforge.net/projects/libjpeg-turbo/files/ -Optionally, you can pass --without-turbovnc to configure to use the -TightVNC encoder instead. *** +*** The libjpeg library you are building against is not libjpeg-turbo. +Performance will be reduced. You can obtain libjpeg-turbo from: +https://sourceforge.net/projects/libjpeg-turbo/files/ *** ========================================================================== ]) - fi - AC_DEFINE(HAVE_TURBOVNC, 1, TurboVNC support enabled) - AC_MSG_RESULT(yes) -else - AC_MSG_RESULT(no) -fi + fi -AM_CONDITIONAL(HAVE_TURBOVNC, test "x$with_turbovnc" = "xyes" ) +fi AC_ARG_WITH(png, [ --without-png disable support for png] @@ -648,7 +635,7 @@ AC_ARG_WITH(png, # -without-png with_png="no" # -with-png=/foo/dir with_png="/foo/dir" -if test "x$with_png" != "xno" -a "x$with_turbovnc" != "xyes"; then +if test "x$with_png" != "xno"; then if test ! -z "$with_png" -a "x$with_png" != "xyes"; then # add user supplied directory to flags: saved_CPPFLAGS="$CPPFLAGS" diff --git a/libvncserver/Makefile.am b/libvncserver/Makefile.am index 6a29b84..c0cd4c3 100644 --- a/libvncserver/Makefile.am +++ b/libvncserver/Makefile.am @@ -46,11 +46,7 @@ EXTRA_DIST=tableinit24.c tableinittctemplate.c tabletranstemplate.c \ if HAVE_LIBZ ZLIBSRCS = zlib.c zrle.c zrleoutstream.c zrlepalettehelper.c ../common/zywrletemplate.c if HAVE_LIBJPEG -if HAVE_TURBOVNC -TIGHTSRCS = turbo.c ../common/turbojpeg.c -else -TIGHTSRCS = tight.c -endif +TIGHTSRCS = tight.c ../common/turbojpeg.c endif endif diff --git a/libvncserver/rfbserver.c b/libvncserver/rfbserver.c index 3f4b896..12d1d72 100644 --- a/libvncserver/rfbserver.c +++ b/libvncserver/rfbserver.c @@ -3,7 +3,7 @@ */ /* - * Copyright (C) 2011 D. R. Commander + * Copyright (C) 2011-2012 D. R. Commander * Copyright (C) 2005 Rohit Kumar, Johannes E. Schindelin * Copyright (C) 2002 RealVNC Ltd. * OSXvnc Copyright (C) 2001 Dan McGuirk . @@ -86,10 +86,10 @@ static int compat_mkdir(const char *path, int mode) #define mkdir compat_mkdir #endif -#ifdef LIBVNCSERVER_HAVE_TURBOVNC +#ifdef LIBVNCSERVER_HAVE_LIBJPEG /* * Map of quality levels to provide compatibility with TightVNC/TigerVNC - * clients + * clients. This emulates the behavior of the TigerVNC Server. */ static const int tight2turbo_qual[10] = { @@ -383,10 +383,8 @@ rfbNewTCPOrUDPClient(rfbScreenInfoPtr rfbScreen, #if defined(LIBVNCSERVER_HAVE_LIBJPEG) || defined(LIBVNCSERVER_HAVE_LIBPNG) cl->tightCompressLevel = TIGHT_DEFAULT_COMPRESSION; #endif -#ifdef LIBVNCSERVER_HAVE_TURBOVNC - cl->tightSubsampLevel = TIGHT_DEFAULT_SUBSAMP; -#endif #ifdef LIBVNCSERVER_HAVE_LIBJPEG + cl->turboSubsampLevel = TURBO_DEFAULT_SUBSAMP; { int i; for (i = 0; i < 4; i++) @@ -1969,11 +1967,12 @@ rfbProcessClientNormalMessage(rfbClientPtr cl) cl->enableServerIdentity = FALSE; #if defined(LIBVNCSERVER_HAVE_LIBZ) || defined(LIBVNCSERVER_HAVE_LIBPNG) cl->tightQualityLevel = -1; -#if defined(LIBVNCSERVER_HAVE_LIBJPEG) || defined(LIBVNCSERVER_HAVE_TURBOVNC) || defined(LIBVNCSERVER_HAVE_LIBPNG) +#if defined(LIBVNCSERVER_HAVE_LIBJPEG) || defined(LIBVNCSERVER_HAVE_LIBPNG) cl->tightCompressLevel = TIGHT_DEFAULT_COMPRESSION; #endif -#ifdef LIBVNCSERVER_HAVE_TURBOVNC - cl->tightSubsampLevel = TIGHT_DEFAULT_SUBSAMP; +#ifdef LIBVNCSERVER_HAVE_LIBJPEG + cl->turboSubsampLevel = TURBO_DEFAULT_SUBSAMP; + cl->turboQualityLevel = -1; #endif #endif @@ -2105,29 +2104,26 @@ rfbProcessClientNormalMessage(rfbClientPtr cl) rfbLog("Using compression level %d for client %s\n", cl->tightCompressLevel, cl->host); #endif -#ifdef LIBVNCSERVER_HAVE_TURBOVNC - } else if ( enc >= (uint32_t)rfbEncodingSubsamp1X && - enc <= (uint32_t)rfbEncodingSubsampGray ) { - cl->tightSubsampLevel = enc & 0xFF; - rfbLog("Using JPEG subsampling %d for client %s\n", - cl->tightSubsampLevel, cl->host); - } else if ( enc >= (uint32_t)rfbEncodingQualityLevel0 && - enc <= (uint32_t)rfbEncodingQualityLevel9 ) { - cl->tightQualityLevel = tight2turbo_qual[enc & 0x0F]; - cl->tightSubsampLevel = tight2turbo_subsamp[enc & 0x0F]; - rfbLog("Using JPEG subsampling %d, Q%d for client %s\n", - cl->tightSubsampLevel, cl->tightQualityLevel, cl->host); - } else if ( enc >= (uint32_t)rfbEncodingFineQualityLevel0 + 1 && - enc <= (uint32_t)rfbEncodingFineQualityLevel100 ) { - cl->tightQualityLevel = enc & 0xFF; - rfbLog("Using image quality level %d for client %s\n", - cl->tightQualityLevel, cl->host); -#else } else if ( enc >= (uint32_t)rfbEncodingQualityLevel0 && enc <= (uint32_t)rfbEncodingQualityLevel9 ) { cl->tightQualityLevel = enc & 0x0F; rfbLog("Using image quality level %d for client %s\n", cl->tightQualityLevel, cl->host); +#ifdef LIBVNCSERVER_HAVE_LIBJPEG + cl->turboQualityLevel = tight2turbo_qual[enc & 0x0F]; + cl->turboSubsampLevel = tight2turbo_subsamp[enc & 0x0F]; + rfbLog("Using JPEG subsampling %d, Q%d for client %s\n", + cl->turboSubsampLevel, cl->turboQualityLevel, cl->host); + } else if ( enc >= (uint32_t)rfbEncodingFineQualityLevel0 + 1 && + enc <= (uint32_t)rfbEncodingFineQualityLevel100 ) { + cl->turboQualityLevel = enc & 0xFF; + rfbLog("Using fine quality level %d for client %s\n", + cl->turboQualityLevel, cl->host); + } else if ( enc >= (uint32_t)rfbEncodingSubsamp1X && + enc <= (uint32_t)rfbEncodingSubsampGray ) { + cl->turboSubsampLevel = enc & 0xFF; + rfbLog("Using subsampling level %d for client %s\n", + cl->turboSubsampLevel, cl->host); #endif } else #endif diff --git a/libvncserver/tight.c b/libvncserver/tight.c index 474e9e3..3650458 100644 --- a/libvncserver/tight.c +++ b/libvncserver/tight.c @@ -2,9 +2,20 @@ * tight.c * * Routines to implement Tight Encoding + * + * Our Tight encoder is based roughly on the TurboVNC v0.6 encoder with some + * additional enhancements from TurboVNC 1.1. For lower compression levels, + * this encoder provides a tremendous reduction in CPU usage (and subsequently, + * an increase in throughput for CPU-limited environments) relative to the + * TightVNC encoder, whereas Compression Level 9 provides a low-bandwidth mode + * that behaves similarly to Compression Levels 5-9 in the old TightVNC + * encoder. */ /* + * Copyright (C) 2010-2012 D. R. Commander. All Rights Reserved. + * Copyright (C) 2005-2008 Sun Microsystems, Inc. All Rights Reserved. + * Copyright (C) 2004 Landmark Graphics Corporation. All Rights Reserved. * Copyright (C) 2000, 2001 Const Kaplinsky. All Rights Reserved. * Copyright (C) 1999 AT&T Laboratories Cambridge. All Rights Reserved. * @@ -20,27 +31,18 @@ * * 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, + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, * USA. */ -/*#include */ #include #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 #endif -#include +#include "turbojpeg.h" + /* Note: The following constant should not be changed. */ #define TIGHT_MIN_TO_COMPRESS 12 @@ -50,9 +52,6 @@ #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 @@ -68,30 +67,24 @@ rfbBool rfbTightDisableGradient = FALSE; /* 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 monoMinRectSize; + int idxZlibLevel, monoZlibLevel, rawZlibLevel; int idxMaxColorsDivisor; - int jpegQuality, jpegThreshold, jpegThreshold24; + int palMaxColorsWithJPEG; } 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 } +static TIGHT_CONF tightConf[4] = { + { 65536, 2048, 6, 0, 0, 0, 4, 24 }, // 0 (used only without JPEG) + { 65536, 2048, 32, 1, 1, 1, 96, 24 }, // 1 + { 65536, 2048, 32, 3, 3, 2, 96, 96 }, // 2 (used only with JPEG) + { 65536, 2048, 32, 7, 7, 5, 96, 256 } // 9 }; #ifdef LIBVNCSERVER_HAVE_LIBPNG @@ -113,8 +106,14 @@ static TIGHT_PNG_CONF tightPngConf[10] = { }; #endif -static TLS int compressLevel = 0; -static TLS int qualityLevel = 0; +static TLS int compressLevel = 1; +static TLS int qualityLevel = 95; +static TLS int subsampLevel = TJ_444; + +static const int subsampLevel2tjsubsamp[4] = { + TJ_444, TJ_420, TJ_422, TJ_GRAYSCALE +}; + /* Stuff dealing with palettes. */ @@ -150,22 +149,24 @@ static TLS char *tightBeforeBuf = NULL; static TLS int tightAfterBufSize = 0; static TLS char *tightAfterBuf = NULL; -static TLS int *prevRowBuf = NULL; +static TLS tjhandle j = NULL; -void rfbTightCleanup(rfbScreenInfoPtr screen) +void rfbTightCleanup (rfbScreenInfoPtr screen) { - if(tightBeforeBufSize) { - free(tightBeforeBuf); - tightBeforeBufSize=0; - tightBeforeBuf = NULL; - } - if(tightAfterBufSize) { - free(tightAfterBuf); - tightAfterBufSize=0; - tightAfterBuf = NULL; - } + if (tightBeforeBufSize) { + free (tightBeforeBuf); + tightBeforeBufSize = 0; + tightBeforeBuf = NULL; + } + if (tightAfterBufSize) { + free (tightAfterBuf); + tightAfterBufSize = 0; + tightAfterBuf = NULL; + } + if (j) tjDestroy(j); } + /* Prototypes for static functions. */ static rfbBool SendRectEncodingTight(rfbClientPtr cl, int x, int y, @@ -176,13 +177,13 @@ 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); + uint32_t *colorPtr, rfbBool needSameColor); static rfbBool CheckSolidTile8 (rfbClientPtr cl, int x, int y, int w, int h, - uint32_t *colorPtr, rfbBool needSameColor); + uint32_t *colorPtr, rfbBool needSameColor); static rfbBool CheckSolidTile16 (rfbClientPtr cl, int x, int y, int w, int h, - uint32_t *colorPtr, rfbBool needSameColor); + uint32_t *colorPtr, rfbBool needSameColor); static rfbBool CheckSolidTile32 (rfbClientPtr cl, int x, int y, int w, int h, - uint32_t *colorPtr, rfbBool needSameColor); + 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); @@ -192,49 +193,40 @@ 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 rfbBool CompressData (rfbClientPtr cl, int streamId, int dataLen, + int zlibLevel, int zlibStrategy); +static rfbBool SendCompressedData (rfbClientPtr cl, char *buf, + int compressedLen); -static void Pack24(rfbClientPtr cl, char *buf, rfbPixelFormat *fmt, int count); +static void FillPalette8 (int count); +static void FillPalette16 (int count); +static void FillPalette32 (int count); +static void FastFillPalette16 (rfbClientPtr cl, uint16_t *data, int w, + int pitch, int h); +static void FastFillPalette32 (rfbClientPtr cl, uint32_t *data, int w, + int pitch, int h); -static void EncodeIndexedRect16(uint8_t *buf, int count); -static void EncodeIndexedRect32(uint8_t *buf, int count); +static void PaletteReset (void); +static int PaletteInsert (uint32_t rgb, int numPixels, int bpp); -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 Pack24 (rfbClientPtr cl, char *buf, rfbPixelFormat *fmt, + int count); -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 void EncodeIndexedRect16 (uint8_t *buf, int count); +static void EncodeIndexedRect32 (uint8_t *buf, int count); -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 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 rfbBool SendJpegRect(rfbClientPtr cl, int x, int y, int w, int h, - int quality); +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); @@ -257,10 +249,10 @@ rfbNumCodedRectsTight(rfbClientPtr cl, /* 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; + return 0; - maxRectSize = tightConf[cl->tightCompressLevel].maxRectSize; - maxRectWidth = tightConf[cl->tightCompressLevel].maxRectWidth; + maxRectSize = tightConf[compressLevel].maxRectSize; + maxRectWidth = tightConf[compressLevel].maxRectWidth; if (w > maxRectWidth || w * h > maxRectSize) { subrectMaxWidth = (w > maxRectWidth) ? maxRectWidth : w; @@ -311,7 +303,34 @@ SendRectEncodingTight(rfbClientPtr cl, rfbSendUpdateBuf(cl); compressLevel = cl->tightCompressLevel; - qualityLevel = cl->tightQualityLevel; + qualityLevel = cl->turboQualityLevel; + subsampLevel = cl->turboSubsampLevel; + + /* We only allow compression levels that have a demonstrable performance + benefit. CL 0 with JPEG reduces CPU usage for workloads that have low + numbers of unique colors, but the same thing can be accomplished by + using CL 0 without JPEG (AKA "Lossless Tight.") For those same + low-color workloads, CL 2 can provide typically 20-40% better + compression than CL 1 (with a commensurate increase in CPU usage.) For + high-color workloads, CL 1 should always be used, as higher compression + levels increase CPU usage for these workloads without providing any + significant reduction in bandwidth. */ + if (qualityLevel != -1) { + if (compressLevel < 1) compressLevel = 1; + if (compressLevel > 2) compressLevel = 2; + } + + /* With JPEG disabled, CL 2 offers no significant bandwidth savings over + CL 1, so we don't include it. */ + else if (compressLevel > 1) compressLevel = 1; + + /* CL 9 (which maps internally to CL 3) is included mainly for backward + compatibility with TightVNC Compression Levels 5-9. It should be used + only in extremely low-bandwidth cases in which it can be shown to have a + benefit. For low-color workloads, it provides typically only 10-20% + better compression than CL 2 with JPEG and CL 1 without JPEG, and it + uses, on average, twice as much CPU time. */ + if (compressLevel == 9) compressLevel = 3; if ( cl->format.depth == 24 && cl->format.redMax == 0xFF && cl->format.greenMax == 0xFF && cl->format.blueMax == 0xFF ) { @@ -331,7 +350,7 @@ SendRectEncodingTight(rfbClientPtr cl, tightBeforeBuf = (char *)malloc(tightBeforeBufSize); else tightBeforeBuf = (char *)realloc(tightBeforeBuf, - tightBeforeBufSize); + tightBeforeBufSize); } /* Calculate maximum number of rows in one non-solid rectangle. */ @@ -359,15 +378,24 @@ SendRectEncodingTight(rfbClientPtr cl, } dh = (dy + MAX_SPLIT_TILE_SIZE <= y + h) ? - MAX_SPLIT_TILE_SIZE : (y + h - dy); + 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); + MAX_SPLIT_TILE_SIZE : (x + w - dx); if (CheckSolidTile(cl, dx, dy, dw, dh, &colorValue, FALSE)) { + if (subsampLevel == TJ_GRAYSCALE && qualityLevel != -1) { + uint32_t r = (colorValue >> 16) & 0xFF; + uint32_t g = (colorValue >> 8) & 0xFF; + uint32_t b = (colorValue) & 0xFF; + double y = (0.257 * (double)r) + (0.504 * (double)g) + + (0.098 * (double)b) + 16.; + colorValue = (int)y + (((int)y) << 8) + (((int)y) << 16); + } + /* Get dimensions of solid-color area. */ FindBestSolidArea(cl, dx, dy, w - (dx - x), h - (dy - y), @@ -415,12 +443,12 @@ SendRectEncodingTight(rfbClientPtr cl, /* 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) ) + !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) ) + !SendRectEncodingTight(cl, x, y_best + h_best, + w, h - (y_best-y) - h_best) ) return FALSE; /* Return after all recursive calls are done. */ @@ -437,6 +465,7 @@ SendRectEncodingTight(rfbClientPtr cl, return SendRectSimple(cl, x, y, w, h); } + static void FindBestSolidArea(rfbClientPtr cl, int x, @@ -456,16 +485,16 @@ FindBestSolidArea(rfbClientPtr cl, 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); + MAX_SPLIT_TILE_SIZE : (y + h - dy); dw = (w_prev > MAX_SPLIT_TILE_SIZE) ? - MAX_SPLIT_TILE_SIZE : w_prev; + 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); + MAX_SPLIT_TILE_SIZE : (x + w_prev - dx); if (!CheckSolidTile(cl, dx, dy, dw, dh, &colorValue, TRUE)) break; dx += dw; @@ -482,6 +511,7 @@ FindBestSolidArea(rfbClientPtr cl, *h_ptr = h_best; } + static void ExtendSolidArea(rfbClientPtr cl, int x, @@ -525,6 +555,7 @@ ExtendSolidArea(rfbClientPtr cl, *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 @@ -544,6 +575,7 @@ static rfbBool CheckSolidTile(rfbClientPtr cl, int x, int y, int w, int h, uint3 } } + #define DEFINE_CHECK_SOLID_FUNCTION(bpp) \ \ static rfbBool \ @@ -554,8 +586,8 @@ CheckSolidTile##bpp(rfbClientPtr cl, int x, int y, int w, int h, \ uint##bpp##_t colorValue; \ int dx, dy; \ \ - fbptr = (uint##bpp##_t *) \ - &cl->scaledScreen->frameBuffer[y * cl->scaledScreen->paddedWidthInBytes + x * (bpp/8)]; \ + fbptr = (uint##bpp##_t *)&cl->scaledScreen->frameBuffer \ + [y * cl->scaledScreen->paddedWidthInBytes + x * (bpp/8)]; \ \ colorValue = *fbptr; \ if (needSameColor && (uint32_t)colorValue != *colorPtr) \ @@ -566,7 +598,8 @@ CheckSolidTile##bpp(rfbClientPtr cl, int x, int y, int w, int h, \ if (colorValue != fbptr[dx]) \ return FALSE; \ } \ - fbptr = (uint##bpp##_t *)((uint8_t *)fbptr + cl->scaledScreen->paddedWidthInBytes); \ + fbptr = (uint##bpp##_t *)((uint8_t *)fbptr \ + + cl->scaledScreen->paddedWidthInBytes); \ } \ \ *colorPtr = (uint32_t)colorValue; \ @@ -598,7 +631,7 @@ SendRectSimple(rfbClientPtr cl, int x, int y, int w, int h) tightBeforeBuf = (char *)malloc(tightBeforeBufSize); else tightBeforeBuf = (char *)realloc(tightBeforeBuf, - tightBeforeBufSize); + tightBeforeBufSize); } if (tightAfterBufSize < maxAfterSize) { @@ -607,7 +640,7 @@ SendRectSimple(rfbClientPtr cl, int x, int y, int w, int h) tightAfterBuf = (char *)malloc(tightAfterBufSize); else tightAfterBuf = (char *)realloc(tightAfterBuf, - tightAfterBufSize); + tightAfterBufSize); } if (w > maxRectWidth || w * h > maxRectSize) { @@ -618,7 +651,7 @@ SendRectSimple(rfbClientPtr cl, int x, int y, int w, int h) 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)) + if (!SendSubrect(cl, x + dx, y + dy, rw, rh)) return FALSE; } } @@ -649,39 +682,68 @@ SendSubrect(rfbClientPtr cl, if (!SendTightHeader(cl, x, y, w, h)) return FALSE; - fbptr = (cl->scaledScreen->frameBuffer + (cl->scaledScreen->paddedWidthInBytes * y) + 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); + if (subsampLevel == TJ_GRAYSCALE && qualityLevel != -1) + return SendJpegRect(cl, x, y, w, h, qualityLevel); paletteMaxColors = w * h / tightConf[compressLevel].idxMaxColorsDivisor; + if(qualityLevel != -1) + paletteMaxColors = tightConf[compressLevel].palMaxColorsWithJPEG; 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); + + if (cl->format.bitsPerPixel == cl->screen->serverFormat.bitsPerPixel && + cl->format.redMax == cl->screen->serverFormat.redMax && + cl->format.greenMax == cl->screen->serverFormat.greenMax && + cl->format.blueMax == cl->screen->serverFormat.blueMax && + cl->format.bitsPerPixel >= 16) { + + /* This is so we can avoid translating the pixels when compressing + with JPEG, since it is unnecessary */ + switch (cl->format.bitsPerPixel) { + case 16: + FastFillPalette16(cl, (uint16_t *)fbptr, w, + cl->scaledScreen->paddedWidthInBytes / 2, h); + break; + default: + FastFillPalette32(cl, (uint32_t *)fbptr, w, + cl->scaledScreen->paddedWidthInBytes / 4, h); + } + + if(paletteNumColors != 0 || qualityLevel == -1) { + (*cl->translateFn)(cl->translateLookupTable, + &cl->screen->serverFormat, &cl->format, fbptr, + tightBeforeBuf, + cl->scaledScreen->paddedWidthInBytes, w, h); + } + } + else { + (*cl->translateFn)(cl->translateLookupTable, &cl->screen->serverFormat, + &cl->format, fbptr, tightBeforeBuf, + cl->scaledScreen->paddedWidthInBytes, w, h); + + 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); - } + if (qualityLevel != -1) { + success = SendJpegRect(cl, x, y, w, h, qualityLevel); } else { success = SendFullColorRect(cl, x, y, w, h); } @@ -696,14 +758,7 @@ SendSubrect(rfbClientPtr cl, 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); - } + success = SendIndexedRect(cl, x, y, w, h); } return success; } @@ -732,8 +787,10 @@ SendTightHeader(rfbClientPtr cl, sz_rfbFramebufferUpdateRectHeader); cl->ublen += sz_rfbFramebufferUpdateRectHeader; - rfbStatRecordEncodingSent(cl, cl->tightEncoding, sz_rfbFramebufferUpdateRectHeader, - sz_rfbFramebufferUpdateRectHeader + w * (cl->format.bitsPerPixel / 8) * h); + rfbStatRecordEncodingSent(cl, cl->tightEncoding, + sz_rfbFramebufferUpdateRectHeader, + sz_rfbFramebufferUpdateRectHeader + + w * (cl->format.bitsPerPixel / 8) * h); return TRUE; } @@ -762,7 +819,7 @@ SendSolidRect(rfbClientPtr cl) memcpy (&cl->updateBuf[cl->ublen], tightBeforeBuf, len); cl->ublen += len; - rfbStatRecordEncodingSentAdd(cl, cl->tightEncoding, len+1); + rfbStatRecordEncodingSentAdd(cl, cl->tightEncoding, len + 1); return TRUE; } @@ -795,7 +852,12 @@ SendMonoRect(rfbClientPtr cl, dataLen = (w + 7) / 8; dataLen *= h; - cl->updateBuf[cl->ublen++] = (streamId | rfbTightExplicitFilter) << 4; + if (tightConf[compressLevel].monoZlibLevel == 0 && + cl->tightEncoding != rfbEncodingTightPng) + cl->updateBuf[cl->ublen++] = + (char)((rfbTightNoZlib | rfbTightExplicitFilter) << 4); + else + cl->updateBuf[cl->ublen++] = (streamId | rfbTightExplicitFilter) << 4; cl->updateBuf[cl->ublen++] = rfbTightFilterPalette; cl->updateBuf[cl->ublen++] = 1; @@ -866,7 +928,12 @@ SendIndexedRect(rfbClientPtr cl, } /* Prepare tight encoding header. */ - cl->updateBuf[cl->ublen++] = (streamId | rfbTightExplicitFilter) << 4; + if (tightConf[compressLevel].idxZlibLevel == 0 && + cl->tightEncoding != rfbEncodingTightPng) + cl->updateBuf[cl->ublen++] = + (char)((rfbTightNoZlib | rfbTightExplicitFilter) << 4); + else + cl->updateBuf[cl->ublen++] = (streamId | rfbTightExplicitFilter) << 4; cl->updateBuf[cl->ublen++] = rfbTightFilterPalette; cl->updateBuf[cl->ublen++] = (char)(paletteNumColors - 1); @@ -886,9 +953,11 @@ SendIndexedRect(rfbClientPtr cl, } else entryLen = 4; - memcpy(&cl->updateBuf[cl->ublen], tightAfterBuf, paletteNumColors * entryLen); + memcpy(&cl->updateBuf[cl->ublen], tightAfterBuf, + paletteNumColors * entryLen); cl->ublen += paletteNumColors * entryLen; - rfbStatRecordEncodingSentAdd(cl, cl->tightEncoding, 3 + paletteNumColors * entryLen); + rfbStatRecordEncodingSentAdd(cl, cl->tightEncoding, + 3 + paletteNumColors * entryLen); break; case 16: @@ -901,7 +970,8 @@ SendIndexedRect(rfbClientPtr cl, memcpy(&cl->updateBuf[cl->ublen], tightAfterBuf, paletteNumColors * 2); cl->ublen += paletteNumColors * 2; - rfbStatRecordEncodingSentAdd(cl, cl->tightEncoding, 3 + paletteNumColors * 2); + rfbStatRecordEncodingSentAdd(cl, cl->tightEncoding, + 3 + paletteNumColors * 2); break; default: @@ -934,7 +1004,11 @@ SendFullColorRect(rfbClientPtr cl, return FALSE; } - cl->updateBuf[cl->ublen++] = 0x00; /* stream id = 0, no flushing, no filter */ + if (tightConf[compressLevel].rawZlibLevel == 0 && + cl->tightEncoding != rfbEncodingTightPng) + cl->updateBuf[cl->ublen++] = (char)(rfbTightNoZlib << 4); + else + cl->updateBuf[cl->ublen++] = 0x00; /* stream id = 0, no flushing, no filter */ rfbStatRecordEncodingSentAdd(cl, cl->tightEncoding, 1); if (usePixelFormat24) { @@ -948,53 +1022,6 @@ SendFullColorRect(rfbClientPtr cl, 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, @@ -1012,6 +1039,9 @@ CompressData(rfbClientPtr cl, return TRUE; } + if (zlibLevel == 0) + return SendCompressedData (cl, tightBeforeBuf, dataLen); + pz = &cl->zsStruct[streamId]; /* Initialize compression stream if needed. */ @@ -1044,15 +1074,16 @@ CompressData(rfbClientPtr cl, } /* Actual compression. */ - if ( deflate (pz, Z_SYNC_FLUSH) != Z_OK || - pz->avail_in != 0 || pz->avail_out == 0 ) { + 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); + return SendCompressedData(cl, tightAfterBuf, + tightAfterBufSize - pz->avail_out); } -static rfbBool SendCompressedData(rfbClientPtr cl, +static rfbBool SendCompressedData(rfbClientPtr cl, char *buf, int compressedLen) { int i, portionLen; @@ -1079,7 +1110,7 @@ static rfbBool SendCompressedData(rfbClientPtr cl, if (!rfbSendUpdateBuf(cl)) return FALSE; } - memcpy(&cl->updateBuf[cl->ublen], &tightAfterBuf[i], portionLen); + memcpy(&cl->updateBuf[cl->ublen], &buf[i], portionLen); cl->ublen += portionLen; } rfbStatRecordEncodingSentAdd(cl, cl->tightEncoding, compressedLen); @@ -1087,6 +1118,7 @@ static rfbBool SendCompressedData(rfbClientPtr cl, return TRUE; } + /* * Code to determine how many different colors used in rectangle. */ @@ -1133,6 +1165,7 @@ FillPalette8(int count) } } + #define DEFINE_FILL_PALETTE_FUNCTION(bpp) \ \ static void \ @@ -1198,13 +1231,117 @@ FillPalette##bpp(int count) { \ DEFINE_FILL_PALETTE_FUNCTION(16) DEFINE_FILL_PALETTE_FUNCTION(32) +#define DEFINE_FAST_FILL_PALETTE_FUNCTION(bpp) \ + \ +static void \ +FastFillPalette##bpp(rfbClientPtr cl, uint##bpp##_t *data, int w, \ + int pitch, int h) \ +{ \ + uint##bpp##_t c0, c1, ci, mask, c0t, c1t, cit; \ + int i, j, i2 = 0, j2, n0, n1, ni; \ + \ + if (cl->translateFn != rfbTranslateNone) { \ + mask = cl->screen->serverFormat.redMax \ + << cl->screen->serverFormat.redShift; \ + mask |= cl->screen->serverFormat.greenMax \ + << cl->screen->serverFormat.greenShift; \ + mask |= cl->screen->serverFormat.blueMax \ + << cl->screen->serverFormat.blueShift; \ + } else mask = ~0; \ + \ + c0 = data[0] & mask; \ + for (j = 0; j < h; j++) { \ + for (i = 0; i < w; i++) { \ + if ((data[j * pitch + i] & mask) != c0) \ + goto done; \ + } \ + } \ + done: \ + if (j >= h) { \ + paletteNumColors = 1; /* Solid rectangle */ \ + return; \ + } \ + if (paletteMaxColors < 2) { \ + paletteNumColors = 0; /* Full-color encoding preferred */ \ + return; \ + } \ + \ + n0 = j * w + i; \ + c1 = data[j * pitch + i] & mask; \ + n1 = 0; \ + i++; if (i >= w) {i = 0; j++;} \ + for (j2 = j; j2 < h; j2++) { \ + for (i2 = i; i2 < w; i2++) { \ + ci = data[j2 * pitch + i2] & mask; \ + if (ci == c0) { \ + n0++; \ + } else if (ci == c1) { \ + n1++; \ + } else \ + goto done2; \ + } \ + i = 0; \ + } \ + done2: \ + (*cl->translateFn)(cl->translateLookupTable, \ + &cl->screen->serverFormat, &cl->format, \ + (char *)&c0, (char *)&c0t, bpp/8, 1, 1); \ + (*cl->translateFn)(cl->translateLookupTable, \ + &cl->screen->serverFormat, &cl->format, \ + (char *)&c1, (char *)&c1t, bpp/8, 1, 1); \ + if (j2 >= h) { \ + if (n0 > n1) { \ + monoBackground = (uint32_t)c0t; \ + monoForeground = (uint32_t)c1t; \ + } else { \ + monoBackground = (uint32_t)c1t; \ + monoForeground = (uint32_t)c0t; \ + } \ + paletteNumColors = 2; /* Two colors */ \ + return; \ + } \ + \ + PaletteReset(); \ + PaletteInsert (c0t, (uint32_t)n0, bpp); \ + PaletteInsert (c1t, (uint32_t)n1, bpp); \ + \ + ni = 1; \ + i2++; if (i2 >= w) {i2 = 0; j2++;} \ + for (j = j2; j < h; j++) { \ + for (i = i2; i < w; i++) { \ + if ((data[j * pitch + i] & mask) == ci) { \ + ni++; \ + } else { \ + (*cl->translateFn)(cl->translateLookupTable, \ + &cl->screen->serverFormat, \ + &cl->format, (char *)&ci, \ + (char *)&cit, bpp/8, 1, 1); \ + if (!PaletteInsert (cit, (uint32_t)ni, bpp)) \ + return; \ + ci = data[j * pitch + i] & mask; \ + ni = 1; \ + } \ + } \ + i2 = 0; \ + } \ + \ + (*cl->translateFn)(cl->translateLookupTable, \ + &cl->screen->serverFormat, &cl->format, \ + (char *)&ci, (char *)&cit, bpp/8, 1, 1); \ + PaletteInsert (cit, (uint32_t)ni, bpp); \ +} + +DEFINE_FAST_FILL_PALETTE_FUNCTION(16) +DEFINE_FAST_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)) +#define HASH_FUNC16(rgb) ((int)((((rgb) >> 8) + (rgb)) & 0xFF)) +#define HASH_FUNC32(rgb) ((int)((((rgb) >> 16) + ((rgb) >> 8)) & 0xFF)) + static void PaletteReset(void) @@ -1213,6 +1350,7 @@ PaletteReset(void) memset(palette.hash, 0, 256 * sizeof(COLOR_LIST *)); } + static int PaletteInsert(uint32_t rgb, int numPixels, @@ -1353,6 +1491,7 @@ EncodeIndexedRect##bpp(uint8_t *buf, int count) { \ DEFINE_IDX_ENCODE_FUNCTION(16) DEFINE_IDX_ENCODE_FUNCTION(32) + #define DEFINE_MONO_ENCODE_FUNCTION(bpp) \ \ static void \ @@ -1409,379 +1548,110 @@ 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. + * JPEG compression stuff. */ -static void -FilterGradient24(rfbClientPtr cl, char *buf, rfbPixelFormat *fmt, int w, int h) +static rfbBool +SendJpegRect(rfbClientPtr cl, int x, int y, int w, int h, int quality) { - 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). - */ + unsigned char *srcbuf; + int ps = cl->screen->serverFormat.bitsPerPixel / 8; + int subsamp = subsampLevel2tjsubsamp[subsampLevel]; + unsigned long size = 0; + int flags = 0, pitch; + unsigned char *tmpbuf = NULL; -#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) + return SendFullColorRect(cl, x, y, w, h); - if ( cl->screen->serverFormat.bitsPerPixel == 8 || fmt->bitsPerPixel == 8 || - w < DETECT_MIN_WIDTH || h < DETECT_MIN_HEIGHT ) { + if (ps < 2) { + rfbLog("Error: JPEG requires 16-bit, 24-bit, or 32-bit pixel format.\n"); return 0; } - - if (qualityLevel != -1) { - if (w * h < JPEG_MIN_RECT_SIZE) { - return 0; - } - } else { - if ( rfbTightDisableGradient || - w * h < tightConf[compressLevel].gradientMinRectSize ) { + if (!j) { + if ((j = tjInitCompress()) == NULL) { + rfbLog("JPEG Error: %s\n", tjGetErrorStr()); 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); + if (tightAfterBufSize < TJBUFSIZE(w, h)) { + if (tightAfterBuf == NULL) + tightAfterBuf = (char *)malloc(TJBUFSIZE(w, h)); + else + tightAfterBuf = (char *)realloc(tightAfterBuf, + TJBUFSIZE(w, h)); + if (!tightAfterBuf) { + rfbLog("Memory allocation failure!\n"); + return 0; } - } 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++; + tightAfterBufSize = TJBUFSIZE(w, h); + } + + if (ps == 2) { + uint16_t *srcptr, pix; + unsigned char *dst; + int inRed, inGreen, inBlue, i, j; + + if((tmpbuf = (unsigned char *)malloc(w * h * 3)) == NULL) + rfbLog("Memory allocation failure!\n"); + srcptr = (uint16_t *)&cl->scaledScreen->frameBuffer + [y * cl->scaledScreen->paddedWidthInBytes + x * ps]; + dst = tmpbuf; + for(j = 0; j < h; j++) { + uint16_t *srcptr2 = srcptr; + unsigned char *dst2 = dst; + for (i = 0; i < w; i++) { + pix = *srcptr2++; + 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); + *dst2++ = (uint8_t)((inRed * 255 + + cl->screen->serverFormat.redMax / 2) + / cl->screen->serverFormat.redMax); + *dst2++ = (uint8_t)((inGreen * 255 + + cl->screen->serverFormat.greenMax / 2) + / cl->screen->serverFormat.greenMax); + *dst2++ = (uint8_t)((inBlue * 255 + + cl->screen->serverFormat.blueMax / 2) + / cl->screen->serverFormat.blueMax); } + srcptr += cl->scaledScreen->paddedWidthInBytes / ps; + dst += w * 3; } - if (w > h) { - x += h; - y = 0; - } else { - x = 0; - y += w; + srcbuf = tmpbuf; + pitch = w * 3; + ps = 3; + } else { + if (cl->screen->serverFormat.bigEndian && ps == 4) + flags |= TJ_ALPHAFIRST; + if (cl->screen->serverFormat.redShift == 16 + && cl->screen->serverFormat.blueShift == 0) + flags |= TJ_BGR; + if (cl->screen->serverFormat.bigEndian) + flags ^= TJ_BGR; + pitch = cl->scaledScreen->paddedWidthInBytes; + srcbuf = (unsigned char *)&cl->scaledScreen->frameBuffer + [y * pitch + x * ps]; + } + + if (tjCompress(j, srcbuf, w, pitch, h, ps, (unsigned char *)tightAfterBuf, + &size, subsamp, quality, flags) == -1) { + rfbLog("JPEG Error: %s\n", tjGetErrorStr()); + if (tmpbuf) { + free(tmpbuf); + tmpbuf = NULL; } - } - - 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); + if (tmpbuf) { + free(tmpbuf); + tmpbuf = NULL; } - 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)) @@ -1791,7 +1661,7 @@ SendJpegRect(rfbClientPtr cl, int x, int y, int w, int h, int quality) cl->updateBuf[cl->ublen++] = (char)(rfbTightJpeg << 4); rfbStatRecordEncodingSentAdd(cl, cl->tightEncoding, 1); - return SendCompressedData(cl, jpegDstDataLen); + return SendCompressedData(cl, tightAfterBuf, (int)size); } static void @@ -1870,43 +1740,6 @@ PrepareRowForImg##bpp(rfbClientPtr cl, uint8_t *dst, int x, int y, int count) { 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. */ @@ -2062,6 +1895,6 @@ static rfbBool SendPngRect(rfbClientPtr cl, int x, int y, int w, int h) { rfbStatRecordEncodingSentAdd(cl, cl->tightEncoding, 1); /* rfbLog("<< SendPngRect\n"); */ - return SendCompressedData(cl, pngDstDataLen); + return SendCompressedData(cl, tightAfterBuf, pngDstDataLen); } #endif diff --git a/libvncserver/turbo.c b/libvncserver/turbo.c deleted file mode 100644 index f205b6d..0000000 --- a/libvncserver/turbo.c +++ /dev/null @@ -1,1566 +0,0 @@ -/* - * turbo.c - * - * Routines to implement TurboVNC Encoding - */ - -/* - * Copyright (C) 2010-2012 D. R. Commander. All Rights Reserved. - * Copyright (C) 2005-2008 Sun Microsystems, Inc. All Rights Reserved. - * Copyright (C) 2004 Landmark Graphics Corporation. All Rights Reserved. - * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, - * USA. - */ - -#include -#include "private.h" - -#include "turbojpeg.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 - -/* - * 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; - int idxZlibLevel, monoZlibLevel, rawZlibLevel; - int idxMaxColorsDivisor; - int palMaxColorsWithJPEG; -} TIGHT_CONF; - -static TIGHT_CONF tightConf[3] = { - { 65536, 2048, 6, 0, 0, 0, 4, 24 }, - { 65536, 2048, 32, 1, 1, 1, 96, 24 }, - { 65536, 2048, 32, 3, 3, 2, 96, 96 } -}; - -static TLS int compressLevel = 1; -static TLS int qualityLevel = 95; -static TLS int subsampLevel = TJ_444; - -static const int subsampLevel2tjsubsamp[4] = { - TJ_444, TJ_420, TJ_422, TJ_GRAYSCALE -}; - - -/* 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 tjhandle j = NULL; - -void rfbTightCleanup (rfbScreenInfoPtr screen) -{ - if (tightBeforeBufSize) { - free (tightBeforeBuf); - tightBeforeBufSize = 0; - tightBeforeBuf = NULL; - } - if (tightAfterBufSize) { - free (tightAfterBuf); - tightAfterBufSize = 0; - tightAfterBuf = NULL; - } - if (j) tjDestroy(j); -} - - -/* Prototypes for static functions. */ - -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 w, int h); -static rfbBool SendIndexedRect (rfbClientPtr cl, int w, int h); -static rfbBool SendFullColorRect (rfbClientPtr cl, int w, int h); - -static rfbBool CompressData (rfbClientPtr cl, int streamId, int dataLen, - int zlibLevel, int zlibStrategy); -static rfbBool SendCompressedData (rfbClientPtr cl, char *buf, - int compressedLen); - -static void FillPalette8 (int count); -static void FillPalette16 (int count); -static void FillPalette32 (int count); -static void FastFillPalette16 (rfbClientPtr cl, uint16_t *data, int w, - int pitch, int h); -static void FastFillPalette32 (rfbClientPtr cl, uint32_t *data, int w, - int pitch, int h); - -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 rfbBool SendJpegRect (rfbClientPtr cl, int x, int y, int w, int h, - int quality); - -/* - * 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[compressLevel].maxRectSize; - maxRectWidth = tightConf[compressLevel].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) -{ - 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; - subsampLevel = cl->tightSubsampLevel; - - /* We only allow compression levels that have a demonstrable performance - benefit. CL 0 with JPEG reduces CPU usage for workloads that have low - numbers of unique colors, but the same thing can be accomplished by - using CL 0 without JPEG (AKA "Lossless Tight.") CL 2 is a mixed bag. - It can be shown to reduce bandwidth (and commensurately increase CPU - usage) by typically 30-40% relative to CL 1, but only when it is used in - conjunction with high-quality JPEG, and only on workloads that have low - numbers of unique colors. Increasing the amount of Zlib compression - beyond CL 2 cannot be shown to provide any significant bandwidth savings - except in very rare corner cases that are not performance-critical to - begin with, and higher Zlib levels increase CPU usage exponentially. */ - if (qualityLevel != -1) { - if (compressLevel < 1) compressLevel = 1; - if (compressLevel > 2) compressLevel = 2; - } - - /* With JPEG disabled, increasing the Zlib compression level beyond CL 1 - offers no significant bandwidth savings, and the CPU usage starts to - increase exponentially. */ - else if (compressLevel > 1) compressLevel = 1; - - 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)) { - - if (subsampLevel == TJ_GRAYSCALE && qualityLevel != -1) { - uint32_t r = (colorValue >> 16) & 0xFF; - uint32_t g = (colorValue >> 8) & 0xFF; - uint32_t b = (colorValue) & 0xFF; - double y = (0.257 * (double)r) + (0.504 * (double)g) - + (0.098 * (double)b) + 16.; - colorValue = (int)y + (((int)y) << 8) + (((int)y) << 16); - } - - /* 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 && - !rfbSendRectEncodingTight(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 && - !rfbSendRectEncodingTight(cl, x_best + w_best, y_best, - w - (x_best-x) - w_best, h_best) ) - return FALSE; - if ( y_best + h_best != y + h && - !rfbSendRectEncodingTight(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))); - - if (subsampLevel == TJ_GRAYSCALE && qualityLevel != -1) - return SendJpegRect(cl, x, y, w, h, qualityLevel); - - paletteMaxColors = w * h / tightConf[compressLevel].idxMaxColorsDivisor; - if(qualityLevel != -1) - paletteMaxColors = tightConf[compressLevel].palMaxColorsWithJPEG; - if ( paletteMaxColors < 2 && - w * h >= tightConf[compressLevel].monoMinRectSize ) { - paletteMaxColors = 2; - } - - if (cl->format.bitsPerPixel == cl->screen->serverFormat.bitsPerPixel && - cl->format.redMax == cl->screen->serverFormat.redMax && - cl->format.greenMax == cl->screen->serverFormat.greenMax && - cl->format.blueMax == cl->screen->serverFormat.blueMax && - cl->format.bitsPerPixel >= 16) { - - /* This is so we can avoid translating the pixels when compressing - with JPEG, since it is unnecessary */ - switch (cl->format.bitsPerPixel) { - case 16: - FastFillPalette16(cl, (uint16_t *)fbptr, w, - cl->scaledScreen->paddedWidthInBytes / 2, h); - break; - default: - FastFillPalette32(cl, (uint32_t *)fbptr, w, - cl->scaledScreen->paddedWidthInBytes / 4, h); - } - - if(paletteNumColors != 0 || qualityLevel == -1) { - (*cl->translateFn)(cl->translateLookupTable, - &cl->screen->serverFormat, &cl->format, fbptr, - tightBeforeBuf, - cl->scaledScreen->paddedWidthInBytes, w, h); - } - } - else { - (*cl->translateFn)(cl->translateLookupTable, &cl->screen->serverFormat, - &cl->format, fbptr, tightBeforeBuf, - cl->scaledScreen->paddedWidthInBytes, w, h); - - 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 (qualityLevel != -1) { - success = SendJpegRect(cl, x, y, w, h, qualityLevel); - } else { - success = SendFullColorRect(cl, w, h); - } - break; - case 1: - /* Solid rectangle */ - success = SendSolidRect(cl); - break; - case 2: - /* Two-color rectangle */ - success = SendMonoRect(cl, w, h); - break; - default: - /* Up to 256 different colors */ - success = SendIndexedRect(cl, 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(rfbEncodingTight); - - memcpy(&cl->updateBuf[cl->ublen], (char *)&rect, - sz_rfbFramebufferUpdateRectHeader); - cl->ublen += sz_rfbFramebufferUpdateRectHeader; - - rfbStatRecordEncodingSent(cl, rfbEncodingTight, - 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, rfbEncodingTight, len + 1); - - return TRUE; -} - -static rfbBool -SendMonoRect(rfbClientPtr cl, - int w, - int h) -{ - int streamId = 1; - int paletteLen, dataLen; - - 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; - - if (tightConf[compressLevel].monoZlibLevel == 0) - cl->updateBuf[cl->ublen++] = - (char)((rfbTightNoZlib | rfbTightExplicitFilter) << 4); - else - 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, rfbEncodingTight, 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, rfbEncodingTight, 7); - break; - - default: - EncodeMonoRect8((uint8_t *)tightBeforeBuf, w, h); - - cl->updateBuf[cl->ublen++] = (char)monoBackground; - cl->updateBuf[cl->ublen++] = (char)monoForeground; - rfbStatRecordEncodingSentAdd(cl, rfbEncodingTight, 5); - } - - return CompressData(cl, streamId, dataLen, - tightConf[compressLevel].monoZlibLevel, - Z_DEFAULT_STRATEGY); -} - -static rfbBool -SendIndexedRect(rfbClientPtr cl, - int w, - int h) -{ - int streamId = 2; - int i, entryLen; - - 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. */ - if (tightConf[compressLevel].idxZlibLevel == 0) - cl->updateBuf[cl->ublen++] = - (char)((rfbTightNoZlib | rfbTightExplicitFilter) << 4); - else - 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, rfbEncodingTight, - 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, rfbEncodingTight, - 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 w, - int h) -{ - int streamId = 0; - int len; - - if (cl->ublen + TIGHT_MIN_TO_COMPRESS + 1 > UPDATE_BUF_SIZE) { - if (!rfbSendUpdateBuf(cl)) - return FALSE; - } - - if (tightConf[compressLevel].rawZlibLevel == 0) - cl->updateBuf[cl->ublen++] = (char)(rfbTightNoZlib << 4); - else - cl->updateBuf[cl->ublen++] = 0x00; /* stream id = 0, no flushing, no filter */ - rfbStatRecordEncodingSentAdd(cl, rfbEncodingTight, 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 -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, rfbEncodingTight, dataLen); - return TRUE; - } - - if (zlibLevel == 0) - return SendCompressedData (cl, tightBeforeBuf, dataLen); - - 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, tightAfterBuf, - tightAfterBufSize - pz->avail_out); -} - -static rfbBool SendCompressedData(rfbClientPtr cl, char *buf, - int compressedLen) -{ - int i, portionLen; - - cl->updateBuf[cl->ublen++] = compressedLen & 0x7F; - rfbStatRecordEncodingSentAdd(cl, rfbEncodingTight, 1); - if (compressedLen > 0x7F) { - cl->updateBuf[cl->ublen-1] |= 0x80; - cl->updateBuf[cl->ublen++] = compressedLen >> 7 & 0x7F; - rfbStatRecordEncodingSentAdd(cl, rfbEncodingTight, 1); - if (compressedLen > 0x3FFF) { - cl->updateBuf[cl->ublen-1] |= 0x80; - cl->updateBuf[cl->ublen++] = compressedLen >> 14 & 0xFF; - rfbStatRecordEncodingSentAdd(cl, rfbEncodingTight, 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], &buf[i], portionLen); - cl->ublen += portionLen; - } - rfbStatRecordEncodingSentAdd(cl, rfbEncodingTight, 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) - -#define DEFINE_FAST_FILL_PALETTE_FUNCTION(bpp) \ - \ -static void \ -FastFillPalette##bpp(rfbClientPtr cl, uint##bpp##_t *data, int w, \ - int pitch, int h) \ -{ \ - uint##bpp##_t c0, c1, ci, mask, c0t, c1t, cit; \ - int i, j, i2 = 0, j2, n0, n1, ni; \ - \ - if (cl->translateFn != rfbTranslateNone) { \ - mask = cl->screen->serverFormat.redMax \ - << cl->screen->serverFormat.redShift; \ - mask |= cl->screen->serverFormat.greenMax \ - << cl->screen->serverFormat.greenShift; \ - mask |= cl->screen->serverFormat.blueMax \ - << cl->screen->serverFormat.blueShift; \ - } else mask = ~0; \ - \ - c0 = data[0] & mask; \ - for (j = 0; j < h; j++) { \ - for (i = 0; i < w; i++) { \ - if ((data[j * pitch + i] & mask) != c0) \ - goto done; \ - } \ - } \ - done: \ - if (j >= h) { \ - paletteNumColors = 1; /* Solid rectangle */ \ - return; \ - } \ - if (paletteMaxColors < 2) { \ - paletteNumColors = 0; /* Full-color encoding preferred */ \ - return; \ - } \ - \ - n0 = j * w + i; \ - c1 = data[j * pitch + i] & mask; \ - n1 = 0; \ - i++; if (i >= w) {i = 0; j++;} \ - for (j2 = j; j2 < h; j2++) { \ - for (i2 = i; i2 < w; i2++) { \ - ci = data[j2 * pitch + i2] & mask; \ - if (ci == c0) { \ - n0++; \ - } else if (ci == c1) { \ - n1++; \ - } else \ - goto done2; \ - } \ - i = 0; \ - } \ - done2: \ - (*cl->translateFn)(cl->translateLookupTable, \ - &cl->screen->serverFormat, &cl->format, \ - (char *)&c0, (char *)&c0t, bpp/8, 1, 1); \ - (*cl->translateFn)(cl->translateLookupTable, \ - &cl->screen->serverFormat, &cl->format, \ - (char *)&c1, (char *)&c1t, bpp/8, 1, 1); \ - if (j2 >= h) { \ - if (n0 > n1) { \ - monoBackground = (uint32_t)c0t; \ - monoForeground = (uint32_t)c1t; \ - } else { \ - monoBackground = (uint32_t)c1t; \ - monoForeground = (uint32_t)c0t; \ - } \ - paletteNumColors = 2; /* Two colors */ \ - return; \ - } \ - \ - PaletteReset(); \ - PaletteInsert (c0t, (uint32_t)n0, bpp); \ - PaletteInsert (c1t, (uint32_t)n1, bpp); \ - \ - ni = 1; \ - i2++; if (i2 >= w) {i2 = 0; j2++;} \ - for (j = j2; j < h; j++) { \ - for (i = i2; i < w; i++) { \ - if ((data[j * pitch + i] & mask) == ci) { \ - ni++; \ - } else { \ - (*cl->translateFn)(cl->translateLookupTable, \ - &cl->screen->serverFormat, \ - &cl->format, (char *)&ci, \ - (char *)&cit, bpp/8, 1, 1); \ - if (!PaletteInsert (cit, (uint32_t)ni, bpp)) \ - return; \ - ci = data[j * pitch + i] & mask; \ - ni = 1; \ - } \ - } \ - i2 = 0; \ - } \ - \ - (*cl->translateFn)(cl->translateLookupTable, \ - &cl->screen->serverFormat, &cl->format, \ - (char *)&ci, (char *)&cit, bpp/8, 1, 1); \ - PaletteInsert (cit, (uint32_t)ni, bpp); \ -} - -DEFINE_FAST_FILL_PALETTE_FUNCTION(16) -DEFINE_FAST_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) - - -/* - * JPEG compression stuff. - */ - -static rfbBool -SendJpegRect(rfbClientPtr cl, int x, int y, int w, int h, int quality) -{ - unsigned char *srcbuf; - int ps = cl->screen->serverFormat.bitsPerPixel / 8; - int subsamp = subsampLevel2tjsubsamp[subsampLevel]; - unsigned long size = 0; - int flags = 0, pitch; - unsigned char *tmpbuf = NULL; - - if (cl->screen->serverFormat.bitsPerPixel == 8) - return SendFullColorRect(cl, w, h); - - if (ps < 2) { - rfbLog("Error: JPEG requires 16-bit, 24-bit, or 32-bit pixel format.\n"); - return 0; - } - if (!j) { - if ((j = tjInitCompress()) == NULL) { - rfbLog("JPEG Error: %s\n", tjGetErrorStr()); - return 0; - } - } - - if (tightAfterBufSize < TJBUFSIZE(w, h)) { - if (tightAfterBuf == NULL) - tightAfterBuf = (char *)malloc(TJBUFSIZE(w, h)); - else - tightAfterBuf = (char *)realloc(tightAfterBuf, - TJBUFSIZE(w, h)); - if (!tightAfterBuf) { - rfbLog("Memory allocation failure!\n"); - return 0; - } - tightAfterBufSize = TJBUFSIZE(w, h); - } - - if (ps == 2) { - uint16_t *srcptr, pix; - unsigned char *dst; - int inRed, inGreen, inBlue, i, j; - - if((tmpbuf = (unsigned char *)malloc(w * h * 3)) == NULL) - rfbLog("Memory allocation failure!\n"); - srcptr = (uint16_t *)&cl->scaledScreen->frameBuffer - [y * cl->scaledScreen->paddedWidthInBytes + x * ps]; - dst = tmpbuf; - for(j = 0; j < h; j++) { - uint16_t *srcptr2 = srcptr; - unsigned char *dst2 = dst; - for (i = 0; i < w; i++) { - pix = *srcptr2++; - 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); - *dst2++ = (uint8_t)((inRed * 255 - + cl->screen->serverFormat.redMax / 2) - / cl->screen->serverFormat.redMax); - *dst2++ = (uint8_t)((inGreen * 255 - + cl->screen->serverFormat.greenMax / 2) - / cl->screen->serverFormat.greenMax); - *dst2++ = (uint8_t)((inBlue * 255 - + cl->screen->serverFormat.blueMax / 2) - / cl->screen->serverFormat.blueMax); - } - srcptr += cl->scaledScreen->paddedWidthInBytes / ps; - dst += w * 3; - } - srcbuf = tmpbuf; - pitch = w * 3; - ps = 3; - } else { - if (cl->screen->serverFormat.bigEndian && ps == 4) - flags |= TJ_ALPHAFIRST; - if (cl->screen->serverFormat.redShift == 16 - && cl->screen->serverFormat.blueShift == 0) - flags |= TJ_BGR; - if (cl->screen->serverFormat.bigEndian) - flags ^= TJ_BGR; - pitch = cl->scaledScreen->paddedWidthInBytes; - srcbuf = (unsigned char *)&cl->scaledScreen->frameBuffer - [y * pitch + x * ps]; - } - - if (tjCompress(j, srcbuf, w, pitch, h, ps, (unsigned char *)tightAfterBuf, - &size, subsamp, quality, flags) == -1) { - rfbLog("JPEG Error: %s\n", tjGetErrorStr()); - if (tmpbuf) { - free(tmpbuf); - tmpbuf = NULL; - } - return 0; - } - - if (tmpbuf) { - free(tmpbuf); - tmpbuf = NULL; - } - - 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, rfbEncodingTight, 1); - - return SendCompressedData(cl, tightAfterBuf, (int)size); -} diff --git a/rfb/rfb.h b/rfb/rfb.h index 91dea83..3f0dbc0 100644 --- a/rfb/rfb.h +++ b/rfb/rfb.h @@ -673,9 +673,10 @@ typedef struct _rfbClientRec { int afterEncBufLen; #if defined(LIBVNCSERVER_HAVE_LIBZ) || defined(LIBVNCSERVER_HAVE_LIBPNG) uint32_t tightEncoding; /* rfbEncodingTight or rfbEncodingTightPng */ -#ifdef LIBVNCSERVER_HAVE_TURBOVNC +#ifdef LIBVNCSERVER_HAVE_LIBJPEG /* TurboVNC Encoding support (extends TightVNC) */ - int tightSubsampLevel; + int turboSubsampLevel; + int turboQualityLevel; // 1-100 scale #endif #endif @@ -875,8 +876,8 @@ extern rfbBool rfbSendRectEncodingZlib(rfbClientPtr cl, int x, int y, int w, #define TIGHT_DEFAULT_COMPRESSION 6 -#ifdef LIBVNCSERVER_HAVE_TURBOVNC -#define TIGHT_DEFAULT_SUBSAMP 0 +#ifdef LIBVNCSERVER_HAVE_LIBJPEG +#define TURBO_DEFAULT_SUBSAMP 0 #endif extern rfbBool rfbTightDisableGradient; diff --git a/rfb/rfbproto.h b/rfb/rfbproto.h index d99fbe5..acb2e7b 100644 --- a/rfb/rfbproto.h +++ b/rfb/rfbproto.h @@ -469,7 +469,6 @@ typedef struct { * 0xFFFFFFF0 .. 0xFFFFFFFF -- cross-encoding compression levels. */ -#ifdef LIBVNCSERVER_HAVE_TURBOVNC #define rfbEncodingFineQualityLevel0 0xFFFFFE00 #define rfbEncodingFineQualityLevel100 0xFFFFFE64 #define rfbEncodingSubsamp1X 0xFFFFFD00 @@ -478,7 +477,6 @@ typedef struct { #define rfbEncodingSubsampGray 0xFFFFFD03 #define rfbEncodingSubsamp8X 0xFFFFFD04 #define rfbEncodingSubsamp16X 0xFFFFFD05 -#endif #define rfbEncodingCompressLevel0 0xFFFFFF00 #define rfbEncodingCompressLevel1 0xFFFFFF01 @@ -734,12 +732,13 @@ typedef struct { * bit 3: if 1, then compression stream 3 should be reset; * bits 7-4: if 1000 (0x08), then the compression type is "fill", * if 1001 (0x09), then the compression type is "jpeg", - * (TurboVNC) if 1010 (0x0A), then the compression type is "basic" - * and no Zlib compression was used, - * (TurboVNC) if 1110 (0x0E), then the compression type is "basic", - * no Zlib compression was used, and a "filter id" byte follows - * this byte, - * (TightVNC) if 1010 (0x0A), then the compression type is "png", + * (Tight only) if 1010 (0x0A), then the compression type is + * "basic" and no Zlib compression was used, + * (Tight only) if 1110 (0x0E), then the compression type is + * "basic", no Zlib compression was used, and a "filter id" byte + * follows this byte, + * (TightPng only) if 1010 (0x0A), then the compression type is + * "png", * if 0xxx, then the compression type is "basic" and Zlib * compression was used, * values greater than 1010 are not valid. @@ -858,11 +857,8 @@ typedef struct { #define rfbTightExplicitFilter 0x04 #define rfbTightFill 0x08 #define rfbTightJpeg 0x09 -#ifdef LIBVNCSERVER_HAVE_TURBOVNC #define rfbTightNoZlib 0x0A -#else #define rfbTightPng 0x0A -#endif #define rfbTightMaxSubencoding 0x0A /* Filters to improve compression efficiency */ diff --git a/test/Makefile.am b/test/Makefile.am index 30498c2..11aea37 100644 --- a/test/Makefile.am +++ b/test/Makefile.am @@ -1,3 +1,14 @@ +# TurboJPEG wrapper tests + +noinst_PROGRAMS=tjunittest tjbench + +tjunittest_SOURCES=tjunittest.c ../common/turbojpeg.c ../common/turbojpeg.h \ + tjutil.c tjutil.h + +tjbench_SOURCES=tjbench.c ../common/turbojpeg.c ../common/turbojpeg.h \ + tjutil.c tjutil.h bmp.c bmp.h + + INCLUDES = -I$(top_srcdir) LDADD = ../libvncserver/libvncserver.la ../libvncclient/libvncclient.la @WSOCKLIB@ diff --git a/test/bmp.c b/test/bmp.c new file mode 100644 index 0000000..7dcbf13 --- /dev/null +++ b/test/bmp.c @@ -0,0 +1,389 @@ +/* Copyright (C)2004 Landmark Graphics Corporation + * Copyright (C)2005 Sun Microsystems, Inc. + * Copyright (C)2010, 2012 D. R. Commander + * + * This library is free software and may be redistributed and/or modified under + * the terms of the wxWindows Library License, Version 3.1 or (at your option) + * any later version. The full license is in the LICENSE.txt file included + * with this distribution. + * + * This library 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 + * wxWindows Library License for more details. +*/ + +#include +#include +#include +#include +#include +#include +#include +#ifdef _WIN32 + #include +#else + #include +#endif +#include "./tjutil.h" +#include "./bmp.h" + +#define byteswap(i) ( \ + (((i) & 0xff000000) >> 24) | \ + (((i) & 0x00ff0000) >> 8) | \ + (((i) & 0x0000ff00) << 8) | \ + (((i) & 0x000000ff) << 24) ) + +#define byteswap16(i) ( \ + (((i) & 0xff00) >> 8) | \ + (((i) & 0x00ff) << 8) ) + +static __inline int littleendian(void) +{ + unsigned int value=1; + unsigned char *ptr=(unsigned char *)(&value); + if(ptr[0]==1 && ptr[3]==0) return 1; + else return 0; +} + +#ifndef BI_BITFIELDS +#define BI_BITFIELDS 3L +#endif +#ifndef BI_RGB +#define BI_RGB 0L +#endif + +#define BMPHDRSIZE 54 +typedef struct _bmphdr +{ + unsigned short bfType; + unsigned int bfSize; + unsigned short bfReserved1, bfReserved2; + unsigned int bfOffBits; + + unsigned int biSize; + int biWidth, biHeight; + unsigned short biPlanes, biBitCount; + unsigned int biCompression, biSizeImage; + int biXPelsPerMeter, biYPelsPerMeter; + unsigned int biClrUsed, biClrImportant; +} bmphdr; + +static const char *__bmperr="No error"; + +static const int ps[BMPPIXELFORMATS]={3, 4, 3, 4, 4, 4}; +static const int roffset[BMPPIXELFORMATS]={0, 0, 2, 2, 3, 1}; +static const int goffset[BMPPIXELFORMATS]={1, 1, 1, 1, 2, 2}; +static const int boffset[BMPPIXELFORMATS]={2, 2, 0, 0, 1, 3}; + +#define _throw(m) {__bmperr=m; retcode=-1; goto finally;} +#define _unix(f) {if((f)==-1) _throw(strerror(errno));} +#define _catch(f) {if((f)==-1) {retcode=-1; goto finally;}} + +#define readme(fd, addr, size) \ + if((bytesread=read(fd, addr, (size)))==-1) _throw(strerror(errno)); \ + if(bytesread!=(size)) _throw("Read error"); + +void pixelconvert(unsigned char *srcbuf, enum BMPPIXELFORMAT srcformat, + int srcpitch, unsigned char *dstbuf, enum BMPPIXELFORMAT dstformat, int dstpitch, + int w, int h, int flip) +{ + unsigned char *srcptr, *srcptr0, *dstptr, *dstptr0; + int i, j; + + srcptr=flip? &srcbuf[srcpitch*(h-1)]:srcbuf; + for(j=0, dstptr=dstbuf; jBMPPIXELFORMATS-1 || align<1) + _throw("invalid argument to loadbmp()"); + if((align&(align-1))!=0) + _throw("Alignment must be a power of 2"); + _unix(fd=open(filename, flags)); + + readme(fd, &bh.bfType, sizeof(unsigned short)); + if(!littleendian()) bh.bfType=byteswap16(bh.bfType); + + if(bh.bfType==0x3650) + { + _catch(loadppm(&fd, buf, w, h, f, align, dstbottomup, 0)); + goto finally; + } + if(bh.bfType==0x3350) + { + _catch(loadppm(&fd, buf, w, h, f, align, dstbottomup, 1)); + goto finally; + } + + readme(fd, &bh.bfSize, sizeof(unsigned int)); + readme(fd, &bh.bfReserved1, sizeof(unsigned short)); + readme(fd, &bh.bfReserved2, sizeof(unsigned short)); + readme(fd, &bh.bfOffBits, sizeof(unsigned int)); + readme(fd, &bh.biSize, sizeof(unsigned int)); + readme(fd, &bh.biWidth, sizeof(int)); + readme(fd, &bh.biHeight, sizeof(int)); + readme(fd, &bh.biPlanes, sizeof(unsigned short)); + readme(fd, &bh.biBitCount, sizeof(unsigned short)); + readme(fd, &bh.biCompression, sizeof(unsigned int)); + readme(fd, &bh.biSizeImage, sizeof(unsigned int)); + readme(fd, &bh.biXPelsPerMeter, sizeof(int)); + readme(fd, &bh.biYPelsPerMeter, sizeof(int)); + readme(fd, &bh.biClrUsed, sizeof(unsigned int)); + readme(fd, &bh.biClrImportant, sizeof(unsigned int)); + + if(!littleendian()) + { + bh.bfSize=byteswap(bh.bfSize); + bh.bfOffBits=byteswap(bh.bfOffBits); + bh.biSize=byteswap(bh.biSize); + bh.biWidth=byteswap(bh.biWidth); + bh.biHeight=byteswap(bh.biHeight); + bh.biPlanes=byteswap16(bh.biPlanes); + bh.biBitCount=byteswap16(bh.biBitCount); + bh.biCompression=byteswap(bh.biCompression); + bh.biSizeImage=byteswap(bh.biSizeImage); + bh.biXPelsPerMeter=byteswap(bh.biXPelsPerMeter); + bh.biYPelsPerMeter=byteswap(bh.biYPelsPerMeter); + bh.biClrUsed=byteswap(bh.biClrUsed); + bh.biClrImportant=byteswap(bh.biClrImportant); + } + + if(bh.bfType!=0x4d42 || bh.bfOffBitsBMPPIXELFORMATS-1 || srcpitch<0) + _throw("bad argument to savebmp()"); + + if(srcpitch==0) srcpitch=w*ps[f]; + + if((temp=strrchr(filename, '.'))!=NULL) + { + if(!strcasecmp(temp, ".ppm")) + return saveppm(filename, buf, w, h, f, srcpitch, srcbottomup); + } + + _unix(fd=open(filename, flags, mode)); + dstpitch=((w*3)+3)&(~3); + + bh.bfType=0x4d42; + bh.bfSize=BMPHDRSIZE+dstpitch*h; + bh.bfReserved1=0; bh.bfReserved2=0; + bh.bfOffBits=BMPHDRSIZE; + bh.biSize=40; + bh.biWidth=w; bh.biHeight=h; + bh.biPlanes=0; bh.biBitCount=24; + bh.biCompression=BI_RGB; bh.biSizeImage=0; + bh.biXPelsPerMeter=0; bh.biYPelsPerMeter=0; + bh.biClrUsed=0; bh.biClrImportant=0; + + if(!littleendian()) + { + bh.bfType=byteswap16(bh.bfType); + bh.bfSize=byteswap(bh.bfSize); + bh.bfOffBits=byteswap(bh.bfOffBits); + bh.biSize=byteswap(bh.biSize); + bh.biWidth=byteswap(bh.biWidth); + bh.biHeight=byteswap(bh.biHeight); + bh.biPlanes=byteswap16(bh.biPlanes); + bh.biBitCount=byteswap16(bh.biBitCount); + bh.biCompression=byteswap(bh.biCompression); + bh.biSizeImage=byteswap(bh.biSizeImage); + bh.biXPelsPerMeter=byteswap(bh.biXPelsPerMeter); + bh.biYPelsPerMeter=byteswap(bh.biYPelsPerMeter); + bh.biClrUsed=byteswap(bh.biClrUsed); + bh.biClrImportant=byteswap(bh.biClrImportant); + } + + writeme(fd, &bh.bfType, sizeof(unsigned short)); + writeme(fd, &bh.bfSize, sizeof(unsigned int)); + writeme(fd, &bh.bfReserved1, sizeof(unsigned short)); + writeme(fd, &bh.bfReserved2, sizeof(unsigned short)); + writeme(fd, &bh.bfOffBits, sizeof(unsigned int)); + writeme(fd, &bh.biSize, sizeof(unsigned int)); + writeme(fd, &bh.biWidth, sizeof(int)); + writeme(fd, &bh.biHeight, sizeof(int)); + writeme(fd, &bh.biPlanes, sizeof(unsigned short)); + writeme(fd, &bh.biBitCount, sizeof(unsigned short)); + writeme(fd, &bh.biCompression, sizeof(unsigned int)); + writeme(fd, &bh.biSizeImage, sizeof(unsigned int)); + writeme(fd, &bh.biXPelsPerMeter, sizeof(int)); + writeme(fd, &bh.biYPelsPerMeter, sizeof(int)); + writeme(fd, &bh.biClrUsed, sizeof(unsigned int)); + writeme(fd, &bh.biClrImportant, sizeof(unsigned int)); + + if((tempbuf=(unsigned char *)malloc(dstpitch*h))==NULL) + _throw("Memory allocation error"); + + pixelconvert(buf, f, srcpitch, tempbuf, BMP_BGR, dstpitch, w, h, + !srcbottomup); + + if((byteswritten=write(fd, tempbuf, dstpitch*h))!=dstpitch*h) + _throw(strerror(errno)); + + finally: + if(tempbuf) free(tempbuf); + if(fd!=-1) close(fd); + return retcode; +} + +const char *bmpgeterr(void) +{ + return __bmperr; +} diff --git a/test/bmp.h b/test/bmp.h new file mode 100644 index 0000000..055b1ee --- /dev/null +++ b/test/bmp.h @@ -0,0 +1,49 @@ +/* Copyright (C)2004 Landmark Graphics Corporation + * Copyright (C)2005 Sun Microsystems, Inc. + * Copyright (C)2011 D. R. Commander + * + * This library is free software and may be redistributed and/or modified under + * the terms of the wxWindows Library License, Version 3.1 or (at your option) + * any later version. The full license is in the LICENSE.txt file included + * with this distribution. + * + * This library 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 + * wxWindows Library License for more details. +*/ + +// This provides rudimentary facilities for loading and saving true color +// BMP and PPM files + +#ifndef __BMP_H__ +#define __BMP_H__ + +#define BMPPIXELFORMATS 6 +enum BMPPIXELFORMAT {BMP_RGB=0, BMP_RGBX, BMP_BGR, BMP_BGRX, BMP_XBGR, BMP_XRGB}; + +#ifdef __cplusplus +extern "C" { +#endif + +// This will load a Windows bitmap from a file and return a buffer with the +// specified pixel format, scanline alignment, and orientation. The width and +// height are returned in w and h. + +int loadbmp(char *filename, unsigned char **buf, int *w, int *h, + enum BMPPIXELFORMAT f, int align, int dstbottomup); + +// This will save a buffer with the specified pixel format, pitch, orientation, +// width, and height as a 24-bit Windows bitmap or PPM (the filename determines +// which format to use) + +int savebmp(char *filename, unsigned char *buf, int w, int h, + enum BMPPIXELFORMAT f, int srcpitch, int srcbottomup); + +const char *bmpgeterr(void); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/test/tjbench.c b/test/tjbench.c new file mode 100644 index 0000000..b1f343f --- /dev/null +++ b/test/tjbench.c @@ -0,0 +1,658 @@ +/* + * Copyright (C)2009-2012 D. R. Commander. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of the libjpeg-turbo Project nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS", + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include "./bmp.h" +#include "./tjutil.h" +#include "./turbojpeg.h" + + +#define _throw(op, err) { \ + printf("ERROR in line %d while %s:\n%s\n", __LINE__, op, err); \ + retval=-1; goto bailout;} +#define _throwunix(m) _throw(m, strerror(errno)) +#define _throwtj(m) _throw(m, tjGetErrorStr()) +#define _throwbmp(m) _throw(m, bmpgeterr()) + +int flags=0, decomponly=0, quiet=0, dotile=0, pf=TJPF_BGR; +char *ext="ppm"; +const char *pixFormatStr[TJ_NUMPF]= +{ + "RGB", "BGR", "RGBX", "BGRX", "XBGR", "XRGB", "GRAY" +}; +const int bmpPF[TJ_NUMPF]= +{ + BMP_RGB, BMP_BGR, BMP_RGBX, BMP_BGRX, BMP_XBGR, BMP_XRGB, -1 +}; +const char *subNameLong[TJ_NUMSAMP]= +{ + "4:4:4", "4:2:2", "4:2:0", "GRAY", "4:4:0" +}; +const char *subName[NUMSUBOPT]={"444", "422", "420", "GRAY", "440"}; +tjscalingfactor *scalingfactors=NULL, sf={1, 1}; int nsf=0; +double benchtime=5.0; + + +char *sigfig(double val, int figs, char *buf, int len) +{ + char format[80]; + int digitsafterdecimal=figs-(int)ceil(log10(fabs(val))); + if(digitsafterdecimal<1) snprintf(format, 80, "%%.0f"); + else snprintf(format, 80, "%%.%df", digitsafterdecimal); + snprintf(buf, len, format, val); + return buf; +} + + +/* Decompression test */ +int decomptest(unsigned char *srcbuf, unsigned char **jpegbuf, + unsigned long *jpegsize, unsigned char *dstbuf, int w, int h, + int subsamp, int jpegqual, char *filename, int tilew, int tileh) +{ + char tempstr[1024], sizestr[20]="\0", qualstr[6]="\0", *ptr; + FILE *file=NULL; tjhandle handle=NULL; + int row, col, i, dstbufalloc=0, retval=0; + double start, elapsed; + int ps=tjPixelSize[pf]; + int bufsize; + int scaledw=TJSCALED(w, sf); + int scaledh=TJSCALED(h, sf); + int pitch=scaledw*ps; + int ntilesw=(w+tilew-1)/tilew, ntilesh=(h+tileh-1)/tileh; + unsigned char *dstptr, *dstptr2; + + if(jpegqual>0) + { + snprintf(qualstr, 6, "_Q%d", jpegqual); + qualstr[5]=0; + } + + if((handle=tjInitDecompress())==NULL) + _throwtj("executing tjInitDecompress()"); + + bufsize=pitch*scaledh; + if(dstbuf==NULL) + { + if((dstbuf=(unsigned char *)malloc(bufsize)) == NULL) + _throwunix("allocating image buffer"); + dstbufalloc=1; + } + /* Set the destination buffer to gray so we know whether the decompressor + attempted to write to it */ + memset(dstbuf, 127, bufsize); + + /* Execute once to preload cache */ + if(tjDecompress2(handle, jpegbuf[0], jpegsize[0], dstbuf, scaledw, + pitch, scaledh, pf, flags)==-1) + _throwtj("executing tjDecompress2()"); + + /* Benchmark */ + for(i=0, start=gettime(); (elapsed=gettime()-start) Frame rate: %f fps\n", (double)i/elapsed); + printf(" Dest. throughput: %f Megapixels/sec\n", + (double)(w*h)/1000000.*(double)i/elapsed); + } + if(sf.num!=1 || sf.denom!=1) + snprintf(sizestr, 20, "%d_%d", sf.num, sf.denom); + else if(tilew!=w || tileh!=h) + snprintf(sizestr, 20, "%dx%d", tilew, tileh); + else snprintf(sizestr, 20, "full"); + if(decomponly) + snprintf(tempstr, 1024, "%s_%s.%s", filename, sizestr, ext); + else + snprintf(tempstr, 1024, "%s_%s%s_%s.%s", filename, subName[subsamp], + qualstr, sizestr, ext); + if(savebmp(tempstr, dstbuf, scaledw, scaledh, bmpPF[pf], pitch, + (flags&TJFLAG_BOTTOMUP)!=0)==-1) + _throwbmp("saving bitmap"); + ptr=strrchr(tempstr, '.'); + snprintf(ptr, 1024-(ptr-tempstr), "-err.%s", ext); + if(srcbuf && sf.num==1 && sf.denom==1) + { + if(!quiet) printf("Compression error written to %s.\n", tempstr); + if(subsamp==TJ_GRAYSCALE) + { + int index, index2; + for(row=0, index=0; row255) y=255; if(y<0) y=0; + dstbuf[rindex]=abs(dstbuf[rindex]-y); + dstbuf[gindex]=abs(dstbuf[gindex]-y); + dstbuf[bindex]=abs(dstbuf[bindex]-y); + } + } + } + else + { + for(row=0; row>>>> %s (%s) <--> JPEG %s Q%d <<<<<\n", pixFormatStr[pf], + (flags&TJFLAG_BOTTOMUP)? "Bottom-up":"Top-down", subNameLong[subsamp], + jpegqual); + + for(tilew=dotile? 8:w, tileh=dotile? 8:h; ; tilew*=2, tileh*=2) + { + if(tilew>w) tilew=w; if(tileh>h) tileh=h; + ntilesw=(w+tilew-1)/tilew; ntilesh=(h+tileh-1)/tileh; + + if((jpegbuf=(unsigned char **)malloc(sizeof(unsigned char *) + *ntilesw*ntilesh))==NULL) + _throwunix("allocating JPEG tile array"); + memset(jpegbuf, 0, sizeof(unsigned char *)*ntilesw*ntilesh); + if((jpegsize=(unsigned long *)malloc(sizeof(unsigned long) + *ntilesw*ntilesh))==NULL) + _throwunix("allocating JPEG size array"); + memset(jpegsize, 0, sizeof(unsigned long)*ntilesw*ntilesh); + + for(i=0; i Frame rate: %f fps\n", (double)i/elapsed); + printf(" Output image size: %d bytes\n", totaljpegsize); + printf(" Compression ratio: %f:1\n", + (double)(w*h*ps)/(double)totaljpegsize); + printf(" Source throughput: %f Megapixels/sec\n", + (double)(w*h)/1000000.*(double)i/elapsed); + printf(" Output bit stream: %f Megabits/sec\n", + (double)totaljpegsize*8./1000000.*(double)i/elapsed); + } + if(tilew==w && tileh==h) + { + snprintf(tempstr, 1024, "%s_%s_Q%d.jpg", filename, subName[subsamp], + jpegqual); + if((file=fopen(tempstr, "wb"))==NULL) + _throwunix("opening reference image"); + if(fwrite(jpegbuf[0], jpegsize[0], 1, file)!=1) + _throwunix("writing reference image"); + fclose(file); file=NULL; + if(!quiet) printf("Reference image written to %s\n", tempstr); + } + + /* Decompression test */ + if(decomptest(srcbuf, jpegbuf, jpegsize, tmpbuf, w, h, subsamp, jpegqual, + filename, tilew, tileh)==-1) + goto bailout; + + for(i=0; i>>>> JPEG %s --> %s (%s) <<<<<\n", subNameLong[subsamp], + pixFormatStr[pf], (flags&TJFLAG_BOTTOMUP)? "Bottom-up":"Top-down"); + } + + for(tilew=dotile? 16:w, tileh=dotile? 16:h; ; tilew*=2, tileh*=2) + { + if(tilew>w) tilew=w; if(tileh>h) tileh=h; + ntilesw=(w+tilew-1)/tilew; ntilesh=(h+tileh-1)/tileh; + + if((jpegbuf=(unsigned char **)malloc(sizeof(unsigned char *) + *ntilesw*ntilesh))==NULL) + _throwunix("allocating JPEG tile array"); + memset(jpegbuf, 0, sizeof(unsigned char *)*ntilesw*ntilesh); + if((jpegsize=(unsigned long *)malloc(sizeof(unsigned long) + *ntilesw*ntilesh))==NULL) + _throwunix("allocating JPEG size array"); + memset(jpegsize, 0, sizeof(unsigned long)*ntilesw*ntilesh); + + for(i=0; i %d x %d", TJSCALED(_w, sf), TJSCALED(_h, sf)); + printf("\n"); + } + else if(quiet==1) + { + printf("%s\t%s\t%s\t", pixFormatStr[pf], + (flags&TJFLAG_BOTTOMUP)? "BU":"TD", subNameLong[subsamp]); + printf("%-4d %-4d\t", tilew, tileh); + } + + _subsamp=subsamp; + if(quiet==1) printf("N/A\tN/A\t"); + jpegsize[0]=srcsize; + memcpy(jpegbuf[0], srcbuf, srcsize); + + if(w==tilew) _tilew=_w; + if(h==tileh) _tileh=_h; + if(decomptest(NULL, jpegbuf, jpegsize, NULL, _w, _h, _subsamp, 0, + filename, _tilew, _tileh)==-1) + goto bailout; + else if(quiet==1) printf("N/A\n"); + + for(i=0; i <%% Quality> [options]\n\n"); + printf(" %s\n", progname); + printf(" [options]\n\n"); + printf("Options:\n\n"); + printf("-bmp = Generate output images in Windows Bitmap format (default=PPM)\n"); + printf("-bottomup = Test bottom-up compression/decompression\n"); + printf("-tile = Test performance of the codec when the image is encoded as separate\n"); + printf(" tiles of varying sizes.\n"); + printf("-forcemmx, -forcesse, -forcesse2, -forcesse3 =\n"); + printf(" Force MMX, SSE, SSE2, or SSE3 code paths in the underlying codec\n"); + printf("-rgb, -bgr, -rgbx, -bgrx, -xbgr, -xrgb =\n"); + printf(" Test the specified color conversion path in the codec (default: BGR)\n"); + printf("-fastupsample = Use fast, inaccurate upsampling code to perform 4:2:2 and 4:2:0\n"); + printf(" YUV decoding\n"); + printf("-quiet = Output results in tabular rather than verbose format\n"); + printf("-scale M/N = scale down the width/height of the decompressed JPEG image by a\n"); + printf(" factor of M/N (M/N = "); + for(i=0; i2) + { + if(i!=nsf-1) printf(", "); + if(i==nsf-2) printf("or "); + } + } + printf(")\n"); + printf("-benchtime = Run each benchmark for at least seconds (default = 5.0)\n\n"); + printf("NOTE: If the quality is specified as a range (e.g. 90-100), a separate\n"); + printf("test will be performed for all quality values in the range.\n\n"); + exit(1); +} + + +int main(int argc, char *argv[]) +{ + unsigned char *srcbuf=NULL; int w, h, i, j; + int minqual=-1, maxqual=-1; char *temp; + int minarg=2; int retval=0; + + if((scalingfactors=tjGetScalingFactors(&nsf))==NULL || nsf==0) + _throwtj("executing tjGetScalingFactors()"); + + if(argc100) + { + puts("ERROR: Quality must be between 1 and 100."); + exit(1); + } + if((temp=strchr(argv[2], '-'))!=NULL && strlen(temp)>1 + && sscanf(&temp[1], "%d", &maxqual)==1 && maxqual>minqual && maxqual>=1 + && maxqual<=100) {} + else maxqual=minqual; + } + + if(argc>minarg) + { + for(i=minarg; i0.0) benchtime=temp; + else usage(argv[0]); + } + if(!strcmp(argv[i], "-?")) usage(argv[0]); + if(!strcasecmp(argv[i], "-bmp")) ext="bmp"; + } + } + + if((sf.num!=1 || sf.denom!=1) && dotile) + { + printf("Disabling tiled compression/decompression tests, because those tests do not\n"); + printf("work when scaled decompression is enabled.\n"); + dotile=0; + } + + if(!decomponly) + { + if(loadbmp(argv[1], &srcbuf, &w, &h, bmpPF[pf], 1, + (flags&TJFLAG_BOTTOMUP)!=0)==-1) + _throwbmp("loading bitmap"); + temp=strrchr(argv[1], '.'); + if(temp!=NULL) *temp='\0'; + } + + if(quiet==1 && !decomponly) + { + printf("All performance values in Mpixels/sec\n\n"); + printf("Bitmap\tBitmap\tJPEG\tJPEG\t%s %s \tComp\tComp\tDecomp\n", + dotile? "Tile ":"Image", dotile? "Tile ":"Image"); + printf("Format\tOrder\tSubsamp\tQual\tWidth Height\tPerf \tRatio\tPerf\n\n"); + } + + if(decomponly) + { + dodecomptest(argv[1]); + printf("\n"); + goto bailout; + } + for(i=maxqual; i>=minqual; i--) + dotest(srcbuf, w, h, TJ_GRAYSCALE, i, argv[1]); + printf("\n"); + for(i=maxqual; i>=minqual; i--) + dotest(srcbuf, w, h, TJ_420, i, argv[1]); + printf("\n"); + for(i=maxqual; i>=minqual; i--) + dotest(srcbuf, w, h, TJ_422, i, argv[1]); + printf("\n"); + for(i=maxqual; i>=minqual; i--) + dotest(srcbuf, w, h, TJ_444, i, argv[1]); + printf("\n"); + + bailout: + if(srcbuf) free(srcbuf); + return retval; +} diff --git a/test/tjunittest.c b/test/tjunittest.c new file mode 100644 index 0000000..c9960eb --- /dev/null +++ b/test/tjunittest.c @@ -0,0 +1,461 @@ +/* + * Copyright (C)2009-2012 D. R. Commander. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of the libjpeg-turbo Project nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS", + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * This program tests the various code paths in the TurboJPEG C Wrapper + */ + +#include +#include +#include +#include +#include "./tjutil.h" +#include "./turbojpeg.h" +#ifdef _WIN32 + #include + #define random() rand() +#endif + + +#define _throwtj() {printf("TurboJPEG ERROR:\n%s\n", tjGetErrorStr()); \ + bailout();} +#define _tj(f) {if((f)==-1) _throwtj();} +#define _throw(m) {printf("ERROR: %s\n", m); bailout();} + +const char *subNameLong[TJ_NUMSAMP]= +{ + "4:4:4", "4:2:2", "4:2:0", "GRAY", "4:4:0" +}; +const char *subName[TJ_NUMSAMP]={"444", "422", "420", "GRAY", "440"}; + +const char *pixFormatStr[TJ_NUMPF]= +{ + "RGB", "BGR", "RGBX", "BGRX", "XBGR", "XRGB", "Grayscale", + "RGBA", "BGRA", "ABGR", "ARGB" +}; + +const int alphaOffset[TJ_NUMPF] = {-1, -1, -1, -1, -1, -1, -1, 3, 3, 0, 0}; + +const int _3byteFormats[]={TJPF_RGB, TJPF_BGR}; +const int _4byteFormats[]={TJPF_RGBX, TJPF_BGRX, TJPF_XBGR, TJPF_XRGB}; +const int _onlyGray[]={TJPF_GRAY}; +const int _onlyRGB[]={TJPF_RGB}; + +int exitStatus=0; +#define bailout() {exitStatus=-1; goto bailout;} + + +void initBuf(unsigned char *buf, int w, int h, int pf, int flags) +{ + int roffset=tjRedOffset[pf]; + int goffset=tjGreenOffset[pf]; + int boffset=tjBlueOffset[pf]; + int ps=tjPixelSize[pf]; + int index, row, col, halfway=16; + + memset(buf, 0, w*h*ps); + if(pf==TJPF_GRAY) + { + for(row=0; row=halfway) buf[index*ps+goffset]=255; + } + } + } + } +} + + +#define checkval(v, cv) { \ + if(vcv+1) { \ + printf("\nComp. %s at %d,%d should be %d, not %d\n", \ + #v, row, col, cv, v); \ + retval=0; exitStatus=-1; goto bailout; \ + }} + +#define checkval0(v) { \ + if(v>1) { \ + printf("\nComp. %s at %d,%d should be 0, not %d\n", #v, row, col, v); \ + retval=0; exitStatus=-1; goto bailout; \ + }} + +#define checkval255(v) { \ + if(v<254) { \ + printf("\nComp. %s at %d,%d should be 255, not %d\n", #v, row, col, v); \ + retval=0; exitStatus=-1; goto bailout; \ + }} + + +int checkBuf(unsigned char *buf, int w, int h, int pf, int subsamp, + tjscalingfactor sf, int flags) +{ + int roffset=tjRedOffset[pf]; + int goffset=tjGreenOffset[pf]; + int boffset=tjBlueOffset[pf]; + int aoffset=alphaOffset[pf]; + int ps=tjPixelSize[pf]; + int index, row, col, retval=1; + int halfway=16*sf.num/sf.denom; + int blocksize=8*sf.num/sf.denom; + + for(row=0; row=0? buf[index*ps+aoffset]:0xFF; + if(((row/blocksize)+(col/blocksize))%2==0) + { + if(row %s Q%d ... ", pixFormatStr[pf], + (flags&TJFLAG_BOTTOMUP)? "Bottom-Up":"Top-Down ", subNameLong[subsamp], + jpegQual); + + if((srcBuf=(unsigned char *)malloc(w*h*tjPixelSize[pf]))==NULL) + _throw("Memory allocation failure"); + initBuf(srcBuf, w, h, pf, flags); + if(*dstBuf && *dstSize>0) memset(*dstBuf, 0, *dstSize); + + t=gettime(); + *dstSize=tjBufSize(w, h, subsamp); + _tj(tjCompress2(handle, srcBuf, w, 0, h, pf, dstBuf, dstSize, subsamp, + jpegQual, flags)); + t=gettime()-t; + + snprintf(tempStr, 1024, "%s_enc_%s_%s_%s_Q%d.jpg", basename, + pixFormatStr[pf], (flags&TJFLAG_BOTTOMUP)? "BU":"TD", subName[subsamp], + jpegQual); + writeJPEG(*dstBuf, *dstSize, tempStr); + printf("Done."); + printf(" %f ms\n Result in %s\n", t*1000., tempStr); + + bailout: + if(srcBuf) free(srcBuf); +} + + +void _decompTest(tjhandle handle, unsigned char *jpegBuf, + unsigned long jpegSize, int w, int h, int pf, char *basename, int subsamp, + int flags, tjscalingfactor sf) +{ + unsigned char *dstBuf=NULL; + int _hdrw=0, _hdrh=0, _hdrsubsamp=-1; double t; + int scaledWidth=TJSCALED(w, sf); + int scaledHeight=TJSCALED(h, sf); + unsigned long dstSize=0; + + printf("JPEG -> %s %s ", pixFormatStr[pf], + (flags&TJFLAG_BOTTOMUP)? "Bottom-Up":"Top-Down "); + if(sf.num!=1 || sf.denom!=1) + printf("%d/%d ... ", sf.num, sf.denom); + else printf("... "); + + _tj(tjDecompressHeader2(handle, jpegBuf, jpegSize, &_hdrw, &_hdrh, + &_hdrsubsamp)); + if(_hdrw!=w || _hdrh!=h || _hdrsubsamp!=subsamp) + _throw("Incorrect JPEG header"); + + dstSize=scaledWidth*scaledHeight*tjPixelSize[pf]; + if((dstBuf=(unsigned char *)malloc(dstSize))==NULL) + _throw("Memory allocation failure"); + memset(dstBuf, 0, dstSize); + + t=gettime(); + _tj(tjDecompress2(handle, jpegBuf, jpegSize, dstBuf, scaledWidth, 0, + scaledHeight, pf, flags)); + t=gettime()-t; + + if(checkBuf(dstBuf, scaledWidth, scaledHeight, pf, subsamp, sf, flags)) + printf("Passed."); + else printf("FAILED!"); + printf(" %f ms\n", t*1000.); + + bailout: + if(dstBuf) free(dstBuf); +} + + +void decompTest(tjhandle handle, unsigned char *jpegBuf, + unsigned long jpegSize, int w, int h, int pf, char *basename, int subsamp, + int flags) +{ + int i, n=0; + tjscalingfactor *sf=tjGetScalingFactors(&n), sf1={1, 1}; + if(!sf || !n) _throwtj(); + + if((subsamp==TJSAMP_444 || subsamp==TJSAMP_GRAY)) + { + for(i=0; i=TJPF_RGBX && pf<=TJPF_XRGB) + decompTest(dhandle, dstBuf, size, w, h, pf+(TJPF_RGBA-TJPF_RGBX), + basename, subsamp, flags); + } + } + + bailout: + if(chandle) tjDestroy(chandle); + if(dhandle) tjDestroy(dhandle); + + if(dstBuf) free(dstBuf); +} + + +void bufSizeTest(void) +{ + int w, h, i, subsamp; + unsigned char *srcBuf=NULL, *jpegBuf=NULL; + tjhandle handle=NULL; + unsigned long jpegSize=0; + + if((handle=tjInitCompress())==NULL) _throwtj(); + + printf("Buffer size regression test\n"); + for(subsamp=0; subsamp + +static double getfreq(void) +{ + LARGE_INTEGER freq; + if(!QueryPerformanceFrequency(&freq)) return 0.0; + return (double)freq.QuadPart; +} + +static double f=-1.0; + +double gettime(void) +{ + LARGE_INTEGER t; + if(f<0.0) f=getfreq(); + if(f==0.0) return (double)GetTickCount()/1000.; + else + { + QueryPerformanceCounter(&t); + return (double)t.QuadPart/f; + } +} + +#else + +#include +#include + +double gettime(void) +{ + struct timeval tv; + if(gettimeofday(&tv, NULL)<0) return 0.0; + else return (double)tv.tv_sec+((double)tv.tv_usec/1000000.); +} + +#endif diff --git a/test/tjutil.h b/test/tjutil.h new file mode 100644 index 0000000..bdad348 --- /dev/null +++ b/test/tjutil.h @@ -0,0 +1,47 @@ +/* + * Copyright (C)2011 D. R. Commander. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of the libjpeg-turbo Project nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS", + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifdef _WIN32 + #ifndef __MINGW32__ + #include + #define snprintf(str, n, format, ...) \ + _snprintf_s(str, n, _TRUNCATE, format, __VA_ARGS__) + #endif + #define strcasecmp stricmp + #define strncasecmp strnicmp +#endif + +#ifndef min + #define min(a,b) ((a)<(b)?(a):(b)) +#endif + +#ifndef max + #define max(a,b) ((a)>(b)?(a):(b)) +#endif + +extern double gettime(void); From 729e965dfff52bf8d76b609c068fbcb36910456f Mon Sep 17 00:00:00 2001 From: DRC Date: Fri, 23 Mar 2012 16:00:33 -0500 Subject: [PATCH 07/11] Extend support for the new TurboVNC encoder to the CMake build system --- CMakeLists.txt | 41 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 39 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 6e7b837..b9a67ca 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,6 +5,9 @@ include(CheckFunctionExists) include(CheckIncludeFile) include(CheckTypeSize) include(TestBigEndian) +include(CheckCSourceCompiles) +include(CheckCXXSourceCompiles) +include(CheckCSourceRuns) set(PACKAGE_NAME "LibVNCServer") set(FULL_PACKAGE_NAME "LibVNCServer") @@ -30,6 +33,40 @@ find_package(X11) find_package(OpenSSL) find_library(LIBGCRYPT_LIBRARIES gcrypt) +# Check whether the version of libjpeg we found was libjpeg-turbo and print a +# warning if not. +set(CMAKE_REQUIRED_LIBRARIES ${JPEG_LIBRARIES}) +set(CMAKE_REQUIRED_FLAGS -I${JPEG_INCLUDE_DIR}) + +set(JPEG_TEST_SOURCE "\n + #include \n + #include \n + int main(void) {\n + struct jpeg_compress_struct cinfo;\n + struct jpeg_error_mgr jerr;\n + cinfo.err=jpeg_std_error(&jerr);\n + jpeg_create_compress(&cinfo);\n + cinfo.input_components = 3;\n + jpeg_set_defaults(&cinfo);\n + cinfo.in_color_space = JCS_EXT_RGB;\n + jpeg_default_colorspace(&cinfo);\n + return 0;\n + }") + +if(CMAKE_CROSSCOMPILING) + check_c_source_compiles("${JPEG_TEST_SOURCE}" FOUND_LIBJPEG_TURBO) +else() + check_c_source_runs("${JPEG_TEST_SOURCE}" FOUND_LIBJPEG_TURBO) +endif() + +set(CMAKE_REQUIRED_LIBRARIES) +set(CMAKE_REQUIRED_FLAGS) +set(CMAKE_REQUIRED_DEFINITIONS) + +if(NOT FOUND_LIBJPEG_TURBO) + message(WARNING "*** The libjpeg library you are building against is not libjpeg-turbo. Performance will be reduced. You can obtain libjpeg-turbo from: https://sourceforge.net/projects/libjpeg-turbo/files/ ***") +endif() + set(CMAKE_REQUIRED_LIBRARIES resolv) check_function_exists(__b64_ntop HAVE_B64) @@ -166,13 +203,13 @@ endif(ZLIB_FOUND) if(JPEG_FOUND) add_definitions(-DLIBVNCSERVER_HAVE_LIBJPEG) include_directories(${JPEG_INCLUDE_DIR}) - set(TIGHT_C ${LIBVNCSERVER_DIR}/tight.c) + set(TIGHT_C ${LIBVNCSERVER_DIR}/tight.c ${COMMON_DIR}/turbojpeg.c) endif(JPEG_FOUND) if(PNG_FOUND) add_definitions(-DLIBVNCSERVER_HAVE_LIBPNG) include_directories(${PNG_INCLUDE_DIR}) - set(TIGHT_C ${LIBVNCSERVER_DIR}/tight.c) + set(TIGHT_C ${LIBVNCSERVER_DIR}/tight.c ${COMMON_DIR}/turbojpeg.c) endif(PNG_FOUND) set(LIBVNCSERVER_SOURCES From 5e142f81a9fd30b1b360032779ff1cc9be83aa84 Mon Sep 17 00:00:00 2001 From: DRC Date: Mon, 9 Apr 2012 15:26:46 -0500 Subject: [PATCH 08/11] Fix memory leak in TurboVNC Note that the memory leak was only occurring with the colorspace emulation code, which is only active when using regular libjpeg (not libjpeg-turbo.) Diagnosed by Christian Beier, using valgrind. Signed-off-by: Johannes Schindelin --- common/turbojpeg.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/common/turbojpeg.c b/common/turbojpeg.c index c145338..11168a2 100644 --- a/common/turbojpeg.c +++ b/common/turbojpeg.c @@ -601,7 +601,7 @@ DLLEXPORT int DLLCALL tjCompress2(tjhandle handle, unsigned char *srcBuf, bailout: if(cinfo->global_state>CSTATE_START) jpeg_abort_compress(cinfo); #ifndef JCS_EXTENSIONS - if(rgbBuf && rgbBuf!=srcBuf) free(rgbBuf); + if(rgbBuf) free(rgbBuf); #endif if(row_pointer) free(row_pointer); return retval; @@ -835,7 +835,7 @@ DLLEXPORT int DLLCALL tjDecompress2(tjhandle handle, unsigned char *jpegBuf, bailout: if(dinfo->global_state>DSTATE_START) jpeg_abort_decompress(dinfo); #ifndef JCS_EXTENSIONS - if(rgbBuf && rgbBuf!=dstBuf) free(rgbBuf); + if(rgbBuf) free(rgbBuf); #endif if(row_pointer) free(row_pointer); return retval; From 7cb8fd9b30e80ecf9bab98746c590d6b5578c929 Mon Sep 17 00:00:00 2001 From: Christian Beier Date: Thu, 12 Apr 2012 16:23:01 +0200 Subject: [PATCH 09/11] Make TurboVNC compress level 3 actually work. --- libvncserver/tight.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libvncserver/tight.c b/libvncserver/tight.c index 3650458..6d5bcbb 100644 --- a/libvncserver/tight.c +++ b/libvncserver/tight.c @@ -330,7 +330,7 @@ SendRectEncodingTight(rfbClientPtr cl, benefit. For low-color workloads, it provides typically only 10-20% better compression than CL 2 with JPEG and CL 1 without JPEG, and it uses, on average, twice as much CPU time. */ - if (compressLevel == 9) compressLevel = 3; + if (cl->tightCompressLevel == 9) compressLevel = 3; if ( cl->format.depth == 24 && cl->format.redMax == 0xFF && cl->format.greenMax == 0xFF && cl->format.blueMax == 0xFF ) { From f35624225b1d2c5ed11d7c47439810aa1d9a4afe Mon Sep 17 00:00:00 2001 From: DRC Date: Wed, 25 Apr 2012 01:13:04 +0200 Subject: [PATCH 10/11] Fix compilation with some libjpeg distributions. --- common/turbojpeg.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/common/turbojpeg.c b/common/turbojpeg.c index 11168a2..b3877ec 100644 --- a/common/turbojpeg.c +++ b/common/turbojpeg.c @@ -31,12 +31,11 @@ #include #include #include -#include -#include #ifndef JCS_EXTENSIONS -#define JPEG_INTERNALS -#include +#define JPEG_INTERNAL_OPTIONS #endif +#include +#include #include #include "./turbojpeg.h" From 1df6bffd9e7ff5106ffb5f3bc1938a14ea1f72cf Mon Sep 17 00:00:00 2001 From: Christian Beier Date: Wed, 25 Apr 2012 12:33:06 +0200 Subject: [PATCH 11/11] Fix turbojpeg tests compilation. --- test/Makefile.am | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/Makefile.am b/test/Makefile.am index 11aea37..ff6e8f2 100644 --- a/test/Makefile.am +++ b/test/Makefile.am @@ -7,9 +7,9 @@ tjunittest_SOURCES=tjunittest.c ../common/turbojpeg.c ../common/turbojpeg.h \ tjbench_SOURCES=tjbench.c ../common/turbojpeg.c ../common/turbojpeg.h \ tjutil.c tjutil.h bmp.c bmp.h +tjbench_LDADD=$(LDADD) -lm - -INCLUDES = -I$(top_srcdir) +INCLUDES = -I$(top_srcdir) -I$(top_srcdir)/common LDADD = ../libvncserver/libvncserver.la ../libvncclient/libvncclient.la @WSOCKLIB@ if HAVE_LIBPTHREAD