TurboJPEG: Thread-safe error message retrieval

Introduce a new C API function (tjGetErrorStr2()) that can be used to
retrieve compression/decompression/transform error messages in a
thread-safe (i.e. instance-specific) manner.  Retrieving error messages
from global functions is still thread-unsafe.

Addresses a concern expressed in #151.
diff --git a/turbojpeg.c b/turbojpeg.c
index f3c9922..8b77100 100644
--- a/turbojpeg.c
+++ b/turbojpeg.c
@@ -96,6 +96,8 @@
 	struct jpeg_decompress_struct dinfo;
 	struct my_error_mgr jerr;
 	int init, headerRead;
+	char errStr[JMSG_LENGTH_MAX];
+	boolean isInstanceError;
 } tjinstance;
 
 static const int pixelsize[TJ_NUMSAMP]={3, 3, 3, 1, 3, 3};
@@ -126,26 +128,31 @@
 	{1, 8}
 };
 
-#define _throw(m) {snprintf(errStr, JMSG_LENGTH_MAX, "%s", m);  \
+#define _throwg(m) {snprintf(errStr, JMSG_LENGTH_MAX, "%s", m);  \
 	retval=-1;  goto bailout;}
+#define _throw(m) {snprintf(this->errStr, JMSG_LENGTH_MAX, "%s", m);  \
+	this->isInstanceError=TRUE;  _throwg(m);}
 #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;  \
-	this->jerr.warning=FALSE;
+	this->jerr.warning=FALSE;  \
+	this->isInstanceError=FALSE;
 #define getcinstance(handle) tjinstance *this=(tjinstance *)handle;  \
 	j_compress_ptr cinfo=NULL;  \
 	if(!this) {snprintf(errStr, JMSG_LENGTH_MAX, "Invalid handle");  \
 		return -1;}  \
 	cinfo=&this->cinfo;  \
-	this->jerr.warning=FALSE;
+	this->jerr.warning=FALSE;  \
+	this->isInstanceError=FALSE;
 #define getdinstance(handle) tjinstance *this=(tjinstance *)handle;  \
 	j_decompress_ptr dinfo=NULL;  \
 	if(!this) {snprintf(errStr, JMSG_LENGTH_MAX, "Invalid handle");  \
 		return -1;}  \
 	dinfo=&this->dinfo;  \
-	this->jerr.warning=FALSE;
+	this->jerr.warning=FALSE;  \
+	this->isInstanceError=FALSE;
 
 static int getPixelFormat(int pixelSize, int flags)
 {
@@ -272,37 +279,36 @@
 	return retval;
 }
 
