From 99a425239dccfbe25966bc026ee3f5ade7cdede9 Mon Sep 17 00:00:00 2001 From: gpsmit Date: Thu, 15 Nov 2018 11:14:09 +0100 Subject: [PATCH 1/9] added test_headless to detector.c and single_json method to image.c for complete headless operation with input and output file locations --- src/jWrite.c | 560 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/jWrite.h | 213 ++++++++++++++++++++ 2 files changed, 773 insertions(+) create mode 100755 src/jWrite.c create mode 100755 src/jWrite.h diff --git a/src/jWrite.c b/src/jWrite.c new file mode 100755 index 00000000000..acbd099a2b2 --- /dev/null +++ b/src/jWrite.c @@ -0,0 +1,560 @@ +// +// jWrite.c version 1v2 +// +// A *really* simple JSON writer in C +// +// see: jWrite.h for info +// +// TonyWilk, Mar 2015 +// + +#include +#include +#include // memset() + +#include "jWrite.h" + +//#include // definintion of uint32_t, int32_t +typedef unsigned int uint32_t; +typedef int int32_t; + + +// the jWrite functions take the above jWriteControl structure pointer +// to maintain state while writing a JSON string. +// +// You can opt to use a single global instance of a jWriteControl structure +// which simplifies the function parameters or to supply your own structure +// +#ifdef JW_GLOBAL_CONTROL_STRUCT +struct jWriteControl g_jWriteControl; // global control struct +#define JWC_DECL // function parameter decl is empty +#define JWC_DECL0 +#define JWC(x) g_jWriteControl.x // functions access global +#define JWC_PARAM // pointer to struct is empty +#define JWC_PARAM0 +#else +#define JWC_DECL struct jWriteControl *jwc, // function parameter is ptr to control struct +#define JWC_DECL0 struct jWriteControl *jwc // function parameter, no params +#define JWC(x) jwc->x // functions use pointer +#define JWC_PARAM jwc, // pointer to stuct +#define JWC_PARAM0 jwc // pointer to stuct, no params +#endif + +//------------------------------------------ +// Internal functions +// +void jwPutch( JWC_DECL char c ); +void jwPutstr( JWC_DECL char *str ); +void jwPutraw( JWC_DECL char *str ); +void modp_itoa10(int32_t value, char* str); +void modp_dtoa2(double value, char* str, int prec); +void jwPretty( JWC_DECL0 ); +enum jwNodeType jwPop( JWC_DECL0 ); +void jwPush( JWC_DECL enum jwNodeType nodeType ); + + +//------------------------------------------ +// jwOpen +// - open writing of JSON starting with rootType = JW_OBJECT or JW_ARRAY +// - initialise with user string buffer of length buflen +// - isPretty=JW_PRETTY adds \n and spaces to prettify output (else JW_COMPACT) +// +void jwOpen( JWC_DECL char *buffer, unsigned int buflen, + enum jwNodeType rootType, int isPretty ) +{ + memset( buffer, 0, buflen ); // zap the whole destination buffer + JWC(buffer)= buffer; + JWC(buflen)= buflen; + JWC(bufp)= buffer; + JWC(nodeStack)[0].nodeType= rootType; + JWC(nodeStack)[0].elementNo= 0; + JWC(stackpos)=0; + JWC(error)= JWRITE_OK; + JWC(callNo)= 1; + JWC(isPretty)= isPretty; + jwPutch( JWC_PARAM (rootType==JW_OBJECT) ? '{' : '[' ); +} + +//------------------------------------------ +// jwClose +// - closes the root JSON object started by jwOpen() +// - returns error code +// +int jwClose( JWC_DECL0 ) +{ + if( JWC(error) == JWRITE_OK ) + { + if( JWC(stackpos) == 0 ) + { + enum jwNodeType node= JWC(nodeStack)[0].nodeType; + if( JWC(isPretty) ) + jwPutch( JWC_PARAM '\n' ); + jwPutch( JWC_PARAM (node == JW_OBJECT) ? '}' : ']'); + }else{ + JWC(error)= JWRITE_NEST_ERROR; // nesting error, not all objects closed when jwClose() called + } + } + return JWC(error); +} + +//------------------------------------------ +// End the current array/object +// +int jwEnd( JWC_DECL0 ) +{ + if( JWC(error) == JWRITE_OK ) + { + enum jwNodeType node; + int lastElemNo= JWC(nodeStack)[JWC(stackpos)].elementNo; + node= jwPop( JWC_PARAM0 ); + if( lastElemNo > 0 ) + jwPretty( JWC_PARAM0 ); + jwPutch( JWC_PARAM (node == JW_OBJECT) ? '}' : ']'); + } + return JWC(error); +} + + +//------------------------------------------ +// jwErrorPos +// - Returns position of error: the nth call to a jWrite function +// +int jwErrorPos( JWC_DECL0 ) +{ + return JWC(callNo); +} + + +//------------------------------------------ +// Object insert functions +// +int _jwObj( JWC_DECL char *key ); + +// put raw string to object (i.e. contents of rawtext without quotes) +// +void jwObj_raw( JWC_DECL char *key, char *rawtext ) +{ + if(_jwObj( JWC_PARAM key ) == JWRITE_OK) + jwPutraw( JWC_PARAM rawtext); +} + +// put "quoted" string to object +// +void jwObj_string( JWC_DECL char *key, char *value ) +{ + if(_jwObj( JWC_PARAM key ) == JWRITE_OK) + jwPutstr( JWC_PARAM value ); +} + +void jwObj_int( JWC_DECL char *key, int value ) +{ + modp_itoa10( value, JWC(tmpbuf) ); + jwObj_raw( JWC_PARAM key, JWC(tmpbuf) ); +} + +void jwObj_double( JWC_DECL char *key, double value ) +{ + modp_dtoa2( value, JWC(tmpbuf), 6 ); + jwObj_raw( JWC_PARAM key, JWC(tmpbuf) ); +} + +void jwObj_bool( JWC_DECL char *key, int oneOrZero ) +{ + jwObj_raw( JWC_PARAM key, (oneOrZero) ? "true" : "false" ); +} + +void jwObj_null( JWC_DECL char *key ) +{ + jwObj_raw( JWC_PARAM key, "null" ); +} + +// put Object in Object +// +void jwObj_object( JWC_DECL char *key ) +{ + if(_jwObj( JWC_PARAM key ) == JWRITE_OK) + { + jwPutch( JWC_PARAM '{' ); + jwPush( JWC_PARAM JW_OBJECT ); + } +} + +// put Array in Object +// +void jwObj_array( JWC_DECL char *key ) +{ + if(_jwObj( JWC_PARAM key ) == JWRITE_OK) + { + jwPutch( JWC_PARAM '[' ); + jwPush( JWC_PARAM JW_ARRAY ); + } +} + +//------------------------------------------ +// Array insert functions +// +int _jwArr( JWC_DECL0 ); + +// put raw string to array (i.e. contents of rawtext without quotes) +// +void jwArr_raw( JWC_DECL char *rawtext ) +{ + if(_jwArr( JWC_PARAM0 ) == JWRITE_OK) + jwPutraw( JWC_PARAM rawtext); +} + +// put "quoted" string to array +// +void jwArr_string( JWC_DECL char *value ) +{ + if(_jwArr( JWC_PARAM0 ) == JWRITE_OK) + jwPutstr( JWC_PARAM value ); +} + +void jwArr_int( JWC_DECL int value ) +{ + modp_itoa10( value, JWC(tmpbuf) ); + jwArr_raw( JWC_PARAM JWC(tmpbuf) ); +} + +void jwArr_double( JWC_DECL double value ) +{ + modp_dtoa2( value, JWC(tmpbuf), 6 ); + jwArr_raw( JWC_PARAM JWC(tmpbuf) ); +} + +void jwArr_bool( JWC_DECL int oneOrZero ) +{ + jwArr_raw( JWC_PARAM (oneOrZero) ? "true" : "false" ); +} + +void jwArr_null( JWC_DECL0 ) +{ + jwArr_raw( JWC_PARAM "null" ); +} + +void jwArr_object( JWC_DECL0 ) +{ + if(_jwArr( JWC_PARAM0 ) == JWRITE_OK) + { + jwPutch( JWC_PARAM '{' ); + jwPush( JWC_PARAM JW_OBJECT ); + } +} + +void jwArr_array( JWC_DECL0 ) +{ + if(_jwArr( JWC_PARAM0 ) == JWRITE_OK) + { + jwPutch( JWC_PARAM '[' ); + jwPush( JWC_PARAM JW_ARRAY ); + } +} + + +//------------------------------------------ +// jwErrorToString +// - returns string describing error code +// +char *jwErrorToString( int err ) +{ + switch( err ) + { + case JWRITE_OK: return "OK"; + case JWRITE_BUF_FULL: return "output buffer full"; + case JWRITE_NOT_ARRAY: return "tried to write Array value into Object"; + case JWRITE_NOT_OBJECT: return "tried to write Object key/value into Array"; + case JWRITE_STACK_FULL: return "array/object nesting > JWRITE_STACK_DEPTH"; + case JWRITE_STACK_EMPTY:return "stack underflow error (too many 'end's)"; + case JWRITE_NEST_ERROR: return "nesting error, not all objects closed when jwClose() called"; + } + return "Unknown error"; +} + +//============================================================================ +// Internal functions +// +void jwPretty( JWC_DECL0 ) +{ + int i; + if( JWC(isPretty) ) + { + jwPutch( JWC_PARAM '\n' ); + for( i=0; i= JWRITE_STACK_DEPTH ) + JWC(error)= JWRITE_STACK_FULL; // array/object nesting > JWRITE_STACK_DEPTH + else + { + JWC(nodeStack[++JWC(stackpos)]).nodeType= nodeType; + JWC(nodeStack[JWC(stackpos)]).elementNo= 0; + } +} + +enum jwNodeType jwPop( JWC_DECL0 ) +{ + enum jwNodeType retval= JWC(nodeStack[JWC(stackpos)]).nodeType; + if( JWC(stackpos) == 0 ) + JWC(error)= JWRITE_STACK_EMPTY; // stack underflow error (too many 'end's) + else + JWC(stackpos)--; + return retval; +} + +void jwPutch( JWC_DECL char c ) +{ + if( (unsigned int)(JWC(bufp) - JWC(buffer)) >= JWC(buflen) ) + { + JWC(error)= JWRITE_BUF_FULL; + }else{ + *JWC(bufp)++ = c; + } +} + +// put string enclosed in quotes +// +void jwPutstr( JWC_DECL char *str ) +{ + jwPutch( JWC_PARAM '\"' ); + while( *str != '\0' ) + jwPutch( JWC_PARAM *str++ ); + jwPutch( JWC_PARAM '\"' ); +} + +// put raw string +// +void jwPutraw( JWC_DECL char *str ) +{ + while( *str != '\0' ) + jwPutch( JWC_PARAM *str++ ); +} + + +// *common Object function* +// - checks error +// - checks current node is OBJECT +// - adds comma if reqd +// - adds "key" : +// +int _jwObj( JWC_DECL char *key ) +{ + if(JWC(error) == JWRITE_OK) + { + JWC(callNo)++; + if( JWC(nodeStack)[JWC(stackpos)].nodeType != JW_OBJECT ) + JWC(error)= JWRITE_NOT_OBJECT; // tried to write Object key/value into Array + else if( JWC(nodeStack)[JWC(stackpos)].elementNo++ > 0 ) + jwPutch( JWC_PARAM ',' ); + jwPretty( JWC_PARAM0 ); + jwPutstr( JWC_PARAM key ); + jwPutch( JWC_PARAM ':' ); + if( JWC(isPretty) ) + jwPutch( JWC_PARAM ' ' ); + } + return JWC(error); +} + +// *common Array function* +// - checks error +// - checks current node is ARRAY +// - adds comma if reqd +// +int _jwArr( JWC_DECL0 ) +{ + if(JWC(error) == JWRITE_OK) + { + JWC(callNo)++; + if( JWC(nodeStack)[JWC(stackpos)].nodeType != JW_ARRAY ) + JWC(error)= JWRITE_NOT_ARRAY; // tried to write array value into Object + else if( JWC(nodeStack)[JWC(stackpos)].elementNo++ > 0 ) + jwPutch( JWC_PARAM ',' ); + jwPretty( JWC_PARAM0 ); + } + return JWC(error); +} + +//================================================================= +// +// modp value-to-string functions +// - modified for C89 +// +// We use these functions as they are a lot faster than sprintf() +// +// Origin of these routines: +/* + *
+ * Copyright © 2007, Nick Galbreath -- nickg [at] modp [dot] com
+ * All rights reserved.
+ * http://code.google.com/p/stringencoders/
+ * Released under the bsd license.
+ * 
+ */ + +static void strreverse(char* begin, char* end) +{ + char aux; + while (end > begin) + aux = *end, *end-- = *begin, *begin++ = aux; +} + +/** \brief convert an signed integer to char buffer + * + * \param[in] value + * \param[out] buf the output buffer. Should be 16 chars or more. + */ +void modp_itoa10(int32_t value, char* str) +{ + char* wstr=str; + // Take care of sign + unsigned int uvalue = (value < 0) ? -value : value; + // Conversion. Number is reversed. + do *wstr++ = (char)(48 + (uvalue % 10)); while(uvalue /= 10); + if (value < 0) *wstr++ = '-'; + *wstr='\0'; + + // Reverse string + strreverse(str,wstr-1); +} + +/** + * Powers of 10 + * 10^0 to 10^9 + */ +static const double pow10[] = {1, 10, 100, 1000, 10000, 100000, 1000000, + 10000000, 100000000, 1000000000}; + +/** \brief convert a floating point number to char buffer with a + * variable-precision format, and no trailing zeros + * + * This is similar to "%.[0-9]f" in the printf style, except it will + * NOT include trailing zeros after the decimal point. This type + * of format oddly does not exists with printf. + * + * If the input value is greater than 1<<31, then the output format + * will be switched exponential format. + * + * \param[in] value + * \param[out] buf The allocated output buffer. Should be 32 chars or more. + * \param[in] precision Number of digits to the right of the decimal point. + * Can only be 0-9. + */ +void modp_dtoa2(double value, char* str, int prec) +{ + /* if input is larger than thres_max, revert to exponential */ + const double thres_max = (double)(0x7FFFFFFF); + int count; + double diff = 0.0; + char* wstr = str; + int neg= 0; + int whole; + double tmp; + uint32_t frac; + + /* Hacky test for NaN + * under -fast-math this won't work, but then you also won't + * have correct nan values anyways. The alternative is + * to link with libmath (bad) or hack IEEE double bits (bad) + */ + if (! (value == value)) { + str[0] = 'n'; str[1] = 'a'; str[2] = 'n'; str[3] = '\0'; + return; + } + + if (prec < 0) { + prec = 0; + } else if (prec > 9) { + /* precision of >= 10 can lead to overflow errors */ + prec = 9; + } + + /* we'll work in positive values and deal with the + negative sign issue later */ + if (value < 0) { + neg = 1; + value = -value; + } + + + whole = (int) value; + tmp = (value - whole) * pow10[prec]; + frac = (uint32_t)(tmp); + diff = tmp - frac; + + if (diff > 0.5) { + ++frac; + /* handle rollover, e.g. case 0.99 with prec 1 is 1.0 */ + if (frac >= pow10[prec]) { + frac = 0; + ++whole; + } + } else if (diff == 0.5 && ((frac == 0) || (frac & 1))) { + /* if halfway, round up if odd, OR + if last digit is 0. That last part is strange */ + ++frac; + } + + /* for very large numbers switch back to native sprintf for exponentials. + anyone want to write code to replace this? */ + /* + normal printf behavior is to print EVERY whole number digit + which can be 100s of characters overflowing your buffers == bad + */ + if (value > thres_max) { + sprintf(str, "%e", neg ? -value : value); + return; + } + + if (prec == 0) { + diff = value - whole; + if (diff > 0.5) { + /* greater than 0.5, round up, e.g. 1.6 -> 2 */ + ++whole; + } else if (diff == 0.5 && (whole & 1)) { + /* exactly 0.5 and ODD, then round up */ + /* 1.5 -> 2, but 2.5 -> 2 */ + ++whole; + } + + //vvvvvvvvvvvvvvvvvvv Diff from modp_dto2 + } else if (frac) { + count = prec; + // now do fractional part, as an unsigned number + // we know it is not 0 but we can have leading zeros, these + // should be removed + while (!(frac % 10)) { + --count; + frac /= 10; + } + //^^^^^^^^^^^^^^^^^^^ Diff from modp_dto2 + + // now do fractional part, as an unsigned number + do { + --count; + *wstr++ = (char)(48 + (frac % 10)); + } while (frac /= 10); + // add extra 0s + while (count-- > 0) *wstr++ = '0'; + // add decimal + *wstr++ = '.'; + } + + // do whole part + // Take care of sign + // Conversion. Number is reversed. + do *wstr++ = (char)(48 + (whole % 10)); while (whole /= 10); + if (neg) { + *wstr++ = '-'; + } + *wstr='\0'; + strreverse(str, wstr-1); +} +//================================================================= + +/* end of jWrite.c */ diff --git a/src/jWrite.h b/src/jWrite.h new file mode 100755 index 00000000000..25971bcf1c4 --- /dev/null +++ b/src/jWrite.h @@ -0,0 +1,213 @@ +// +// jWrite.h +// +// A *really* simple JSON writer in C (C89) +// - a collection of functions to generate JSON semi-automatically +// +// The idea is to simplify writing native C values into a JSON string and +// to provide some error trapping to ensure that the result is valid JSON. +// +// Example: +// jwOpen( buffer, buflen, JW_OBJECT, JW_PRETTY ); // open root node as object +// jwObj_string( "key", "value" ); +// jwObj_int( "int", 1 ); +// jwObj_array( "anArray"); +// jwArr_int( 0 ); +// jwArr_int( 1 ); +// jwArr_int( 2 ); +// jwEnd(); +// err= jwClose(); // close root object +// +// results in: +// +// { +// "key": "value", +// "int": 1, +// "anArray": [ +// 0, +// 1, +// 2 +// ] +// } +// +// Note that jWrite handles string quoting and getting commas in the right place. +// If the sequence of calls is incorrect +// e.g. +// jwOpen( buffer, buflen, JW_OBJECT, 1 ); +// jwObj_string( "key", "value" ); +// jwArr_int( 0 ); +// ... +// +// then the error code returned from jwClose() would indicate that you attempted to +// put an array element into an object (instead of a key:value pair) +// To locate the error, the supplied buffer has the JSON created upto the error point +// and a call to jwErrorPos() would return the function call at which the error occurred +// - in this case 3, the 3rd function call "jwArr_int(0)" is not correct at this point. +// +// The root JSON type can be JW_OBJECT or JW_ARRAY. +// +// For more information on each function, see the prototypes below. +// +// +// GLOBAL vs. Application-Supplied Control Structure +// ------------------------------------------------- +// jWrite requires a jWriteControl structure to save the internal state. +// For many applications it is much simpler for this to be a global variable as +// used by the above examples. +// +// To use multiple instances of jWrite, an application has to supply unique instances +// of jWriteControl structures. +// +// This feature is enabled by commenting out the definition of JW_GLOBAL_CONTROL_STRUCT +// +// All the jWrite functions then take an additional parameter: a ptr to the structure +// e.g. +// struct jWriteControl jwc; +// +// jwOpen( &jwc, buffer, buflen, JW_OBJECT, 1 ); +// jwObj_string( &jwc, "key", "value" ); +// jwObj_int( &jwc, "int", 1 ); +// jwObj_array( &jwc, "anArray"); +// jwArr_int( &jwc, 0 ); +// jwArr_int( &jwc, 1 ); +// jwArr_int( &jwc, 2 ); +// jwEnd( &jwc ); +// err= jwClose( &jwc ); +// +// - which is more flexible, but a pain to type in ! +// +// TonyWilk, Mar 2015 +// +// +//#define JW_GLOBAL_CONTROL_STRUCT // <--- comment this out to use applic-supplied jWriteControl + +#define JWRITE_STACK_DEPTH 32 // max nesting depth of objects/arrays + +#define JW_COMPACT 0 // output string control for jwOpen() +#define JW_PRETTY 1 // pretty adds \n and indentation + +enum jwNodeType{ + JW_OBJECT= 1, + JW_ARRAY +}; + +struct jwNodeStack{ + enum jwNodeType nodeType; + int elementNo; +}; + +struct jWriteControl{ + char *buffer; // pointer to application's buffer + unsigned int buflen; // length of buffer + char *bufp; // current write position in buffer + char tmpbuf[32]; // local buffer for int/double convertions + int error; // error code + int callNo; // API call on which error occurred + struct jwNodeStack nodeStack[JWRITE_STACK_DEPTH]; // stack of array/object nodes + int stackpos; + int isPretty; // 1= pretty output (inserts \n and spaces) +}; + +// Error Codes +// ----------- +#define JWRITE_OK 0 +#define JWRITE_BUF_FULL 1 // output buffer full +#define JWRITE_NOT_ARRAY 2 // tried to write Array value into Object +#define JWRITE_NOT_OBJECT 3 // tried to write Object key/value into Array +#define JWRITE_STACK_FULL 4 // array/object nesting > JWRITE_STACK_DEPTH +#define JWRITE_STACK_EMPTY 5 // stack underflow error (too many 'end's) +#define JWRITE_NEST_ERROR 6 // nesting error, not all objects closed when jwClose() called + + +// API functions +// ------------- + +// Returns '\0'-termianted string describing the error (as returned by jwClose()) +// +char *jwErrorToString( int err ); + + +#ifdef JW_GLOBAL_CONTROL_STRUCT /* USING GLOBAL g_jWriteControl */ + +// jwOpen +// - initialises jWrite with the application supplied 'buffer' of length 'buflen' +// in operation, the buffer will always contain a valid '\0'-terminated string +// - jWrite will not overrun the buffer (it returns an "output buffer full" error) +// - rootType is the base JSON type: JW_OBJECT or JW_ARRAY +// - isPretty controls 'prettifying' the output: JW_PRETTY or JW_COMPACT +void jwOpen( char *buffer, unsigned int buflen, enum jwNodeType rootType, int isPretty ); + +// jwClose +// - closes the element opened by jwOpen() +// - returns error code (0 = JWRITE_OK) +// - after an error, all following jWrite calls are skipped internally +// so the error code is for the first error detected +int jwClose( ); + +// jwErrorPos +// - if jwClose returned an error, this function returns the number of the jWrite function call +// which caused that error. +int jwErrorPos( ); + +// Object insertion functions +// - used to insert "key":"value" pairs into an object +// +void jwObj_string( char *key, char *value ); +void jwObj_int( char *key, int value ); +void jwObj_double( char *key, double value ); +void jwObj_bool( char *key, int oneOrZero ); +void jwObj_null( char *key ); +void jwObj_object( char *key ); +void jwObj_array( char *key ); + +// Array insertion functions +// - used to insert "value" elements into an array +// +void jwArr_string( char *value ); +void jwArr_int( int value ); +void jwArr_double( double value ); +void jwArr_bool( int oneOrZero ); +void jwArr_null( ); +void jwArr_object( ); +void jwArr_array( ); + +// jwEnd +// - defines the end of an Object or Array definition +int jwEnd( ); + + +// these 'raw' routines write the JSON value as the contents of rawtext +// i.e. enclosing quotes are not added +// - use if your app. supplies its own value->string functions +// +void jwObj_raw( char *key, char *rawtext ); +void jwArr_raw( char *rawtext ); + +#else /* JW_GLOBAL_CONTROL_STRUCT not defined */ +// Same API functions with app-supplied control struct option +// +void jwOpen( struct jWriteControl *jwc, char *buffer, unsigned int buflen, enum jwNodeType rootType, int isPretty ); +int jwClose( struct jWriteControl *jwc ); +int jwErrorPos( struct jWriteControl *jwc ); +void jwObj_string( struct jWriteControl *jwc, char *key, char *value ); +void jwObj_int( struct jWriteControl *jwc, char *key, int value ); +void jwObj_double( struct jWriteControl *jwc, char *key, double value ); +void jwObj_bool( struct jWriteControl *jwc, char *key, int oneOrZero ); +void jwObj_null( struct jWriteControl *jwc, char *key ); +void jwObj_object( struct jWriteControl *jwc, char *key ); +void jwObj_array( struct jWriteControl *jwc, char *key ); +void jwArr_string( struct jWriteControl *jwc, char *value ); +void jwArr_int( struct jWriteControl *jwc, int value ); +void jwArr_double( struct jWriteControl *jwc, double value ); +void jwArr_bool( struct jWriteControl *jwc, int oneOrZero ); +void jwArr_null( struct jWriteControl *jwc ); +void jwArr_object( struct jWriteControl *jwc ); +void jwArr_array( struct jWriteControl *jwc ); +int jwEnd( struct jWriteControl *jwc ); +void jwObj_raw( struct jWriteControl *jwc, char *key, char *rawtext ); +void jwArr_raw( struct jWriteControl *jwc, char *rawtext ); + +#endif /* JW_GLOBAL_CONTROL_STRUCT */ + + +/* end of jWrite.h */ From 8bf9fc843582eb4a2148a7d326d9c1b3c5f247b9 Mon Sep 17 00:00:00 2001 From: gpsmit Date: Thu, 15 Nov 2018 11:18:11 +0100 Subject: [PATCH 2/9] added test_headless to detector.c and single_json method to image.c for complete headless operation with input and output file locations --- Makefile | 2 +- src/detector.c | 71 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/image.c | 58 +++++++++++++++++++++++++++++++++++++++++ src/image.h | 1 + 4 files changed, 131 insertions(+), 1 deletion(-) mode change 100644 => 100755 src/detector.c mode change 100644 => 100755 src/image.c mode change 100644 => 100755 src/image.h diff --git a/Makefile b/Makefile index 3f10ed353ae..fe14c8dc699 100644 --- a/Makefile +++ b/Makefile @@ -108,7 +108,7 @@ CFLAGS+= -DCUDNN_HALF ARCH+= -gencode arch=compute_70,code=[sm_70,compute_70] endif -OBJ=http_stream.o gemm.o utils.o cuda.o convolutional_layer.o list.o image.o activations.o im2col.o col2im.o blas.o crop_layer.o dropout_layer.o maxpool_layer.o softmax_layer.o data.o matrix.o network.o connected_layer.o cost_layer.o parser.o option_list.o darknet.o detection_layer.o captcha.o route_layer.o writing.o box.o nightmare.o normalization_layer.o avgpool_layer.o coco.o dice.o yolo.o detector.o layer.o compare.o classifier.o local_layer.o swag.o shortcut_layer.o activation_layer.o rnn_layer.o gru_layer.o rnn.o rnn_vid.o crnn_layer.o demo.o tag.o cifar.o go.o batchnorm_layer.o art.o region_layer.o reorg_layer.o reorg_old_layer.o super.o voxel.o tree.o yolo_layer.o upsample_layer.o +OBJ=jWrite.o http_stream.o gemm.o utils.o cuda.o convolutional_layer.o list.o image.o activations.o im2col.o col2im.o blas.o crop_layer.o dropout_layer.o maxpool_layer.o softmax_layer.o data.o matrix.o network.o connected_layer.o cost_layer.o parser.o option_list.o darknet.o detection_layer.o captcha.o route_layer.o writing.o box.o nightmare.o normalization_layer.o avgpool_layer.o coco.o dice.o yolo.o detector.o layer.o compare.o classifier.o local_layer.o swag.o shortcut_layer.o activation_layer.o rnn_layer.o gru_layer.o rnn.o rnn_vid.o crnn_layer.o demo.o tag.o cifar.o go.o batchnorm_layer.o art.o region_layer.o reorg_layer.o reorg_old_layer.o super.o voxel.o tree.o yolo_layer.o upsample_layer.o ifeq ($(GPU), 1) LDFLAGS+= -lstdc++ OBJ+=convolutional_kernels.o activation_kernels.o im2col_kernels.o col2im_kernels.o blas_kernels.o crop_layer_kernels.o dropout_layer_kernels.o maxpool_layer_kernels.o network_kernels.o avgpool_layer_kernels.o diff --git a/src/detector.c b/src/detector.c old mode 100644 new mode 100755 index ba4d809b3a6..15de484cb5a --- a/src/detector.c +++ b/src/detector.c @@ -1230,12 +1230,82 @@ void test_detector(char *datacfg, char *cfgfile, char *weightfile, char *filenam free_network(net); } +void test_headless(char *datacfg, char *cfgfile, char *weightfile, float thresh, float hier_thresh, char *in_filename, char *out_filename) +{ + + char *name_list = datacfg; + int names_size = 0; + char **names = get_labels_custom(name_list, &names_size); //get_labels(name_list); + + network net = parse_network_cfg_custom(cfgfile, 1); // set batch=1 + if(weightfile){ + load_weights(&net, weightfile); + } + //set_batch_network(&net, 1); + fuse_conv_batchnorm(net); + calculate_binary_weights(net); + if (net.layers[net.n - 1].classes != names_size) { + printf(" Error: in the file %s number of names %d that isn't equal to classes=%d in the file %s \n", + name_list, names_size, net.layers[net.n - 1].classes, cfgfile); + if(net.layers[net.n - 1].classes > names_size) getchar(); + } + srand(2222222); + + char input[1024]; + float nms=.45; // 0.4F + + FILE *outputfile; + FILE *inputfile; + inputfile = fopen(in_filename, "r"); + outputfile = fopen(out_filename, "w"); + printf("Headless operation - writing all output to: %s \n", out_filename); + fprintf(outputfile, "{\n\"darknet headless output\":\n"); + fprintf(outputfile, "[\n"); + int first = 1; + while(fgets(input, 1024, inputfile) != NULL){ + input[strlen(input) - 1] = '\0'; + if(first == 0){ + fprintf(outputfile, ",\n"); + } + image im = load_image(input,0,0,net.c); + int letterbox = 0; + image sized = resize_image(im, net.w, net.h); + layer l = net.layers[net.n-1]; + float *X = sized.data; + + double time = get_time_point(); + network_predict(net, X); + + printf("%s: Predicted in %lf milli-seconds.\n", input, ((double)get_time_point() - time) / 1000); + + int nboxes = 0; + detection *dets = get_network_boxes(&net, im.w, im.h, thresh, hier_thresh, 0, 1, &nboxes, letterbox); + if (nms) do_nms_sort(dets, nboxes, l.classes, nms); + single_json(im, dets, nboxes, thresh, names, l.classes, input, outputfile); + first = 0; + // free memory + free_detections(dets, nboxes); + free_image(im); + free_image(sized); + + } + + fprintf(outputfile, "\n]\n}"); + fclose(outputfile); + fclose(inputfile); + + free_ptrs(names, net.layers[net.n - 1].classes); + free_network(net); + +} + void run_detector(int argc, char **argv) { int dont_show = find_arg(argc, argv, "-dont_show"); int show = find_arg(argc, argv, "-show"); check_mistakes = find_arg(argc, argv, "-check_mistakes"); int http_stream_port = find_int_arg(argc, argv, "-http_port", -1); + char *in_filename = find_char_arg(argc, argv, "-in_filename", 0); char *out_filename = find_char_arg(argc, argv, "-out_filename", 0); char *outfile = find_char_arg(argc, argv, "-out", 0); char *prefix = find_char_arg(argc, argv, "-prefix", 0); @@ -1288,6 +1358,7 @@ void run_detector(int argc, char **argv) if (weights[strlen(weights) - 1] == 0x0d) weights[strlen(weights) - 1] = 0; char *filename = (argc > 6) ? argv[6]: 0; if(0==strcmp(argv[2], "test")) test_detector(datacfg, cfg, weights, filename, thresh, hier_thresh, dont_show, ext_output, save_labels); + else if(0==strcmp(argv[2], "test_headless")) test_headless(datacfg, cfg, weights, thresh, hier_thresh, in_filename, out_filename); else if(0==strcmp(argv[2], "train")) train_detector(datacfg, cfg, weights, gpus, ngpus, clear, dont_show); else if(0==strcmp(argv[2], "valid")) validate_detector(datacfg, cfg, weights, outfile); else if(0==strcmp(argv[2], "recall")) validate_detector_recall(datacfg, cfg, weights); diff --git a/src/image.c b/src/image.c old mode 100644 new mode 100755 index 8566a4f8eb4..53c4c64a7ff --- a/src/image.c +++ b/src/image.c @@ -25,6 +25,11 @@ #define CV_RGB(r, g, b) cvScalar( (b), (g), (r), 0 ) #endif +// specific to jWrite.c and jWrite.h +#define _CRT_SECURE_NO_WARNINGS // stop complaining about deprecated functions +#include "jWrite.h" +// END specific to jWrite.c and jWrite.h + extern int check_mistakes; int windows = 0; @@ -415,6 +420,59 @@ void draw_detections_v3(image im, detection *dets, int num, float thresh, char * free(selected_detections); } +void single_json(image im, detection *dets, int num, float thresh, char **names, int classes, char *input, FILE *output) +{ + struct jWriteControl pic; + char buffer[4096]; + + jwOpen(&pic, buffer, 4096, JW_OBJECT, JW_COMPACT); + jwObj_string(&pic, "fileName", input); + jwObj_array(&pic, "predictions"); + fwrite(buffer, strlen(buffer), 1, output); + int selected_detections_num; + detection_with_class* selected_detections = get_actual_detections(dets, num, thresh, &selected_detections_num); + + int first = 1; + qsort(selected_detections, selected_detections_num, sizeof(*selected_detections), compare_by_lefts); + int i; + for (i = 0; i < selected_detections_num; ++i) { + if(first == 0){ + fprintf(output, ","); + } + struct jWriteControl prd; + char predbuffer[4096]; + jwOpen(&prd, predbuffer, 4096, JW_OBJECT, JW_COMPACT); //open predictions object + box bjson = selected_detections[i].det.bbox; + int leftxjson = (bjson.x - bjson.w / 2.)*im.w; + int widthjson = bjson.w*im.w; + int topyjson = (bjson.y - bjson.h / 2.)*im.h; + int heighthjson = bjson.h*im.h; + jwObj_int(&prd, "left_x", leftxjson); + jwObj_int(&prd, "top_y", topyjson); + jwObj_int(&prd, "width", widthjson); + jwObj_int(&prd, "height", heighthjson); + //labels + jwObj_array(&prd, "labels"); //labels array placeholder + + int j; + for (j = 0; j < classes; ++j) { + if (selected_detections[i].det.prob[j] > thresh) { + jwArr_object(&prd); + jwObj_string(&prd, "label", names[j]); // add object class: predicted class + jwObj_double(&prd, "score", selected_detections[i].det.prob[j] * 100); // prob: probability + jwEnd(&prd); //end label object + } + } + jwEnd(&prd); //end labels array + jwClose(&prd); //end prediction object + fwrite(predbuffer, strlen(predbuffer), 1, output); + first = 0; + } + fprintf(output, "]"); + fprintf(output, "}"); + free(selected_detections); +} + void draw_detections(image im, int num, float thresh, box *boxes, float **probs, char **names, image **alphabet, int classes) { int i; diff --git a/src/image.h b/src/image.h old mode 100644 new mode 100755 index 84b122496f0..bdd0f16858c --- a/src/image.h +++ b/src/image.h @@ -24,6 +24,7 @@ void draw_label(image a, int r, int c, image label, const float *rgb); void write_label(image a, int r, int c, image *characters, char *string, float *rgb); void draw_detections(image im, int num, float thresh, box *boxes, float **probs, char **names, image **labels, int classes); void draw_detections_v3(image im, detection *dets, int num, float thresh, char **names, image **alphabet, int classes, int ext_output); +void single_json(image im, detection *dets, int num, float thresh, char **names, int classes, char *input, FILE *output); image image_distance(image a, image b); void scale_image(image m, float s); image crop_image(image im, int dx, int dy, int w, int h); From bc42f7778c6df4c1d33d510c3d276959c6ba8c29 Mon Sep 17 00:00:00 2001 From: gpsmit Date: Thu, 15 Nov 2018 14:04:50 +0100 Subject: [PATCH 3/9] Update README.md added new command line detector test_headless explanation in readme --- README.md | 57 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/README.md b/README.md index ad506796edb..27665c3ff96 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,60 @@ +Bulk processing of an inputlist of images and an output json-formatted file containing all darknet yolo predictions of all images. + +Based on AlexeyAB's darknet fork: + +I created a new test phase: **test_headless** which takes a **names** file, **cfg** file and **weights** file, with an input file (with image paths) and output json file, like so: + +`./darknet detector test_headless data/openimages.names cfg/yolov3-openimages.cfg ../yolov3-openimages.weights -in_filename input.txt -out_filename output.json` + +example output: +```javascript +{ + "darknet headless output": [ + { + "fileName": "/Users/gpsmit/Downloads/marine.jpg", + "predictions": [ + { + "height": 332, + "labels": [ + { + "label": "Clothing", + "score": 30.192739 + } + ], + "left_x": 4, + "top_y": 137, + "width": 482 + }, + { + "height": 375, + "labels": [ + { + "label": "Person", + "score": 40.249172 + } + ], + "left_x": 13, + "top_y": 84, + "width": 471 + }, + { + "height": 216, + "labels": [ + { + "label": "Weapon", + "score": 32.131096 + } + ], + "left_x": 237, + "top_y": 190, + "width": 652 + } + ] + } + ] +} +``` + # Yolo-v3 and Yolo-v2 for Windows and Linux ### (neural network for object detection) - Tensor Cores can be used on [Linux](https://github.com/AlexeyAB/darknet#how-to-compile-on-linux) and [Windows](https://github.com/AlexeyAB/darknet#how-to-compile-on-windows) From a6dcd7c51505cd3fd3ec0d9c10d8eb41800c2265 Mon Sep 17 00:00:00 2001 From: gpsmit Date: Thu, 15 Nov 2018 14:07:50 +0100 Subject: [PATCH 4/9] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 27665c3ff96..9d1d8616fb7 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -Bulk processing of an inputlist of images and an output json-formatted file containing all darknet yolo predictions of all images. +Bulk processing of an input file containing image paths into a json-formatted output file containing all darknet yolo predictions of all the input images. Based on AlexeyAB's darknet fork: From d03393e551030d38455e61b4d496a88b76997b16 Mon Sep 17 00:00:00 2001 From: gpsmit Date: Mon, 18 Feb 2019 13:11:09 +0100 Subject: [PATCH 5/9] Updated stb libraries with newer versions to resolve a stb assertion error i encountered on certain images --- src/stb_image.h | 730 +++++++++++++++++++++++++++++++----------- src/stb_image_write.h | 359 +++++++++++++++------ 2 files changed, 799 insertions(+), 290 deletions(-) mode change 100644 => 100755 src/stb_image.h mode change 100644 => 100755 src/stb_image_write.h diff --git a/src/stb_image.h b/src/stb_image.h old mode 100644 new mode 100755 index a056138dd04..5a6c863f29d --- a/src/stb_image.h +++ b/src/stb_image.h @@ -1,5 +1,5 @@ -/* stb_image - v2.16 - public domain image loader - http://nothings.org/stb_image.h - no warranty implied; use at your own risk +/* stb_image - v2.20 - public domain image loader - http://nothings.org/stb + no warranty implied; use at your own risk Do this: #define STB_IMAGE_IMPLEMENTATION @@ -48,6 +48,10 @@ LICENSE RECENT REVISION HISTORY: + 2.20 (2019-02-07) support utf8 filenames in Windows; fix warnings and platform ifdefs + 2.19 (2018-02-11) fix warning + 2.18 (2018-01-30) fix warnings + 2.17 (2018-01-29) bugfix, 1-bit BMP, 16-bitness query, fix warnings 2.16 (2017-07-23) all functions have 16-bit variants; optimizations; bugfixes 2.15 (2017-03-18) fix png-1,2,4; all Imagenet JPGs; no runtime SSE detection on GCC 2.14 (2017-03-03) remove deprecated STBI_JPEG_OLD; fixes for Imagenet JPGs @@ -74,29 +78,31 @@ RECENT REVISION HISTORY: Thatcher Ulrich (psd) Nicolas Guillemot (vertical flip) Ken Miller (pgm, ppm) Richard Mitton (16-bit PSD) github:urraka (animated gif) Junggon Kim (PNM comments) - Daniel Gibson (16-bit TGA) + Christopher Forseth (animated gif) Daniel Gibson (16-bit TGA) socks-the-fox (16-bit PNG) Jeremy Sawicki (handle all ImageNet JPGs) - Optimizations & bugfixes - Fabian "ryg" Giesen + Optimizations & bugfixes Mikhail Morozov (1-bit BMP) + Fabian "ryg" Giesen Anael Seghezzi (is-16-bit query) Arseny Kapoulkine John-Mark Allen + Carmelo J Fdez-Aguera Bug & warning fixes Marc LeBlanc David Woo Guillaume George Martins Mozeiko Christpher Lloyd Jerry Jansson Joseph Thomson Phil Jordan Dave Moore Roy Eltham Hayaki Saito Nathan Reed Won Chun Luke Graham Johan Duparc Nick Verigakis - the Horde3D community Thomas Ruf Ronny Chevalier Baldur Karlsson - Janez Zemva John Bartholomew Michal Cichon github:rlyeh - Jonathan Blow Ken Hamada Tero Hanninen github:romigrou - Laurent Gomila Cort Stratton Sergio Gonzalez github:svdijk - Aruelien Pocheville Thibault Reuille Cass Everitt github:snagar - Ryamond Barbiero Paul Du Bois Engin Manap github:Zelex - Michaelangel007@github Philipp Wiesemann Dale Weiler github:grim210 - Oriol Ferrer Mesia Josh Tobin Matthew Gregan github:sammyhw - Blazej Dariusz Roszkowski Gregory Mullen github:phprus - Christian Floisand Kevin Schmidt github:poppolopoppo + the Horde3D community Thomas Ruf Ronny Chevalier github:rlyeh + Janez Zemva John Bartholomew Michal Cichon github:romigrou + Jonathan Blow Ken Hamada Tero Hanninen github:svdijk + Laurent Gomila Cort Stratton Sergio Gonzalez github:snagar + Aruelien Pocheville Thibault Reuille Cass Everitt github:Zelex + Ryamond Barbiero Paul Du Bois Engin Manap github:grim210 + Aldo Culquicondor Philipp Wiesemann Dale Weiler github:sammyhw + Oriol Ferrer Mesia Josh Tobin Matthew Gregan github:phprus + Julian Raschke Gregory Mullen Baldur Karlsson github:poppolopoppo + Christian Floisand Kevin Schmidt JR Smith github:darealshinji + Blazej Dariusz Roszkowski github:Michaelangel007 */ #ifndef STBI_INCLUDE_STB_IMAGE_H @@ -105,10 +111,8 @@ RECENT REVISION HISTORY: // DOCUMENTATION // // Limitations: -// - no 16-bit-per-channel PNG // - no 12-bit-per-channel JPEG // - no JPEGs with arithmetic coding -// - no 1-bit BMP // - GIF always returns *comp=4 // // Basic usage (see HDR discussion below for HDR usage): @@ -159,6 +163,16 @@ RECENT REVISION HISTORY: // // =========================================================================== // +// UNICODE: +// +// If compiling for Windows and you wish to use Unicode filenames, compile +// with +// #define STBI_WINDOWS_UTF8 +// and pass utf8-encoded filenames. Call stbiw_convert_wchar_to_utf8 to convert +// Windows wchar_t filenames to utf8. +// +// =========================================================================== +// // Philosophy // // stb libraries are designed with the following priorities: @@ -169,12 +183,12 @@ RECENT REVISION HISTORY: // // Sometimes I let "good performance" creep up in priority over "easy to maintain", // and for best performance I may provide less-easy-to-use APIs that give higher -// performance, in addition to the easy to use ones. Nevertheless, it's important +// performance, in addition to the easy-to-use ones. Nevertheless, it's important // to keep in mind that from the standpoint of you, a client of this library, // all you care about is #1 and #3, and stb libraries DO NOT emphasize #3 above all. // // Some secondary priorities arise directly from the first two, some of which -// make more explicit reasons why performance can't be emphasized. +// provide more explicit reasons why performance can't be emphasized. // // - Portable ("ease of use") // - Small source code footprint ("easy to maintain") @@ -217,11 +231,10 @@ RECENT REVISION HISTORY: // // HDR image support (disable by defining STBI_NO_HDR) // -// stb_image now supports loading HDR images in general, and currently -// the Radiance .HDR file format, although the support is provided -// generically. You can still load any file through the existing interface; -// if you attempt to load an HDR file, it will be automatically remapped to -// LDR, assuming gamma 2.2 and an arbitrary scale factor defaulting to 1; +// stb_image supports loading HDR images in general, and currently the Radiance +// .HDR file format specifically. You can still load any file through the existing +// interface; if you attempt to load an HDR file, it will be automatically remapped +// to LDR, assuming gamma 2.2 and an arbitrary scale factor defaulting to 1; // both of these constants can be reconfigured through this interface: // // stbi_hdr_to_ldr_gamma(2.2f); @@ -255,7 +268,7 @@ RECENT REVISION HISTORY: // // By default we convert iphone-formatted PNGs back to RGB, even though // they are internally encoded differently. You can disable this conversion -// by by calling stbi_convert_iphone_png_to_rgb(0), in which case +// by calling stbi_convert_iphone_png_to_rgb(0), in which case // you will always just get the native iphone "format" through (which // is BGR stored in RGB). // @@ -317,6 +330,7 @@ enum STBI_rgb_alpha = 4 }; +#include typedef unsigned char stbi_uc; typedef unsigned short stbi_us; @@ -360,6 +374,14 @@ STBIDEF stbi_uc *stbi_load_from_file (FILE *f, int *x, int *y, int *channels_in // for stbi_load_from_file, file pointer is left pointing immediately after image #endif +#ifndef STBI_NO_GIF +STBIDEF stbi_uc *stbi_load_gif_from_memory(stbi_uc const *buffer, int len, int **delays, int *x, int *y, int *z, int *comp, int req_comp); +#endif + +#ifdef STBI_WINDOWS_UTF8 +STBIDEF int stbi_convert_wchar_to_utf8(char *buffer, size_t bufferlen, const wchar_t* input); +#endif + //////////////////////////////////// // // 16-bits-per-channel interface @@ -416,11 +438,14 @@ STBIDEF void stbi_image_free (void *retval_from_stbi_load); // get image dimensions & components without fully decoding STBIDEF int stbi_info_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp); STBIDEF int stbi_info_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp); +STBIDEF int stbi_is_16_bit_from_memory(stbi_uc const *buffer, int len); +STBIDEF int stbi_is_16_bit_from_callbacks(stbi_io_callbacks const *clbk, void *user); #ifndef STBI_NO_STDIO -STBIDEF int stbi_info (char const *filename, int *x, int *y, int *comp); -STBIDEF int stbi_info_from_file (FILE *f, int *x, int *y, int *comp); - +STBIDEF int stbi_info (char const *filename, int *x, int *y, int *comp); +STBIDEF int stbi_info_from_file (FILE *f, int *x, int *y, int *comp); +STBIDEF int stbi_is_16_bit (char const *filename); +STBIDEF int stbi_is_16_bit_from_file(FILE *f); #endif @@ -504,7 +529,7 @@ STBIDEF int stbi_zlib_decode_noheader_buffer(char *obuffer, int olen, const ch #include #if !defined(STBI_NO_LINEAR) || !defined(STBI_NO_HDR) -#include // ldexp +#include // ldexp, pow #endif #ifndef STBI_NO_STDIO @@ -516,6 +541,12 @@ STBIDEF int stbi_zlib_decode_noheader_buffer(char *obuffer, int olen, const ch #define STBI_ASSERT(x) assert(x) #endif +#ifdef __cplusplus +#define STBI_EXTERN extern "C" +#else +#define STBI_EXTERN extern +#endif + #ifndef _MSC_VER #ifdef __cplusplus @@ -640,14 +671,18 @@ static int stbi__cpuid3(void) #define STBI_SIMD_ALIGN(type, name) __declspec(align(16)) type name +#if !defined(STBI_NO_JPEG) && defined(STBI_SSE2) static int stbi__sse2_available(void) { int info3 = stbi__cpuid3(); return ((info3 >> 26) & 1) != 0; } +#endif + #else // assume GCC-style if not VC++ #define STBI_SIMD_ALIGN(type, name) type name __attribute__((aligned(16))) +#if !defined(STBI_NO_JPEG) && defined(STBI_SSE2) static int stbi__sse2_available(void) { // If we're even attempting to compile this on GCC/Clang, that means @@ -655,6 +690,8 @@ static int stbi__sse2_available(void) // instructions at will, and so are we. return 1; } +#endif + #endif #endif @@ -784,6 +821,7 @@ static int stbi__jpeg_info(stbi__context *s, int *x, int *y, int *comp); static int stbi__png_test(stbi__context *s); static void *stbi__png_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); static int stbi__png_info(stbi__context *s, int *x, int *y, int *comp); +static int stbi__png_is16(stbi__context *s); #endif #ifndef STBI_NO_BMP @@ -802,6 +840,7 @@ static int stbi__tga_info(stbi__context *s, int *x, int *y, int *comp); static int stbi__psd_test(stbi__context *s); static void *stbi__psd_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri, int bpc); static int stbi__psd_info(stbi__context *s, int *x, int *y, int *comp); +static int stbi__psd_is16(stbi__context *s); #endif #ifndef STBI_NO_HDR @@ -819,6 +858,7 @@ static int stbi__pic_info(stbi__context *s, int *x, int *y, int *comp); #ifndef STBI_NO_GIF static int stbi__gif_test(stbi__context *s); static void *stbi__gif_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static void *stbi__load_gif_main(stbi__context *s, int **delays, int *x, int *y, int *z, int *comp, int req_comp); static int stbi__gif_info(stbi__context *s, int *x, int *y, int *comp); #endif @@ -893,11 +933,13 @@ static int stbi__mad3sizes_valid(int a, int b, int c, int add) } // returns 1 if "a*b*c*d + add" has no negative terms/factors and doesn't overflow +#if !defined(STBI_NO_LINEAR) || !defined(STBI_NO_HDR) static int stbi__mad4sizes_valid(int a, int b, int c, int d, int add) { return stbi__mul2sizes_valid(a, b) && stbi__mul2sizes_valid(a*b, c) && stbi__mul2sizes_valid(a*b*c, d) && stbi__addsizes_valid(a*b*c*d, add); } +#endif // mallocs with size overflow checking static void *stbi__malloc_mad2(int a, int b, int add) @@ -912,11 +954,13 @@ static void *stbi__malloc_mad3(int a, int b, int c, int add) return stbi__malloc(a*b*c + add); } +#if !defined(STBI_NO_LINEAR) || !defined(STBI_NO_HDR) static void *stbi__malloc_mad4(int a, int b, int c, int d, int add) { if (!stbi__mad4sizes_valid(a, b, c, d, add)) return NULL; return stbi__malloc(a*b*c*d + add); } +#endif // stbi__err - error // stbi__errpf - error returning pointer to float @@ -1054,6 +1098,20 @@ static void stbi__vertical_flip(void *image, int w, int h, int bytes_per_pixel) } } +#ifndef STBI_NO_GIF +static void stbi__vertical_flip_slices(void *image, int w, int h, int z, int bytes_per_pixel) +{ + int slice; + int slice_size = w * h * bytes_per_pixel; + + stbi_uc *bytes = (stbi_uc *)image; + for (slice = 0; slice < z; ++slice) { + stbi__vertical_flip(bytes, w, h, bytes_per_pixel); + bytes += slice_size; + } +} +#endif + static unsigned char *stbi__load_and_postprocess_8bit(stbi__context *s, int *x, int *y, int *comp, int req_comp) { stbi__result_info ri; @@ -1103,7 +1161,7 @@ static stbi__uint16 *stbi__load_and_postprocess_16bit(stbi__context *s, int *x, return (stbi__uint16 *) result; } -#ifndef STBI_NO_HDR +#if !defined(STBI_NO_HDR) && !defined(STBI_NO_LINEAR) static void stbi__float_postprocess(float *result, int *x, int *y, int *comp, int req_comp) { if (stbi__vertically_flip_on_load && result != NULL) { @@ -1115,10 +1173,38 @@ static void stbi__float_postprocess(float *result, int *x, int *y, int *comp, in #ifndef STBI_NO_STDIO +#if defined(_MSC_VER) && defined(STBI_WINDOWS_UTF8) +STBI_EXTERN __declspec(dllimport) int __stdcall MultiByteToWideChar(unsigned int cp, unsigned long flags, const char *str, int cbmb, wchar_t *widestr, int cchwide); +STBI_EXTERN __declspec(dllimport) int __stdcall WideCharToMultiByte(unsigned int cp, unsigned long flags, const wchar_t *widestr, int cchwide, char *str, int cbmb, const char *defchar, int *used_default); +#endif + +#if defined(_MSC_VER) && defined(STBI_WINDOWS_UTF8) +STBIDEF int stbi_convert_wchar_to_utf8(char *buffer, size_t bufferlen, const wchar_t* input) +{ + return WideCharToMultiByte(65001 /* UTF8 */, 0, input, -1, buffer, bufferlen, NULL, NULL); +} +#endif + static FILE *stbi__fopen(char const *filename, char const *mode) { FILE *f; -#if defined(_MSC_VER) && _MSC_VER >= 1400 +#if defined(_MSC_VER) && defined(STBI_WINDOWS_UTF8) + wchar_t wMode[64]; + wchar_t wFilename[1024]; + if (0 == MultiByteToWideChar(65001 /* UTF8 */, 0, filename, -1, wFilename, sizeof(wFilename))) + return 0; + + if (0 == MultiByteToWideChar(65001 /* UTF8 */, 0, mode, -1, wMode, sizeof(wMode))) + return 0; + +#if _MSC_VER >= 1400 + if (0 != _wfopen_s(&f, wFilename, wMode)) + f = 0; +#else + f = _wfopen(wFilename, wMode); +#endif + +#elif defined(_MSC_VER) && _MSC_VER >= 1400 if (0 != fopen_s(&f, filename, mode)) f=0; #else @@ -1205,6 +1291,22 @@ STBIDEF stbi_uc *stbi_load_from_callbacks(stbi_io_callbacks const *clbk, void *u return stbi__load_and_postprocess_8bit(&s,x,y,comp,req_comp); } +#ifndef STBI_NO_GIF +STBIDEF stbi_uc *stbi_load_gif_from_memory(stbi_uc const *buffer, int len, int **delays, int *x, int *y, int *z, int *comp, int req_comp) +{ + unsigned char *result; + stbi__context s; + stbi__start_mem(&s,buffer,len); + + result = (unsigned char*) stbi__load_gif_main(&s, delays, x, y, z, comp, req_comp); + if (stbi__vertically_flip_on_load) { + stbi__vertical_flip_slices( result, *x, *y, *z, *comp ); + } + + return result; +} +#endif + #ifndef STBI_NO_LINEAR static float *stbi__loadf_main(stbi__context *s, int *x, int *y, int *comp, int req_comp) { @@ -1288,12 +1390,16 @@ STBIDEF int stbi_is_hdr (char const *filename) return result; } -STBIDEF int stbi_is_hdr_from_file(FILE *f) +STBIDEF int stbi_is_hdr_from_file(FILE *f) { #ifndef STBI_NO_HDR + long pos = ftell(f); + int res; stbi__context s; stbi__start_file(&s,f); - return stbi__hdr_test(&s); + res = stbi__hdr_test(&s); + fseek(f, pos, SEEK_SET); + return res; #else STBI_NOTUSED(f); return 0; @@ -1491,18 +1597,18 @@ static unsigned char *stbi__convert_format(unsigned char *data, int img_n, int r // convert source image with img_n components to one with req_comp components; // avoid switch per pixel, so use switch per scanline and massive macros switch (STBI__COMBO(img_n, req_comp)) { - STBI__CASE(1,2) { dest[0]=src[0], dest[1]=255; } break; + STBI__CASE(1,2) { dest[0]=src[0]; dest[1]=255; } break; STBI__CASE(1,3) { dest[0]=dest[1]=dest[2]=src[0]; } break; - STBI__CASE(1,4) { dest[0]=dest[1]=dest[2]=src[0], dest[3]=255; } break; + STBI__CASE(1,4) { dest[0]=dest[1]=dest[2]=src[0]; dest[3]=255; } break; STBI__CASE(2,1) { dest[0]=src[0]; } break; STBI__CASE(2,3) { dest[0]=dest[1]=dest[2]=src[0]; } break; - STBI__CASE(2,4) { dest[0]=dest[1]=dest[2]=src[0], dest[3]=src[1]; } break; - STBI__CASE(3,4) { dest[0]=src[0],dest[1]=src[1],dest[2]=src[2],dest[3]=255; } break; + STBI__CASE(2,4) { dest[0]=dest[1]=dest[2]=src[0]; dest[3]=src[1]; } break; + STBI__CASE(3,4) { dest[0]=src[0];dest[1]=src[1];dest[2]=src[2];dest[3]=255; } break; STBI__CASE(3,1) { dest[0]=stbi__compute_y(src[0],src[1],src[2]); } break; - STBI__CASE(3,2) { dest[0]=stbi__compute_y(src[0],src[1],src[2]), dest[1] = 255; } break; + STBI__CASE(3,2) { dest[0]=stbi__compute_y(src[0],src[1],src[2]); dest[1] = 255; } break; STBI__CASE(4,1) { dest[0]=stbi__compute_y(src[0],src[1],src[2]); } break; - STBI__CASE(4,2) { dest[0]=stbi__compute_y(src[0],src[1],src[2]), dest[1] = src[3]; } break; - STBI__CASE(4,3) { dest[0]=src[0],dest[1]=src[1],dest[2]=src[2]; } break; + STBI__CASE(4,2) { dest[0]=stbi__compute_y(src[0],src[1],src[2]); dest[1] = src[3]; } break; + STBI__CASE(4,3) { dest[0]=src[0];dest[1]=src[1];dest[2]=src[2]; } break; default: STBI_ASSERT(0); } #undef STBI__CASE @@ -1540,18 +1646,18 @@ static stbi__uint16 *stbi__convert_format16(stbi__uint16 *data, int img_n, int r // convert source image with img_n components to one with req_comp components; // avoid switch per pixel, so use switch per scanline and massive macros switch (STBI__COMBO(img_n, req_comp)) { - STBI__CASE(1,2) { dest[0]=src[0], dest[1]=0xffff; } break; + STBI__CASE(1,2) { dest[0]=src[0]; dest[1]=0xffff; } break; STBI__CASE(1,3) { dest[0]=dest[1]=dest[2]=src[0]; } break; - STBI__CASE(1,4) { dest[0]=dest[1]=dest[2]=src[0], dest[3]=0xffff; } break; + STBI__CASE(1,4) { dest[0]=dest[1]=dest[2]=src[0]; dest[3]=0xffff; } break; STBI__CASE(2,1) { dest[0]=src[0]; } break; STBI__CASE(2,3) { dest[0]=dest[1]=dest[2]=src[0]; } break; - STBI__CASE(2,4) { dest[0]=dest[1]=dest[2]=src[0], dest[3]=src[1]; } break; - STBI__CASE(3,4) { dest[0]=src[0],dest[1]=src[1],dest[2]=src[2],dest[3]=0xffff; } break; + STBI__CASE(2,4) { dest[0]=dest[1]=dest[2]=src[0]; dest[3]=src[1]; } break; + STBI__CASE(3,4) { dest[0]=src[0];dest[1]=src[1];dest[2]=src[2];dest[3]=0xffff; } break; STBI__CASE(3,1) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]); } break; - STBI__CASE(3,2) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]), dest[1] = 0xffff; } break; + STBI__CASE(3,2) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]); dest[1] = 0xffff; } break; STBI__CASE(4,1) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]); } break; - STBI__CASE(4,2) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]), dest[1] = src[3]; } break; - STBI__CASE(4,3) { dest[0]=src[0],dest[1]=src[1],dest[2]=src[2]; } break; + STBI__CASE(4,2) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]); dest[1] = src[3]; } break; + STBI__CASE(4,3) { dest[0]=src[0];dest[1]=src[1];dest[2]=src[2]; } break; default: STBI_ASSERT(0); } #undef STBI__CASE @@ -1575,7 +1681,11 @@ static float *stbi__ldr_to_hdr(stbi_uc *data, int x, int y, int comp) for (k=0; k < n; ++k) { output[i*comp + k] = (float) (pow(data[i*comp+k]/255.0f, stbi__l2h_gamma) * stbi__l2h_scale); } - if (k < comp) output[i*comp + k] = data[i*comp+k]/255.0f; + } + if (n < comp) { + for (i=0; i < x*y; ++i) { + output[i*comp + n] = data[i*comp + n]/255.0f; + } } STBI_FREE(data); return output; @@ -1705,7 +1815,8 @@ typedef struct static int stbi__build_huffman(stbi__huffman *h, int *count) { - int i,j,k=0,code; + int i,j,k=0; + unsigned int code; // build size list for each symbol (from JPEG spec) for (i=0; i < 16; ++i) for (j=0; j < count[i]; ++j) @@ -1721,7 +1832,7 @@ static int stbi__build_huffman(stbi__huffman *h, int *count) if (h->size[k] == j) { while (h->size[k] == j) h->code[k++] = (stbi__uint16) (code++); - if (code-1 >= (1 << j)) return stbi__err("bad code lengths","Corrupt JPEG"); + if (code-1 >= (1u << j)) return stbi__err("bad code lengths","Corrupt JPEG"); } // compute largest code + 1 for this size, preshifted as needed later h->maxcode[j] = code << (16-j); @@ -1765,7 +1876,7 @@ static void stbi__build_fast_ac(stbi__int16 *fast_ac, stbi__huffman *h) if (k < m) k += (~0U << magbits) + 1; // if the result is small enough, we can fit it in fast_ac table if (k >= -128 && k <= 127) - fast_ac[i] = (stbi__int16) ((k << 8) + (run << 4) + (len + magbits)); + fast_ac[i] = (stbi__int16) ((k * 256) + (run * 16) + (len + magbits)); } } } @@ -1774,7 +1885,7 @@ static void stbi__build_fast_ac(stbi__int16 *fast_ac, stbi__huffman *h) static void stbi__grow_buffer_unsafe(stbi__jpeg *j) { do { - int b = j->nomore ? 0 : stbi__get8(j->s); + unsigned int b = j->nomore ? 0 : stbi__get8(j->s); if (b == 0xff) { int c = stbi__get8(j->s); while (c == 0xff) c = stbi__get8(j->s); // consume fill bytes @@ -1790,7 +1901,7 @@ static void stbi__grow_buffer_unsafe(stbi__jpeg *j) } // (1 << n) - 1 -static stbi__uint32 stbi__bmask[17]={0,1,3,7,15,31,63,127,255,511,1023,2047,4095,8191,16383,32767,65535}; +static const stbi__uint32 stbi__bmask[17]={0,1,3,7,15,31,63,127,255,511,1023,2047,4095,8191,16383,32767,65535}; // decode a jpeg huffman value from the bitstream stbi_inline static int stbi__jpeg_huff_decode(stbi__jpeg *j, stbi__huffman *h) @@ -1843,7 +1954,7 @@ stbi_inline static int stbi__jpeg_huff_decode(stbi__jpeg *j, stbi__huffman *h) } // bias[n] = (-1<rgb = 0; for (i=0; i < s->img_n; ++i) { - static unsigned char rgb[3] = { 'R', 'G', 'B' }; + static const unsigned char rgb[3] = { 'R', 'G', 'B' }; z->img_comp[i].id = stbi__get8(s); if (s->img_n == 3 && z->img_comp[i].id == rgb[i]) ++z->rgb; @@ -3093,8 +3204,8 @@ static int stbi__decode_jpeg_image(stbi__jpeg *j) } else if (stbi__DNL(m)) { int Ld = stbi__get16be(j->s); stbi__uint32 NL = stbi__get16be(j->s); - if (Ld != 4) stbi__err("bad DNL len", "Corrupt JPEG"); - if (NL != j->s->img_y) stbi__err("bad DNL height", "Corrupt JPEG"); + if (Ld != 4) return stbi__err("bad DNL len", "Corrupt JPEG"); + if (NL != j->s->img_y) return stbi__err("bad DNL height", "Corrupt JPEG"); } else { if (!stbi__process_marker(j, m)) return 0; } @@ -3668,7 +3779,7 @@ static stbi_uc *load_jpeg_image(stbi__jpeg *z, int *out_x, int *out_y, int *comp if (n == 1) for (i=0; i < z->s->img_x; ++i) out[i] = y[i]; else - for (i=0; i < z->s->img_x; ++i) *out++ = y[i], *out++ = 255; + for (i=0; i < z->s->img_x; ++i) { *out++ = y[i]; *out++ = 255; } } } } @@ -3912,18 +4023,18 @@ static int stbi__zexpand(stbi__zbuf *z, char *zout, int n) // need to make room return 1; } -static int stbi__zlength_base[31] = { +static const int stbi__zlength_base[31] = { 3,4,5,6,7,8,9,10,11,13, 15,17,19,23,27,31,35,43,51,59, 67,83,99,115,131,163,195,227,258,0,0 }; -static int stbi__zlength_extra[31]= +static const int stbi__zlength_extra[31]= { 0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,0,0 }; -static int stbi__zdist_base[32] = { 1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193, +static const int stbi__zdist_base[32] = { 1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193, 257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577,0,0}; -static int stbi__zdist_extra[32] = +static const int stbi__zdist_extra[32] = { 0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13}; static int stbi__parse_huffman_block(stbi__zbuf *a) @@ -3970,7 +4081,7 @@ static int stbi__parse_huffman_block(stbi__zbuf *a) static int stbi__compute_huffman_codes(stbi__zbuf *a) { - static stbi_uc length_dezigzag[19] = { 16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15 }; + static const stbi_uc length_dezigzag[19] = { 16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15 }; stbi__zhuffman z_codelength; stbi_uc lencodes[286+32+137];//padding for maximum single op stbi_uc codelength_sizes[19]; @@ -4229,7 +4340,7 @@ static stbi__pngchunk stbi__get_chunk_header(stbi__context *s) static int stbi__check_png_header(stbi__context *s) { - static stbi_uc png_sig[8] = { 137,80,78,71,13,10,26,10 }; + static const stbi_uc png_sig[8] = { 137,80,78,71,13,10,26,10 }; int i; for (i=0; i < 8; ++i) if (stbi__get8(s) != png_sig[i]) return stbi__err("bad png sig","Not a PNG"); @@ -4275,7 +4386,7 @@ static int stbi__paeth(int a, int b, int c) return c; } -static stbi_uc stbi__depth_scale_table[9] = { 0, 0xff, 0x55, 0, 0x11, 0,0,0, 0x01 }; +static const stbi_uc stbi__depth_scale_table[9] = { 0, 0xff, 0x55, 0, 0x11, 0,0,0, 0x01 }; // create the png data from post-deflated data static int stbi__create_png_image_raw(stbi__png *a, stbi_uc *raw, stbi__uint32 raw_len, int out_n, stbi__uint32 x, stbi__uint32 y, int depth, int color) @@ -4295,8 +4406,10 @@ static int stbi__create_png_image_raw(stbi__png *a, stbi_uc *raw, stbi__uint32 r a->out = (stbi_uc *) stbi__malloc_mad3(x, y, output_bytes, 0); // extra bytes to write off the end into if (!a->out) return stbi__err("outofmem", "Out of memory"); + if (!stbi__mad3sizes_valid(img_n, x, depth, 7)) return stbi__err("too large", "Corrupt PNG"); img_width_bytes = (((img_n * x * depth) + 7) >> 3); img_len = (img_width_bytes + 1) * y; + // we used to check for exact match between raw_len and img_len on non-interlaced PNGs, // but issue #276 reported a PNG in the wild that had extra data at the end (all zeros), // so just check for raw_len < img_len always. @@ -4675,12 +4788,12 @@ static void stbi__de_iphone(stbi__png *z) } } -#define STBI__PNG_TYPE(a,b,c,d) (((a) << 24) + ((b) << 16) + ((c) << 8) + (d)) +#define STBI__PNG_TYPE(a,b,c,d) (((unsigned) (a) << 24) + ((unsigned) (b) << 16) + ((unsigned) (c) << 8) + (unsigned) (d)) static int stbi__parse_png_file(stbi__png *z, int scan, int req_comp) { stbi_uc palette[1024], pal_img_n=0; - stbi_uc has_trans=0, tc[3]; + stbi_uc has_trans=0, tc[3]={0}; stbi__uint16 tc16[3]; stbi__uint32 ioff=0, idata_limit=0, i, pal_len=0; int first=1,k,interlace=0, color=0, is_iphone=0; @@ -4912,6 +5025,19 @@ static int stbi__png_info(stbi__context *s, int *x, int *y, int *comp) p.s = s; return stbi__png_info_raw(&p, x, y, comp); } + +static int stbi__png_is16(stbi__context *s) +{ + stbi__png p; + p.s = s; + if (!stbi__png_info_raw(&p, NULL, NULL, NULL)) + return 0; + if (p.depth != 16) { + stbi__rewind(p.s); + return 0; + } + return 1; +} #endif // Microsoft/Windows BMP image @@ -4945,11 +5071,11 @@ static int stbi__high_bit(unsigned int z) { int n=0; if (z == 0) return -1; - if (z >= 0x10000) n += 16, z >>= 16; - if (z >= 0x00100) n += 8, z >>= 8; - if (z >= 0x00010) n += 4, z >>= 4; - if (z >= 0x00004) n += 2, z >>= 2; - if (z >= 0x00002) n += 1, z >>= 1; + if (z >= 0x10000) { n += 16; z >>= 16; } + if (z >= 0x00100) { n += 8; z >>= 8; } + if (z >= 0x00010) { n += 4; z >>= 4; } + if (z >= 0x00004) { n += 2; z >>= 2; } + if (z >= 0x00002) { n += 1; z >>= 1; } return n; } @@ -4963,21 +5089,27 @@ static int stbi__bitcount(unsigned int a) return a & 0xff; } -static int stbi__shiftsigned(int v, int shift, int bits) -{ - int result; - int z=0; - - if (shift < 0) v <<= -shift; - else v >>= shift; - result = v; - - z = bits; - while (z < 8) { - result += v >> z; - z += bits; - } - return result; +// extract an arbitrarily-aligned N-bit value (N=bits) +// from v, and then make it 8-bits long and fractionally +// extend it to full full range. +static int stbi__shiftsigned(unsigned int v, int shift, int bits) +{ + static unsigned int mul_table[9] = { + 0, + 0xff/*0b11111111*/, 0x55/*0b01010101*/, 0x49/*0b01001001*/, 0x11/*0b00010001*/, + 0x21/*0b00100001*/, 0x41/*0b01000001*/, 0x81/*0b10000001*/, 0x01/*0b00000001*/, + }; + static unsigned int shift_table[9] = { + 0, 0,0,1,0,2,4,6,0, + }; + if (shift < 0) + v <<= -shift; + else + v >>= shift; + STBI_ASSERT(v >= 0 && v < 256); + v >>= (8-bits); + STBI_ASSERT(bits >= 0 && bits <= 8); + return (int) ((unsigned) v * mul_table[bits]) >> shift_table[bits]; } typedef struct @@ -5007,7 +5139,6 @@ static void *stbi__bmp_parse_header(stbi__context *s, stbi__bmp_data *info) } if (stbi__get16le(s) != 1) return stbi__errpuc("bad BMP", "bad BMP"); info->bpp = stbi__get16le(s); - if (info->bpp == 1) return stbi__errpuc("monochrome", "BMP type not supported: 1-bit"); if (hsz != 12) { int compress = stbi__get32le(s); if (compress == 1 || compress == 2) return stbi__errpuc("BMP RLE", "BMP type not supported: RLE"); @@ -5125,29 +5256,49 @@ static void *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int req pal[i][3] = 255; } stbi__skip(s, info.offset - 14 - info.hsz - psize * (info.hsz == 12 ? 3 : 4)); - if (info.bpp == 4) width = (s->img_x + 1) >> 1; + if (info.bpp == 1) width = (s->img_x + 7) >> 3; + else if (info.bpp == 4) width = (s->img_x + 1) >> 1; else if (info.bpp == 8) width = s->img_x; else { STBI_FREE(out); return stbi__errpuc("bad bpp", "Corrupt BMP"); } pad = (-width)&3; - for (j=0; j < (int) s->img_y; ++j) { - for (i=0; i < (int) s->img_x; i += 2) { - int v=stbi__get8(s),v2=0; - if (info.bpp == 4) { - v2 = v & 15; - v >>= 4; + if (info.bpp == 1) { + for (j=0; j < (int) s->img_y; ++j) { + int bit_offset = 7, v = stbi__get8(s); + for (i=0; i < (int) s->img_x; ++i) { + int color = (v>>bit_offset)&0x1; + out[z++] = pal[color][0]; + out[z++] = pal[color][1]; + out[z++] = pal[color][2]; + if (target == 4) out[z++] = 255; + if (i+1 == (int) s->img_x) break; + if((--bit_offset) < 0) { + bit_offset = 7; + v = stbi__get8(s); + } } - out[z++] = pal[v][0]; - out[z++] = pal[v][1]; - out[z++] = pal[v][2]; - if (target == 4) out[z++] = 255; - if (i+1 == (int) s->img_x) break; - v = (info.bpp == 8) ? stbi__get8(s) : v2; - out[z++] = pal[v][0]; - out[z++] = pal[v][1]; - out[z++] = pal[v][2]; - if (target == 4) out[z++] = 255; + stbi__skip(s, pad); + } + } else { + for (j=0; j < (int) s->img_y; ++j) { + for (i=0; i < (int) s->img_x; i += 2) { + int v=stbi__get8(s),v2=0; + if (info.bpp == 4) { + v2 = v & 15; + v >>= 4; + } + out[z++] = pal[v][0]; + out[z++] = pal[v][1]; + out[z++] = pal[v][2]; + if (target == 4) out[z++] = 255; + if (i+1 == (int) s->img_x) break; + v = (info.bpp == 8) ? stbi__get8(s) : v2; + out[z++] = pal[v][0]; + out[z++] = pal[v][1]; + out[z++] = pal[v][2]; + if (target == 4) out[z++] = 255; + } + stbi__skip(s, pad); } - stbi__skip(s, pad); } } else { int rshift=0,gshift=0,bshift=0,ashift=0,rcount=0,gcount=0,bcount=0,acount=0; @@ -5188,7 +5339,7 @@ static void *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int req int bpp = info.bpp; for (i=0; i < (int) s->img_x; ++i) { stbi__uint32 v = (bpp == 16 ? (stbi__uint32) stbi__get16le(s) : stbi__get32le(s)); - int a; + unsigned int a; out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mr, rshift, rcount)); out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mg, gshift, gcount)); out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mb, bshift, bcount)); @@ -5212,7 +5363,7 @@ static void *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int req stbi_uc *p1 = out + j *s->img_x*target; stbi_uc *p2 = out + (s->img_y-1-j)*s->img_x*target; for (i=0; i < (int) s->img_x*target; ++i) { - t = p1[i], p1[i] = p2[i], p2[i] = t; + t = p1[i]; p1[i] = p2[i]; p2[i] = t; } } } @@ -5236,14 +5387,14 @@ static void *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int req static int stbi__tga_get_comp(int bits_per_pixel, int is_grey, int* is_rgb16) { // only RGB or RGBA (incl. 16bit) or grey allowed - if(is_rgb16) *is_rgb16 = 0; + if (is_rgb16) *is_rgb16 = 0; switch(bits_per_pixel) { case 8: return STBI_grey; case 16: if(is_grey) return STBI_grey_alpha; - // else: fall-through + // fallthrough case 15: if(is_rgb16) *is_rgb16 = 1; - return STBI_rgb; - case 24: // fall-through + return STBI_rgb; + case 24: // fallthrough case 32: return bits_per_pixel/8; default: return 0; } @@ -5702,7 +5853,7 @@ static void *stbi__psd_load(stbi__context *s, int *x, int *y, int *comp, int req // Else if n is 128, noop. // Endloop - // The RLE-compressed data is preceeded by a 2-byte data count for each row in the data, + // The RLE-compressed data is preceded by a 2-byte data count for each row in the data, // which we're going to just skip. stbi__skip(s, h * channelCount * 2 ); @@ -6038,11 +6189,13 @@ typedef struct typedef struct { int w,h; - stbi_uc *out, *old_out; // output buffer (always 4 components) - int flags, bgindex, ratio, transparent, eflags, delay; + stbi_uc *out; // output buffer (always 4 components) + stbi_uc *background; // The current "background" as far as a gif is concerned + stbi_uc *history; + int flags, bgindex, ratio, transparent, eflags; stbi_uc pal[256][4]; stbi_uc lpal[256][4]; - stbi__gif_lzw codes[4096]; + stbi__gif_lzw codes[8192]; stbi_uc *color_table; int parse, step; int lflags; @@ -6050,6 +6203,7 @@ typedef struct int max_x, max_y; int cur_x, cur_y; int line_size; + int delay; } stbi__gif; static int stbi__gif_test_raw(stbi__context *s) @@ -6125,6 +6279,7 @@ static int stbi__gif_info_raw(stbi__context *s, int *x, int *y, int *comp) static void stbi__out_gif_code(stbi__gif *g, stbi__uint16 code) { stbi_uc *p, *c; + int idx; // recurse to decode the prefixes, since the linked-list is backwards, // and working backwards through an interleaved image would be nasty @@ -6133,10 +6288,12 @@ static void stbi__out_gif_code(stbi__gif *g, stbi__uint16 code) if (g->cur_y >= g->max_y) return; - p = &g->out[g->cur_x + g->cur_y]; - c = &g->color_table[g->codes[code].suffix * 4]; + idx = g->cur_x + g->cur_y; + p = &g->out[idx]; + g->history[idx / 4] = 1; - if (c[3] >= 128) { + c = &g->color_table[g->codes[code].suffix * 4]; + if (c[3] > 128) { // don't render transparent pixels; p[0] = c[2]; p[1] = c[1]; p[2] = c[0]; @@ -6210,11 +6367,16 @@ static stbi_uc *stbi__process_gif_raster(stbi__context *s, stbi__gif *g) stbi__skip(s,len); return g->out; } else if (code <= avail) { - if (first) return stbi__errpuc("no clear code", "Corrupt GIF"); + if (first) { + return stbi__errpuc("no clear code", "Corrupt GIF"); + } if (oldcode >= 0) { p = &g->codes[avail++]; - if (avail > 4096) return stbi__errpuc("too many codes", "Corrupt GIF"); + if (avail > 8192) { + return stbi__errpuc("too many codes", "Corrupt GIF"); + } + p->prefix = (stbi__int16) oldcode; p->first = g->codes[oldcode].first; p->suffix = (code == avail) ? p->first : g->codes[code].first; @@ -6236,62 +6398,73 @@ static stbi_uc *stbi__process_gif_raster(stbi__context *s, stbi__gif *g) } } -static void stbi__fill_gif_background(stbi__gif *g, int x0, int y0, int x1, int y1) -{ - int x, y; - stbi_uc *c = g->pal[g->bgindex]; - for (y = y0; y < y1; y += 4 * g->w) { - for (x = x0; x < x1; x += 4) { - stbi_uc *p = &g->out[y + x]; - p[0] = c[2]; - p[1] = c[1]; - p[2] = c[0]; - p[3] = 0; - } - } -} - // this function is designed to support animated gifs, although stb_image doesn't support it -static stbi_uc *stbi__gif_load_next(stbi__context *s, stbi__gif *g, int *comp, int req_comp) +// two back is the image from two frames ago, used for a very specific disposal format +static stbi_uc *stbi__gif_load_next(stbi__context *s, stbi__gif *g, int *comp, int req_comp, stbi_uc *two_back) { - int i; - stbi_uc *prev_out = 0; - - if (g->out == 0 && !stbi__gif_header(s, g, comp,0)) - return 0; // stbi__g_failure_reason set by stbi__gif_header + int dispose; + int first_frame; + int pi; + int pcount; + STBI_NOTUSED(req_comp); - if (!stbi__mad3sizes_valid(g->w, g->h, 4, 0)) - return stbi__errpuc("too large", "GIF too large"); + // on first frame, any non-written pixels get the background colour (non-transparent) + first_frame = 0; + if (g->out == 0) { + if (!stbi__gif_header(s, g, comp,0)) return 0; // stbi__g_failure_reason set by stbi__gif_header + g->out = (stbi_uc *) stbi__malloc(4 * g->w * g->h); + g->background = (stbi_uc *) stbi__malloc(4 * g->w * g->h); + g->history = (stbi_uc *) stbi__malloc(g->w * g->h); + if (g->out == 0) return stbi__errpuc("outofmem", "Out of memory"); + + // image is treated as "transparent" at the start - ie, nothing overwrites the current background; + // background colour is only used for pixels that are not rendered first frame, after that "background" + // color refers to the color that was there the previous frame. + memset( g->out, 0x00, 4 * g->w * g->h ); + memset( g->background, 0x00, 4 * g->w * g->h ); // state of the background (starts transparent) + memset( g->history, 0x00, g->w * g->h ); // pixels that were affected previous frame + first_frame = 1; + } else { + // second frame - how do we dispoase of the previous one? + dispose = (g->eflags & 0x1C) >> 2; + pcount = g->w * g->h; - prev_out = g->out; - g->out = (stbi_uc *) stbi__malloc_mad3(4, g->w, g->h, 0); - if (g->out == 0) return stbi__errpuc("outofmem", "Out of memory"); + if ((dispose == 3) && (two_back == 0)) { + dispose = 2; // if I don't have an image to revert back to, default to the old background + } - switch ((g->eflags & 0x1C) >> 2) { - case 0: // unspecified (also always used on 1st frame) - stbi__fill_gif_background(g, 0, 0, 4 * g->w, 4 * g->w * g->h); - break; - case 1: // do not dispose - if (prev_out) memcpy(g->out, prev_out, 4 * g->w * g->h); - g->old_out = prev_out; - break; - case 2: // dispose to background - if (prev_out) memcpy(g->out, prev_out, 4 * g->w * g->h); - stbi__fill_gif_background(g, g->start_x, g->start_y, g->max_x, g->max_y); - break; - case 3: // dispose to previous - if (g->old_out) { - for (i = g->start_y; i < g->max_y; i += 4 * g->w) - memcpy(&g->out[i + g->start_x], &g->old_out[i + g->start_x], g->max_x - g->start_x); + if (dispose == 3) { // use previous graphic + for (pi = 0; pi < pcount; ++pi) { + if (g->history[pi]) { + memcpy( &g->out[pi * 4], &two_back[pi * 4], 4 ); + } } - break; + } else if (dispose == 2) { + // restore what was changed last frame to background before that frame; + for (pi = 0; pi < pcount; ++pi) { + if (g->history[pi]) { + memcpy( &g->out[pi * 4], &g->background[pi * 4], 4 ); + } + } + } else { + // This is a non-disposal case eithe way, so just + // leave the pixels as is, and they will become the new background + // 1: do not dispose + // 0: not specified. + } + + // background is what out is after the undoing of the previou frame; + memcpy( g->background, g->out, 4 * g->w * g->h ); } + // clear my history; + memset( g->history, 0x00, g->w * g->h ); // pixels that were affected previous frame + for (;;) { - switch (stbi__get8(s)) { + int tag = stbi__get8(s); + switch (tag) { case 0x2C: /* Image Descriptor */ { - int prev_trans = -1; stbi__int32 x, y, w, h; stbi_uc *o; @@ -6324,19 +6497,24 @@ static stbi_uc *stbi__gif_load_next(stbi__context *s, stbi__gif *g, int *comp, i stbi__gif_parse_colortable(s,g->lpal, 2 << (g->lflags & 7), g->eflags & 0x01 ? g->transparent : -1); g->color_table = (stbi_uc *) g->lpal; } else if (g->flags & 0x80) { - if (g->transparent >= 0 && (g->eflags & 0x01)) { - prev_trans = g->pal[g->transparent][3]; - g->pal[g->transparent][3] = 0; - } g->color_table = (stbi_uc *) g->pal; } else - return stbi__errpuc("missing color table", "Corrupt GIF"); - + return stbi__errpuc("missing color table", "Corrupt GIF"); + o = stbi__process_gif_raster(s, g); if (o == NULL) return NULL; - if (prev_trans != -1) - g->pal[g->transparent][3] = (stbi_uc) prev_trans; + // if this was the first frame, + pcount = g->w * g->h; + if (first_frame && (g->bgindex > 0)) { + // if first frame, any pixel not drawn to gets the background color + for (pi = 0; pi < pcount; ++pi) { + if (g->history[pi] == 0) { + g->pal[g->bgindex][3] = 255; // just in case it was made transparent, undo that; It will be reset next frame if need be; + memcpy( &g->out[pi * 4], &g->pal[g->bgindex], 4 ); + } + } + } return o; } @@ -6344,19 +6522,35 @@ static stbi_uc *stbi__gif_load_next(stbi__context *s, stbi__gif *g, int *comp, i case 0x21: // Comment Extension. { int len; - if (stbi__get8(s) == 0xF9) { // Graphic Control Extension. + int ext = stbi__get8(s); + if (ext == 0xF9) { // Graphic Control Extension. len = stbi__get8(s); if (len == 4) { g->eflags = stbi__get8(s); - g->delay = stbi__get16le(s); - g->transparent = stbi__get8(s); + g->delay = 10 * stbi__get16le(s); // delay - 1/100th of a second, saving as 1/1000ths. + + // unset old transparent + if (g->transparent >= 0) { + g->pal[g->transparent][3] = 255; + } + if (g->eflags & 0x01) { + g->transparent = stbi__get8(s); + if (g->transparent >= 0) { + g->pal[g->transparent][3] = 0; + } + } else { + // don't need transparent + stbi__skip(s, 1); + g->transparent = -1; + } } else { stbi__skip(s, len); break; } - } - while ((len = stbi__get8(s)) != 0) + } + while ((len = stbi__get8(s)) != 0) { stbi__skip(s, len); + } break; } @@ -6367,28 +6561,93 @@ static stbi_uc *stbi__gif_load_next(stbi__context *s, stbi__gif *g, int *comp, i return stbi__errpuc("unknown code", "Corrupt GIF"); } } +} - STBI_NOTUSED(req_comp); +static void *stbi__load_gif_main(stbi__context *s, int **delays, int *x, int *y, int *z, int *comp, int req_comp) +{ + if (stbi__gif_test(s)) { + int layers = 0; + stbi_uc *u = 0; + stbi_uc *out = 0; + stbi_uc *two_back = 0; + stbi__gif g; + int stride; + memset(&g, 0, sizeof(g)); + if (delays) { + *delays = 0; + } + + do { + u = stbi__gif_load_next(s, &g, comp, req_comp, two_back); + if (u == (stbi_uc *) s) u = 0; // end of animated gif marker + + if (u) { + *x = g.w; + *y = g.h; + ++layers; + stride = g.w * g.h * 4; + + if (out) { + out = (stbi_uc*) STBI_REALLOC( out, layers * stride ); + if (delays) { + *delays = (int*) STBI_REALLOC( *delays, sizeof(int) * layers ); + } + } else { + out = (stbi_uc*)stbi__malloc( layers * stride ); + if (delays) { + *delays = (int*) stbi__malloc( layers * sizeof(int) ); + } + } + memcpy( out + ((layers - 1) * stride), u, stride ); + if (layers >= 2) { + two_back = out - 2 * stride; + } + + if (delays) { + (*delays)[layers - 1U] = g.delay; + } + } + } while (u != 0); + + // free temp buffer; + STBI_FREE(g.out); + STBI_FREE(g.history); + STBI_FREE(g.background); + + // do the final conversion after loading everything; + if (req_comp && req_comp != 4) + out = stbi__convert_format(out, 4, req_comp, layers * g.w, g.h); + + *z = layers; + return out; + } else { + return stbi__errpuc("not GIF", "Image was not as a gif type."); + } } static void *stbi__gif_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) { stbi_uc *u = 0; - stbi__gif* g = (stbi__gif*) stbi__malloc(sizeof(stbi__gif)); - memset(g, 0, sizeof(*g)); + stbi__gif g; + memset(&g, 0, sizeof(g)); STBI_NOTUSED(ri); - u = stbi__gif_load_next(s, g, comp, req_comp); + u = stbi__gif_load_next(s, &g, comp, req_comp, 0); if (u == (stbi_uc *) s) u = 0; // end of animated gif marker if (u) { - *x = g->w; - *y = g->h; + *x = g.w; + *y = g.h; + + // moved conversion to after successful load so that the same + // can be done for multiple frames. if (req_comp && req_comp != 4) - u = stbi__convert_format(u, 4, req_comp, g->w, g->h); + u = stbi__convert_format(u, 4, req_comp, g.w, g.h); } - else if (g->out) - STBI_FREE(g->out); - STBI_FREE(g); + + // free buffers needed for multiple frame loading; + STBI_FREE(g.history); + STBI_FREE(g.background); + return u; } @@ -6667,7 +6926,7 @@ static int stbi__bmp_info(stbi__context *s, int *x, int *y, int *comp) #ifndef STBI_NO_PSD static int stbi__psd_info(stbi__context *s, int *x, int *y, int *comp) { - int channelCount, dummy; + int channelCount, dummy, depth; if (!x) x = &dummy; if (!y) y = &dummy; if (!comp) comp = &dummy; @@ -6687,7 +6946,8 @@ static int stbi__psd_info(stbi__context *s, int *x, int *y, int *comp) } *y = stbi__get32be(s); *x = stbi__get32be(s); - if (stbi__get16be(s) != 8) { + depth = stbi__get16be(s); + if (depth != 8 && depth != 16) { stbi__rewind( s ); return 0; } @@ -6698,6 +6958,33 @@ static int stbi__psd_info(stbi__context *s, int *x, int *y, int *comp) *comp = 4; return 1; } + +static int stbi__psd_is16(stbi__context *s) +{ + int channelCount, depth; + if (stbi__get32be(s) != 0x38425053) { + stbi__rewind( s ); + return 0; + } + if (stbi__get16be(s) != 1) { + stbi__rewind( s ); + return 0; + } + stbi__skip(s, 6); + channelCount = stbi__get16be(s); + if (channelCount < 0 || channelCount > 16) { + stbi__rewind( s ); + return 0; + } + (void) stbi__get32be(s); + (void) stbi__get32be(s); + depth = stbi__get16be(s); + if (depth != 16) { + stbi__rewind( s ); + return 0; + } + return 1; +} #endif #ifndef STBI_NO_PIC @@ -6928,6 +7215,19 @@ static int stbi__info_main(stbi__context *s, int *x, int *y, int *comp) return stbi__err("unknown image type", "Image not of any known type, or corrupt"); } +static int stbi__is_16_main(stbi__context *s) +{ + #ifndef STBI_NO_PNG + if (stbi__png_is16(s)) return 1; + #endif + + #ifndef STBI_NO_PSD + if (stbi__psd_is16(s)) return 1; + #endif + + return 0; +} + #ifndef STBI_NO_STDIO STBIDEF int stbi_info(char const *filename, int *x, int *y, int *comp) { @@ -6949,6 +7249,27 @@ STBIDEF int stbi_info_from_file(FILE *f, int *x, int *y, int *comp) fseek(f,pos,SEEK_SET); return r; } + +STBIDEF int stbi_is_16_bit(char const *filename) +{ + FILE *f = stbi__fopen(filename, "rb"); + int result; + if (!f) return stbi__err("can't fopen", "Unable to open file"); + result = stbi_is_16_bit_from_file(f); + fclose(f); + return result; +} + +STBIDEF int stbi_is_16_bit_from_file(FILE *f) +{ + int r; + stbi__context s; + long pos = ftell(f); + stbi__start_file(&s, f); + r = stbi__is_16_main(&s); + fseek(f,pos,SEEK_SET); + return r; +} #endif // !STBI_NO_STDIO STBIDEF int stbi_info_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp) @@ -6965,10 +7286,31 @@ STBIDEF int stbi_info_from_callbacks(stbi_io_callbacks const *c, void *user, int return stbi__info_main(&s,x,y,comp); } +STBIDEF int stbi_is_16_bit_from_memory(stbi_uc const *buffer, int len) +{ + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__is_16_main(&s); +} + +STBIDEF int stbi_is_16_bit_from_callbacks(stbi_io_callbacks const *c, void *user) +{ + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *) c, user); + return stbi__is_16_main(&s); +} + #endif // STB_IMAGE_IMPLEMENTATION /* revision history: + 2.20 (2019-02-07) support utf8 filenames in Windows; fix warnings and platform ifdefs + 2.19 (2018-02-11) fix warning + 2.18 (2018-01-30) fix warnings + 2.17 (2018-01-29) change sbti__shiftsigned to avoid clang -O2 bug + 1-bit BMP + *_is_16_bit api + avoid warnings 2.16 (2017-07-23) all functions have 16-bit variants; STBI_NO_STDIO works again; compilation fixes; diff --git a/src/stb_image_write.h b/src/stb_image_write.h old mode 100644 new mode 100755 index 9d553e0d7d0..00ab092ac86 --- a/src/stb_image_write.h +++ b/src/stb_image_write.h @@ -1,4 +1,4 @@ -/* stb_image_write - v1.07 - public domain - http://nothings.org/stb/stb_image_write.h +/* stb_image_write - v1.10 - public domain - http://nothings.org/stb/stb_image_write.h writes out PNG/BMP/TGA/JPEG/HDR images to C stdio - Sean Barrett 2010-2015 no warranty implied; use at your own risk @@ -10,34 +10,54 @@ Will probably not work correctly with strict-aliasing optimizations. + If using a modern Microsoft Compiler, non-safe versions of CRT calls may cause + compilation warnings or even errors. To avoid this, also before #including, + + #define STBI_MSC_SECURE_CRT + ABOUT: - This header file is a library for writing images to C stdio. It could be - adapted to write to memory or a general streaming interface; let me know. + This header file is a library for writing images to C stdio or a callback. The PNG output is not optimal; it is 20-50% larger than the file - written by a decent optimizing implementation. This library is designed - for source code compactness and simplicity, not optimal image file size - or run-time performance. + written by a decent optimizing implementation; though providing a custom + zlib compress function (see STBIW_ZLIB_COMPRESS) can mitigate that. + This library is designed for source code compactness and simplicity, + not optimal image file size or run-time performance. BUILDING: You can #define STBIW_ASSERT(x) before the #include to avoid using assert.h. You can #define STBIW_MALLOC(), STBIW_REALLOC(), and STBIW_FREE() to replace malloc,realloc,free. - You can define STBIW_MEMMOVE() to replace memmove() + You can #define STBIW_MEMMOVE() to replace memmove() + You can #define STBIW_ZLIB_COMPRESS to use a custom zlib-style compress function + for PNG compression (instead of the builtin one), it must have the following signature: + unsigned char * my_compress(unsigned char *data, int data_len, int *out_len, int quality); + The returned data will be freed with STBIW_FREE() (free() by default), + so it must be heap allocated with STBIW_MALLOC() (malloc() by default), + +UNICODE: + + If compiling for Windows and you wish to use Unicode filenames, compile + with + #define STBIW_WINDOWS_UTF8 + and pass utf8-encoded filenames. Call stbiw_convert_wchar_to_utf8 to convert + Windows wchar_t filenames to utf8. USAGE: - There are four functions, one for each image file format: + There are five functions, one for each image file format: int stbi_write_png(char const *filename, int w, int h, int comp, const void *data, int stride_in_bytes); int stbi_write_bmp(char const *filename, int w, int h, int comp, const void *data); int stbi_write_tga(char const *filename, int w, int h, int comp, const void *data); + int stbi_write_jpg(char const *filename, int w, int h, int comp, const void *data, int quality); int stbi_write_hdr(char const *filename, int w, int h, int comp, const float *data); - int stbi_write_jpg(char const *filename, int w, int h, int comp, const float *data); - There are also four equivalent functions that use an arbitrary write function. You are + void stbi_flip_vertically_on_write(int flag); // flag is non-zero to flip data vertically + + There are also five equivalent functions that use an arbitrary write function. You are expected to open/close your file-equivalent before and after calling these: int stbi_write_png_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data, int stride_in_bytes); @@ -49,6 +69,12 @@ where the callback is: void stbi_write_func(void *context, void *data, int size); + You can configure it with these global variables: + int stbi_write_tga_with_rle; // defaults to true; set to 0 to disable RLE + int stbi_write_png_compression_level; // defaults to 8; set to higher for more compression + int stbi_write_force_png_filter; // defaults to -1; set to 0..5 to force a filter mode + + You can define STBI_WRITE_NO_STDIO to disable the file variant of these functions, so the library will not use stdio.h at all. However, this will also disable HDR writing, because it requires stdio for formatted output. @@ -75,6 +101,9 @@ writer, both because it is in BGR order and because it may have padding at the end of the line.) + PNG allows you to set the deflate compression level by setting the global + variable 'stbi_write_png_compression_level' (it defaults to 8). + HDR expects linear float data. Since the format is always 32-bit rgb(e) data, alpha (if provided) is discarded, and for monochrome data it is replicated across all three channels. @@ -88,21 +117,17 @@ CREDITS: - PNG/BMP/TGA - Sean Barrett - HDR - Baldur Karlsson - TGA monochrome: - Jean-Sebastien Guay - misc enhancements: - Tim Kelsey - TGA RLE - Alan Hickman - initial file IO callback implementation - Emmanuel Julien - JPEG - Jon Olick (original jo_jpeg.cpp code) - Daniel Gibson + + Sean Barrett - PNG/BMP/TGA + Baldur Karlsson - HDR + Jean-Sebastien Guay - TGA monochrome + Tim Kelsey - misc enhancements + Alan Hickman - TGA RLE + Emmanuel Julien - initial file IO callback implementation + Jon Olick - original jo_jpeg.cpp code + Daniel Gibson - integrate JPEG, allow external zlib + Aarni Koskela - allow choosing PNG filter + bugfixes: github:Chribba Guillaume Chereau @@ -114,7 +139,13 @@ Thatcher Ulrich github:poppolopoppo Patrick Boettcher - + github:xeekworx + Cap Petschulat + Simon Rodriguez + Ivan Tikhonov + github:ignotion + Adam Schackart + LICENSE See end of file for license information. @@ -124,15 +155,25 @@ LICENSE #ifndef INCLUDE_STB_IMAGE_WRITE_H #define INCLUDE_STB_IMAGE_WRITE_H -#ifdef __cplusplus -extern "C" { -#endif +#include +// if STB_IMAGE_WRITE_STATIC causes problems, try defining STBIWDEF to 'inline' or 'static inline' +#ifndef STBIWDEF #ifdef STB_IMAGE_WRITE_STATIC -#define STBIWDEF static +#define STBIWDEF static +#else +#ifdef __cplusplus +#define STBIWDEF extern "C" #else -#define STBIWDEF extern +#define STBIWDEF extern +#endif +#endif +#endif + +#ifndef STB_IMAGE_WRITE_STATIC // C++ forbids static forward declarations extern int stbi_write_tga_with_rle; +extern int stbi_write_png_compression_level; +extern int stbi_write_force_png_filter; #endif #ifndef STBI_WRITE_NO_STDIO @@ -141,6 +182,10 @@ STBIWDEF int stbi_write_bmp(char const *filename, int w, int h, int comp, const STBIWDEF int stbi_write_tga(char const *filename, int w, int h, int comp, const void *data); STBIWDEF int stbi_write_hdr(char const *filename, int w, int h, int comp, const float *data); STBIWDEF int stbi_write_jpg(char const *filename, int x, int y, int comp, const void *data, int quality); + +#ifdef STBI_WINDOWS_UTF8 +STBIWDEF int stbiw_convert_wchar_to_utf8(char *buffer, size_t bufferlen, const wchar_t* input); +#endif #endif typedef void stbi_write_func(void *context, void *data, int size); @@ -151,9 +196,7 @@ STBIWDEF int stbi_write_tga_to_func(stbi_write_func *func, void *context, int w, STBIWDEF int stbi_write_hdr_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const float *data); STBIWDEF int stbi_write_jpg_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data, int quality); -#ifdef __cplusplus -} -#endif +STBIWDEF void stbi_flip_vertically_on_write(int flip_boolean); #endif//INCLUDE_STB_IMAGE_WRITE_H @@ -208,6 +251,23 @@ STBIWDEF int stbi_write_jpg_to_func(stbi_write_func *func, void *context, int x, #define STBIW_UCHAR(x) (unsigned char) ((x) & 0xff) +#ifdef STB_IMAGE_WRITE_STATIC +static int stbi__flip_vertically_on_write=0; +static int stbi_write_png_compression_level = 8; +static int stbi_write_tga_with_rle = 1; +static int stbi_write_force_png_filter = -1; +#else +int stbi_write_png_compression_level = 8; +int stbi__flip_vertically_on_write=0; +int stbi_write_tga_with_rle = 1; +int stbi_write_force_png_filter = -1; +#endif + +STBIWDEF void stbi_flip_vertically_on_write(int flag) +{ + stbi__flip_vertically_on_write = flag; +} + typedef struct { stbi_write_func *func; @@ -228,9 +288,52 @@ static void stbi__stdio_write(void *context, void *data, int size) fwrite(data,1,size,(FILE*) context); } +#if defined(_MSC_VER) && defined(STBI_WINDOWS_UTF8) +#ifdef __cplusplus +#define STBIW_EXTERN extern "C" +#else +#define STBIW_EXTERN extern +#endif +STBIW_EXTERN __declspec(dllimport) int __stdcall MultiByteToWideChar(unsigned int cp, unsigned long flags, const char *str, int cbmb, wchar_t *widestr, int cchwide); +STBIW_EXTERN __declspec(dllimport) int __stdcall WideCharToMultiByte(unsigned int cp, unsigned long flags, const wchar_t *widestr, int cchwide, char *str, int cbmb, const char *defchar, int *used_default); + +STBIWDEF int stbiw_convert_wchar_to_utf8(char *buffer, size_t bufferlen, const wchar_t* input) +{ + return WideCharToMultiByte(65001 /* UTF8 */, 0, input, -1, buffer, bufferlen, NULL, NULL); +} +#endif + +static FILE *stbiw__fopen(char const *filename, char const *mode) +{ + FILE *f; +#if defined(_MSC_VER) && defined(STBI_WINDOWS_UTF8) + wchar_t wMode[64]; + wchar_t wFilename[1024]; + if (0 == MultiByteToWideChar(65001 /* UTF8 */, 0, filename, -1, wFilename, sizeof(wFilename))) + return 0; + + if (0 == MultiByteToWideChar(65001 /* UTF8 */, 0, mode, -1, wMode, sizeof(wMode))) + return 0; + +#if _MSC_VER >= 1400 + if (0 != _wfopen_s(&f, wFilename, wMode)) + f = 0; +#else + f = _wfopen(wFilename, wMode); +#endif + +#elif defined(_MSC_VER) && _MSC_VER >= 1400 + if (0 != fopen_s(&f, filename, mode)) + f=0; +#else + f = fopen(filename, mode); +#endif + return f; +} + static int stbi__start_write_file(stbi__write_context *s, const char *filename) { - FILE *f = fopen(filename, "wb"); + FILE *f = stbiw__fopen(filename, "wb"); stbi__start_write_callbacks(s, stbi__stdio_write, (void *) f); return f != NULL; } @@ -245,12 +348,6 @@ static void stbi__end_write_file(stbi__write_context *s) typedef unsigned int stbiw_uint32; typedef int stb_image_write_test[sizeof(stbiw_uint32)==4 ? 1 : -1]; -#ifdef STB_IMAGE_WRITE_STATIC -static int stbi_write_tga_with_rle = 1; -#else -int stbi_write_tga_with_rle = 1; -#endif - static void stbiw__writefv(stbi__write_context *s, const char *fmt, va_list v) { while (*fmt) { @@ -341,6 +438,9 @@ static void stbiw__write_pixels(stbi__write_context *s, int rgb_dir, int vdir, i if (y <= 0) return; + if (stbi__flip_vertically_on_write) + vdir *= -1; + if (vdir < 0) j_end = -1, j = y-1; else @@ -412,11 +512,21 @@ static int stbi_write_tga_core(stbi__write_context *s, int x, int y, int comp, v "111 221 2222 11", 0, 0, format, 0, 0, 0, 0, 0, x, y, (colorbytes + has_alpha) * 8, has_alpha * 8); } else { int i,j,k; + int jend, jdir; stbiw__writef(s, "111 221 2222 11", 0,0,format+8, 0,0,0, 0,0,x,y, (colorbytes + has_alpha) * 8, has_alpha * 8); - for (j = y - 1; j >= 0; --j) { - unsigned char *row = (unsigned char *) data + j * x * comp; + if (stbi__flip_vertically_on_write) { + j = 0; + jend = y; + jdir = 1; + } else { + j = y-1; + jend = -1; + jdir = -1; + } + for (; j != jend; j += jdir) { + unsigned char *row = (unsigned char *) data + j * x * comp; int len; for (i = 0; i < x; i += len) { @@ -492,7 +602,7 @@ STBIWDEF int stbi_write_tga(char const *filename, int x, int y, int comp, const #define stbiw__max(a, b) ((a) > (b) ? (a) : (b)) -void stbiw__linear_to_rgbe(unsigned char *rgbe, float *linear) +static void stbiw__linear_to_rgbe(unsigned char *rgbe, float *linear) { int exponent; float maxcomp = stbiw__max(linear[0], stbiw__max(linear[1], linear[2])); @@ -509,7 +619,7 @@ void stbiw__linear_to_rgbe(unsigned char *rgbe, float *linear) } } -void stbiw__write_run_data(stbi__write_context *s, int length, unsigned char databyte) +static void stbiw__write_run_data(stbi__write_context *s, int length, unsigned char databyte) { unsigned char lengthbyte = STBIW_UCHAR(length+128); STBIW_ASSERT(length+128 <= 255); @@ -517,7 +627,7 @@ void stbiw__write_run_data(stbi__write_context *s, int length, unsigned char dat s->func(s->context, &databyte, 1); } -void stbiw__write_dump_data(stbi__write_context *s, int length, unsigned char *data) +static void stbiw__write_dump_data(stbi__write_context *s, int length, unsigned char *data) { unsigned char lengthbyte = STBIW_UCHAR(length); STBIW_ASSERT(length <= 128); // inconsistent with spec but consistent with official code @@ -525,7 +635,7 @@ void stbiw__write_dump_data(stbi__write_context *s, int length, unsigned char *d s->func(s->context, data, length); } -void stbiw__write_hdr_scanline(stbi__write_context *s, int width, int ncomp, unsigned char *scratch, float *scanline) +static void stbiw__write_hdr_scanline(stbi__write_context *s, int width, int ncomp, unsigned char *scratch, float *scanline) { unsigned char scanlineheader[4] = { 2, 2, 0, 0 }; unsigned char rgbe[4]; @@ -626,11 +736,15 @@ static int stbi_write_hdr_core(stbi__write_context *s, int x, int y, int comp, f char header[] = "#?RADIANCE\n# Written by stb_image_write.h\nFORMAT=32-bit_rle_rgbe\n"; s->func(s->context, header, sizeof(header)-1); +#ifdef STBI_MSC_SECURE_CRT + len = sprintf_s(buffer, "EXPOSURE= 1.0000000000000\n\n-Y %d +X %d\n", y, x); +#else len = sprintf(buffer, "EXPOSURE= 1.0000000000000\n\n-Y %d +X %d\n", y, x); +#endif s->func(s->context, buffer, len); for(i=0; i < y; i++) - stbiw__write_hdr_scanline(s, x, comp, scratch, data + comp*i*x); + stbiw__write_hdr_scanline(s, x, comp, scratch, data + comp*x*(stbi__flip_vertically_on_write ? y-1-i : i)); STBIW_FREE(scratch); return 1; } @@ -662,6 +776,7 @@ STBIWDEF int stbi_write_hdr(char const *filename, int x, int y, int comp, const // PNG writer // +#ifndef STBIW_ZLIB_COMPRESS // stretchy buffer; stbiw__sbpush() == vector<>::push_back() -- stbiw__sbcount() == vector<>::size() #define stbiw__sbraw(a) ((int *) (a) - 2) #define stbiw__sbm(a) stbiw__sbraw(a)[0] @@ -742,8 +857,14 @@ static unsigned int stbiw__zhash(unsigned char *data) #define stbiw__ZHASH 16384 -unsigned char * stbi_zlib_compress(unsigned char *data, int data_len, int *out_len, int quality) +#endif // STBIW_ZLIB_COMPRESS + +STBIWDEF unsigned char * stbi_zlib_compress(unsigned char *data, int data_len, int *out_len, int quality) { +#ifdef STBIW_ZLIB_COMPRESS + // user provided a zlib compress implementation, use that + return STBIW_ZLIB_COMPRESS(data, data_len, out_len, quality); +#else // use builtin static unsigned short lengthc[] = { 3,4,5,6,7,8,9,10,11,13,15,17,19,23,27,31,35,43,51,59,67,83,99,115,131,163,195,227,258, 259 }; static unsigned char lengtheb[]= { 0,0,0,0,0,0,0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0 }; static unsigned short distc[] = { 1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193,257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577, 32768 }; @@ -752,6 +873,8 @@ unsigned char * stbi_zlib_compress(unsigned char *data, int data_len, int *out_l int i,j, bitcount=0; unsigned char *out = NULL; unsigned char ***hash_table = (unsigned char***) STBIW_MALLOC(stbiw__ZHASH * sizeof(char**)); + if (hash_table == NULL) + return NULL; if (quality < 5) quality = 5; stbiw__sbpush(out, 0x78); // DEFLATE 32K window @@ -845,10 +968,14 @@ unsigned char * stbi_zlib_compress(unsigned char *data, int data_len, int *out_l // make returned pointer freeable STBIW_MEMMOVE(stbiw__sbraw(out), out, *out_len); return (unsigned char *) stbiw__sbraw(out); +#endif // STBIW_ZLIB_COMPRESS } static unsigned int stbiw__crc32(unsigned char *buffer, int len) { +#ifdef STBIW_CRC32 + return STBIW_CRC32(buffer, len); +#else static unsigned int crc_table[256] = { 0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3, @@ -890,6 +1017,7 @@ static unsigned int stbiw__crc32(unsigned char *buffer, int len) for (i=0; i < len; ++i) crc = (crc >> 8) ^ crc_table[buffer[i] ^ (crc & 0xff)]; return ~crc; +#endif } #define stbiw__wpng4(o,a,b,c,d) ((o)[0]=STBIW_UCHAR(a),(o)[1]=STBIW_UCHAR(b),(o)[2]=STBIW_UCHAR(c),(o)[3]=STBIW_UCHAR(d),(o)+=4) @@ -911,61 +1039,91 @@ static unsigned char stbiw__paeth(int a, int b, int c) } // @OPTIMIZE: provide an option that always forces left-predict or paeth predict -unsigned char *stbi_write_png_to_mem(unsigned char *pixels, int stride_bytes, int x, int y, int n, int *out_len) +static void stbiw__encode_png_line(unsigned char *pixels, int stride_bytes, int width, int height, int y, int n, int filter_type, signed char *line_buffer) { + static int mapping[] = { 0,1,2,3,4 }; + static int firstmap[] = { 0,1,0,5,6 }; + int *mymap = (y != 0) ? mapping : firstmap; + int i; + int type = mymap[filter_type]; + unsigned char *z = pixels + stride_bytes * (stbi__flip_vertically_on_write ? height-1-y : y); + int signed_stride = stbi__flip_vertically_on_write ? -stride_bytes : stride_bytes; + + if (type==0) { + memcpy(line_buffer, z, width*n); + return; + } + + // first loop isn't optimized since it's just one pixel + for (i = 0; i < n; ++i) { + switch (type) { + case 1: line_buffer[i] = z[i]; break; + case 2: line_buffer[i] = z[i] - z[i-signed_stride]; break; + case 3: line_buffer[i] = z[i] - (z[i-signed_stride]>>1); break; + case 4: line_buffer[i] = (signed char) (z[i] - stbiw__paeth(0,z[i-signed_stride],0)); break; + case 5: line_buffer[i] = z[i]; break; + case 6: line_buffer[i] = z[i]; break; + } + } + switch (type) { + case 1: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - z[i-n]; break; + case 2: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - z[i-signed_stride]; break; + case 3: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - ((z[i-n] + z[i-signed_stride])>>1); break; + case 4: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - stbiw__paeth(z[i-n], z[i-signed_stride], z[i-signed_stride-n]); break; + case 5: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - (z[i-n]>>1); break; + case 6: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - stbiw__paeth(z[i-n], 0,0); break; + } +} + +STBIWDEF unsigned char *stbi_write_png_to_mem(const unsigned char *pixels, int stride_bytes, int x, int y, int n, int *out_len) +{ + int force_filter = stbi_write_force_png_filter; int ctype[5] = { -1, 0, 4, 2, 6 }; unsigned char sig[8] = { 137,80,78,71,13,10,26,10 }; unsigned char *out,*o, *filt, *zlib; signed char *line_buffer; - int i,j,k,p,zlen; + int j,zlen; if (stride_bytes == 0) stride_bytes = x * n; + if (force_filter >= 5) { + force_filter = -1; + } + filt = (unsigned char *) STBIW_MALLOC((x*n+1) * y); if (!filt) return 0; line_buffer = (signed char *) STBIW_MALLOC(x * n); if (!line_buffer) { STBIW_FREE(filt); return 0; } for (j=0; j < y; ++j) { - static int mapping[] = { 0,1,2,3,4 }; - static int firstmap[] = { 0,1,0,5,6 }; - int *mymap = (j != 0) ? mapping : firstmap; - int best = 0, bestval = 0x7fffffff; - for (p=0; p < 2; ++p) { - for (k= p?best:0; k < 5; ++k) { // @TODO: clarity: rewrite this to go 0..5, and 'continue' the unwanted ones during 2nd pass - int type = mymap[k],est=0; - unsigned char *z = pixels + stride_bytes*j; - for (i=0; i < n; ++i) - switch (type) { - case 0: line_buffer[i] = z[i]; break; - case 1: line_buffer[i] = z[i]; break; - case 2: line_buffer[i] = z[i] - z[i-stride_bytes]; break; - case 3: line_buffer[i] = z[i] - (z[i-stride_bytes]>>1); break; - case 4: line_buffer[i] = (signed char) (z[i] - stbiw__paeth(0,z[i-stride_bytes],0)); break; - case 5: line_buffer[i] = z[i]; break; - case 6: line_buffer[i] = z[i]; break; - } - for (i=n; i < x*n; ++i) { - switch (type) { - case 0: line_buffer[i] = z[i]; break; - case 1: line_buffer[i] = z[i] - z[i-n]; break; - case 2: line_buffer[i] = z[i] - z[i-stride_bytes]; break; - case 3: line_buffer[i] = z[i] - ((z[i-n] + z[i-stride_bytes])>>1); break; - case 4: line_buffer[i] = z[i] - stbiw__paeth(z[i-n], z[i-stride_bytes], z[i-stride_bytes-n]); break; - case 5: line_buffer[i] = z[i] - (z[i-n]>>1); break; - case 6: line_buffer[i] = z[i] - stbiw__paeth(z[i-n], 0,0); break; - } - } - if (p) break; - for (i=0; i < x*n; ++i) + int filter_type; + if (force_filter > -1) { + filter_type = force_filter; + stbiw__encode_png_line((unsigned char*)(pixels), stride_bytes, x, y, j, n, force_filter, line_buffer); + } else { // Estimate the best filter by running through all of them: + int best_filter = 0, best_filter_val = 0x7fffffff, est, i; + for (filter_type = 0; filter_type < 5; filter_type++) { + stbiw__encode_png_line((unsigned char*)(pixels), stride_bytes, x, y, j, n, filter_type, line_buffer); + + // Estimate the entropy of the line using this filter; the less, the better. + est = 0; + for (i = 0; i < x*n; ++i) { est += abs((signed char) line_buffer[i]); - if (est < bestval) { bestval = est; best = k; } + } + if (est < best_filter_val) { + best_filter_val = est; + best_filter = filter_type; + } + } + if (filter_type != best_filter) { // If the last iteration already got us the best filter, don't redo it + stbiw__encode_png_line((unsigned char*)(pixels), stride_bytes, x, y, j, n, best_filter, line_buffer); + filter_type = best_filter; } } - // when we get here, best contains the filter type, and line_buffer contains the data - filt[j*(x*n+1)] = (unsigned char) best; + // when we get here, filter_type contains the filter type, and line_buffer contains the data + filt[j*(x*n+1)] = (unsigned char) filter_type; STBIW_MEMMOVE(filt+j*(x*n+1)+1, line_buffer, x*n); } STBIW_FREE(line_buffer); - zlib = stbi_zlib_compress(filt, y*( x*n+1), &zlen, 8); // increase 8 to get smaller but use more memory + zlib = stbi_zlib_compress(filt, y*( x*n+1), &zlen, stbi_write_png_compression_level); STBIW_FREE(filt); if (!zlib) return 0; @@ -1008,9 +1166,10 @@ STBIWDEF int stbi_write_png(char const *filename, int x, int y, int comp, const { FILE *f; int len; - unsigned char *png = stbi_write_png_to_mem((unsigned char *) data, stride_bytes, x, y, comp, &len); + unsigned char *png = stbi_write_png_to_mem((const unsigned char *) data, stride_bytes, x, y, comp, &len); if (png == NULL) return 0; - f = fopen(filename, "wb"); + + f = stbiw__fopen(filename, "wb"); if (!f) { STBIW_FREE(png); return 0; } fwrite(png, 1, len, f); fclose(f); @@ -1022,7 +1181,7 @@ STBIWDEF int stbi_write_png(char const *filename, int x, int y, int comp, const STBIWDEF int stbi_write_png_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data, int stride_bytes) { int len; - unsigned char *png = stbi_write_png_to_mem((unsigned char *) data, stride_bytes, x, y, comp, &len); + unsigned char *png = stbi_write_png_to_mem((const unsigned char *) data, stride_bytes, x, y, comp, &len); if (png == NULL) return 0; func(context, png, len); STBIW_FREE(png); @@ -1317,15 +1476,17 @@ static int stbi_write_jpg_core(stbi__write_context *s, int width, int height, in for(x = 0; x < width; x += 8) { float YDU[64], UDU[64], VDU[64]; for(row = y, pos = 0; row < y+8; ++row) { + int p; + if(row < height) { + p = (stbi__flip_vertically_on_write ? (height-1-row) : row)*width*comp; + } else { + // row >= height => use last input row (=> first if flipping) + p = stbi__flip_vertically_on_write ? 0 : ((height-1)*width*comp); + } for(col = x; col < x+8; ++col, ++pos) { - int p = row*width*comp + col*comp; float r, g, b; - if(row >= height) { - p -= width*comp*(row+1 - height); - } - if(col >= width) { - p -= comp*(col+1 - width); - } + // if col >= width => use pixel from last input column + p += ((col < width) ? col : (width-1))*comp; r = imageData[p+0]; g = imageData[p+ofsG]; @@ -1377,6 +1538,12 @@ STBIWDEF int stbi_write_jpg(char const *filename, int x, int y, int comp, const #endif // STB_IMAGE_WRITE_IMPLEMENTATION /* Revision history + 1.10 (2019-02-07) + support utf8 filenames in Windows; fix warnings and platform ifdefs + 1.09 (2018-02-11) + fix typo in zlib quality API, improve STB_I_W_STATIC in C++ + 1.08 (2018-01-29) + add stbi__flip_vertically_on_write, external zlib, zlib quality, choose PNG filter 1.07 (2017-07-24) doc fix 1.06 (2017-07-23) From d66a9aea4e024a7dc0ad962a53446b6ad5678d82 Mon Sep 17 00:00:00 2001 From: gpsmit Date: Wed, 20 Feb 2019 10:31:08 +0100 Subject: [PATCH 6/9] using stbi_assert instead of default assert to avoid core dump on bad images --- src/image.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/image.c b/src/image.c index 19b60dcf8bf..d5180bffcc5 100755 --- a/src/image.c +++ b/src/image.c @@ -6,6 +6,7 @@ #include #include +#define STBI_ASSERT(x) #define STB_IMAGE_IMPLEMENTATION #include "stb_image.h" #define STB_IMAGE_WRITE_IMPLEMENTATION From 843f928fdcce2c8c3e0d466db2f327a625dc2a06 Mon Sep 17 00:00:00 2001 From: gpsmit Date: Thu, 19 Sep 2019 12:18:04 +0200 Subject: [PATCH 7/9] using stbi_assert in more places to prevent darknet from core dumping on assert failure --- src/image.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/image.c b/src/image.c index 7dc82ce69c1..31fc077f84e 100755 --- a/src/image.c +++ b/src/image.c @@ -41,7 +41,7 @@ float get_color(int c, int x, int max) static float get_pixel(image m, int x, int y, int c) { - assert(x < m.w && y < m.h && c < m.c); + STBI_ASSERT(x < m.w && y < m.h && c < m.c); return m.data[c*m.h*m.w + y*m.w + x]; } static float get_pixel_extend(image m, int x, int y, int c) @@ -59,12 +59,12 @@ static float get_pixel_extend(image m, int x, int y, int c) static void set_pixel(image m, int x, int y, int c, float val) { if (x < 0 || y < 0 || c < 0 || x >= m.w || y >= m.h || c >= m.c) return; - assert(x < m.w && y < m.h && c < m.c); + STBI_ASSERT(x < m.w && y < m.h && c < m.c); m.data[c*m.h*m.w + y*m.w + x] = val; } static void add_pixel(image m, int x, int y, int c, float val) { - assert(x < m.w && y < m.h && c < m.c); + STBI_ASSERT(x < m.w && y < m.h && c < m.c); m.data[c*m.h*m.w + y*m.w + x] += val; } @@ -550,7 +550,7 @@ void draw_detections(image im, int num, float thresh, box *boxes, float **probs, void transpose_image(image im) { - assert(im.w == im.h); + STBI_ASSERT(im.w == im.h); int n, m; int c; for(c = 0; c < im.c; ++c){ @@ -566,7 +566,7 @@ void transpose_image(image im) void rotate_image_cw(image im, int times) { - assert(im.w == im.h); + STBI_ASSERT(im.w == im.h); times = (times + 400) % 4; int i, x, y, c; int n = im.w; @@ -1095,7 +1095,7 @@ float three_way_min(float a, float b, float c) // http://www.cs.rit.edu/~ncs/color/t_convert.html void rgb_to_hsv(image im) { - assert(im.c == 3); + STBI_ASSERT(im.c == 3); int i, j; float r, g, b; float h, s, v; @@ -1132,7 +1132,7 @@ void rgb_to_hsv(image im) void hsv_to_rgb(image im) { - assert(im.c == 3); + STBI_ASSERT(im.c == 3); int i, j; float r, g, b; float h, s, v; @@ -1173,7 +1173,7 @@ void hsv_to_rgb(image im) image grayscale_image(image im) { - assert(im.c == 3); + STBI_ASSERT(im.c == 3); int i, j, k; image gray = make_image(im.w, im.h, 1); float scale[] = {0.587, 0.299, 0.114}; @@ -1199,7 +1199,7 @@ image threshold_image(image im, float thresh) image blend_image(image fore, image back, float alpha) { - assert(fore.w == back.w && fore.h == back.h && fore.c == back.c); + STBI_ASSERT(fore.w == back.w && fore.h == back.h && fore.c == back.c); image blend = make_image(fore.w, fore.h, fore.c); int i, j, k; for(k = 0; k < fore.c; ++k){ From 5dc2d756ec719d17037d1fdaba205fe0b6c9b9d5 Mon Sep 17 00:00:00 2001 From: gpsmit Date: Thu, 19 Sep 2019 12:43:15 +0200 Subject: [PATCH 8/9] Update README.md --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 777d934951e..b451a60d836 100644 --- a/README.md +++ b/README.md @@ -665,6 +665,9 @@ public: #endif }; ``` +---- + +Fork description: Bulk processing of an input file containing image paths into a json-formatted output file containing all darknet yolo predictions of all the input images. From 547b30388e3e9461ab22200cb5d6c5f5343f47c1 Mon Sep 17 00:00:00 2001 From: Stefano Sinigardi Date: Mon, 28 Aug 2023 07:33:05 +0200 Subject: [PATCH 9/9] restore cpp compatibility restore cpp compatibility --- 3rdparty/stb/include/stb_image.h | 10 - 3rdparty/stb/include/stb_image_write.h | 6 +- Makefile | 2 +- README.md | 28 +-- bad.list | 1 - src/detector.c | 4 +- src/image.c | 4 +- src/jWrite.c | 330 +++++++++++++------------ src/jWrite.h | 115 ++++----- 9 files changed, 240 insertions(+), 260 deletions(-) delete mode 100644 bad.list diff --git a/3rdparty/stb/include/stb_image.h b/3rdparty/stb/include/stb_image.h index 2361aa1f3bf..5e807a0a6e7 100644 --- a/3rdparty/stb/include/stb_image.h +++ b/3rdparty/stb/include/stb_image.h @@ -218,16 +218,6 @@ RECENT REVISION HISTORY: // // =========================================================================== // -// UNICODE: -// -// If compiling for Windows and you wish to use Unicode filenames, compile -// with -// #define STBI_WINDOWS_UTF8 -// and pass utf8-encoded filenames. Call stbiw_convert_wchar_to_utf8 to convert -// Windows wchar_t filenames to utf8. -// -// =========================================================================== -// // Philosophy // // stb libraries are designed with the following priorities: diff --git a/3rdparty/stb/include/stb_image_write.h b/3rdparty/stb/include/stb_image_write.h index efe53dca42d..e4b32ed1bc3 100644 --- a/3rdparty/stb/include/stb_image_write.h +++ b/3rdparty/stb/include/stb_image_write.h @@ -10,11 +10,6 @@ Will probably not work correctly with strict-aliasing optimizations. - If using a modern Microsoft Compiler, non-safe versions of CRT calls may cause - compilation warnings or even errors. To avoid this, also before #including, - - #define STBI_MSC_SECURE_CRT - ABOUT: This header file is a library for writing images to C stdio or a callback. @@ -1103,6 +1098,7 @@ static void stbiw__encode_png_line(unsigned char *pixels, int stride_bytes, int int type = mymap[filter_type]; unsigned char *z = pixels + stride_bytes * (stbi__flip_vertically_on_write ? height-1-y : y); int signed_stride = stbi__flip_vertically_on_write ? -stride_bytes : stride_bytes; + if (type==0) { memcpy(line_buffer, z, width*n); return; diff --git a/Makefile b/Makefile index 7fcaf33f08d..6c93282f274 100644 --- a/Makefile +++ b/Makefile @@ -167,7 +167,7 @@ LDFLAGS+= -L/usr/local/zed/lib -lsl_zed endif endif -OBJ=image_opencv.o http_stream.o gemm.o utils.o dark_cuda.o convolutional_layer.o list.o image.o activations.o im2col.o col2im.o blas.o crop_layer.o dropout_layer.o maxpool_layer.o softmax_layer.o data.o matrix.o network.o connected_layer.o cost_layer.o parser.o option_list.o darknet.o detection_layer.o captcha.o route_layer.o writing.o box.o nightmare.o normalization_layer.o avgpool_layer.o coco.o dice.o yolo.o detector.o layer.o compare.o classifier.o local_layer.o swag.o shortcut_layer.o representation_layer.o activation_layer.o rnn_layer.o gru_layer.o rnn.o rnn_vid.o crnn_layer.o demo.o tag.o cifar.o go.o batchnorm_layer.o art.o region_layer.o reorg_layer.o reorg_old_layer.o super.o voxel.o tree.o yolo_layer.o gaussian_yolo_layer.o upsample_layer.o lstm_layer.o conv_lstm_layer.o scale_channels_layer.o jWrite.o sam_layer.o +OBJ=image_opencv.o http_stream.o gemm.o utils.o dark_cuda.o convolutional_layer.o list.o image.o activations.o im2col.o col2im.o blas.o crop_layer.o dropout_layer.o maxpool_layer.o softmax_layer.o data.o matrix.o network.o connected_layer.o cost_layer.o parser.o option_list.o darknet.o detection_layer.o captcha.o route_layer.o writing.o box.o nightmare.o normalization_layer.o avgpool_layer.o coco.o dice.o yolo.o detector.o layer.o compare.o classifier.o local_layer.o swag.o shortcut_layer.o representation_layer.o activation_layer.o rnn_layer.o gru_layer.o rnn.o rnn_vid.o crnn_layer.o demo.o tag.o cifar.o go.o batchnorm_layer.o art.o region_layer.o reorg_layer.o reorg_old_layer.o super.o voxel.o tree.o yolo_layer.o gaussian_yolo_layer.o upsample_layer.o lstm_layer.o conv_lstm_layer.o scale_channels_layer.o sam_layer.o jWrite.o ifeq ($(GPU), 1) LDFLAGS+= -lstdc++ OBJ+=convolutional_kernels.o activation_kernels.o im2col_kernels.o col2im_kernels.o blas_kernels.o crop_layer_kernels.o dropout_layer_kernels.o maxpool_layer_kernels.o network_kernels.o avgpool_layer_kernels.o diff --git a/README.md b/README.md index 37164c219a0..0302b628f29 100644 --- a/README.md +++ b/README.md @@ -821,24 +821,20 @@ public: #endif }; ``` ----- - -Fork description: - -Bulk processing of an input file containing image paths into a json-formatted output file containing all darknet yolo predictions of all the input images. -Based on AlexeyAB's darknet fork: +## Json output -I created a new test phase: **test_headless** which takes a **names** file, **cfg** file and **weights** file, with an input file (with image paths) and output json file, like so: +New command within detector: **test_headless** which takes a **names** file, **cfg** file and **weights** file, with an input file (with image paths) and output json file: -`./darknet detector test_headless data/openimages.names cfg/yolov3-openimages.cfg ../yolov3-openimages.weights -in_filename input.txt -out_filename output.json` +`./darknet detector test_headless data/coco.names cfg/yolov3.cfg ../yolov3.weights -in_filename input.txt -out_filename output.json` example output: -```javascript + +```json { "darknet headless output": [ { - "fileName": "/Users/gpsmit/Downloads/marine.jpg", + "fileName": "/path/to/source/image.jpg", "predictions": [ { "height": 332, @@ -863,18 +859,6 @@ example output: "left_x": 13, "top_y": 84, "width": 471 - }, - { - "height": 216, - "labels": [ - { - "label": "Weapon", - "score": 32.131096 - } - ], - "left_x": 237, - "top_y": 190, - "width": 652 } ] } diff --git a/bad.list b/bad.list deleted file mode 100644 index c210070643d..00000000000 --- a/bad.list +++ /dev/null @@ -1 +0,0 @@ -/Users/gps-werk/Downloads/IMG_4210.HEIC diff --git a/src/detector.c b/src/detector.c index 1b97fb73769..7984f511098 100755 --- a/src/detector.c +++ b/src/detector.c @@ -100,7 +100,7 @@ void train_detector(char *datacfg, char *cfgfile, char *weightfile, int *gpus, i } int save_after_iterations = option_find_int(options, "saveweights", (net.max_batches < 10000) ? 1000 : 10000 ); // configure when to write weights. Very useful for smaller datasets! - int save_last_weights_after = option_find_int(options, "savelast", 100); + int save_last_weights_after = option_find_int(options, "savelast", 100); printf("Weights are saved after: %d iterations. Last weights (*_last.weight) are stored every %d iterations. \n", save_after_iterations, save_last_weights_after ); @@ -1858,7 +1858,7 @@ void test_headless(char *datacfg, char *cfgfile, char *weightfile, float thresh, fclose(outputfile); fclose(inputfile); - free_ptrs(names, net.layers[net.n - 1].classes); + free_ptrs((void**)names, net.layers[net.n - 1].classes); free_network(net); } diff --git a/src/image.c b/src/image.c index 6d4c32e7c1d..7aaab4f98d3 100755 --- a/src/image.c +++ b/src/image.c @@ -22,7 +22,9 @@ #endif // specific to jWrite.c and jWrite.h -#define _CRT_SECURE_NO_WARNINGS // stop complaining about deprecated functions +#ifndef _CRT_SECURE_NO_WARNINGS +#define _CRT_SECURE_NO_WARNINGS +#endif #include "jWrite.h" // END specific to jWrite.c and jWrite.h diff --git a/src/jWrite.c b/src/jWrite.c index acbd099a2b2..128b4b043b9 100755 --- a/src/jWrite.c +++ b/src/jWrite.c @@ -1,20 +1,22 @@ // -// jWrite.c version 1v2 +// jWrite.c version 1v2 // // A *really* simple JSON writer in C // // see: jWrite.h for info // // TonyWilk, Mar 2015 +// source: https://www.codeproject.com/Articles/887604/jWrite-a-really-simple-JSON-writer-in-C +// license: https://www.codeproject.com/info/cpol10.aspx // #include #include -#include // memset() +#include // memset() #include "jWrite.h" -//#include // definintion of uint32_t, int32_t +//#include // definintion of uint32_t, int32_t typedef unsigned int uint32_t; typedef int int32_t; @@ -26,18 +28,18 @@ typedef int int32_t; // which simplifies the function parameters or to supply your own structure // #ifdef JW_GLOBAL_CONTROL_STRUCT -struct jWriteControl g_jWriteControl; // global control struct -#define JWC_DECL // function parameter decl is empty +struct jWriteControl g_jWriteControl; // global control struct +#define JWC_DECL // function parameter decl is empty #define JWC_DECL0 -#define JWC(x) g_jWriteControl.x // functions access global -#define JWC_PARAM // pointer to struct is empty +#define JWC(x) g_jWriteControl.x // functions access global +#define JWC_PARAM // pointer to struct is empty #define JWC_PARAM0 #else -#define JWC_DECL struct jWriteControl *jwc, // function parameter is ptr to control struct -#define JWC_DECL0 struct jWriteControl *jwc // function parameter, no params -#define JWC(x) jwc->x // functions use pointer -#define JWC_PARAM jwc, // pointer to stuct -#define JWC_PARAM0 jwc // pointer to stuct, no params +#define JWC_DECL struct jWriteControl *jwc, // function parameter is ptr to control struct +#define JWC_DECL0 struct jWriteControl *jwc // function parameter, no params +#define JWC(x) jwc->x // functions use pointer +#define JWC_PARAM jwc, // pointer to stuct +#define JWC_PARAM0 jwc // pointer to stuct, no params #endif //------------------------------------------ @@ -59,20 +61,20 @@ void jwPush( JWC_DECL enum jwNodeType nodeType ); // - initialise with user string buffer of length buflen // - isPretty=JW_PRETTY adds \n and spaces to prettify output (else JW_COMPACT) // -void jwOpen( JWC_DECL char *buffer, unsigned int buflen, - enum jwNodeType rootType, int isPretty ) -{ - memset( buffer, 0, buflen ); // zap the whole destination buffer - JWC(buffer)= buffer; - JWC(buflen)= buflen; - JWC(bufp)= buffer; - JWC(nodeStack)[0].nodeType= rootType; - JWC(nodeStack)[0].elementNo= 0; - JWC(stackpos)=0; - JWC(error)= JWRITE_OK; - JWC(callNo)= 1; - JWC(isPretty)= isPretty; - jwPutch( JWC_PARAM (rootType==JW_OBJECT) ? '{' : '[' ); +void jwOpen( JWC_DECL char *buffer, unsigned int buflen, + enum jwNodeType rootType, int isPretty ) +{ + memset( buffer, 0, buflen ); // zap the whole destination buffer + JWC(buffer)= buffer; + JWC(buflen)= buflen; + JWC(bufp)= buffer; + JWC(nodeStack)[0].nodeType= rootType; + JWC(nodeStack)[0].elementNo= 0; + JWC(stackpos)=0; + JWC(error)= JWRITE_OK; + JWC(callNo)= 1; + JWC(isPretty)= isPretty; + jwPutch( JWC_PARAM (rootType==JW_OBJECT) ? '{' : '[' ); } //------------------------------------------ @@ -82,19 +84,19 @@ void jwOpen( JWC_DECL char *buffer, unsigned int buflen, // int jwClose( JWC_DECL0 ) { - if( JWC(error) == JWRITE_OK ) - { - if( JWC(stackpos) == 0 ) - { - enum jwNodeType node= JWC(nodeStack)[0].nodeType; - if( JWC(isPretty) ) - jwPutch( JWC_PARAM '\n' ); - jwPutch( JWC_PARAM (node == JW_OBJECT) ? '}' : ']'); - }else{ - JWC(error)= JWRITE_NEST_ERROR; // nesting error, not all objects closed when jwClose() called - } - } - return JWC(error); + if( JWC(error) == JWRITE_OK ) + { + if( JWC(stackpos) == 0 ) + { + enum jwNodeType node= JWC(nodeStack)[0].nodeType; + if( JWC(isPretty) ) + jwPutch( JWC_PARAM '\n' ); + jwPutch( JWC_PARAM (node == JW_OBJECT) ? '}' : ']'); + }else{ + JWC(error)= JWRITE_NEST_ERROR; // nesting error, not all objects closed when jwClose() called + } + } + return JWC(error); } //------------------------------------------ @@ -102,16 +104,16 @@ int jwClose( JWC_DECL0 ) // int jwEnd( JWC_DECL0 ) { - if( JWC(error) == JWRITE_OK ) - { - enum jwNodeType node; - int lastElemNo= JWC(nodeStack)[JWC(stackpos)].elementNo; - node= jwPop( JWC_PARAM0 ); - if( lastElemNo > 0 ) - jwPretty( JWC_PARAM0 ); - jwPutch( JWC_PARAM (node == JW_OBJECT) ? '}' : ']'); - } - return JWC(error); + if( JWC(error) == JWRITE_OK ) + { + enum jwNodeType node; + int lastElemNo= JWC(nodeStack)[JWC(stackpos)].elementNo; + node= jwPop( JWC_PARAM0 ); + if( lastElemNo > 0 ) + jwPretty( JWC_PARAM0 ); + jwPutch( JWC_PARAM (node == JW_OBJECT) ? '}' : ']'); + } + return JWC(error); } @@ -121,7 +123,7 @@ int jwEnd( JWC_DECL0 ) // int jwErrorPos( JWC_DECL0 ) { - return JWC(callNo); + return JWC(callNo); } @@ -134,60 +136,63 @@ int _jwObj( JWC_DECL char *key ); // void jwObj_raw( JWC_DECL char *key, char *rawtext ) { - if(_jwObj( JWC_PARAM key ) == JWRITE_OK) - jwPutraw( JWC_PARAM rawtext); + if(_jwObj( JWC_PARAM key ) == JWRITE_OK) + jwPutraw( JWC_PARAM rawtext); } // put "quoted" string to object // void jwObj_string( JWC_DECL char *key, char *value ) { - if(_jwObj( JWC_PARAM key ) == JWRITE_OK) - jwPutstr( JWC_PARAM value ); + if(_jwObj( JWC_PARAM key ) == JWRITE_OK) + jwPutstr( JWC_PARAM value ); } void jwObj_int( JWC_DECL char *key, int value ) { - modp_itoa10( value, JWC(tmpbuf) ); - jwObj_raw( JWC_PARAM key, JWC(tmpbuf) ); + modp_itoa10( value, JWC(tmpbuf) ); + jwObj_raw( JWC_PARAM key, JWC(tmpbuf) ); } void jwObj_double( JWC_DECL char *key, double value ) { - modp_dtoa2( value, JWC(tmpbuf), 6 ); - jwObj_raw( JWC_PARAM key, JWC(tmpbuf) ); + modp_dtoa2( value, JWC(tmpbuf), 6 ); + jwObj_raw( JWC_PARAM key, JWC(tmpbuf) ); } void jwObj_bool( JWC_DECL char *key, int oneOrZero ) { - jwObj_raw( JWC_PARAM key, (oneOrZero) ? "true" : "false" ); + char trueKey[8] = "true"; + char falseKey[8] = "false"; + jwObj_raw( JWC_PARAM key, (oneOrZero) ? trueKey : falseKey ); } void jwObj_null( JWC_DECL char *key ) { - jwObj_raw( JWC_PARAM key, "null" ); + char nullKey[8] = "null"; + jwObj_raw( JWC_PARAM key, nullKey ); } // put Object in Object // void jwObj_object( JWC_DECL char *key ) { - if(_jwObj( JWC_PARAM key ) == JWRITE_OK) - { - jwPutch( JWC_PARAM '{' ); - jwPush( JWC_PARAM JW_OBJECT ); - } + if(_jwObj( JWC_PARAM key ) == JWRITE_OK) + { + jwPutch( JWC_PARAM '{' ); + jwPush( JWC_PARAM JW_OBJECT ); + } } // put Array in Object // void jwObj_array( JWC_DECL char *key ) { - if(_jwObj( JWC_PARAM key ) == JWRITE_OK) - { - jwPutch( JWC_PARAM '[' ); - jwPush( JWC_PARAM JW_ARRAY ); - } + if(_jwObj( JWC_PARAM key ) == JWRITE_OK) + { + jwPutch( JWC_PARAM '[' ); + jwPush( JWC_PARAM JW_ARRAY ); + } } //------------------------------------------ @@ -199,56 +204,59 @@ int _jwArr( JWC_DECL0 ); // void jwArr_raw( JWC_DECL char *rawtext ) { - if(_jwArr( JWC_PARAM0 ) == JWRITE_OK) - jwPutraw( JWC_PARAM rawtext); + if(_jwArr( JWC_PARAM0 ) == JWRITE_OK) + jwPutraw( JWC_PARAM rawtext); } // put "quoted" string to array // void jwArr_string( JWC_DECL char *value ) { - if(_jwArr( JWC_PARAM0 ) == JWRITE_OK) - jwPutstr( JWC_PARAM value ); + if(_jwArr( JWC_PARAM0 ) == JWRITE_OK) + jwPutstr( JWC_PARAM value ); } void jwArr_int( JWC_DECL int value ) { - modp_itoa10( value, JWC(tmpbuf) ); - jwArr_raw( JWC_PARAM JWC(tmpbuf) ); + modp_itoa10( value, JWC(tmpbuf) ); + jwArr_raw( JWC_PARAM JWC(tmpbuf) ); } void jwArr_double( JWC_DECL double value ) { - modp_dtoa2( value, JWC(tmpbuf), 6 ); - jwArr_raw( JWC_PARAM JWC(tmpbuf) ); + modp_dtoa2( value, JWC(tmpbuf), 6 ); + jwArr_raw( JWC_PARAM JWC(tmpbuf) ); } void jwArr_bool( JWC_DECL int oneOrZero ) { - jwArr_raw( JWC_PARAM (oneOrZero) ? "true" : "false" ); + char trueKey[8] = "true"; + char falseKey[8] = "false"; + jwArr_raw( JWC_PARAM (oneOrZero) ? trueKey : falseKey ); } void jwArr_null( JWC_DECL0 ) { - jwArr_raw( JWC_PARAM "null" ); + char nullKey[8] = "null"; + jwArr_raw( JWC_PARAM nullKey ); } void jwArr_object( JWC_DECL0 ) { - if(_jwArr( JWC_PARAM0 ) == JWRITE_OK) - { - jwPutch( JWC_PARAM '{' ); - jwPush( JWC_PARAM JW_OBJECT ); - } + if(_jwArr( JWC_PARAM0 ) == JWRITE_OK) + { + jwPutch( JWC_PARAM '{' ); + jwPush( JWC_PARAM JW_OBJECT ); + } } void jwArr_array( JWC_DECL0 ) { - if(_jwArr( JWC_PARAM0 ) == JWRITE_OK) - { - jwPutch( JWC_PARAM '[' ); - jwPush( JWC_PARAM JW_ARRAY ); - } + if(_jwArr( JWC_PARAM0 ) == JWRITE_OK) + { + jwPutch( JWC_PARAM '[' ); + jwPush( JWC_PARAM JW_ARRAY ); + } } @@ -258,17 +266,17 @@ void jwArr_array( JWC_DECL0 ) // char *jwErrorToString( int err ) { - switch( err ) - { - case JWRITE_OK: return "OK"; - case JWRITE_BUF_FULL: return "output buffer full"; - case JWRITE_NOT_ARRAY: return "tried to write Array value into Object"; - case JWRITE_NOT_OBJECT: return "tried to write Object key/value into Array"; - case JWRITE_STACK_FULL: return "array/object nesting > JWRITE_STACK_DEPTH"; - case JWRITE_STACK_EMPTY:return "stack underflow error (too many 'end's)"; - case JWRITE_NEST_ERROR: return "nesting error, not all objects closed when jwClose() called"; - } - return "Unknown error"; + switch( err ) + { + case JWRITE_OK: return "OK"; + case JWRITE_BUF_FULL: return "output buffer full"; + case JWRITE_NOT_ARRAY: return "tried to write Array value into Object"; + case JWRITE_NOT_OBJECT: return "tried to write Object key/value into Array"; + case JWRITE_STACK_FULL: return "array/object nesting > JWRITE_STACK_DEPTH"; + case JWRITE_STACK_EMPTY: return "stack underflow error (too many 'end's)"; + case JWRITE_NEST_ERROR: return "nesting error, not all objects closed when jwClose() called"; + } + return "Unknown error"; } //============================================================================ @@ -276,64 +284,64 @@ char *jwErrorToString( int err ) // void jwPretty( JWC_DECL0 ) { - int i; - if( JWC(isPretty) ) - { - jwPutch( JWC_PARAM '\n' ); - for( i=0; i= JWRITE_STACK_DEPTH ) - JWC(error)= JWRITE_STACK_FULL; // array/object nesting > JWRITE_STACK_DEPTH - else - { - JWC(nodeStack[++JWC(stackpos)]).nodeType= nodeType; - JWC(nodeStack[JWC(stackpos)]).elementNo= 0; - } + if( (JWC(stackpos)+1) >= JWRITE_STACK_DEPTH ) + JWC(error)= JWRITE_STACK_FULL; // array/object nesting > JWRITE_STACK_DEPTH + else + { + JWC(nodeStack[++JWC(stackpos)]).nodeType= nodeType; + JWC(nodeStack[JWC(stackpos)]).elementNo= 0; + } } enum jwNodeType jwPop( JWC_DECL0 ) { - enum jwNodeType retval= JWC(nodeStack[JWC(stackpos)]).nodeType; - if( JWC(stackpos) == 0 ) - JWC(error)= JWRITE_STACK_EMPTY; // stack underflow error (too many 'end's) - else - JWC(stackpos)--; - return retval; + enum jwNodeType retval= JWC(nodeStack[JWC(stackpos)]).nodeType; + if( JWC(stackpos) == 0 ) + JWC(error)= JWRITE_STACK_EMPTY; // stack underflow error (too many 'end's) + else + JWC(stackpos)--; + return retval; } void jwPutch( JWC_DECL char c ) { - if( (unsigned int)(JWC(bufp) - JWC(buffer)) >= JWC(buflen) ) - { - JWC(error)= JWRITE_BUF_FULL; - }else{ - *JWC(bufp)++ = c; - } + if( (unsigned int)(JWC(bufp) - JWC(buffer)) >= JWC(buflen) ) + { + JWC(error)= JWRITE_BUF_FULL; + }else{ + *JWC(bufp)++ = c; + } } // put string enclosed in quotes // void jwPutstr( JWC_DECL char *str ) { - jwPutch( JWC_PARAM '\"' ); - while( *str != '\0' ) - jwPutch( JWC_PARAM *str++ ); - jwPutch( JWC_PARAM '\"' ); + jwPutch( JWC_PARAM '\"' ); + while( *str != '\0' ) + jwPutch( JWC_PARAM *str++ ); + jwPutch( JWC_PARAM '\"' ); } // put raw string // void jwPutraw( JWC_DECL char *str ) { - while( *str != '\0' ) - jwPutch( JWC_PARAM *str++ ); + while( *str != '\0' ) + jwPutch( JWC_PARAM *str++ ); } @@ -345,20 +353,20 @@ void jwPutraw( JWC_DECL char *str ) // int _jwObj( JWC_DECL char *key ) { - if(JWC(error) == JWRITE_OK) - { - JWC(callNo)++; - if( JWC(nodeStack)[JWC(stackpos)].nodeType != JW_OBJECT ) - JWC(error)= JWRITE_NOT_OBJECT; // tried to write Object key/value into Array - else if( JWC(nodeStack)[JWC(stackpos)].elementNo++ > 0 ) - jwPutch( JWC_PARAM ',' ); - jwPretty( JWC_PARAM0 ); - jwPutstr( JWC_PARAM key ); - jwPutch( JWC_PARAM ':' ); - if( JWC(isPretty) ) - jwPutch( JWC_PARAM ' ' ); - } - return JWC(error); + if(JWC(error) == JWRITE_OK) + { + JWC(callNo)++; + if( JWC(nodeStack)[JWC(stackpos)].nodeType != JW_OBJECT ) + JWC(error)= JWRITE_NOT_OBJECT; // tried to write Object key/value into Array + else if( JWC(nodeStack)[JWC(stackpos)].elementNo++ > 0 ) + jwPutch( JWC_PARAM ',' ); + jwPretty( JWC_PARAM0 ); + jwPutstr( JWC_PARAM key ); + jwPutch( JWC_PARAM ':' ); + if( JWC(isPretty) ) + jwPutch( JWC_PARAM ' ' ); + } + return JWC(error); } // *common Array function* @@ -368,16 +376,16 @@ int _jwObj( JWC_DECL char *key ) // int _jwArr( JWC_DECL0 ) { - if(JWC(error) == JWRITE_OK) - { - JWC(callNo)++; - if( JWC(nodeStack)[JWC(stackpos)].nodeType != JW_ARRAY ) - JWC(error)= JWRITE_NOT_ARRAY; // tried to write array value into Object - else if( JWC(nodeStack)[JWC(stackpos)].elementNo++ > 0 ) - jwPutch( JWC_PARAM ',' ); - jwPretty( JWC_PARAM0 ); - } - return JWC(error); + if(JWC(error) == JWRITE_OK) + { + JWC(callNo)++; + if( JWC(nodeStack)[JWC(stackpos)].nodeType != JW_ARRAY ) + JWC(error)= JWRITE_NOT_ARRAY; // tried to write array value into Object + else if( JWC(nodeStack)[JWC(stackpos)].elementNo++ > 0 ) + jwPutch( JWC_PARAM ',' ); + jwPretty( JWC_PARAM0 ); + } + return JWC(error); } //================================================================= @@ -452,10 +460,10 @@ void modp_dtoa2(double value, char* str, int prec) int count; double diff = 0.0; char* wstr = str; - int neg= 0; - int whole; - double tmp; - uint32_t frac; + int neg= 0; + int whole; + double tmp; + uint32_t frac; /* Hacky test for NaN * under -fast-math this won't work, but then you also won't diff --git a/src/jWrite.h b/src/jWrite.h index 25971bcf1c4..8bd5b2ba808 100755 --- a/src/jWrite.h +++ b/src/jWrite.h @@ -8,34 +8,34 @@ // to provide some error trapping to ensure that the result is valid JSON. // // Example: -// jwOpen( buffer, buflen, JW_OBJECT, JW_PRETTY ); // open root node as object -// jwObj_string( "key", "value" ); -// jwObj_int( "int", 1 ); -// jwObj_array( "anArray"); -// jwArr_int( 0 ); -// jwArr_int( 1 ); -// jwArr_int( 2 ); -// jwEnd(); -// err= jwClose(); // close root object +// jwOpen( buffer, buflen, JW_OBJECT, JW_PRETTY ); // open root node as object +// jwObj_string( "key", "value" ); +// jwObj_int( "int", 1 ); +// jwObj_array( "anArray"); +// jwArr_int( 0 ); +// jwArr_int( 1 ); +// jwArr_int( 2 ); +// jwEnd(); +// err= jwClose(); // close root object // // results in: // -// { -// "key": "value", -// "int": 1, -// "anArray": [ -// 0, -// 1, -// 2 -// ] -// } +// { +// "key": "value", +// "int": 1, +// "anArray": [ +// 0, +// 1, +// 2 +// ] +// } // // Note that jWrite handles string quoting and getting commas in the right place. // If the sequence of calls is incorrect // e.g. -// jwOpen( buffer, buflen, JW_OBJECT, 1 ); -// jwObj_string( "key", "value" ); -// jwArr_int( 0 ); +// jwOpen( buffer, buflen, JW_OBJECT, 1 ); +// jwObj_string( "key", "value" ); +// jwArr_int( 0 ); // ... // // then the error code returned from jwClose() would indicate that you attempted to @@ -62,61 +62,62 @@ // // All the jWrite functions then take an additional parameter: a ptr to the structure // e.g. -// struct jWriteControl jwc; -// -// jwOpen( &jwc, buffer, buflen, JW_OBJECT, 1 ); -// jwObj_string( &jwc, "key", "value" ); -// jwObj_int( &jwc, "int", 1 ); -// jwObj_array( &jwc, "anArray"); -// jwArr_int( &jwc, 0 ); -// jwArr_int( &jwc, 1 ); -// jwArr_int( &jwc, 2 ); -// jwEnd( &jwc ); -// err= jwClose( &jwc ); +// struct jWriteControl jwc; +// +// jwOpen( &jwc, buffer, buflen, JW_OBJECT, 1 ); +// jwObj_string( &jwc, "key", "value" ); +// jwObj_int( &jwc, "int", 1 ); +// jwObj_array( &jwc, "anArray"); +// jwArr_int( &jwc, 0 ); +// jwArr_int( &jwc, 1 ); +// jwArr_int( &jwc, 2 ); +// jwEnd( &jwc ); +// err= jwClose( &jwc ); // // - which is more flexible, but a pain to type in ! // // TonyWilk, Mar 2015 +// source: https://www.codeproject.com/Articles/887604/jWrite-a-really-simple-JSON-writer-in-C +// license: https://www.codeproject.com/info/cpol10.aspx // -// -//#define JW_GLOBAL_CONTROL_STRUCT // <--- comment this out to use applic-supplied jWriteControl +// #define JW_GLOBAL_CONTROL_STRUCT // <--- comment this out to use applic-supplied jWriteControl -#define JWRITE_STACK_DEPTH 32 // max nesting depth of objects/arrays +#define JWRITE_STACK_DEPTH 32 // max nesting depth of objects/arrays -#define JW_COMPACT 0 // output string control for jwOpen() -#define JW_PRETTY 1 // pretty adds \n and indentation +#define JW_COMPACT 0 // output string control for jwOpen() +#define JW_PRETTY 1 // pretty adds \n and indentation enum jwNodeType{ - JW_OBJECT= 1, - JW_ARRAY + JW_OBJECT= 1, + JW_ARRAY }; struct jwNodeStack{ - enum jwNodeType nodeType; - int elementNo; + enum jwNodeType nodeType; + int elementNo; }; struct jWriteControl{ - char *buffer; // pointer to application's buffer - unsigned int buflen; // length of buffer - char *bufp; // current write position in buffer - char tmpbuf[32]; // local buffer for int/double convertions - int error; // error code - int callNo; // API call on which error occurred - struct jwNodeStack nodeStack[JWRITE_STACK_DEPTH]; // stack of array/object nodes - int stackpos; - int isPretty; // 1= pretty output (inserts \n and spaces) + char *buffer; // pointer to application's buffer + unsigned int buflen; // length of buffer + char *bufp; // current write position in buffer + char tmpbuf[32]; // local buffer for int/double convertions + int error; // error code + int callNo; // API call on which error occurred + struct jwNodeStack nodeStack[JWRITE_STACK_DEPTH]; // stack of array/object nodes + int stackpos; + int isPretty; // 1= pretty output (inserts \n and spaces) }; // Error Codes // ----------- #define JWRITE_OK 0 -#define JWRITE_BUF_FULL 1 // output buffer full -#define JWRITE_NOT_ARRAY 2 // tried to write Array value into Object -#define JWRITE_NOT_OBJECT 3 // tried to write Object key/value into Array -#define JWRITE_STACK_FULL 4 // array/object nesting > JWRITE_STACK_DEPTH -#define JWRITE_STACK_EMPTY 5 // stack underflow error (too many 'end's) -#define JWRITE_NEST_ERROR 6 // nesting error, not all objects closed when jwClose() called +#define JWRITE_BUF_FULL 1 // output buffer full +#define JWRITE_NOT_ARRAY 2 // tried to write Array value into Object +#define JWRITE_NOT_OBJECT 3 // tried to write Object key/value into Array +#define JWRITE_STACK_FULL 4 // array/object nesting > JWRITE_STACK_DEPTH +#define JWRITE_STACK_EMPTY 5 // stack underflow error (too many 'end's) +#define JWRITE_NEST_ERROR 6 // nesting error, not all objects closed when jwClose() called // API functions @@ -127,7 +128,7 @@ struct jWriteControl{ char *jwErrorToString( int err ); -#ifdef JW_GLOBAL_CONTROL_STRUCT /* USING GLOBAL g_jWriteControl */ +#ifdef JW_GLOBAL_CONTROL_STRUCT /* USING GLOBAL g_jWriteControl */ // jwOpen // - initialises jWrite with the application supplied 'buffer' of length 'buflen'