diff options
author | Christian Beier <dontmind@freeshell.org> | 2012-04-25 12:55:03 +0200 |
---|---|---|
committer | Christian Beier <dontmind@freeshell.org> | 2012-04-25 12:55:03 +0200 |
commit | 413ca0dfef83b98f7b230e0596ac086d42eaad4e (patch) | |
tree | b36ca82ac24402f3f0f5a7085f0561757e7b56f5 | |
parent | 2524573678fbdb195cbc9ba2f92e0f58c205ad10 (diff) | |
parent | 1df6bffd9e7ff5106ffb5f3bc1938a14ea1f72cf (diff) | |
download | libtdevnc-413ca0dfef83b98f7b230e0596ac086d42eaad4e.tar.gz libtdevnc-413ca0dfef83b98f7b230e0596ac086d42eaad4e.zip |
Merge branch 'turbovnc'
Conflicts, resolved manually:
AUTHORS
-rw-r--r-- | AUTHORS | 2 | ||||
-rw-r--r-- | CMakeLists.txt | 41 | ||||
-rw-r--r-- | common/turbojpeg.c | 849 | ||||
-rw-r--r-- | common/turbojpeg.h | 529 | ||||
-rw-r--r-- | configure.ac | 59 | ||||
-rw-r--r-- | libvncserver/Makefile.am | 2 | ||||
-rw-r--r-- | libvncserver/rfbserver.c | 43 | ||||
-rw-r--r-- | libvncserver/tight.c | 999 | ||||
-rw-r--r-- | rfb/rfb.h | 9 | ||||
-rw-r--r-- | rfb/rfbproto.h | 30 | ||||
-rw-r--r-- | test/Makefile.am | 13 | ||||
-rw-r--r-- | test/bmp.c | 389 | ||||
-rw-r--r-- | test/bmp.h | 49 | ||||
-rw-r--r-- | test/tjbench.c | 658 | ||||
-rw-r--r-- | test/tjunittest.c | 461 | ||||
-rw-r--r-- | test/tjutil.c | 66 | ||||
-rw-r--r-- | test/tjutil.h | 47 | ||||
-rw-r--r-- | x11vnc/Makefile.am | 2 |
18 files changed, 3652 insertions, 596 deletions
@@ -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, Mateus Cesar Groess and Philip Van Hoof. +Kyle J. McKay, Mateus Cesar Groess, Philip Van Hoof 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 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 <stdio.h>\n + #include <jpeglib.h>\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 diff --git a/common/turbojpeg.c b/common/turbojpeg.c new file mode 100644 index 0000000..b3877ec --- /dev/null +++ b/common/turbojpeg.c @@ -0,0 +1,849 @@ +/* + * 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. + */ + +/* TurboJPEG/OSS: this implements the TurboJPEG API using libjpeg-turbo */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#ifndef JCS_EXTENSIONS +#define JPEG_INTERNAL_OPTIONS +#endif +#include <jpeglib.h> +#include <jerror.h> +#include <setjmp.h> +#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 (based on example in example.c) */ + +static char errStr[JMSG_LENGTH_MAX]="No error"; + +struct my_error_mgr +{ + struct jpeg_error_mgr pub; + jmp_buf setjmp_buffer; +}; +typedef struct my_error_mgr *my_error_ptr; + +static void my_error_exit(j_common_ptr cinfo) +{ + my_error_ptr myerr=(my_error_ptr)cinfo->err; + (*cinfo->err->output_message)(cinfo); + 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, errStr); +} + + +/* Global structures, macros, etc. */ + +enum {COMPRESS=1, DECOMPRESS=2}; + +typedef struct _tjinstance +{ + struct jpeg_compress_struct cinfo; + struct jpeg_decompress_struct dinfo; + 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; i<NUMSUBOPT; i++) + { + if(dinfo->num_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; k<dinfo->num_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(src<endOfRow) \ + { \ + dst[RGB_RED]=src[ROFFSET]; \ + dst[RGB_GREEN]=src[GOFFSET]; \ + dst[RGB_BLUE]=src[BOFFSET]; \ + dst+=RGB_PIXELSIZE; src+=PS; \ + } \ + src+=rowPad; \ + } \ +} + +static unsigned char *toRGB(unsigned char *src, int width, int pitch, + int height, int pixelFormat, unsigned char *dst) +{ + unsigned char *retval=src; + switch(pixelFormat) + { + case TJPF_RGB: + #if RGB_RED!=0 || RGB_GREEN!=1 || RGB_BLUE!=2 || RGB_PIXELSIZE!=3 + retval=dst; TORGB(3, 0, 1, 2); + #endif + break; + case TJPF_BGR: + #if RGB_RED!=2 || RGB_GREEN!=1 || RGB_BLUE!=0 || RGB_PIXELSIZE!=3 + retval=dst; TORGB(3, 2, 1, 0); + #endif + break; + case TJPF_RGBX: + case TJPF_RGBA: + #if RGB_RED!=0 || RGB_GREEN!=1 || RGB_BLUE!=2 || RGB_PIXELSIZE!=4 + retval=dst; TORGB(4, 0, 1, 2); + #endif + break; + case TJPF_BGRX: + case TJPF_BGRA: + #if RGB_RED!=2 || RGB_GREEN!=1 || RGB_BLUE!=0 || RGB_PIXELSIZE!=4 + retval=dst; TORGB(4, 2, 1, 0); + #endif + break; + case TJPF_XRGB: + case TJPF_ARGB: + #if RGB_RED!=1 || RGB_GREEN!=2 || RGB_BLUE!=3 || RGB_PIXELSIZE!=4 + retval=dst; TORGB(4, 1, 2, 3); + #endif + break; + case TJPF_XBGR: + case TJPF_ABGR: + #if RGB_RED!=3 || RGB_GREEN!=2 || RGB_BLUE!=1 || RGB_PIXELSIZE!=4 + retval=dst; TORGB(4, 3, 2, 1); + #endif + break; + } + return retval; +} + +#define FROMRGB(PS, ROFFSET, GOFFSET, BOFFSET, SETALPHA) { \ + int rowPad=pitch-width*PS; \ + while(height--) \ + { \ + unsigned char *endOfRow=dst+width*PS; \ + while(dst<endOfRow) \ + { \ + dst[ROFFSET]=src[RGB_RED]; \ + dst[GOFFSET]=src[RGB_GREEN]; \ + dst[BOFFSET]=src[RGB_BLUE]; \ + SETALPHA \ + dst+=PS; src+=RGB_PIXELSIZE; \ + } \ + dst+=rowPad; \ + } \ +} + +static void fromRGB(unsigned char *src, unsigned char *dst, int width, + int pitch, int height, int pixelFormat) +{ + switch(pixelFormat) + { + case TJPF_RGB: + #if RGB_RED!=0 || RGB_GREEN!=1 || RGB_BLUE!=2 || RGB_PIXELSIZE!=3 + FROMRGB(3, 0, 1, 2,); + #endif + break; + case TJPF_BGR: + #if RGB_RED!=2 || RGB_GREEN!=1 || RGB_BLUE!=0 || RGB_PIXELSIZE!=3 + FROMRGB(3, 2, 1, 0,); + #endif + break; + case TJPF_RGBX: + #if RGB_RED!=0 || RGB_GREEN!=1 || RGB_BLUE!=2 || RGB_PIXELSIZE!=4 + FROMRGB(4, 0, 1, 2,); + #endif + break; + case TJPF_RGBA: + #if RGB_RED!=0 || RGB_GREEN!=1 || RGB_BLUE!=2 || RGB_PIXELSIZE!=4 + FROMRGB(4, 0, 1, 2, dst[3]=0xFF;); + #endif + break; + case TJPF_BGRX: + #if RGB_RED!=2 || RGB_GREEN!=1 || RGB_BLUE!=0 || RGB_PIXELSIZE!=4 + FROMRGB(4, 2, 1, 0,); + #endif + break; + case TJPF_BGRA: + #if RGB_RED!=2 || RGB_GREEN!=1 || RGB_BLUE!=0 || RGB_PIXELSIZE!=4 + FROMRGB(4, 2, 1, 0, dst[3]=0xFF;); return; + #endif + break; + case TJPF_XRGB: + #if RGB_RED!=1 || RGB_GREEN!=2 || RGB_BLUE!=3 || RGB_PIXELSIZE!=4 + FROMRGB(4, 1, 2, 3,); return; + #endif + break; + case TJPF_ARGB: + #if RGB_RED!=1 || RGB_GREEN!=2 || RGB_BLUE!=3 || RGB_PIXELSIZE!=4 + FROMRGB(4, 1, 2, 3, dst[0]=0xFF;); return; + #endif + break; + case TJPF_XBGR: + #if RGB_RED!=3 || RGB_GREEN!=2 || RGB_BLUE!=1 || RGB_PIXELSIZE!=4 + FROMRGB(4, 3, 2, 1,); return; + #endif + break; + case TJPF_ABGR: + #if RGB_RED!=3 || RGB_GREEN!=2 || RGB_BLUE!=1 || RGB_PIXELSIZE!=4 + FROMRGB(4, 3, 2, 1, dst[0]=0xFF;); return; + #endif + break; + } +} + +#endif + + +/* General API functions */ + +DLLEXPORT char* DLLCALL tjGetErrorStr(void) +{ + return errStr; +} + + +DLLEXPORT int DLLCALL tjDestroy(tjhandle handle) +{ + getinstance(handle); + if(setjmp(this->jerr.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 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) +{ + 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); +} + + +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"); + + // 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; +} + + +DLLEXPORT unsigned long DLLCALL TJBUFSIZE(int width, int height) +{ + unsigned long retval=0; + if(width<1 || height<1) + _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=PAD(width, 16) * PAD(height, 16) * 6 + 2048; + + bailout: + return retval; +} + + +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; + #ifndef JCS_EXTENSIONS + unsigned char *rgbBuf=NULL; + #endif + + getinstance(handle) + if((this->init&COMPRESS)==0) + _throw("tjCompress2(): Instance has not been initialized for compression"); + + 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; + } + + if(pitch==0) pitch=width*tjPixelSize[pixelFormat]; + + #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"); + + if(setCompDefaults(cinfo, pixelFormat, jpegSubsamp, jpegQual)==-1) + return -1; + + this->jdst.next_output_byte=*jpegBuf; + this->jdst.free_in_buffer=tjBufSize(width, height, jpegSubsamp); + + jpeg_start_compress(cinfo, TRUE); + if((row_pointer=(JSAMPROW *)malloc(sizeof(JSAMPROW)*height))==NULL) + _throw("tjCompress2(): Memory allocation failure"); + for(i=0; i<height; i++) + { + if(flags&TJFLAG_BOTTOMUP) row_pointer[i]=&srcBuf[(height-i-1)*pitch]; + else row_pointer[i]=&srcBuf[i*pitch]; + } + while(cinfo->next_scanline<cinfo->image_height) + { + jpeg_write_scanlines(cinfo, &row_pointer[cinfo->next_scanline], + cinfo->image_height-cinfo->next_scanline); + } + jpeg_finish_compress(cinfo); + *jpegSize=tjBufSize(width, height, jpegSubsamp) + -(unsigned long)(this->jdst.free_in_buffer); + + bailout: + if(cinfo->global_state>CSTATE_START) jpeg_abort_compress(cinfo); + #ifndef JCS_EXTENSIONS + if(rgbBuf) 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; +} + + +/* Decompressor */ + +static boolean fill_input_buffer(j_decompress_ptr dinfo) +{ + ERREXIT(dinfo, JERR_BUFFER_SIZE); + return TRUE; +} + +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 src_noop(j_decompress_ptr dinfo) +{ +} + +static tjhandle _tjInitDecompress(tjinstance *this) +{ + /* 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(&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; +} + +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 handle, + unsigned char *jpegBuf, unsigned long jpegSize, int *width, int *height, + int *jpegSubsamp) +{ + int retval=0; + + getinstance(handle); + if((this->init&DECOMPRESS)==0) + _throw("tjDecompressHeader2(): Instance has not been initialized for decompression"); + + if(jpegBuf==NULL || jpegSize<=0 || width==NULL || height==NULL + || jpegSubsamp==NULL) + _throw("tjDecompressHeader2(): Invalid argument"); + + if(setjmp(this->jerr.setjmp_buffer)) + { + /* If we get here, the JPEG code has signaled an error. */ + return -1; + } + + this->jsrc.bytes_in_buffer=jpegSize; + this->jsrc.next_input_byte=jpegBuf; + jpeg_read_header(dinfo, TRUE); + + *width=dinfo->image_width; + *height=dinfo->image_height; + *jpegSubsamp=getSubsamp(dinfo); + + jpeg_abort_decompress(dinfo); + + 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 tjscalingfactor* DLLCALL tjGetScalingFactors(int *numscalingfactors) +{ + if(numscalingfactors==NULL) + { + snprintf(errStr, JMSG_LENGTH_MAX, + "tjGetScalingFactors(): Invalid argument"); + return NULL; + } + + *numscalingfactors=NUMSF; + return (tjscalingfactor *)sf; +} + + +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 + + getinstance(handle); + if((this->init&DECOMPRESS)==0) + _throw("tjDecompress2(): Instance has not been initialized for decompression"); + + if(jpegBuf==NULL || jpegSize<=0 || dstBuf==NULL || width<0 || pitch<0 + || height<0 || pixelFormat<0 || pixelFormat>=TJ_NUMPF) + _throw("tjDecompress2(): Invalid argument"); + + 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(this->jerr.setjmp_buffer)) + { + /* If we get here, the JPEG code has signaled an error. */ + retval=-1; + goto bailout; + } + + 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; + } + + if(flags&TJFLAG_FASTUPSAMPLE) dinfo->do_fancy_upsampling=FALSE; + + jpegwidth=dinfo->image_width; jpegheight=dinfo->image_height; + if(width==0) width=jpegwidth; + if(height==0) height=jpegheight; + for(i=0; i<NUMSF; i++) + { + scaledw=TJSCALED(jpegwidth, sf[i]); + scaledh=TJSCALED(jpegheight, sf[i]); + if(scaledw<=width && scaledh<=height) + break; + } + if(scaledw>width || 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; + } + #endif + + 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_scanline<dinfo->output_height) + { + jpeg_read_scanlines(dinfo, &row_pointer[dinfo->output_scanline], + dinfo->output_height-dinfo->output_scanline); + } + jpeg_finish_decompress(dinfo); + + #ifndef JCS_EXTENSIONS + fromRGB(rgbBuf, _dstBuf, width, _pitch, height, pixelFormat); + #endif + + bailout: + if(dinfo->global_state>DSTATE_START) jpeg_abort_decompress(dinfo); + #ifndef JCS_EXTENSIONS + if(rgbBuf) free(rgbBuf); + #endif + if(row_pointer) free(row_pointer); + return retval; +} + +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) +{ + return tjDecompress2(handle, jpegBuf, jpegSize, dstBuf, width, pitch, + height, getPixelFormat(pixelSize, flags), flags); +} diff --git a/common/turbojpeg.h b/common/turbojpeg.h new file mode 100644 index 0000000..ab8adda --- /dev/null +++ b/common/turbojpeg.h @@ -0,0 +1,529 @@ +/* + * 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. + */ + +#ifndef __TURBOJPEG_H__ +#define __TURBOJPEG_H__ + +#if defined(_WIN32) && defined(DLLDEFINE) +#define DLLEXPORT __declspec(dllexport) +#else +#define DLLEXPORT +#endif +#define DLLCALL + + +/** + * @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 <tt>char pixel[]</tt>, + * then the red component will be <tt>pixel[tjRedOffset[TJ_BGRX]]</tt>. + */ +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 + * <tt>char pixel[]</tt>, then the green component will be + * <tt>pixel[tjGreenOffset[TJ_BGRX]]</tt>. + */ +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 <tt>char pixel[]</tt>, + * then the blue component will be <tt>pixel[tjBlueOffset[TJ_BGRX]]</tt>. + */ +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; + + +/** + * Pad the given width to the nearest 32-bit boundary + */ +#define TJPAD(width) (((width)+3)&(~3)) + +/** + * Compute the scaled value of <tt>dimension</tt> using the given scaling + * factor. This macro performs the integer equivalent of <tt>ceil(dimension * + * scalingFactor)</tt>. + */ +#define TJSCALED(dimension, scalingFactor) ((dimension * scalingFactor.num \ + + scalingFactor.denom - 1) / scalingFactor.denom) + + +#ifdef __cplusplus +extern "C" { +#endif + + +/** + * 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); + + +/** + * 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 + * <tt>width * #tjPixelSize[pixelFormat]</tt> if the image is unpadded, + * or <tt>#TJPAD(width * #tjPixelSize[pixelFormat])</tt> 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 <tt>width * #tjPixelSize[pixelFormat]</tt>. + * @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 <tt>*jpegBuf</tt> 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, <tt>*jpegSize</tt> should be set to the + * size of your pre-allocated buffer. In any case, unless you have + * set #TJFLAG_NOREALLOC, you should always check <tt>*jpegBuf</tt> 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 <tt>*jpegBuf</tt> points to a + * pre-allocated buffer, then <tt>*jpegSize</tt> should be set to the + * size of the buffer. Upon return, <tt>*jpegSize</tt> 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 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); + + +/** + * Create a TurboJPEG decompressor instance. + * + * @return a handle to the newly-created instance, or NULL if an error + * occurred (see #tjGetErrorStr().) +*/ +DLLEXPORT tjhandle DLLCALL tjInitDecompress(void); + + +/** + * 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); + + +/** + * 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 tjscalingfactor* DLLCALL tjGetScalingFactors(int *numscalingfactors); + + +/** + * 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 <tt>pitch * scaledHeight</tt> + * bytes in size, where <tt>scaledHeight</tt> 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 + * <tt>scaledWidth * #tjPixelSize[pixelFormat]</tt> if the decompressed + * image is unpadded, else <tt>#TJPAD(scaledWidth * + * #tjPixelSize[pixelFormat])</tt> if each line of the decompressed + * image is padded to the nearest 32-bit boundary, as is the case for + * Windows bitmaps. (NOTE: <tt>scaledWidth</tt> 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 <tt>scaledWidth + * * #tjPixelSize[pixelFormat]</tt>. + * @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); + + +/** + * 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); + + +/** + * 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); + + +/* 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 + +DLLEXPORT unsigned long DLLCALL TJBUFSIZE(int width, int height); + +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); + +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); + + +/** + * @} + */ + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/configure.ac b/configure.ac index f157c12..4e9c889 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,13 +577,52 @@ 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 <stdio.h> + #include <jpeglib.h>], + [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 + + if test "x$HAVE_LIBJPEG_TURBO" != "xtrue"; then + AC_MSG_WARN([ +========================================================================== +*** 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 + fi AC_ARG_WITH(png, diff --git a/libvncserver/Makefile.am b/libvncserver/Makefile.am index fce398d..c0cd4c3 100644 --- a/libvncserver/Makefile.am +++ b/libvncserver/Makefile.am @@ -46,7 +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 -TIGHTSRCS = tight.c +TIGHTSRCS = tight.c ../common/turbojpeg.c endif endif diff --git a/libvncserver/rfbserver.c b/libvncserver/rfbserver.c index 23c4d77..e18f31d 100644 --- a/libvncserver/rfbserver.c +++ b/libvncserver/rfbserver.c @@ -3,6 +3,7 @@ */ /* + * 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 <mcguirk@incompleteness.net>. @@ -86,6 +87,21 @@ static int compat_mkdir(const char *path, int mode) #define mkdir compat_mkdir #endif +#ifdef LIBVNCSERVER_HAVE_LIBJPEG +/* + * Map of quality levels to provide compatibility with TightVNC/TigerVNC + * clients. This emulates the behavior of the TigerVNC Server. + */ + +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); @@ -383,6 +399,7 @@ rfbNewTCPOrUDPClient(rfbScreenInfoPtr rfbScreen, cl->tightCompressLevel = TIGHT_DEFAULT_COMPRESSION; #endif #ifdef LIBVNCSERVER_HAVE_LIBJPEG + cl->turboSubsampLevel = TURBO_DEFAULT_SUBSAMP; { int i; for (i = 0; i < 4; i++) @@ -1963,6 +1980,16 @@ 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_LIBPNG) + cl->tightCompressLevel = TIGHT_DEFAULT_COMPRESSION; +#endif +#ifdef LIBVNCSERVER_HAVE_LIBJPEG + cl->turboSubsampLevel = TURBO_DEFAULT_SUBSAMP; + cl->turboQualityLevel = -1; +#endif +#endif for (i = 0; i < msg.se.nEncodings; i++) { @@ -2097,6 +2124,22 @@ rfbProcessClientNormalMessage(rfbClientPtr cl) 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..6d5bcbb 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 <stdio.h>*/ #include <rfb/rfb.h> #include "private.h" -#ifdef WIN32 -#define XMD_H -#undef FAR -#define NEEDFAR_POINTERS -#endif - -#ifdef _RPCNDR_H /* This Windows header typedefs 'boolean', jpeglib has to know */ -#define HAVE_BOOLEAN -#endif #ifdef LIBVNCSERVER_HAVE_LIBPNG #include <png.h> #endif -#include <jpeglib.h> +#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 (cl->tightCompressLevel == 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) { @@ -949,53 +1023,6 @@ SendFullColorRect(rfbClientPtr cl, } static rfbBool -SendGradientRect(rfbClientPtr cl, - int x, - int y, - int w, - int h) -{ - int streamId = 3; - int len; - -#ifdef LIBVNCSERVER_HAVE_LIBPNG - if (CanSendPngRect(cl, w, h)) { - return SendPngRect(cl, x, y, w, h); - } -#endif - - if (cl->format.bitsPerPixel == 8) - return SendFullColorRect(cl, x, y, w, h); - - if (cl->ublen + TIGHT_MIN_TO_COMPRESS + 2 > UPDATE_BUF_SIZE) { - if (!rfbSendUpdateBuf(cl)) - return FALSE; - } - - if (prevRowBuf == NULL) - prevRowBuf = (int *)malloc(2048 * 3 * sizeof(int)); - - cl->updateBuf[cl->ublen++] = (streamId | rfbTightExplicitFilter) << 4; - cl->updateBuf[cl->ublen++] = rfbTightFilterGradient; - rfbStatRecordEncodingSentAdd(cl, cl->tightEncoding, 2); - - if (usePixelFormat24) { - FilterGradient24(cl, tightBeforeBuf, &cl->format, w, h); - len = 3; - } else if (cl->format.bitsPerPixel == 32) { - FilterGradient32(cl, (uint32_t *)tightBeforeBuf, &cl->format, w, h); - len = 4; - } else { - FilterGradient16(cl, (uint16_t *)tightBeforeBuf, &cl->format, w, h); - len = 2; - } - - return CompressData(cl, streamId, w * h * len, - tightConf[compressLevel].gradientZlibLevel, - Z_FILTERED); -} - -static rfbBool CompressData(rfbClientPtr cl, int streamId, int dataLen, @@ -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 @@ -1871,43 +1741,6 @@ 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 @@ -681,6 +681,11 @@ typedef struct _rfbClientRec { int afterEncBufLen; #if defined(LIBVNCSERVER_HAVE_LIBZ) || defined(LIBVNCSERVER_HAVE_LIBPNG) uint32_t tightEncoding; /* rfbEncodingTight or rfbEncodingTightPng */ +#ifdef LIBVNCSERVER_HAVE_LIBJPEG + /* TurboVNC Encoding support (extends TightVNC) */ + int turboSubsampLevel; + int turboQualityLevel; // 1-100 scale +#endif #endif #ifdef LIBVNCSERVER_WITH_WEBSOCKETS @@ -880,6 +885,10 @@ extern rfbBool rfbSendRectEncodingZlib(rfbClientPtr cl, int x, int y, int w, #define TIGHT_DEFAULT_COMPRESSION 6 +#ifdef LIBVNCSERVER_HAVE_LIBJPEG +#define TURBO_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..acb2e7b 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,15 @@ typedef struct { * 0xFFFFFFF0 .. 0xFFFFFFFF -- cross-encoding compression levels. */ +#define rfbEncodingFineQualityLevel0 0xFFFFFE00 +#define rfbEncodingFineQualityLevel100 0xFFFFFE64 +#define rfbEncodingSubsamp1X 0xFFFFFD00 +#define rfbEncodingSubsamp4X 0xFFFFFD01 +#define rfbEncodingSubsamp2X 0xFFFFFD02 +#define rfbEncodingSubsampGray 0xFFFFFD03 +#define rfbEncodingSubsamp8X 0xFFFFFD04 +#define rfbEncodingSubsamp16X 0xFFFFFD05 + #define rfbEncodingCompressLevel0 0xFFFFFF00 #define rfbEncodingCompressLevel1 0xFFFFFF01 #define rfbEncodingCompressLevel2 0xFFFFFF02 @@ -719,12 +732,20 @@ 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", + * (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. * - * 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,6 +857,7 @@ typedef struct { #define rfbTightExplicitFilter 0x04 #define rfbTightFill 0x08 #define rfbTightJpeg 0x09 +#define rfbTightNoZlib 0x0A #define rfbTightPng 0x0A #define rfbTightMaxSubencoding 0x0A diff --git a/test/Makefile.am b/test/Makefile.am index 30498c2..ff6e8f2 100644 --- a/test/Makefile.am +++ b/test/Makefile.am @@ -1,4 +1,15 @@ -INCLUDES = -I$(top_srcdir) +# 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 +tjbench_LDADD=$(LDADD) -lm + +INCLUDES = -I$(top_srcdir) -I$(top_srcdir)/common LDADD = ../libvncserver/libvncserver.la ../libvncclient/libvncclient.la @WSOCKLIB@ if HAVE_LIBPTHREAD 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 <fcntl.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <errno.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#ifdef _WIN32 + #include <io.h> +#else + #include <unistd.h> +#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; j<h; j++, + srcptr+=flip? -srcpitch:srcpitch, dstptr+=dstpitch) + { + for(i=0, srcptr0=srcptr, dstptr0=dstptr; i<w; i++, + srcptr0+=ps[srcformat], dstptr0+=ps[dstformat]) + { + dstptr0[roffset[dstformat]]=srcptr0[roffset[srcformat]]; + dstptr0[goffset[dstformat]]=srcptr0[goffset[srcformat]]; + dstptr0[boffset[dstformat]]=srcptr0[boffset[srcformat]]; + } + } +} + +int loadppm(int *fd, unsigned char **buf, int *w, int *h, + enum BMPPIXELFORMAT f, int align, int dstbottomup, int ascii) +{ + FILE *fs=NULL; int retcode=0, scalefactor, dstpitch; + unsigned char *tempbuf=NULL; char temps[255], temps2[255]; + int numread=0, totalread=0, pixel[3], i, j; + + if((fs=fdopen(*fd, "r"))==NULL) _throw(strerror(errno)); + + do + { + if(!fgets(temps, 255, fs)) _throw("Read error"); + if(strlen(temps)==0 || temps[0]=='\n') continue; + if(sscanf(temps, "%s", temps2)==1 && temps2[1]=='#') continue; + switch(totalread) + { + case 0: + if((numread=sscanf(temps, "%d %d %d", w, h, &scalefactor))==EOF) + _throw("Read error"); + break; + case 1: + if((numread=sscanf(temps, "%d %d", h, &scalefactor))==EOF) + _throw("Read error"); + break; + case 2: + if((numread=sscanf(temps, "%d", &scalefactor))==EOF) + _throw("Read error"); + break; + } + totalread+=numread; + } while(totalread<3); + if((*w)<1 || (*h)<1 || scalefactor<1) _throw("Corrupt PPM header"); + + dstpitch=(((*w)*ps[f])+(align-1))&(~(align-1)); + if((*buf=(unsigned char *)malloc(dstpitch*(*h)))==NULL) + _throw("Memory allocation error"); + if(ascii) + { + for(j=0; j<*h; j++) + { + for(i=0; i<*w; i++) + { + if(fscanf(fs, "%d%d%d", &pixel[0], &pixel[1], &pixel[2])!=3) + _throw("Read error"); + (*buf)[j*dstpitch+i*ps[f]+roffset[f]]=(unsigned char)(pixel[0]*255/scalefactor); + (*buf)[j*dstpitch+i*ps[f]+goffset[f]]=(unsigned char)(pixel[1]*255/scalefactor); + (*buf)[j*dstpitch+i*ps[f]+boffset[f]]=(unsigned char)(pixel[2]*255/scalefactor); + } + } + } + else + { + if(scalefactor!=255) + _throw("Binary PPMs must have 8-bit components"); + if((tempbuf=(unsigned char *)malloc((*w)*(*h)*3))==NULL) + _throw("Memory allocation error"); + if(fread(tempbuf, (*w)*(*h)*3, 1, fs)!=1) _throw("Read error"); + pixelconvert(tempbuf, BMP_RGB, (*w)*3, *buf, f, dstpitch, *w, *h, dstbottomup); + } + + finally: + if(fs) {fclose(fs); *fd=-1;} + if(tempbuf) free(tempbuf); + return retcode; +} + + +int loadbmp(char *filename, unsigned char **buf, int *w, int *h, + enum BMPPIXELFORMAT f, int align, int dstbottomup) +{ + int fd=-1, bytesread, srcpitch, srcbottomup=1, srcps, dstpitch, + retcode=0; + unsigned char *tempbuf=NULL; + bmphdr bh; int flags=O_RDONLY; + + dstbottomup=dstbottomup? 1:0; + #ifdef _WIN32 + flags|=O_BINARY; + #endif + if(!filename || !buf || !w || !h || f<0 || f>BMPPIXELFORMATS-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.bfOffBits<BMPHDRSIZE + || bh.biWidth<1 || bh.biHeight==0) + _throw("Corrupt bitmap header"); + if((bh.biBitCount!=24 && bh.biBitCount!=32) || bh.biCompression!=BI_RGB) + _throw("Only uncompessed RGB bitmaps are supported"); + + *w=bh.biWidth; *h=bh.biHeight; srcps=bh.biBitCount/8; + if(*h<0) {*h=-(*h); srcbottomup=0;} + srcpitch=(((*w)*srcps)+3)&(~3); + dstpitch=(((*w)*ps[f])+(align-1))&(~(align-1)); + + if(srcpitch*(*h)+bh.bfOffBits!=bh.bfSize) _throw("Corrupt bitmap header"); + if((tempbuf=(unsigned char *)malloc(srcpitch*(*h)))==NULL + || (*buf=(unsigned char *)malloc(dstpitch*(*h)))==NULL) + _throw("Memory allocation error"); + if(lseek(fd, (long)bh.bfOffBits, SEEK_SET)!=(long)bh.bfOffBits) + _throw(strerror(errno)); + _unix(bytesread=read(fd, tempbuf, srcpitch*(*h))); + if(bytesread!=srcpitch*(*h)) _throw("Read error"); + + pixelconvert(tempbuf, BMP_BGR, srcpitch, *buf, f, dstpitch, *w, *h, + srcbottomup!=dstbottomup); + + finally: + if(tempbuf) free(tempbuf); + if(fd!=-1) close(fd); + return retcode; +} + +#define writeme(fd, addr, size) \ + if((byteswritten=write(fd, addr, (size)))==-1) _throw(strerror(errno)); \ + if(byteswritten!=(size)) _throw("Write error"); + +int saveppm(char *filename, unsigned char *buf, int w, int h, + enum BMPPIXELFORMAT f, int srcpitch, int srcbottomup) +{ + FILE *fs=NULL; int retcode=0; + unsigned char *tempbuf=NULL; + + if((fs=fopen(filename, "wb"))==NULL) _throw(strerror(errno)); + if(fprintf(fs, "P6\n")<1) _throw("Write error"); + if(fprintf(fs, "%d %d\n", w, h)<1) _throw("Write error"); + if(fprintf(fs, "255\n")<1) _throw("Write error"); + + if((tempbuf=(unsigned char *)malloc(w*h*3))==NULL) + _throw("Memory allocation error"); + + pixelconvert(buf, f, srcpitch, tempbuf, BMP_RGB, w*3, w, h, + srcbottomup); + + if((fwrite(tempbuf, w*h*3, 1, fs))!=1) _throw("Write error"); + + finally: + if(tempbuf) free(tempbuf); + if(fs) fclose(fs); + return retcode; +} + +int savebmp(char *filename, unsigned char *buf, int w, int h, + enum BMPPIXELFORMAT f, int srcpitch, int srcbottomup) +{ + int fd=-1, byteswritten, dstpitch, retcode=0; + int flags=O_RDWR|O_CREAT|O_TRUNC; + unsigned char *tempbuf=NULL; char *temp; + bmphdr bh; int mode; + + #ifdef _WIN32 + flags|=O_BINARY; mode=_S_IREAD|_S_IWRITE; + #else + mode=S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH; + #endif + if(!filename || !buf || w<1 || h<1 || f<0 || f>BMPPIXELFORMATS-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 <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <math.h> +#include <errno.h> +#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)<benchtime; i++) + { + int tile=0; + for(row=0, dstptr=dstbuf; row<ntilesh; row++, dstptr+=pitch*tileh) + { + for(col=0, dstptr2=dstptr; col<ntilesw; col++, tile++, dstptr2+=ps*tilew) + { + int width=dotile? min(tilew, w-col*tilew):scaledw; + int height=dotile? min(tileh, h-row*tileh):scaledh; + if(tjDecompress2(handle, jpegbuf[tile], jpegsize[tile], dstptr2, width, + pitch, height, pf, flags)==-1) + _throwtj("executing tjDecompress2()"); + } + } + } + + if(tjDestroy(handle)==-1) _throwtj("executing tjDestroy()"); + handle=NULL; + + if(quiet) + { + printf("%s\n", + sigfig((double)(w*h)/1000000.*(double)i/elapsed, 4, tempstr, 1024)); + } + else + { + printf("D--> 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; row<h; row++, index+=pitch) + { + for(col=0, index2=index; col<w; col++, index2+=ps) + { + int rindex=index2+tjRedOffset[pf]; + int gindex=index2+tjGreenOffset[pf]; + int bindex=index2+tjBlueOffset[pf]; + int y=(int)((double)srcbuf[rindex]*0.299 + + (double)srcbuf[gindex]*0.587 + + (double)srcbuf[bindex]*0.114 + 0.5); + if(y>255) 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<h; row++) + for(col=0; col<w*ps; col++) + dstbuf[pitch*row+col] + =abs(dstbuf[pitch*row+col]-srcbuf[pitch*row+col]); + } + if(savebmp(tempstr, dstbuf, w, h, bmpPF[pf], pitch, + (flags&TJFLAG_BOTTOMUP)!=0)==-1) + _throwbmp("saving bitmap"); + } + + bailout: + if(file) {fclose(file); file=NULL;} + if(handle) {tjDestroy(handle); handle=NULL;} + if(dstbuf && dstbufalloc) {free(dstbuf); dstbuf=NULL;} + return retval; +} + + +void dotest(unsigned char *srcbuf, int w, int h, int subsamp, int jpegqual, + char *filename) +{ + char tempstr[1024], tempstr2[80]; + FILE *file=NULL; tjhandle handle=NULL; + unsigned char **jpegbuf=NULL, *tmpbuf=NULL, *srcptr, *srcptr2; + double start, elapsed; + int totaljpegsize=0, row, col, i, tilew=w, tileh=h, retval=0; + unsigned long *jpegsize=NULL; + int ps=tjPixelSize[pf], ntilesw=1, ntilesh=1, pitch=w*ps; + + if((tmpbuf=(unsigned char *)malloc(pitch*h)) == NULL) + _throwunix("allocating temporary image buffer"); + + if(!quiet) + printf(">>>>> %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<ntilesw*ntilesh; i++) + { + if((jpegbuf[i]=(unsigned char *)malloc(tjBufSize(tilew, tileh, + subsamp)))==NULL) + _throwunix("allocating JPEG tiles"); + } + + /* Compression test */ + if(quiet==1) + printf("%s\t%s\t%s\t%d\t", pixFormatStr[pf], + (flags&TJFLAG_BOTTOMUP)? "BU":"TD", subNameLong[subsamp], jpegqual); + for(i=0; i<h; i++) + memcpy(&tmpbuf[pitch*i], &srcbuf[w*ps*i], w*ps); + if((handle=tjInitCompress())==NULL) + _throwtj("executing tjInitCompress()"); + + /* Execute once to preload cache */ + if(tjCompress2(handle, srcbuf, tilew, pitch, tileh, pf, &jpegbuf[0], + &jpegsize[0], subsamp, jpegqual, flags)==-1) + _throwtj("executing tjCompress2()"); + + /* Benchmark */ + for(i=0, start=gettime(); (elapsed=gettime()-start)<benchtime; i++) + { + int tile=0; + totaljpegsize=0; + for(row=0, srcptr=srcbuf; row<ntilesh; row++, srcptr+=pitch*tileh) + { + for(col=0, srcptr2=srcptr; col<ntilesw; col++, tile++, + srcptr2+=ps*tilew) + { + int width=min(tilew, w-col*tilew); + int height=min(tileh, h-row*tileh); + if(tjCompress2(handle, srcptr2, width, pitch, height, pf, + &jpegbuf[tile], &jpegsize[tile], subsamp, jpegqual, flags)==-1) + _throwtj("executing tjCompress()2"); + totaljpegsize+=jpegsize[tile]; + } + } + } + + if(tjDestroy(handle)==-1) _throwtj("executing tjDestroy()"); + handle=NULL; + + if(quiet==1) printf("%-4d %-4d\t", tilew, tileh); + if(quiet) + { + printf("%s%c%s%c", + sigfig((double)(w*h)/1000000.*(double)i/elapsed, 4, tempstr, 1024), + quiet==2? '\n':'\t', + sigfig((double)(w*h*ps)/(double)totaljpegsize, 4, tempstr2, 80), + quiet==2? '\n':'\t'); + } + else + { + printf("\n%s size: %d x %d\n", dotile? "Tile":"Image", tilew, + tileh); + printf("C--> 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<ntilesw*ntilesh; i++) + { + if(jpegbuf[i]) free(jpegbuf[i]); jpegbuf[i]=NULL; + } + free(jpegbuf); jpegbuf=NULL; + free(jpegsize); jpegsize=NULL; + + if(tilew==w && tileh==h) break; + } + + bailout: + if(file) {fclose(file); file=NULL;} + if(jpegbuf) + { + for(i=0; i<ntilesw*ntilesh; i++) + { + if(jpegbuf[i]) free(jpegbuf[i]); jpegbuf[i]=NULL; + } + free(jpegbuf); jpegbuf=NULL; + } + if(jpegsize) {free(jpegsize); jpegsize=NULL;} + if(tmpbuf) {free(tmpbuf); tmpbuf=NULL;} + if(handle) {tjDestroy(handle); handle=NULL;} + return; +} + + +void dodecomptest(char *filename) +{ + FILE *file=NULL; tjhandle handle=NULL; + unsigned char **jpegbuf=NULL, *srcbuf=NULL; + unsigned long *jpegsize=NULL, srcsize; + int w=0, h=0, subsamp=-1, _w, _h, _tilew, _tileh, _subsamp; + char *temp=NULL; + int i, tilew, tileh, ntilesw=1, ntilesh=1, retval=0; + + if((file=fopen(filename, "rb"))==NULL) + _throwunix("opening file"); + if(fseek(file, 0, SEEK_END)<0 || (srcsize=ftell(file))<0) + _throwunix("determining file size"); + if((srcbuf=(unsigned char *)malloc(srcsize))==NULL) + _throwunix("allocating memory"); + if(fseek(file, 0, SEEK_SET)<0) + _throwunix("setting file position"); + if(fread(srcbuf, srcsize, 1, file)<1) + _throwunix("reading JPEG data"); + fclose(file); file=NULL; + + temp=strrchr(filename, '.'); + if(temp!=NULL) *temp='\0'; + + if((handle=tjInitDecompress())==NULL) + _throwtj("executing tjInitDecompress()"); + if(tjDecompressHeader2(handle, srcbuf, srcsize, &w, &h, &subsamp)==-1) + _throwtj("executing tjDecompressHeader2()"); + + if(quiet==1) + { + printf("All performance values in Mpixels/sec\n\n"); + printf("Bitmap\tBitmap\tJPEG\t%s %s \tXform\tComp\tDecomp\n", + dotile? "Tile ":"Image", dotile? "Tile ":"Image"); + printf("Format\tOrder\tSubsamp\tWidth Height\tPerf \tRatio\tPerf\n\n"); + } + else if(!quiet) + { + printf(">>>>> 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<ntilesw*ntilesh; i++) + { + if((jpegbuf[i]=(unsigned char *)malloc(tjBufSize(tilew, tileh, + subsamp)))==NULL) + _throwunix("allocating JPEG tiles"); + } + + _w=w; _h=h; _tilew=tilew; _tileh=tileh; + if(!quiet) + { + printf("\n%s size: %d x %d", dotile? "Tile":"Image", _tilew, + _tileh); + if(sf.num!=1 || sf.denom!=1) + printf(" --> %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<ntilesw*ntilesh; i++) + { + free(jpegbuf[i]); jpegbuf[i]=NULL; + } + free(jpegbuf); jpegbuf=NULL; + if(jpegsize) {free(jpegsize); jpegsize=NULL;} + + if(tilew==w && tileh==h) break; + } + + bailout: + if(file) {fclose(file); file=NULL;} + if(jpegbuf) + { + for(i=0; i<ntilesw*ntilesh; i++) + { + if(jpegbuf[i]) free(jpegbuf[i]); jpegbuf[i]=NULL; + } + free(jpegbuf); jpegbuf=NULL; + } + if(jpegsize) {free(jpegsize); jpegsize=NULL;} + if(srcbuf) {free(srcbuf); srcbuf=NULL;} + if(handle) {tjDestroy(handle); handle=NULL;} + return; +} + + +void usage(char *progname) +{ + int i; + printf("USAGE: %s\n", progname); + printf(" <Inputfile (BMP|PPM)> <%% Quality> [options]\n\n"); + printf(" %s\n", progname); + printf(" <Inputfile (JPG)> [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; i<nsf; i++) + { + printf("%d/%d", scalingfactors[i].num, scalingfactors[i].denom); + if(nsf==2 && i!=nsf-1) printf(" or "); + else if(nsf>2) + { + if(i!=nsf-1) printf(", "); + if(i==nsf-2) printf("or "); + } + } + printf(")\n"); + printf("-benchtime <t> = Run each benchmark for at least <t> 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(argc<minarg) usage(argv[0]); + + temp=strrchr(argv[1], '.'); + if(temp!=NULL) + { + if(!strcasecmp(temp, ".bmp")) ext="bmp"; + if(!strcasecmp(temp, ".jpg") || !strcasecmp(temp, ".jpeg")) decomponly=1; + } + + printf("\n"); + + if(!decomponly) + { + minarg=3; + if(argc<minarg) usage(argv[0]); + if((minqual=atoi(argv[2]))<1 || minqual>100) + { + 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; i<argc; i++) + { + if(!strcasecmp(argv[i], "-tile")) + { + dotile=1; + } + if(!strcasecmp(argv[i], "-forcesse3")) + { + printf("Forcing SSE3 code\n\n"); + flags|=TJFLAG_FORCESSE3; + } + if(!strcasecmp(argv[i], "-forcesse2")) + { + printf("Forcing SSE2 code\n\n"); + flags|=TJFLAG_FORCESSE2; + } + if(!strcasecmp(argv[i], "-forcesse")) + { + printf("Forcing SSE code\n\n"); + flags|=TJFLAG_FORCESSE; + } + if(!strcasecmp(argv[i], "-forcemmx")) + { + printf("Forcing MMX code\n\n"); + flags|=TJFLAG_FORCEMMX; + } + if(!strcasecmp(argv[i], "-fastupsample")) + { + printf("Using fast upsampling code\n\n"); + flags|=TJFLAG_FASTUPSAMPLE; + } + if(!strcasecmp(argv[i], "-rgb")) pf=TJPF_RGB; + if(!strcasecmp(argv[i], "-rgbx")) pf=TJPF_RGBX; + if(!strcasecmp(argv[i], "-bgr")) pf=TJPF_BGR; + if(!strcasecmp(argv[i], "-bgrx")) pf=TJPF_BGRX; + if(!strcasecmp(argv[i], "-xbgr")) pf=TJPF_XBGR; + if(!strcasecmp(argv[i], "-xrgb")) pf=TJPF_XRGB; + if(!strcasecmp(argv[i], "-bottomup")) flags|=TJFLAG_BOTTOMUP; + if(!strcasecmp(argv[i], "-quiet")) quiet=1; + if(!strcasecmp(argv[i], "-qq")) quiet=2; + if(!strcasecmp(argv[i], "-scale") && i<argc-1) + { + int temp1=0, temp2=0, match=0; + if(sscanf(argv[++i], "%d/%d", &temp1, &temp2)==2) + { + for(j=0; j<nsf; j++) + { + if(temp1==scalingfactors[j].num && temp2==scalingfactors[j].denom) + { + sf=scalingfactors[j]; + match=1; break; + } + } + if(!match) usage(argv[0]); + } + else usage(argv[0]); + } + if(!strcasecmp(argv[i], "-benchtime") && i<argc-1) + { + double temp=atof(argv[++i]); + if(temp>0.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 <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include "./tjutil.h" +#include "./turbojpeg.h" +#ifdef _WIN32 + #include <time.h> + #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<h; row++) + { + for(col=0; col<w; col++) + { + if(flags&TJFLAG_BOTTOMUP) index=(h-row-1)*w+col; + else index=row*w+col; + if(((row/8)+(col/8))%2==0) buf[index]=(row<halfway)? 255:0; + else buf[index]=(row<halfway)? 76:226; + } + } + } + else + { + for(row=0; row<h; row++) + { + for(col=0; col<w; col++) + { + if(flags&TJFLAG_BOTTOMUP) index=(h-row-1)*w+col; + else index=row*w+col; + if(((row/8)+(col/8))%2==0) + { + if(row<halfway) + { + buf[index*ps+roffset]=255; + buf[index*ps+goffset]=255; + buf[index*ps+boffset]=255; + } + } + else + { + buf[index*ps+roffset]=255; + if(row>=halfway) buf[index*ps+goffset]=255; + } + } + } + } +} + + +#define checkval(v, cv) { \ + if(v<cv-1 || v>cv+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<h; row++) + { + for(col=0; col<w; col++) + { + unsigned char r, g, b, a; + if(flags&TJFLAG_BOTTOMUP) index=(h-row-1)*w+col; + else index=row*w+col; + r=buf[index*ps+roffset]; + g=buf[index*ps+goffset]; + b=buf[index*ps+boffset]; + a=aoffset>=0? buf[index*ps+aoffset]:0xFF; + if(((row/blocksize)+(col/blocksize))%2==0) + { + if(row<halfway) + { + checkval255(r); checkval255(g); checkval255(b); + } + else + { + checkval0(r); checkval0(g); checkval0(b); + } + } + else + { + if(subsamp==TJSAMP_GRAY) + { + if(row<halfway) + { + checkval(r, 76); checkval(g, 76); checkval(b, 76); + } + else + { + checkval(r, 226); checkval(g, 226); checkval(b, 226); + } + } + else + { + if(row<halfway) + { + checkval255(r); checkval0(g); checkval0(b); + } + else + { + checkval255(r); checkval255(g); checkval0(b); + } + } + } + checkval255(a); + } + } + + bailout: + if(retval==0) + { + printf("\n"); + for(row=0; row<h; row++) + { + for(col=0; col<w; col++) + { + printf("%.3d/%.3d/%.3d ", buf[(row*w+col)*ps+roffset], + buf[(row*w+col)*ps+goffset], buf[(row*w+col)*ps+boffset]); + } + printf("\n"); + } + } + return retval; +} + + +void writeJPEG(unsigned char *jpegBuf, unsigned long jpegSize, char *filename) +{ + FILE *file=fopen(filename, "wb"); + if(!file || fwrite(jpegBuf, jpegSize, 1, file)!=1) + { + printf("ERROR: Could not write to %s.\n%s\n", filename, strerror(errno)); + bailout(); + } + + bailout: + if(file) fclose(file); +} + + +void compTest(tjhandle handle, unsigned char **dstBuf, + unsigned long *dstSize, int w, int h, int pf, char *basename, + int subsamp, int jpegQual, int flags) +{ + char tempStr[1024]; unsigned char *srcBuf=NULL; + double t; + + printf("%s %s -> %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<n; i++) + _decompTest(handle, jpegBuf, jpegSize, w, h, pf, basename, subsamp, + flags, sf[i]); + } + else + _decompTest(handle, jpegBuf, jpegSize, w, h, pf, basename, subsamp, flags, + sf1); + + bailout: + printf("\n"); +} + + +void doTest(int w, int h, const int *formats, int nformats, int subsamp, + char *basename) +{ + tjhandle chandle=NULL, dhandle=NULL; + unsigned char *dstBuf=NULL; + unsigned long size=0; int pfi, pf, i; + + size=tjBufSize(w, h, subsamp); + if((dstBuf=(unsigned char *)malloc(size))==NULL) + _throw("Memory allocation failure."); + + if((chandle=tjInitCompress())==NULL || (dhandle=tjInitDecompress())==NULL) + _throwtj(); + + for(pfi=0; pfi<nformats; pfi++) + { + for(i=0; i<2; i++) + { + int flags=0; + if(subsamp==TJSAMP_422 || subsamp==TJSAMP_420 || subsamp==TJSAMP_440) + flags|=TJFLAG_FASTUPSAMPLE; + if(i==1) flags|=TJFLAG_BOTTOMUP; + pf=formats[pfi]; + compTest(chandle, &dstBuf, &size, w, h, pf, basename, subsamp, 100, + flags); + decompTest(dhandle, dstBuf, size, w, h, pf, basename, subsamp, + flags); + if(pf>=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<TJ_NUMSAMP; subsamp++) + { + for(w=1; w<48; w++) + { + int maxh=(w==1)? 2048:48; + for(h=1; h<maxh; h++) + { + if(h%100==0) printf("%.4d x %.4d\b\b\b\b\b\b\b\b\b\b\b", w, h); + if((srcBuf=(unsigned char *)malloc(w*h*4))==NULL) + _throw("Memory allocation failure"); + if((jpegBuf=(unsigned char *)malloc(tjBufSize(w, h, subsamp))) + ==NULL) + _throw("Memory allocation failure"); + jpegSize=tjBufSize(w, h, subsamp); + + for(i=0; i<w*h*4; i++) + { + if(random()<RAND_MAX/2) srcBuf[i]=0; + else srcBuf[i]=255; + } + + _tj(tjCompress2(handle, srcBuf, w, 0, h, TJPF_BGRX, &jpegBuf, + &jpegSize, subsamp, 100, 0)); + free(srcBuf); srcBuf=NULL; + free(jpegBuf); jpegBuf=NULL; + + if((srcBuf=(unsigned char *)malloc(h*w*4))==NULL) + _throw("Memory allocation failure"); + if((jpegBuf=(unsigned char *)malloc(tjBufSize(h, w, subsamp))) + ==NULL) + _throw("Memory allocation failure"); + jpegSize=tjBufSize(h, w, subsamp); + + for(i=0; i<h*w*4; i++) + { + if(random()<RAND_MAX/2) srcBuf[i]=0; + else srcBuf[i]=255; + } + + _tj(tjCompress2(handle, srcBuf, h, 0, w, TJPF_BGRX, &jpegBuf, + &jpegSize, subsamp, 100, 0)); + free(srcBuf); srcBuf=NULL; + free(jpegBuf); jpegBuf=NULL; + } + } + } + printf("Done. \n"); + + bailout: + if(srcBuf) free(srcBuf); + if(jpegBuf) free(jpegBuf); + if(handle) tjDestroy(handle); +} + + +int main(int argc, char *argv[]) +{ + #ifdef _WIN32 + srand((unsigned int)time(NULL)); + #endif + doTest(35, 39, _3byteFormats, 2, TJSAMP_444, "test"); + doTest(39, 41, _4byteFormats, 4, TJSAMP_444, "test"); + doTest(41, 35, _3byteFormats, 2, TJSAMP_422, "test"); + doTest(35, 39, _4byteFormats, 4, TJSAMP_422, "test"); + doTest(39, 41, _3byteFormats, 2, TJSAMP_420, "test"); + doTest(41, 35, _4byteFormats, 4, TJSAMP_420, "test"); + doTest(35, 39, _3byteFormats, 2, TJSAMP_440, "test"); + doTest(39, 41, _4byteFormats, 4, TJSAMP_440, "test"); + doTest(35, 39, _onlyGray, 1, TJSAMP_GRAY, "test"); + doTest(39, 41, _3byteFormats, 2, TJSAMP_GRAY, "test"); + doTest(41, 35, _4byteFormats, 4, TJSAMP_GRAY, "test"); + bufSizeTest(); + + return exitStatus; +} diff --git a/test/tjutil.c b/test/tjutil.c new file mode 100644 index 0000000..6618d15 --- /dev/null +++ b/test/tjutil.c @@ -0,0 +1,66 @@ +/* + * 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 + +#include <windows.h> + +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 <stdlib.h> +#include <sys/time.h> + +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 <stdio.h> + #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); 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@ |