-static int setDecompDefaults(struct jpeg_decompress_struct *dinfo,
-	int pixelFormat, int flags)
+static int setDecompDefaults(tjinstance *this, int pixelFormat, int flags)
 {
 	int retval=0;
 
 	switch(pixelFormat)
 	{
 		case TJPF_GRAY:
-			dinfo->out_color_space=JCS_GRAYSCALE;  break;
+			this->dinfo.out_color_space=JCS_GRAYSCALE;  break;
 		#if JCS_EXTENSIONS==1
 		case TJPF_RGB:
-			dinfo->out_color_space=JCS_EXT_RGB;  break;
+			this->dinfo.out_color_space=JCS_EXT_RGB;  break;
 		case TJPF_BGR:
-			dinfo->out_color_space=JCS_EXT_BGR;  break;
+			this->dinfo.out_color_space=JCS_EXT_BGR;  break;
 		case TJPF_RGBX:
-			dinfo->out_color_space=JCS_EXT_RGBX;  break;
+			this->dinfo.out_color_space=JCS_EXT_RGBX;  break;
 		case TJPF_BGRX:
-			dinfo->out_color_space=JCS_EXT_BGRX;  break;
+			this->dinfo.out_color_space=JCS_EXT_BGRX;  break;
 		case TJPF_XRGB:
-			dinfo->out_color_space=JCS_EXT_XRGB;  break;
+			this->dinfo.out_color_space=JCS_EXT_XRGB;  break;
 		case TJPF_XBGR:
-			dinfo->out_color_space=JCS_EXT_XBGR;  break;
+			this->dinfo.out_color_space=JCS_EXT_XBGR;  break;
 		#if JCS_ALPHA_EXTENSIONS==1
 		case TJPF_RGBA:
-			dinfo->out_color_space=JCS_EXT_RGBA;  break;
+			this->dinfo.out_color_space=JCS_EXT_RGBA;  break;
 		case TJPF_BGRA:
-			dinfo->out_color_space=JCS_EXT_BGRA;  break;
+			this->dinfo.out_color_space=JCS_EXT_BGRA;  break;
 		case TJPF_ARGB:
-			dinfo->out_color_space=JCS_EXT_ARGB;  break;
+			this->dinfo.out_color_space=JCS_EXT_ARGB;  break;
 		case TJPF_ABGR:
-			dinfo->out_color_space=JCS_EXT_ABGR;  break;
+			this->dinfo.out_color_space=JCS_EXT_ABGR;  break;
 		#endif
 		#else
 		case TJPF_RGB:
@@ -315,15 +321,15 @@
 		case TJPF_BGRA:
 		case TJPF_ARGB:
 		case TJPF_ABGR:
-			dinfo->out_color_space=JCS_RGB;  break;
+			this->dinfo.out_color_space=JCS_RGB;  break;
 		#endif
 		case TJPF_CMYK:
-			dinfo->out_color_space=JCS_CMYK;  break;
+			this->dinfo.out_color_space=JCS_CMYK;  break;
 		default:
 			_throw("Unsupported pixel format");
 	}
 
-	if(flags&TJFLAG_FASTDCT) dinfo->dct_method=JDCT_FASTEST;
+	if(flags&TJFLAG_FASTDCT) this->dinfo.dct_method=JDCT_FASTEST;
 
 	bailout:
 	return retval;
@@ -542,6 +548,18 @@
 
 /* General API functions */
 
+DLLEXPORT char* DLLCALL tjGetErrorStr2(tjhandle handle)
+{
+	tjinstance *this=(tjinstance *)handle;
+	if(this && this->isInstanceError)
+	{
+		this->isInstanceError=FALSE;
+		return this->errStr;
+	}
+	else return errStr;
+}
+
+
 DLLEXPORT char* DLLCALL tjGetErrorStr(void)
 {
 	return errStr;
@@ -615,6 +633,7 @@
 		return NULL;
 	}
 	MEMZERO(this, sizeof(tjinstance));
+	snprintf(this->errStr, JMSG_LENGTH_MAX, "No error");
 	return _tjInitCompress(this);
 }
 
