#define PAD(v, p) ((v+(p)-1)&(~((p)-1)))
-int checkbufyuv(unsigned char *buf, unsigned long size, int w, int h,
- int subsamp)
+int checkbufyuv(unsigned char *buf, int w, int h, int subsamp)
{
int i, j;
int hsf=_hsf[subsamp], vsf=_vsf[subsamp];
int cw=pw/hsf, ch=ph/vsf;
int ypitch=PAD(pw, 4), uvpitch=PAD(cw, 4);
int retval=1;
- unsigned long correctsize=ypitch*ph + (subsamp==TJ_GRAYSCALE? 0:uvpitch*ch*2);
-
- if(size!=correctsize)
- {
- printf("\nIncorrect size %lu. Should be %lu\n", size, correctsize);
- retval=0; goto bailout;
- }
for(i=0; i<16; i++)
{
char tempstr[1024]; unsigned char *bmpbuf=NULL;
const char *pixformat; double t;
- if(yuv==YUVENCODE) flags|=TJ_YUV;
-
if(flags&TJ_BGR)
{
if(ps==3) pixformat="BGR";
printf("ERROR: Could not allocate buffer\n"); bailout();
}
initbuf(bmpbuf, w, h, ps, flags);
- memset(jpegbuf, 0, TJBUFSIZE(w, h));
+ memset(jpegbuf, 0,
+ yuv==YUVENCODE? TJBUFSIZEYUV(w, h, subsamp):TJBUFSIZE(w, h));
t=rrtime();
- _catch(tjCompress(hnd, bmpbuf, w, 0, h, ps, jpegbuf, size, subsamp, qual,
- flags));
+ if(yuv==YUVENCODE)
+ {
+ _catch(tjEncodeYUV(hnd, bmpbuf, w, 0, h, ps, jpegbuf, subsamp, flags));
+ *size=TJBUFSIZEYUV(w, h, subsamp);
+ }
+ else
+ {
+ _catch(tjCompress(hnd, bmpbuf, w, 0, h, ps, jpegbuf, size, subsamp, qual,
+ flags));
+ }
t=rrtime()-t;
if(yuv==YUVENCODE)
writejpeg(jpegbuf, *size, tempstr);
if(yuv==YUVENCODE)
{
- if(checkbufyuv(jpegbuf, *size, w, h, subsamp)) printf("Passed.");
+ if(checkbufyuv(jpegbuf, w, h, subsamp)) printf("Passed.");
else {printf("FAILED!"); exitstatus=-1;}
}
else printf("Done.");
int temp1, temp2;
unsigned long size=0;
- if(yuv==YUVDECODE) flags|=TJ_YUV;
- else if(yuv==YUVENCODE) return;
+ if(yuv==YUVENCODE) return;
if(flags&TJ_BGR)
{
printf("Scaled size mismatch\n"); bailout();
}
- if(yuv==YUVDECODE)
- size=TJBUFSIZEYUV(w, h, subsamp);
- else
- size=scaledw*scaledh*ps;
- if((bmpbuf=(unsigned char *)malloc(size+1))==NULL)
+ if(yuv==YUVDECODE) size=TJBUFSIZEYUV(w, h, subsamp);
+ else size=scaledw*scaledh*ps+1;
+ if((bmpbuf=(unsigned char *)malloc(size))==NULL)
{
printf("ERROR: Could not allocate buffer\n"); bailout();
}
- memset(bmpbuf, 0, size+1);
+ memset(bmpbuf, 0, size);
t=rrtime();
- _catch(tjDecompress(hnd, jpegbuf, jpegsize, bmpbuf, scaledw, 0, scaledh, ps,
- flags));
+ if(yuv==YUVDECODE)
+ {
+ _catch(tjDecompressToYUV(hnd, jpegbuf, jpegsize, bmpbuf, flags));
+ }
+ else
+ {
+ _catch(tjDecompress(hnd, jpegbuf, jpegsize, bmpbuf, scaledw, 0, scaledh, ps,
+ flags));
+ }
t=rrtime()-t;
if(yuv==YUVDECODE)
{
- if(checkbufyuv(bmpbuf, size, w, h, subsamp))
- printf("Passed.");
+ if(checkbufyuv(bmpbuf, w, h, subsamp)) printf("Passed.");
else {printf("FAILED!"); exitstatus=-1;}
}
else
tjhandle hnd=NULL, dhnd=NULL; unsigned char *jpegbuf=NULL;
unsigned long size;
- if((jpegbuf=(unsigned char *)malloc(TJBUFSIZE(w, h))) == NULL)
+ size=(yuv==YUVENCODE? TJBUFSIZEYUV(w, h, subsamp):TJBUFSIZE(w, h));
+ if((jpegbuf=(unsigned char *)malloc(size)) == NULL)
{
puts("ERROR: Could not allocate buffer."); bailout();
}
const char *_pfname[]={"RGB", "RGBX", "BGR", "BGRX", "XBGR", "XRGB"};
const char *_subnamel[NUMSUBOPT]={"4:4:4", "4:2:2", "4:2:0", "GRAY"};
const char *_subnames[NUMSUBOPT]={"444", "422", "420", "GRAY"};
-const int _hsf[NUMSUBOPT]={1, 2, 2, 1};
-const int _vsf[NUMSUBOPT]={1, 1, 2, 1};
void printsigfig(double val, int figs)
{
int i, j, ITER, rgbbufalloc=0;
double start, elapsed;
int ps=_ps[pf];
- int hsf=_hsf[jpegsub], vsf=_vsf[jpegsub];
- int pw=PAD(w, hsf), ph=PAD(h, vsf);
- int cw=pw/hsf, ch=ph/vsf;
- int ypitch=PAD(pw, 4), uvpitch=PAD(cw, 4);
- int yuvsize=ypitch*ph + (jpegsub==TJ_GRAYSCALE? 0:uvpitch*ch*2);
- int scaledw=(flags&TJ_YUV)? w : (w+scalefactor-1)/scalefactor;
- int scaledh=(flags&TJ_YUV)? h : (h+scalefactor-1)/scalefactor;
+ int yuvsize=TJBUFSIZEYUV(w, h, jpegsub), bufsize;
+ int scaledw=(yuv==YUVDECODE)? w : (w+scalefactor-1)/scalefactor;
+ int scaledh=(yuv==YUVDECODE)? h : (h+scalefactor-1)/scalefactor;
int pitch=scaledw*ps;
if(qual>0)
flags |= _flags[pf];
if(bu) flags |= TJ_BOTTOMUP;
- if(yuv==YUVDECODE) flags |= TJ_YUV;
if((hnd=tjInitDecompress())==NULL)
_throwtj("executing tjInitDecompress()");
if(rgbbuf==NULL)
{
- if((rgbbuf=(unsigned char *)malloc(max(yuvsize, pitch*scaledh))) == NULL)
+ bufsize=(yuv==YUVDECODE? yuvsize:pitch*h);
+ if((rgbbuf=(unsigned char *)malloc(bufsize)) == NULL)
_throwunix("allocating image buffer");
rgbbufalloc=1;
}
// Grey image means decompressor did nothing
- memset(rgbbuf, 127, max(yuvsize, pitch*scaledh));
+ memset(rgbbuf, 127, bufsize);
- if(tjDecompress(hnd, jpegbuf[0], comptilesize[0], rgbbuf, scaledw, pitch,
- scaledh, ps, flags)==-1)
+ if(yuv==YUVDECODE)
+ {
+ if(tjDecompressToYUV(hnd, jpegbuf[0], comptilesize[0], rgbbuf, flags)==-1)
+ _throwtj("executing tjDecompressToYUV()");
+ }
+ else if(tjDecompress(hnd, jpegbuf[0], comptilesize[0], rgbbuf, scaledw,
+ pitch, scaledh, ps, flags)==-1)
_throwtj("executing tjDecompress()");
ITER=0;
start=rrtime();
for(j=0; j<w; j+=tilesizex)
{
int tempw=min(tilesizex, w-j), temph=min(tilesizey, h-i);
- if(tjDecompress(hnd, jpegbuf[tilen], comptilesize[tilen],
+ if(yuv==YUVDECODE)
+ {
+ if(tjDecompressToYUV(hnd, jpegbuf[tilen], comptilesize[tilen],
+ &rgbbuf[pitch*i+ps*j], flags)==-1)
+ _throwtj("executing tjDecompressToYUV()");
+ }
+ else if(tjDecompress(hnd, jpegbuf[tilen], comptilesize[tilen],
&rgbbuf[pitch*i+ps*j], scaledw, pitch, scaledh, ps, flags)==-1)
_throwtj("executing tjDecompress()");
tilen++;
}
}
ITER++;
- } while((elapsed=rrtime()-start)<5.);
+ } while((elapsed=rrtime()-start)<0.05);
if(tjDestroy(hnd)==-1) _throwtj("executing tjDestroy()");
hnd=NULL;
if(quiet)
|(fastupsample?TJ_FASTUPSAMPLE:0);
int ps=_ps[pf], tilen;
int pitch=w*ps, yuvsize;
- int hsf=_hsf[jpegsub], vsf=_vsf[jpegsub];
- int pw=PAD(w, hsf), ph=PAD(h, vsf);
- int cw=pw/hsf, ch=ph/vsf;
- int ypitch=PAD(pw, 4), uvpitch=PAD(cw, 4);
flags |= _flags[pf];
if(bu) flags |= TJ_BOTTOMUP;
- if(yuv==YUVENCODE) flags |= TJ_YUV;
- yuvsize=ypitch*ph + (jpegsub==TJ_GRAYSCALE? 0:uvpitch*ch*2);
- if((rgbbuf=(unsigned char *)malloc(max(yuvsize, pitch*h))) == NULL)
+ if(yuv==YUVENCODE) yuvsize=TJBUFSIZEYUV(w, h, jpegsub);
+ if((rgbbuf=(unsigned char *)malloc(max(yuvsize, pitch*h+1))) == NULL)
_throwunix("allocating image buffer");
if(!quiet)
memset(jpegbuf, 0, sizeof(unsigned char *)*numtilesx*numtilesy);
for(i=0; i<numtilesx*numtilesy; i++)
{
- if((jpegbuf[i]=(unsigned char *)malloc(TJBUFSIZE(tilesizex, tilesizey))) == NULL)
+ if((jpegbuf[i]=(unsigned char *)malloc(
+ yuv==YUVENCODE? TJBUFSIZEYUV(tilesizex, tilesizey, jpegsub)
+ : TJBUFSIZE(tilesizex, tilesizey))) == NULL)
_throwunix("allocating image buffers");
}
for(i=0; i<h; i++) memcpy(&rgbbuf[pitch*i], &srcbuf[w*ps*i], w*ps);
if((hnd=tjInitCompress())==NULL)
_throwtj("executing tjInitCompress()");
- if(tjCompress(hnd, rgbbuf, tilesizex, pitch, tilesizey, ps,
+ if(yuv==YUVENCODE)
+ {
+ if(tjEncodeYUV(hnd, rgbbuf, tilesizex, pitch, tilesizey, ps,
+ jpegbuf[0], jpegsub, flags)==-1)
+ _throwtj("executing tjEncodeYUV()");
+ comptilesize[0]=TJBUFSIZEYUV(tilesizex, tilesizey, jpegsub);
+ }
+ else if(tjCompress(hnd, rgbbuf, tilesizex, pitch, tilesizey, ps,
jpegbuf[0], &comptilesize[0], jpegsub, qual, flags)==-1)
_throwtj("executing tjCompress()");
ITER=0;
for(j=0; j<w; j+=tilesizex)
{
int tempw=min(tilesizex, w-j), temph=min(tilesizey, h-i);
- if(tjCompress(hnd, &rgbbuf[pitch*i+j*ps], tempw, pitch,
+ if(yuv==YUVENCODE)
+ {
+ if(tjEncodeYUV(hnd, &rgbbuf[pitch*i+j*ps], tempw, pitch,
+ temph, ps, jpegbuf[tilen], jpegsub, flags)==-1)
+ _throwtj("executing tjEncodeYUV()");
+ comptilesize[tilen]=TJBUFSIZEYUV(tempw, temph, jpegsub);
+ }
+ else if(tjCompress(hnd, &rgbbuf[pitch*i+j*ps], tempw, pitch,
temph, ps, jpegbuf[tilen], &comptilesize[tilen], jpegsub, qual,
flags)==-1)
_throwtj("executing tjCompress()");
}
}
ITER++;
- } while((elapsed=rrtime()-start)<5.);
+ } while((elapsed=rrtime()-start)<0.05);
if(tjDestroy(hnd)==-1) _throwtj("executing tjDestroy()");
hnd=NULL;
if(quiet==1)
global:
TJBUFSIZEYUV;
tjDecompressHeader2;
+ tjDecompressToYUV;
+ tjEncodeYUV;
} TURBOJPEG_1.0;
global:
TJBUFSIZEYUV;
tjDecompressHeader2;
+ tjDecompressToYUV;
+ tjEncodeYUV;
} TURBOJPEG_1.0;
TURBOJPEG_1.2
/* Use fast, inaccurate 4:2:2 and 4:2:0 YUV upsampling routines
(libjpeg and libjpeg-turbo versions only) */
#define TJ_YUV 512
- /* If passed to tjCompress(), this causes TurboJPEG to use the accelerated
- color conversion routines in the underlying codec to produce a planar
- YUV image that is suitable for X Video. Specifically, if the chrominance
- components are subsampled along the horizontal dimension, then the width
- of the luminance plane is padded to 2 in the output image (same goes for
- the height of the luminance plane, if the chrominance components are
- subsampled along the vertical dimension.) Also, each line of each plane
- in the output image is padded to 4 bytes. Although this will work with
- any subsampling option, it is really only useful in combination with
- TJ_420, which produces an image compatible with the I420 (AKA "YUV420P")
- format.
-
- If passed to tjDecompress(), this tells TurboJPEG to perform JPEG
- decompression but to leave out the color conversion step, so a planar YUV
- image is generated instead of an RGB image. The padding of the planes in
- this image is the same as in the above case. Note that, if the width or
- height of the output image is not a multiple of 8 (or a multiple of 16
- along any dimension in which chrominance subsampling is used), then an
- intermediate buffer copy will be performed within TurboJPEG.
- */
+ /* Nothing to see here. Pay no attention to the man behind the curtain. */
typedef void* tjhandle;
and returns a handle to the instance. Most applications will only
need to call this once at the beginning of the program or once for each
concurrent thread. Don't try to create a new instance every time you
- compress an image, because this will cause performance to suffer.
+ compress an image, because this may cause performance to suffer in some
+ TurboJPEG implementations.
RETURNS: NULL on error
*/
tjInitCompress()
[INPUT] srcbuf = pointer to user-allocated image buffer containing RGB or
grayscale pixels to be compressed
- [INPUT] width = width (in pixels) of the source image
+ [INPUT] width = width (in pixels) of the source image
[INPUT] pitch = bytes per line of the source image (width*pixelsize if the
bitmap is unpadded, else TJPAD(width*pixelsize) if each line of the bitmap
is padded to the nearest 32-bit boundary, such as is the case for Windows
the appropriate size for this buffer based on the image width and height.
[OUTPUT] size = pointer to unsigned long which receives the size (in bytes)
of the compressed image
- [INPUT] jpegsubsamp = Specifies either 4:2:0, 4:2:2, or 4:4:4 subsampling.
- When the image is converted from the RGB to YCbCr colorspace as part of
- the JPEG compression process, every other Cb and Cr (chrominance) pixel
- can be discarded to produce a smaller image with little perceptible loss
- of image clarity (the human eye is more sensitive to small changes in
- brightness than small changes in color.)
+ [INPUT] jpegsubsamp = Specifies either 4:2:0, 4:2:2, 4:4:4, or grayscale
+ subsampling. When the image is converted from the RGB to YCbCr colorspace
+ as part of the JPEG compression process, every other Cb and Cr
+ (chrominance) pixel can be discarded to produce a smaller image with
+ little perceptible loss of image clarity (the human eye is more sensitive
+ to small changes in brightness than small changes in color.)
TJ_420: 4:2:0 subsampling. Discards every other Cb, Cr pixel in both
horizontal and vertical directions
int subsamp);
+/*
+ int tjEncodeYUV(tjhandle j,
+ unsigned char *srcbuf, int width, int pitch, int height, int pixelsize,
+ unsigned char *dstbuf, int subsamp, int flags)
+
+ This function uses the accelerated color conversion routines in TurboJPEG's
+ underlying codec to produce a planar YUV image that is suitable for X Video.
+ Specifically, if the chrominance components are subsampled along the
+ horizontal dimension, then the width of the luminance plane is padded to 2 in
+ the output image (same goes for the height of the luminance plane, if the
+ chrominance components are subsampled along the vertical dimension.) Also,
+ each line of each plane in the output image is padded to 4 bytes. Although
+ this will work with any subsampling option, it is really only useful in
+ combination with TJ_420, which produces an image compatible with the I420
+ (AKA "YUV420P") format.
+
+ [INPUT] j = instance handle previously returned from a call to
+ tjInitCompress()
+ [INPUT] srcbuf = pointer to user-allocated image buffer containing RGB or
+ grayscale pixels to be encoded
+ [INPUT] width = width (in pixels) of the source image
+ [INPUT] pitch = bytes per line of the source image (width*pixelsize if the
+ bitmap is unpadded, else TJPAD(width*pixelsize) if each line of the bitmap
+ is padded to the nearest 32-bit boundary, such as is the case for Windows
+ bitmaps. You can also be clever and use this parameter to skip lines,
+ etc. Setting this parameter to 0 is the equivalent of setting it to
+ width*pixelsize.
+ [INPUT] height = height (in pixels) of the source image
+ [INPUT] pixelsize = size (in bytes) of each pixel in the source image
+ RGBX/BGRX/XRGB/XBGR: 4, RGB/BGR: 3, Grayscale: 1
+ [INPUT] dstbuf = pointer to user-allocated image buffer which will receive
+ the YUV image. Use the TJBUFSIZEYUV(width, height, subsamp) function to
+ determine the appropriate size for this buffer based on the image width,
+ height, and level of subsampling.
+ [INPUT] subsamp = Specifies either 4:2:0, 4:2:2, 4:4:4, or grayscale
+ subsampling (see description under tjCompress())
+ [INPUT] flags = the bitwise OR of one or more of the flags described in the
+ "Flags" section above
+
+ RETURNS: 0 on success, -1 on error
+*/
+DLLEXPORT int DLLCALL tjEncodeYUV(tjhandle j,
+ unsigned char *srcbuf, int width, int pitch, int height, int pixelsize,
+ unsigned char *dstbuf, int subsamp, int flags);
+
+
/*
tjhandle tjInitDecompress(void)
structures, and returns a handle to the instance. Most applications will
only need to call this once at the beginning of the program or once for each
concurrent thread. Don't try to create a new instance every time you
- decompress an image, because this will cause performance to suffer.
+ decompress an image, because this may cause performance to suffer in some
+ TurboJPEG implementations.
RETURNS: NULL on error
*/
[INPUT] flags = the bitwise OR of one or more of the flags described in the
"Flags" section above.
- NOTE: The width, pitch, height, and pixelsize parameters are ignored if
- decompressing to a YUV planar image.
-
RETURNS: 0 on success, -1 on error
*/
DLLEXPORT int DLLCALL tjDecompress(tjhandle j,
int flags);
+/*
+ int tjDecompressToYUV(tjhandle j,
+ unsigned char *srcbuf, unsigned long size,
+ unsigned char *dstbuf, int flags)
+
+ This function performs JPEG decompression but leaves out the color conversion
+ step, so a planar YUV image is generated instead of an RGB image. The
+ padding of the planes in this image is the same as in tjEncodeYUV().
+ Note that, if the width or height of the output image is not a multiple of 8
+ (or a multiple of 16 along any dimension in which chrominance subsampling is
+ used), then an intermediate buffer copy will be performed within TurboJPEG.
+
+ [INPUT] j = instance handle previously returned from a call to
+ tjInitDecompress()
+ [INPUT] srcbuf = pointer to a user-allocated buffer containing the JPEG image
+ to decompress
+ [INPUT] size = size of the JPEG image buffer (in bytes)
+ [INPUT] dstbuf = pointer to user-allocated image buffer which will receive
+ the YUV image. Use the TJBUFSIZEYUV(width, height, subsamp) function to
+ determine the appropriate size for this buffer based on the image width,
+ height, and level of subsampling.
+ [INPUT] flags = the bitwise OR of one or more of the flags described in the
+ "Flags" section above.
+
+ RETURNS: 0 on success, -1 on error
+*/
+DLLEXPORT int DLLCALL tjDecompressToYUV(tjhandle j,
+ unsigned char *srcbuf, unsigned long size,
+ unsigned char *dstbuf, int flags);
+
+
/*
int tjDestroy(tjhandle h)
return (tjhandle)j;
}
+
DLLEXPORT unsigned long DLLCALL TJBUFSIZE(int width, int height)
{
unsigned long retval=0;
return retval;
}
+
DLLEXPORT unsigned long DLLCALL TJBUFSIZEYUV(int width, int height,
int subsamp)
{
return retval;
}
+
DLLEXPORT int DLLCALL tjCompress(tjhandle h,
unsigned char *srcbuf, int width, int pitch, int height, int ps,
unsigned char *dstbuf, unsigned long *size,
}
+DLLEXPORT int DLLCALL tjEncodeYUV(tjhandle h,
+ unsigned char *srcbuf, int width, int pitch, int height, int ps,
+ unsigned char *dstbuf, int subsamp, int flags)
+{
+ unsigned long size;
+ return tjCompress(h, srcbuf, width, pitch, height, ps, dstbuf, &size,
+ subsamp, 0, flags|TJ_YUV);
+}
+
+
// DEC
static boolean fill_input_buffer (struct jpeg_decompress_struct *dinfo)
jpeg_read_header(&j->dinfo, TRUE);
- jpegwidth=j->dinfo.image_width; jpegheight=j->dinfo.image_height;
- if(width==0) width=jpegwidth;
- if(height==0) height=jpegheight;
- if(width<jpegwidth || height<jpegheight)
- {
- for(i=1; i<=8; i*=2)
- {
- scaledw=(jpegwidth+i-1)/i;
- scaledh=(jpegheight+i-1)/i;
- if(scaledw<=width && scaledh<=height)
- break;
- }
- if(scaledw>width || scaledh>height)
- _throw("Could not scale down to desired image dimensions");
- width=scaledw; height=scaledh;
- scale_denom=i;
- }
-
if(flags&TJ_YUV)
{
j_decompress_ptr dinfo=&j->dinfo;
th[i]=compptr->v_samp_factor*DCTSIZE;
tmpbufsize+=iw[i]*th[i];
if((outbuf[i]=(JSAMPROW *)malloc(sizeof(JSAMPROW)*ch[i]))==NULL)
- _throw("Memory allocation failed in tjInitDecompress()");
+ _throw("Memory allocation failed in tjDecompress()");
for(row=0; row<ch[i]; row++)
{
outbuf[i][row]=ptr;
if(usetmpbuf)
{
if((_tmpbuf=(JSAMPLE *)malloc(sizeof(JSAMPLE)*tmpbufsize))==NULL)
- _throw("Memory allocation failed in tjInitDecompress()");
+ _throw("Memory allocation failed in tjDecompress()");
ptr=_tmpbuf;
for(i=0; i<dinfo->num_components; i++)
{
jpeg_component_info *compptr=&dinfo->comp_info[i];
if((tmpbuf[i]=(JSAMPROW *)malloc(sizeof(JSAMPROW)*th[i]))==NULL)
- _throw("Memory allocation failed in tjInitDecompress()");
+ _throw("Memory allocation failed in tjDecompress()");
for(row=0; row<th[i]; row++)
{
tmpbuf[i][row]=ptr;
if(flags&TJ_YUV) j->dinfo.raw_data_out=TRUE;
else
{
+ jpegwidth=j->dinfo.image_width; jpegheight=j->dinfo.image_height;
+ if(width==0) width=jpegwidth;
+ if(height==0) height=jpegheight;
+ if(width<jpegwidth || height<jpegheight)
+ {
+ for(i=1; i<=8; i*=2)
+ {
+ scaledw=(jpegwidth+i-1)/i;
+ scaledh=(jpegheight+i-1)/i;
+ if(scaledw<=width && scaledh<=height)
+ break;
+ }
+ if(scaledw>width || scaledh>height)
+ _throw("Could not scale down to desired image dimensions");
+ width=scaledw; height=scaledh;
+ scale_denom=i;
+ }
j->dinfo.scale_num=scale_num;
j->dinfo.scale_denom=scale_denom;
}
}
+DLLEXPORT int DLLCALL tjDecompressToYUV(tjhandle h,
+ unsigned char *srcbuf, unsigned long size,
+ unsigned char *dstbuf, int flags)
+{
+ return tjDecompress(h, srcbuf, size, dstbuf, 1, 0, 1, 3, flags|TJ_YUV);
+}
+
+
// General
DLLEXPORT char* DLLCALL tjGetErrorStr(void)