@@ -624,7 +643,7 @@
 {
 	unsigned long retval=0;  int mcuw, mcuh, chromasf;
 	if(width<1 || height<1 || jpegSubsamp<0 || jpegSubsamp>=NUMSUBOPT)
-		_throw("tjBufSize(): Invalid argument");
+		_throwg("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
@@ -642,7 +661,7 @@
 {
 	unsigned long retval=0;
 	if(width<1 || height<1)
-		_throw("TJBUFSIZE(): Invalid argument");
+		_throwg("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
@@ -660,7 +679,7 @@
 	int retval=0, nc, i;
 
 	if(subsamp<0 || subsamp>=NUMSUBOPT)
-		_throw("tjBufSizeYUV2(): Invalid argument");
+		_throwg("tjBufSizeYUV2(): Invalid argument");
 
 	nc=(subsamp==TJSAMP_GRAY? 1:3);
 	for(i=0; i<nc; i++)
@@ -694,10 +713,10 @@
 	int pw, nc, retval=0;
 
 	if(width<1 || subsamp<0 || subsamp>=TJ_NUMSAMP)
-		_throw("tjPlaneWidth(): Invalid argument");
+		_throwg("tjPlaneWidth(): Invalid argument");
 	nc=(subsamp==TJSAMP_GRAY? 1:3);
 	if(componentID<0 || componentID>=nc)
-		_throw("tjPlaneWidth(): Invalid argument");
+		_throwg("tjPlaneWidth(): Invalid argument");
 
 	pw=PAD(width, tjMCUWidth[subsamp]/8);
 	if(componentID==0)
@@ -715,10 +734,10 @@
 	int ph, nc, retval=0;
 
 	if(height<1 || subsamp<0 || subsamp>=TJ_NUMSAMP)
-		_throw("tjPlaneHeight(): Invalid argument");
+		_throwg("tjPlaneHeight(): Invalid argument");
 	nc=(subsamp==TJSAMP_GRAY? 1:3);
 	if(componentID<0 || componentID>=nc)
-		_throw("tjPlaneHeight(): Invalid argument");
+		_throwg("tjPlaneHeight(): Invalid argument");
 
 	ph=PAD(height, tjMCUHeight[subsamp]/8);
 	if(componentID==0)
@@ -738,7 +757,7 @@
 	int pw, ph;
 
 	if(width<1 || height<1 || subsamp<0 || subsamp>=NUMSUBOPT)
-		_throw("tjPlaneSizeYUV(): Invalid argument");
+		_throwg("tjPlaneSizeYUV(): Invalid argument");
 
 	pw=tjPlaneWidth(componentID, width, subsamp);
 	ph=tjPlaneHeight(componentID, height, subsamp);
@@ -1032,6 +1051,8 @@
 	unsigned char *dstPlanes[3];
 	int pw0, ph0, strides[3], retval=-1;
 
+	getcinstance(handle);
+
 	if(width<=0 || height<=0 || dstBuf==NULL || pad<0 || !isPow2(pad)
 		|| subsamp<0 || subsamp>=NUMSUBOPT)
 		_throw("tjEncodeYUV3(): Invalid argument");
@@ -1224,6 +1245,8 @@
 	const unsigned char *srcPlanes[3];
 	int pw0, ph0, strides[3], retval=-1;
 
+	getcinstance(handle);
+
 	if(srcBuf==NULL || width<=0 || pad<1 || height<=0 || subsamp<0
 		|| subsamp>=NUMSUBOPT)
 		_throw("tjCompressFromYUV(): Invalid argument");
@@ -1292,6 +1315,7 @@
 		return NULL;
 	}
 	MEMZERO(this, sizeof(tjinstance));
+	snprintf(this->errStr, JMSG_LENGTH_MAX, "No error");
 	return _tjInitDecompress(this);
 }
 
@@ -1410,7 +1434,7 @@
 
 	jpeg_mem_src_tj(dinfo, jpegBuf, jpegSize);
 	jpeg_read_header(dinfo, TRUE);
-	if(setDecompDefaults(dinfo, pixelFormat, flags)==-1)
+	if(setDecompDefaults(this, pixelFormat, flags)==-1)
 	{
 		retval=-1;  goto bailout;
 	}
@@ -1616,7 +1640,7 @@
 	dinfo->marker->read_markers=old_read_markers;
 	dinfo->marker->reset_marker_reader=old_reset_marker_reader;
 
-	if(setDecompDefaults(dinfo, pixelFormat, flags)==-1)
+	if(setDecompDefaults(this, pixelFormat, flags)==-1)
 	{
 		retval=-1;  goto bailout;
 	}
@@ -1728,6 +1752,8 @@
 	const unsigned char *srcPlanes[3];
 	int pw0, ph0, strides[3], retval=-1;
 
+	getdinstance(handle);
+
 	if(srcBuf==NULL || pad<0 || !isPow2(pad) || subsamp<0 || subsamp>=NUMSUBOPT
 		|| width<=0 || height<=0)
 		_throw("tjDecodeYUV(): Invalid argument");
@@ -2025,6 +2051,7 @@
 		return NULL;
 	}
 	MEMZERO(this, sizeof(tjinstance));
+	snprintf(this->errStr, JMSG_LENGTH_MAX, "No error");
 	handle=_tjInitCompress(this);
 	if(!handle) return NULL;
 	handle=_tjInitDecompress(this);