From 2016fd6f5c3d0576722ff49e1aa34fdb69eaf8a9 Mon Sep 17 00:00:00 2001 From: Leon Styhre <leon@leonstyhre.com> Date: Sat, 5 Aug 2023 10:49:38 +0200 Subject: [PATCH] Squashed 'external/CImg/' changes from 4d061dcd6..049267da1 049267da1 . d974f83e1 . 143c6f781 . 8b641fa30 . 79ab52e6f . 593667dcf Auto-commit for release 3.2.6_pre a5305a976 . 6fa06681b . cdc9ee87a . 5f40398d1 Start work on next version 3.2.6. 78d1f0d7d Final release 3.2.5 620e4155c . be214a006 . 1f9896c3a Add support for 'id#ind' in math parser. 44800102a Add support for 'id#ind' in math parser. a6657d3d1 Auto-commit for release 3.2.5_pre 70e9a2cc3 . a77287fe0 . 05fd7a869 . 81c22bb46 . 1f14f5eff . bd1228f0f . 19e44e564 . 3f19980fc Start work on v.3.2.5. 504f5c653 . 26862961d Fix line width. 860b3747d Optimize cimg_math_parser::mp_vector_norm() by avoiding copies. 44eb192e9 . c8d8710ee . 1cf8f5c27 . 7b6ec0809 . 99871e252 Simplify CImg<T>::draw_polygon() and CImg<T>::draw_line(). 30ec22fde . a2c50471e . d2f63a9f8 Fix possible issue in 'CImg::draw_polygon()' (better fix). e0675260c Fix possible issue in 'CImg::draw_polygon()'. bb9980723 . c431612a1 . 3606c5f5b Auto-commit for release 3.2.4_pre d5d63e21f . acea91b06 . 9a3c772fa . ccb519bc0 . 9bf62d16b . 0936bc4b7 math evaluator: Add function 'unitnorm(V,p). 5236bd690 . 95ff538e1 . 3285740e9 . 7d01d9b70 Start work on 3.2.4 c7868e298 . 903cc805d . f03139316 math_parser(): Add function 'c2o()' (coordinates to offset). ff49ec20f math_parser(): Add function 'o2c()' (offset to coordinates). 6cbef2c1d Start working on version 3.2.3 ba9bb02a8 Final release 3.2.2 998d0157c . 1bb2d4f00 . 722646c42 Attempt to fix compilation on Windows. 2f3c64511 . 113a16334 . e67bdf5d9 . 31fe9269c . 64839222d Better support of 'CImg<unsigned long long>' and 'CImg<long long>'. 32b3bb6ff Add specific cimg::type() traits for 'unsigned long long' and 'long long'. 806ef2861 Start work on 3.2.2 b558be730 Final release 3.2.1 85aa2ac2e . bcafdc4d9 . a6961a5bd . 33b78048e . 3c063517e . 9c76c1842 . 87863667a . d60290f8d . 3b75fa436 . f1815c88a . 6f575e662 . 5d6be92b4 Allow dynamic arrays to have larger size (32bits integers, i.e 2G elements). c7d1772b8 . 977a1bb81 . 2cf046326 . 54d2b753c . f4a0f772d . d11a0380e . 4828190aa math_parser: Check that specified '#index' of functions are not NaN. 3b71fa0ce Function 'v2s()': Add option to 0-pad integer values. 652e6604b . ea3197ce4 Fix #374 c88e03819 math_parser: Add function that computes the square root of complex numbers. b58775209 Add detection of the not operator in fast pre-evaluation of math expressions. 5e3585553 Add detection of the not operator in fast pre-evaluation of math expressions. 209c38da9 . 6ba3d0a11 Fix #319. 4e9412e8d Start work on next version 3.2.1 57028920e . 9772e0204 . 446c39411 Math evaluator: Add new function 'abort()'. 3c2ecee1f Make fast string comparisons more robust when expressions contain blank characters. af9ecf8d0 Optimize evaluation of string comparisons. edfcda038 . 00cab3cea . a0af5312c . 6a92e46f2 . 7ae52c28d cimg_math_parser(): Add 'gamma()' function. ad516d976 . b71e2db76 . a9633d4ed . 9d8406e57 . 103d23625 . 6349c771f . bef50f9d8 . 391a24013 . d4669fbbc . 5e77a9c7e . 86440098e . 6b9eee37c . 616484469 . e79edfdb9 . e616c2443 . 772fa4dca . git-subtree-dir: external/CImg git-subtree-split: 049267da171c50ab48f93c7285f096d7d686e8f3 --- CImg.h | 1667 ++++++++++++++++++--------- examples/CImg_demo.cpp | 2 +- examples/image_surface3d.cpp | 4 +- html/header.html | 8 +- html/header_doxygen.html | 8 +- html/img/book_cimg_en.jpg | Bin 0 -> 36326 bytes html/index.html | 15 +- resources/compile_win_visualcpp.bat | 2 +- 8 files changed, 1123 insertions(+), 583 deletions(-) create mode 100644 html/img/book_cimg_en.jpg diff --git a/CImg.h b/CImg.h index 9ac9175b0..3d07e7834 100644 --- a/CImg.h +++ b/CImg.h @@ -54,7 +54,7 @@ // Set version number of the library. #ifndef cimg_version -#define cimg_version 320 +#define cimg_version 326 /*----------------------------------------------------------- # @@ -2693,6 +2693,7 @@ namespace cimg_library { static T min() { return ~max(); } static T max() { return (T)1<<(8*sizeof(T) - 1); } static T inf() { return max(); } + static T nan() { return inf(); } static T cut(const double val) { return val<(double)min()?min():val>(double)max()?max():(T)val; } static const char* format() { return "%s"; } static const char* format_s() { return "%s"; } @@ -2711,6 +2712,7 @@ namespace cimg_library { static bool min() { return false; } static bool max() { return true; } static bool inf() { return max(); } + static bool nan() { return inf(); } static bool is_inf() { return false; } static bool cut(const double val) { return val<(double)min()?min():val>(double)max()?max():(bool)val; } static const char* format() { return "%s"; } @@ -2727,6 +2729,7 @@ namespace cimg_library { static unsigned char min() { return 0; } static unsigned char max() { return (unsigned char)-1; } static unsigned char inf() { return max(); } + static unsigned char nan() { return inf(); } static unsigned char cut(const double val) { return val<(double)min()?min():val>(double)max()?max():(unsigned char)val; } static const char* format() { return "%u"; } @@ -2744,6 +2747,7 @@ namespace cimg_library { static char min() { return 0; } static char max() { return (char)-1; } static char inf() { return max(); } + static char nan() { return inf(); } static char cut(const double val) { return val<(double)min()?min():val>(double)max()?max():(unsigned char)val; } static const char* format() { return "%u"; } @@ -2760,6 +2764,7 @@ namespace cimg_library { static char min() { return ~max(); } static char max() { return (char)((unsigned char)-1>>1); } static char inf() { return max(); } + static char nan() { return inf(); } static char cut(const double val) { return val<(double)min()?min():val>(double)max()?max():(char)val; } static const char* format() { return "%d"; } static const char* format_s() { return "%d"; } @@ -2776,6 +2781,7 @@ namespace cimg_library { static signed char min() { return ~max(); } static signed char max() { return (signed char)((unsigned char)-1>>1); } static signed char inf() { return max(); } + static signed char nan() { return inf(); } static signed char cut(const double val) { return val<(double)min()?min():val>(double)max()?max():(signed char)val; } static const char* format() { return "%d"; } @@ -2792,6 +2798,7 @@ namespace cimg_library { static unsigned short min() { return 0; } static unsigned short max() { return (unsigned short)-1; } static unsigned short inf() { return max(); } + static unsigned short nan() { return inf(); } static unsigned short cut(const double val) { return val<(double)min()?min():val>(double)max()?max():(unsigned short)val; } static const char* format() { return "%u"; } @@ -2808,6 +2815,7 @@ namespace cimg_library { static short min() { return ~max(); } static short max() { return (short)((unsigned short)-1>>1); } static short inf() { return max(); } + static short nan() { return inf(); } static short cut(const double val) { return val<(double)min()?min():val>(double)max()?max():(short)val; } static const char* format() { return "%d"; } static const char* format_s() { return "%d"; } @@ -2823,6 +2831,7 @@ namespace cimg_library { static unsigned int min() { return 0; } static unsigned int max() { return (unsigned int)-1; } static unsigned int inf() { return max(); } + static unsigned int nan() { return inf(); } static unsigned int cut(const double val) { return val<(double)min()?min():val>(double)max()?max():(unsigned int)val; } static const char* format() { return "%u"; } @@ -2839,6 +2848,7 @@ namespace cimg_library { static int min() { return ~max(); } static int max() { return (int)(~0U>>1); } static int inf() { return max(); } + static int nan() { return inf(); } static int cut(const double val) { return val<(double)min()?min():val>(double)max()?max():(int)val; } static const char* format() { return "%d"; } static const char* format_s() { return "%d"; } @@ -2854,6 +2864,7 @@ namespace cimg_library { static cimg_uint64 min() { return 0; } static cimg_uint64 max() { return (cimg_uint64)-1; } static cimg_uint64 inf() { return max(); } + static cimg_uint64 nan() { return inf(); } static cimg_uint64 cut(const double val) { return val<(double)min()?min():val>(double)max()?max():(cimg_uint64)val; } static const char* format() { return cimg_fuint64; } @@ -2870,6 +2881,7 @@ namespace cimg_library { static cimg_int64 min() { return ~max(); } static cimg_int64 max() { return (cimg_int64)((cimg_uint64)-1>>1); } static cimg_int64 inf() { return max(); } + static cimg_int64 nan() { return inf(); } static cimg_int64 cut(const double val) { return val<(double)min()?min():val>(double)max()?max():(cimg_int64)val; } @@ -2878,6 +2890,43 @@ namespace cimg_library { static long format(const long val) { return (long)val; } }; +#if !(UINTPTR_MAX==0xffffffff || defined(__arm__) || defined(_M_ARM) || ((ULONG_MAX)==(UINT_MAX))) + template<> struct type<unsigned long long> { + static const char* string() { static const char *const s = "uint64"; return s; } + static bool is_float() { return false; } + static bool is_inf(const cimg_uint64) { return false; } + static bool is_nan(const cimg_uint64) { return false; } + static bool is_finite(const cimg_uint64) { return true; } + static cimg_uint64 min() { return 0; } + static cimg_uint64 max() { return (cimg_uint64)-1; } + static cimg_uint64 inf() { return max(); } + static cimg_uint64 nan() { return inf(); } + static cimg_uint64 cut(const double val) { + return val<(double)min()?min():val>(double)max()?max():(cimg_uint64)val; } + static const char* format() { return cimg_fuint64; } + static const char* format_s() { return cimg_fuint64; } + static cimg_uint64 format(const cimg_uint64 val) { return val; } + }; + + template<> struct type<long long> { + static const char* string() { static const char *const s = "int64"; return s; } + static bool is_float() { return false; } + static bool is_inf(const cimg_int64) { return false; } + static bool is_nan(const cimg_int64) { return false; } + static bool is_finite(const cimg_int64) { return true; } + static long long min() { return ~max(); } + static long long max() { return (cimg_int64)((cimg_uint64)-1>>1); } + static long long inf() { return max(); } + static long long nan() { return max(); } + static long long cut(const double val) { + return val<(double)min()?min():val>(double)max()?max():(cimg_int64)val; + } + static const char* format() { return cimg_fint64; } + static const char* format_s() { return cimg_fint64; } + static long format(const long val) { return (long)val; } + }; +#endif + template<> struct type<double> { static const char* string() { static const char *const s = "float64"; return s; } static bool is_float() { return true; } @@ -3120,6 +3169,17 @@ namespace cimg_library { template<> struct superset<cimg_uint64,double> { typedef double type; }; template<> struct superset<cimg_int64,float> { typedef double type; }; template<> struct superset<cimg_int64,double> { typedef double type; }; +#if !(UINTPTR_MAX==0xffffffff || defined(__arm__) || defined(_M_ARM) || ((ULONG_MAX)==(UINT_MAX))) + template<> struct superset<unsigned long long,char> { typedef cimg_int64 type; }; + template<> struct superset<unsigned long long,signed char> { typedef cimg_int64 type; }; + template<> struct superset<unsigned long long,short> { typedef cimg_int64 type; }; + template<> struct superset<unsigned long long,int> { typedef cimg_int64 type; }; + template<> struct superset<unsigned long long,cimg_int64> { typedef cimg_int64 type; }; + template<> struct superset<unsigned long long,float> { typedef double type; }; + template<> struct superset<unsigned long long,double> { typedef double type; }; + template<> struct superset<long long,float> { typedef double type; }; + template<> struct superset<long long,double> { typedef double type; }; +#endif template<> struct superset<float,cimg_uint64> { typedef double type; }; template<> struct superset<float,cimg_int64> { typedef double type; }; template<> struct superset<float,double> { typedef double type; }; @@ -5883,21 +5943,21 @@ namespace cimg_library { return a; } - // Conversion functions to get more precision when trying to store unsigned ints values as floats. - inline unsigned int float2uint(const float f) { + // Conversion functions to get more precision when trying to store 'unsigned int' values as 'float'. + inline unsigned int float2uint(const float value) { int tmp = 0; - std::memcpy(&tmp,&f,sizeof(float)); - if (tmp>=0) return (unsigned int)f; + std::memcpy(&tmp,&value,sizeof(float)); + if (tmp>=0) return (unsigned int)value; unsigned int u; // use memcpy instead of assignment to avoid undesired optimizations by C++-compiler. - std::memcpy(&u,&f,sizeof(float)); + std::memcpy(&u,&value,sizeof(float)); return ((u)<<2)>>2; // set sign & exponent bit to 0 } - inline float uint2float(const unsigned int u) { - if (u<(1U<<19)) return (float)u; // Consider safe storage of unsigned int as floats until 19bits (i.e 524287) + inline float uint2float(const unsigned int value) { + if (value<(1U<<19)) return (float)value; // Consider 'uint32' safely stored as floats until 19bits (i.e 524287) float f; - const unsigned int v = u|(3U<<(8*sizeof(unsigned int)-2)); // set sign & exponent bit to 1 + const unsigned int v = value | (3U<<(8*sizeof(unsigned int)-2)); // set sign & exponent bit to 1 // use memcpy instead of simple assignment to avoid undesired optimizations by C++-compiler. std::memcpy(&f,&v,sizeof(float)); return f; @@ -6005,7 +6065,7 @@ namespace cimg_library { } inline void srand(cimg_uint64 *const p_rng) { -#if cimg_OS==1 +#if cimg_OS==1 || defined(__BORLANDC__) *p_rng = cimg::time() + (cimg_uint64)getpid(); #elif cimg_OS==2 *p_rng = cimg::time() + (cimg_uint64)_getpid(); @@ -6312,7 +6372,10 @@ namespace cimg_library { **/ template<typename T> inline T mod(const T& x, const T& m) { - if (!m) throw CImgArgumentException("cimg::mod(): Specified modulo value is 0."); + if (!m) { + if (cimg::type<T>::is_float()) return cimg::type<T>::nan(); + else throw CImgArgumentException("cimg::mod(): Specified modulo value is 0."); + } const double dx = (double)x, dm = (double)m; if (!cimg::type<double>::is_finite(dm)) return x; if (cimg::type<double>::is_finite(dx)) return (T)(dx - dm * std::floor(dx / dm)); @@ -6937,7 +7000,7 @@ namespace cimg_library { //! Ellipsize a string. /** \param str C-string. - \param l Max number of characters. + \param l Max number of printed characters. \param is_ending Tell if the dots are placed at the end or at the center of the ellipsized string. **/ inline char *strellipsize(char *const str, const unsigned int l=64, @@ -6959,7 +7022,7 @@ namespace cimg_library { /** \param str C-string. \param res output C-string. - \param l Max number of characters. + \param l Max number of printed characters. String 'res' must be a size of at least 'l+1'. \param is_ending Tell if the dots are placed at the end or at the center of the ellipsized string. **/ inline char *strellipsize(const char *const str, char *const res, const unsigned int l=64, @@ -13096,7 +13159,7 @@ namespace cimg_library { const unsigned int omode = cimg::exception_mode(); cimg::exception_mode(0); try { - _fill(expression,true,1,0,0,"operator=",0); + _fill(expression,true,3,0,0,"operator=",0,0); } catch (CImgException&) { cimg::exception_mode(omode); load(expression); @@ -13171,7 +13234,7 @@ namespace cimg_library { instead of assigning them. **/ CImg<T>& operator+=(const char *const expression) { - return *this+=(+*this)._fill(expression,true,1,0,0,"operator+=",this); + return *this+=(+*this)._fill(expression,true,3,0,0,"operator+=",this,0); } //! In-place addition operator. @@ -13292,7 +13355,7 @@ namespace cimg_library { Similar to operator+=(const char*), except that it performs a subtraction instead of an addition. **/ CImg<T>& operator-=(const char *const expression) { - return *this-=(+*this)._fill(expression,true,1,0,0,"operator-=",this); + return *this-=(+*this)._fill(expression,true,3,0,0,"operator-=",this,0); } //! In-place subtraction operator. @@ -13396,7 +13459,7 @@ namespace cimg_library { Similar to operator+=(const char*), except that it performs a multiplication instead of an addition. **/ CImg<T>& operator*=(const char *const expression) { - return mul((+*this)._fill(expression,true,1,0,0,"operator*=",this)); + return mul((+*this)._fill(expression,true,3,0,0,"operator*=",this,0)); } //! In-place multiplication operator. @@ -13661,7 +13724,7 @@ namespace cimg_library { Similar to operator+=(const char*), except that it performs a division instead of an addition. **/ CImg<T>& operator/=(const char *const expression) { - return div((+*this)._fill(expression,true,1,0,0,"operator/=",this)); + return div((+*this)._fill(expression,true,3,0,0,"operator/=",this,0)); } //! In-place division operator. @@ -13725,7 +13788,7 @@ namespace cimg_library { Similar to operator+=(const char*), except that it performs a modulo operation instead of an addition. **/ CImg<T>& operator%=(const char *const expression) { - return *this%=(+*this)._fill(expression,true,1,0,0,"operator%=",this); + return *this%=(+*this)._fill(expression,true,3,0,0,"operator%=",this,0); } //! In-place modulo operator. @@ -13791,7 +13854,7 @@ namespace cimg_library { Similar to operator+=(const char*), except that it performs a bitwise AND operation instead of an addition. **/ CImg<T>& operator&=(const char *const expression) { - return *this&=(+*this)._fill(expression,true,1,0,0,"operator&=",this); + return *this&=(+*this)._fill(expression,true,3,0,0,"operator&=",this,0); } //! In-place bitwise AND operator. @@ -13857,7 +13920,7 @@ namespace cimg_library { Similar to operator+=(const char*), except that it performs a bitwise OR operation instead of an addition. **/ CImg<T>& operator|=(const char *const expression) { - return *this|=(+*this)._fill(expression,true,1,0,0,"operator|=",this); + return *this|=(+*this)._fill(expression,true,3,0,0,"operator|=",this,0); } //! In-place bitwise OR operator. @@ -13927,7 +13990,7 @@ namespace cimg_library { - It does \e not compute the \e power of pixel values. For this purpose, use pow(const char*) instead. **/ CImg<T>& operator^=(const char *const expression) { - return *this^=(+*this)._fill(expression,true,1,0,0,"operator^=",this); + return *this^=(+*this)._fill(expression,true,3,0,0,"operator^=",this,0); } //! In-place bitwise XOR operator. @@ -13995,7 +14058,7 @@ namespace cimg_library { Similar to operator+=(const char*), except that it performs a bitwise left shift instead of an addition. **/ CImg<T>& operator<<=(const char *const expression) { - return *this<<=(+*this)._fill(expression,true,1,0,0,"operator<<=",this); + return *this<<=(+*this)._fill(expression,true,3,0,0,"operator<<=",this,0); } //! In-place bitwise left shift operator. @@ -14062,7 +14125,7 @@ namespace cimg_library { Similar to operator+=(const char*), except that it performs a bitwise right shift instead of an addition. **/ CImg<T>& operator>>=(const char *const expression) { - return *this>>=(+*this)._fill(expression,true,1,0,0,"operator>>=",this); + return *this>>=(+*this)._fill(expression,true,3,0,0,"operator>>=",this,0); } //! In-place bitwise right shift operator. @@ -14144,7 +14207,7 @@ namespace cimg_library { \param expression Value string describing the way pixel values are compared. **/ bool operator==(const char *const expression) const { - return *this==(+*this)._fill(expression,true,1,0,0,"operator==",this); + return *this==(+*this)._fill(expression,true,3,0,0,"operator==",this,0); } //! Test if two images have the same size and values. @@ -16788,7 +16851,7 @@ namespace cimg_library { if (*(ptrs++)!=(T)-128) ptrs+=2; else if ((ptrs+=3)<ptre) { const unsigned int - w = (unsigned int)*(ptrs - 3), + w = (unsigned int)cimg::float2uint((float)*(ptrs - 3)), h = (unsigned int)*(ptrs - 2), s = (unsigned int)*(ptrs - 1); if (!h && !s) { @@ -16820,7 +16883,7 @@ namespace cimg_library { for (unsigned int o = 0; o<nb_primitives; ++o) { if (*(ptrs++)==(T)-128 && (ptrs+=3)<ptre) { const unsigned int - w = (unsigned int)*(ptrs - 3), + w = (unsigned int)cimg::float2uint((float)*(ptrs - 3)), h = (unsigned int)*(ptrs - 2), s = (unsigned int)*(ptrs - 1); if (!h && !s) { @@ -16885,10 +16948,10 @@ namespace cimg_library { CImgList<charT> variable_def, macro_def, macro_body; char *user_macro; - unsigned int mempos, mem_img_median, mem_img_norm, mem_img_index, debug_indent, result_dim, break_type, - constcache_size; + unsigned int mempos, mem_img_median, mem_img_norm, mem_img_index, debug_indent, + result_dim, result_end_dim, break_type, constcache_size; bool is_parallelizable, is_noncritical_run, is_end_code, is_fill, return_new_comp, need_input_copy; - double *result; + double *result, *result_end; cimg_uint64 rng; const char *const calling_function, *s_op, *ss_op; typedef double (*mp_func)(_cimg_math_parser&); @@ -16901,11 +16964,13 @@ namespace cimg_library { #define _cimg_mp_size(arg) (_cimg_mp_is_scalar(arg)?0U:(unsigned int)memtype[arg] - 1) // Size (0=scalar, N>0=vectorN) #define _cimg_mp_calling_function s_calling_function()._data #define _cimg_mp_op(s) s_op = s; ss_op = ss -#define _cimg_mp_check_type(arg,n_arg,mode,N) check_type(arg,n_arg,mode,N,ss,se,saved_char) #define _cimg_mp_check_const_scalar(arg,n_arg,mode) check_const_scalar(arg,n_arg,mode,ss,se,saved_char) #define _cimg_mp_check_const_index(arg) check_const_index(arg,ss,se,saved_char) -#define _cimg_mp_check_matrix_square(arg,n_arg) check_matrix_square(arg,n_arg,ss,se,saved_char) +#define _cimg_mp_check_notnan_index(arg) check_notnan_index(arg,ss,se,saved_char) #define _cimg_mp_check_list() check_list(ss,se,saved_char) +#define _cimg_mp_check_matrix_square(arg,n_arg) check_matrix_square(arg,n_arg,ss,se,saved_char) +#define _cimg_mp_check_type(arg,n_arg,mode,N) check_type(arg,n_arg,mode,N,ss,se,saved_char) + #define _cimg_mp_defunc(mp) (*(mp_func)(*(mp).opcode))(mp) #define _cimg_mp_return(x) { *se = saved_char; s_op = previous_s_op; ss_op = previous_ss_op; return x; } #define _cimg_mp_return_nan() _cimg_mp_return(_cimg_mp_slot_nan) @@ -16945,9 +17010,10 @@ namespace cimg_library { p_break((CImg<ulongT>*)(cimg_ulong)-2),imgin(img_input), imgout(img_output?*img_output:CImg<T>::empty()),imglist(list_images?*list_images:CImgList<T>::empty()), img_stats(_img_stats),list_stats(_list_stats),list_median(_list_median),list_norm(_list_norm),user_macro(0), - mem_img_median(~0U),mem_img_norm(~0U),mem_img_index(~0U),debug_indent(0),result_dim(0),break_type(0), - constcache_size(0),is_parallelizable(true),is_noncritical_run(false),is_fill(_is_fill),need_input_copy(false), - rng((cimg::_rand(),cimg::rng())),calling_function(funcname?funcname:"cimg_math_parser") { + mem_img_median(~0U),mem_img_norm(~0U),mem_img_index(~0U),debug_indent(0),result_dim(0),result_end_dim(0), + break_type(0),constcache_size(0),is_parallelizable(true),is_noncritical_run(false),is_fill(_is_fill), + need_input_copy(false),result_end(0),rng((cimg::_rand(),cimg::rng())), + calling_function(funcname?funcname:"cimg_math_parser") { #if cimg_use_openmp!=0 rng+=omp_get_thread_num(); @@ -17026,11 +17092,11 @@ namespace cimg_library { fill(cimg::type<double>::nan()); else if (ind_result!=_cimg_mp_slot_t) mem[ind_result] = cimg::type<double>::nan(); } + if (mem._width>=256 && mem._width - mempos>=mem._width/2) mem.resize(mempos,1,1,1,-1); + result_dim = _cimg_mp_size(ind_result); + result = mem._data + ind_result; // Free resources used for compiling expression and prepare evaluation. - result_dim = _cimg_mp_size(ind_result); - if (mem._width>=256 && mem._width - mempos>=mem._width/2) mem.resize(mempos,1,1,1,-1); - result = mem._data + ind_result; memtype.assign(); constcache_vals.assign(); constcache_inds.assign(); @@ -17060,8 +17126,8 @@ namespace cimg_library { p_code_end(0),p_break((CImg<ulongT>*)(cimg_ulong)-2), imgin(CImg<T>::const_empty()),imgout(CImg<T>::empty()),imglist(CImgList<T>::empty()), img_stats(_img_stats),list_stats(_list_stats),list_median(_list_median),list_norm(_list_norm),debug_indent(0), - result_dim(0),break_type(0),constcache_size(0),is_parallelizable(true),is_noncritical_run(false),is_fill(false), - need_input_copy(false),rng(0),calling_function(0) { + result_dim(0),result_end_dim(0),break_type(0),constcache_size(0),is_parallelizable(true), + is_noncritical_run(false),is_fill(false),need_input_copy(false),result_end(0),rng(0),calling_function(0) { mem.assign(1 + _cimg_mp_slot_c,1,1,1,0); // Allow to skip 'is_empty?' test in operator()() result = mem._data; } @@ -17071,9 +17137,11 @@ namespace cimg_library { p_code_end(mp.p_code_end),p_break(mp.p_break), imgin(mp.imgin),imgout(mp.imgout),imglist(mp.imglist), img_stats(mp.img_stats),list_stats(mp.list_stats),list_median(mp.list_median),list_norm(mp.list_norm), - debug_indent(0),result_dim(mp.result_dim),break_type(0),constcache_size(0), + debug_indent(0),result_dim(mp.result_dim),result_end_dim(mp.result_end_dim),break_type(0),constcache_size(0), is_parallelizable(mp.is_parallelizable),is_noncritical_run(mp.is_noncritical_run),is_fill(mp.is_fill), - need_input_copy(mp.need_input_copy),result(mem._data + (mp.result - mp.mem._data)), + need_input_copy(mp.need_input_copy), + result(mem._data + (mp.result - mp.mem._data)), + result_end(mp.result_end?mem._data + (mp.result_end - mp.mem._data):0), rng((cimg::_rand(),cimg::rng())),calling_function(0) { #if cimg_use_openmp!=0 @@ -17261,22 +17329,23 @@ namespace cimg_library { _cimg_mp_scalar6(mp_ixyzc,_cimg_mp_slot_x,_cimg_mp_slot_y,_cimg_mp_slot_z,pos - 21,0,0); } switch (*ss1) { - case 'm' : arg1 = 4; arg2 = 0; break; // im - case 'M' : arg1 = 5; arg2 = 1; break; // iM case 'a' : arg1 = 6; arg2 = 2; break; // ia - case 'v' : arg1 = 7; arg2 = 3; break; // iv - case 'd' : arg1 = 8; arg2 = 3; break; // id - case 's' : arg1 = 9; arg2 = 12; break; // is - case 'p' : arg1 = 10; arg2 = 13; break; // ip case 'c' : // ic if (reserved_label[11]!=~0U) _cimg_mp_return(reserved_label[11]); if (mem_img_median==~0U) mem_img_median = imgin?const_scalar(imgin.median()):0; _cimg_mp_return(mem_img_median); break; + case 'd' : arg1 = 8; arg2 = 3; break; // id + case 'm' : arg1 = 4; arg2 = 0; break; // im + case 'M' : arg1 = 5; arg2 = 1; break; // iM case 'n' : // in if (reserved_label[12]!=~0U) _cimg_mp_return(reserved_label[12]); - if (mem_img_norm==~0U) mem_img_norm = imgin?const_scalar(imgin.magnitude()):0; + if (mem_img_norm==~0U) mem_img_norm = imgin?const_scalar(imgin.magnitude(2)):0; _cimg_mp_return(mem_img_norm); + break; + case 'p' : arg1 = 10; arg2 = 13; break; // ip + case 's' : arg1 = 9; arg2 = 12; break; // is + case 'v' : arg1 = 7; arg2 = 3; break; // iv } } else if (*ss1=='m') switch (*ss) { @@ -17350,6 +17419,7 @@ namespace cimg_library { if (*ss2=='#') { // Index specified s0 = ss3; while (s0<ve1 && (*s0!=',' || level[s0 - expr._data]!=clevel1)) ++s0; p1 = compile(ss3,s0++,depth1,0,block_flags); + _cimg_mp_check_notnan_index(p1); _cimg_mp_check_list(); } else { p1 = ~0U; s0 = ss2; } arg1 = compile(s0,ve1,depth1,0,block_flags); // Offset @@ -17408,6 +17478,7 @@ namespace cimg_library { if (*ss2=='#') { // Index specified s0 = ss3; while (s0<ve1 && (*s0!=',' || level[s0 - expr._data]!=clevel1)) ++s0; p1 = compile(ss3,s0++,depth1,0,block_flags); + _cimg_mp_check_notnan_index(p1); _cimg_mp_check_list(); } else { p1 = ~0U; s0 = ss2; } arg1 = is_relative?0U:(unsigned int)_cimg_mp_slot_x; @@ -18757,6 +18828,7 @@ namespace cimg_library { s0 = ss3; while (s0<se1 && (*s0!=',' || level[s0 - expr._data]!=clevel1)) ++s0; p1 = compile(ss3,s0++,depth1,0,block_flags); _cimg_mp_check_const_index(p1); + _cimg_mp_check_notnan_index(p1); _cimg_mp_check_list(); } else { p1 = ~0U; s0 = ss2; } s1 = s0; while (s1<se1 && (*s1!=',' || level[s1 - expr._data]!=clevel1)) ++s1; @@ -18801,6 +18873,7 @@ namespace cimg_library { if (*ss2=='#') { // Index specified s0 = ss3; while (s0<se1 && (*s0!=',' || level[s0 - expr._data]!=clevel1)) ++s0; p1 = compile(ss3,s0++,depth1,0,block_flags); + _cimg_mp_check_notnan_index(p1); } else { p1 = ~0U; s0 = ss2; } s1 = s0; while (s1<se1 && (*s1!=',' || level[s1 - expr._data]!=clevel1)) ++s1; arg1 = compile(s0,s1,depth1,0,block_flags); // Offset @@ -18895,6 +18968,7 @@ namespace cimg_library { s0 = ss3; while (s0<se1 && (*s0!=',' || level[s0 - expr._data]!=clevel1)) ++s0; p1 = compile(ss3,s0++,depth1,0,block_flags); _cimg_mp_check_const_index(p1); + _cimg_mp_check_notnan_index(p1); _cimg_mp_check_list(); } else { p1 = ~0U; s0 = ss2; } arg1 = is_relative?0U:(unsigned int)_cimg_mp_slot_x; @@ -18971,6 +19045,7 @@ namespace cimg_library { if (*ss2=='#') { // Index specified s0 = ss3; while (s0<se1 && (*s0!=',' || level[s0 - expr._data]!=clevel1)) ++s0; p1 = compile(ss3,s0++,depth1,0,block_flags); + _cimg_mp_check_notnan_index(p1); } else { p1 = ~0U; s0 = ss2; } arg1 = is_relative?0U:(unsigned int)_cimg_mp_slot_x; arg2 = is_relative?0U:(unsigned int)_cimg_mp_slot_y; @@ -19050,6 +19125,17 @@ namespace cimg_library { switch (*ss) { case 'a' : + +#ifdef cimg_mp_func_abort + if (!std::strncmp(ss,"abort(",6)) { // Abort + _cimg_mp_op("Function 'abort()'"); + if (pexpr[se2 - expr._data]=='(') { // no arguments? + CImg<ulongT>::vector((ulongT)mp_abort,_cimg_mp_slot_nan).move_to(code); + _cimg_mp_return_nan(); + } + } +#endif + if (!std::strncmp(ss,"abs(",4)) { // Absolute value _cimg_mp_op("Function 'abs()'"); arg1 = compile(ss4,se1,depth1,0,block_flags); @@ -19219,6 +19305,45 @@ namespace cimg_library { break; case 'c' : + if (!std::strncmp(ss,"c2o(",4)) { // Coordinates to offset + _cimg_mp_op("Function 'c2o()'"); + if (*ss4=='#') { // Index specified + s0 = ss5; while (s0<se1 && (*s0!=',' || level[s0 - expr._data]!=clevel1)) ++s0; + p1 = compile(ss5,s0++,depth1,0,block_flags); + _cimg_mp_check_notnan_index(p1); + _cimg_mp_check_list(); + } else { p1 = ~0U; s0 = ss4; } + s1 = s0; while (s1<se1 && (*s1!=',' || level[s1 - expr._data]!=clevel1)) ++s1; + pos = compile(s0,s1,depth1,0,block_flags); + p2 = _cimg_mp_size(pos); + if (p2) { // Coordinates specified as a vector + if (s1!=se1) compile(s0,se1,depth1,0,block_flags); // -> Error too much arguments + if (p2>4) { + *s1 = 0; s1 = s0; _cimg_mp_strerr; + throw CImgArgumentException("[" cimg_appname "_math_parser] " + "CImg<%s>::%s: %s: Argument '%s' is a vector of size %u (should be <=4), " + "in expression '%s'.", + pixel_type(),_cimg_mp_calling_function,s_op,s1,p2,s0); + } + arg1 = pos + 1; + arg2 = p2>1?pos + 2:0; + arg3 = p2>2?pos + 3:0; + arg4 = p2>3?pos + 4:0; + } else { // Coordinates specified as scalars + arg1 = pos; arg2 = arg3 = arg4 = 0; + if (s1<se1) { + s0 = ++s1; while (s0<se1 && (*s0!=',' || level[s0 - expr._data]!=clevel1)) ++s0; + arg2 = compile(s1,s0,depth1,0,block_flags); + if (s0<se1) { + s1 = ++s0; while (s1<se1 && (*s1!=',' || level[s1 - expr._data]!=clevel1)) ++s1; + arg3 = compile(s0,s1,depth1,0,block_flags); + arg4 = s1<se1?compile(++s1,se1,depth1,0,block_flags):0; + } else arg3 = 0; + } else arg2 = 0; + } + _cimg_mp_scalar5(mp_c2o,p1,arg1,arg2,arg3,arg4); + } + if (!std::strncmp(ss,"cabs(",5)) { // Complex absolute value _cimg_mp_op("Function 'cabs()'"); arg1 = compile(ss5,se1,depth1,0,block_flags); @@ -19306,6 +19431,17 @@ namespace cimg_library { _cimg_mp_return(pos); } + if (!std::strncmp(ss,"csqrt(",6)) { // Complex square root + _cimg_mp_op("Function 'csqrt()'"); + arg1 = compile(ss6,se1,depth1,0,block_flags); + _cimg_mp_check_type(arg1,0,3,2); + pos = vector(2); + if (_cimg_mp_is_scalar(arg1)) CImg<ulongT>::vector((ulongT)mp_complex_sqrt,pos,arg1,0).move_to(code); + else CImg<ulongT>::vector((ulongT)mp_complex_sqrt,pos,arg1 + 1,arg1 + 2).move_to(code); + return_new_comp = true; + _cimg_mp_return(pos); + } + if (!std::strncmp(ss,"ctan(",5)) { // Complex tangent _cimg_mp_op("Function 'ctan()'"); arg1 = compile(ss5,se1,depth1,0,block_flags); @@ -19446,12 +19582,14 @@ namespace cimg_library { if (!std::strncmp(ss,"crop(",5)) { // Image or vector crop _cimg_mp_op("Function 'crop()'"); is_sth = false; // is image crop ? + arg1 = 0; if (*ss5=='#') { // Index specified s0 = ss6; while (s0<se1 && (*s0!=',' || level[s0 - expr._data]!=clevel1)) ++s0; p1 = compile(ss6,s0++,depth1,0,block_flags); + _cimg_mp_check_notnan_index(p1); + _cimg_mp_check_list(); pos = 2; is_sth = true; - _cimg_mp_check_list(); } else { p1 = ~0U; s0 = ss5; need_input_copy = true; pos = 1; } if (s0<se1) for (s = s0; s<se; ++s, ++pos) { ns = s; while (ns<se && (*ns!=',' || level[ns - expr._data]!=clevel1) && @@ -19822,6 +19960,7 @@ namespace cimg_library { _cimg_mp_op("Function 'd()'"); if (*ss2=='#') { // Index specified p1 = compile(ss3,se1,depth1,0,block_flags); + _cimg_mp_check_notnan_index(p1); _cimg_mp_check_list(); } else { if (ss2!=se1) break; p1 = ~0U; } _cimg_mp_scalar1(mp_image_d,p1); @@ -19836,6 +19975,7 @@ namespace cimg_library { if (*s0=='#') { // Index specified s1 = ++s0; while (s1<se1 && (*s1!=',' || level[s1 - expr._data]!=clevel1)) ++s1; p1 = compile(s0,s1++,depth1,0,block_flags); + _cimg_mp_check_notnan_index(p1); } else { p1 = 11; s1 = s0; } _cimg_mp_check_list(); _cimg_mp_check_const_scalar(p1,1,1); @@ -19856,6 +19996,7 @@ namespace cimg_library { if (*s0=='#') { // Index specified s1 = ++s0; while (s1<se1 && (*s1!=',' || level[s1 - expr._data]!=clevel1)) ++s1; p1 = compile(s0,s1++,depth1,0,block_flags); + _cimg_mp_check_notnan_index(p1); } else { p1 = 11; s1 = s0; } _cimg_mp_check_list(); @@ -19896,6 +20037,7 @@ namespace cimg_library { if (*s0=='#') { // Index specified s1 = ++s0; while (s1<se1 && (*s1!=',' || level[s1 - expr._data]!=clevel1)) ++s1; p1 = compile(s0,s1++,depth1,0,block_flags); + _cimg_mp_check_notnan_index(p1); } else { p1 = 11; s1 = s0; } _cimg_mp_check_list(); CImg<ulongT>::vector((ulongT)mp_da_freeze,_cimg_mp_slot_nan,p1).move_to(code); @@ -19908,6 +20050,7 @@ namespace cimg_library { if (ss[10]=='#') { // Index specified s0 = ss + 11; while (s0<se1 && (*s0!=',' || level[s0 - expr._data]!=clevel1)) ++s0; p1 = compile(ss + 11,s0++,depth1,0,block_flags); + _cimg_mp_check_notnan_index(p1); } else { p1 = 11; s0 = ss + 10; } _cimg_mp_check_list(); @@ -19927,6 +20070,7 @@ namespace cimg_library { if (ss[8]=='#') { // Index specified s0 = ss + 9; while (s0<se1 && (*s0!=',' || level[s0 - expr._data]!=clevel1)) ++s0; p1 = compile(ss + 9,s0++,depth1,0,block_flags); + _cimg_mp_check_notnan_index(p1); } else { p1 = 11; s0 = ss + 8; } _cimg_mp_check_list(); _cimg_mp_scalar1(mp_da_size,p1); @@ -20091,6 +20235,7 @@ namespace cimg_library { if (*ss5=='#') { // Index specified s0 = ss6; while (s0<se1 && (*s0!=',' || level[s0 - expr._data]!=clevel1)) ++s0; p1 = compile(ss6,s0++,depth1,0,block_flags); + _cimg_mp_check_notnan_index(p1); _cimg_mp_check_list(); } else { p1 = ~0U; s0 = ss5; } @@ -20349,12 +20494,12 @@ namespace cimg_library { if (*ss8=='#') { // Index specified s0 = ss + 9; while (s0<se1 && (*s0!=',' || level[s0 - expr._data]!=clevel1)) ++s0; p1 = compile(ss + 9,s0++,depth1,0,block_flags); - pos = 2; + _cimg_mp_check_notnan_index(p1); _cimg_mp_check_list(); - } else { p1 = ~0U; s0 = ss8; pos = 1; } + } else { p1 = ~0U; s0 = ss8; } if (s0==se1) compile(s0,se1,depth1,0,block_flags); // 'missing' argument error CImg<ulongT>::vector((ulongT)mp_ellipse,_cimg_mp_slot_nan,0,p1).move_to(l_opcode); - for (s = s0; s<se; ++s, ++pos) { + for (s = s0; s<se; ++s) { ns = s; while (ns<se && (*ns!=',' || level[ns - expr._data]!=clevel1) && (*ns!=')' || level[ns - expr._data]!=clevel)) ++ns; arg2 = compile(s,ns,depth1,0,block_flags); @@ -20449,8 +20594,10 @@ namespace cimg_library { if (s1!=se1) { const bool is_inside_end = (bool)(block_flags&8); if (!is_inside_end) code.swap(code_end); - compile(s1,se1,depth1,p_ref,8); + pos = compile(s1,se1,depth1,p_ref,8); if (!is_inside_end) code.swap(code_end); + result_end_dim = _cimg_mp_size(pos); + result_end = mem._data + pos; is_end_code = true; } _cimg_mp_return_nan(); @@ -20558,6 +20705,7 @@ namespace cimg_library { s0 = ss5; while (s0<se1 && (*s0!=',' || level[s0 - expr._data]!=clevel1)) ++s0; if (*ss5=='#') { // Index specified p1 = compile(ss6,s0,depth1,0,block_flags); + _cimg_mp_check_notnan_index(p1); _cimg_mp_check_list(); arg1 = ~0U; } else { // Vector specified @@ -20639,6 +20787,16 @@ namespace cimg_library { break; case 'g' : +#if cimg_use_cpp11==1 + if (!std::strncmp(ss,"gamma(",6)) { // Gamma + _cimg_mp_op("Function 'gamma()'"); + arg1 = compile(ss6,se1,depth1,0,block_flags); + if (_cimg_mp_is_vector(arg1)) _cimg_mp_vector1_v(mp_gamma,arg1); + if (_cimg_mp_is_const_scalar(arg1)) _cimg_mp_const_scalar(std::tgamma(mem[arg1])); + _cimg_mp_scalar1(mp_gamma,arg1); + } +#endif + if (!std::strncmp(ss,"gauss(",6)) { // Gaussian function _cimg_mp_op("Function 'gauss()'"); s1 = ss6; while (s1<se1 && (*s1!=',' || level[s1 - expr._data]!=clevel1)) ++s1; @@ -20710,6 +20868,7 @@ namespace cimg_library { _cimg_mp_op("Function 'h()'"); if (*ss2=='#') { // Index specified p1 = compile(ss3,se1,depth1,0,block_flags); + _cimg_mp_check_notnan_index(p1); _cimg_mp_check_list(); } else { if (ss2!=se1) break; p1 = ~0U; } _cimg_mp_scalar1(mp_image_h,p1); @@ -20721,6 +20880,7 @@ namespace cimg_library { _cimg_mp_op("Function 'ic()'"); if (*ss3=='#') { // Index specified p1 = compile(ss4,se1,depth1,0,block_flags); + _cimg_mp_check_notnan_index(p1); _cimg_mp_check_list(); } else { if (ss3!=se1) break; p1 = ~0U; } pos = scalar(); @@ -20733,6 +20893,7 @@ namespace cimg_library { _cimg_mp_op("Function 'in()'"); if (*ss3=='#') { // Index specified p1 = compile(ss4,se1,depth1,0,block_flags); + _cimg_mp_check_notnan_index(p1); _cimg_mp_check_list(); } else { if (ss3!=se1) break; p1 = ~0U; } pos = scalar(); @@ -21265,8 +21426,9 @@ namespace cimg_library { if (*ss5=='#') { // Index specified s0 = ss6; while (s0<se1 && (*s0!=',' || level[s0 - expr._data]!=clevel1)) ++s0; p1 = compile(ss6,s0++,depth1,0,block_flags); - is_sth = true; // is_index_specified? + _cimg_mp_check_notnan_index(p1); _cimg_mp_check_list(); + is_sth = true; // is_index_specified? } else { s0 = ss5; p1 = get_mem_img_index(); is_sth = false; } arg1 = s0<se1?compile(s0,se1,depth1,0,block_flags):~0U; if (arg1!=~0U) { @@ -21292,51 +21454,69 @@ namespace cimg_library { _cimg_mp_const_scalar((double)arg1); } - if ((cimg_sscanf(ss,"norm%u%c",&(arg1=~0U),&sep)==2 && sep=='(') || - !std::strncmp(ss,"norminf(",8) || !std::strncmp(ss,"norm(",5) || - (!std::strncmp(ss,"norm",4) && ss5<se1 && (s=std::strchr(ss5,'('))!=0)) { // Lp norm - _cimg_mp_op("Function 'normP()'"); - if (*ss4=='(') { arg1 = 2; s = ss5; } - else if (*ss4=='i' && *ss5=='n' && *ss6=='f' && *ss7=='(') { arg1 = ~0U; s = ss8; } - else if (arg1==~0U) { - arg1 = compile(ss4,s++,depth1,0,block_flags); - _cimg_mp_check_const_scalar(arg1,0,2); - arg1 = (unsigned int)mem[arg1]; - } else s = std::strchr(ss4,'(') + 1; + if (!std::strncmp(ss,"normp(",6)) { // Lp norm, with variable argument p. + _cimg_mp_op("Function 'normp()'"); + s1 = ss6; while (s1<se1 && (*s1!=',' || level[s1 - expr._data]!=clevel1)) ++s1; + arg1 = compile(ss6,s1,depth1,0,block_flags); + arg2 = s1<se1?compile(++s1,se1,depth1,0,block_flags):2; + _cimg_mp_check_type(arg2,0,1,0); + p1 = _cimg_mp_size(arg1); + _cimg_mp_scalar3(mp_vector_normp,arg1,p1,arg2); + } + + if (!std::strncmp(ss,"norm",4) && ss4<se1 && (s = std::strchr(ss4,'('))!=0) { // Lp norm (constant p) + _cimg_mp_op("Function 'norm()'"); + arg1 = s!=ss4?compile(ss4,s,depth1,0,block_flags):2; + _cimg_mp_check_const_scalar(arg1,0,0); + val = mem[arg1]; is_sth = true; // Tell if all arguments are constant - pos = scalar(); - switch (arg1) { - case 0 : op = mp_norm0; CImg<ulongT>::vector((ulongT)op,pos,0).move_to(l_opcode); break; - case 1 : op = mp_norm1; CImg<ulongT>::vector((ulongT)op,pos,0).move_to(l_opcode); break; - case 2 : op = mp_norm2; CImg<ulongT>::vector((ulongT)op,pos,0).move_to(l_opcode); break; - case ~0U : op = mp_norminf; CImg<ulongT>::vector((ulongT)op,pos,0).move_to(l_opcode); break; - default : op = mp_normp; CImg<ulongT>::vector((ulongT)op,pos,0,(ulongT)(arg1==~0U?-1:(int)arg1)). - move_to(l_opcode); - } - for ( ; s<se; ++s) { + CImg<ulongT>::vector(0,0,0,arg1).move_to(l_opcode); + for (++s; s<se; ++s) { ns = s; while (ns<se && (*ns!=',' || level[ns - expr._data]!=clevel1) && (*ns!=')' || level[ns - expr._data]!=clevel)) ++ns; arg2 = compile(s,ns,depth1,0,block_flags); if (_cimg_mp_is_vector(arg2)) - CImg<ulongT>::sequence(_cimg_mp_size(arg2),arg2 + 1, - arg2 + (ulongT)_cimg_mp_size(arg2)). + CImg<ulongT>::sequence(_cimg_mp_size(arg2),arg2 + 1,arg2 + (ulongT)_cimg_mp_size(arg2)). move_to(l_opcode); else CImg<ulongT>::vector(arg2).move_to(l_opcode); is_sth&=_cimg_mp_is_const_scalar(arg2); s = ns; } - (l_opcode>'y').move_to(opcode); + op = val==2?_mp_vector_norm2:val==1?_mp_vector_norm1:!val?_mp_vector_norm0: + cimg::type<double>::is_inf(val)?_mp_vector_norminf:_mp_vector_normp; + opcode[0] = (ulongT)op; opcode[2] = opcode._height; if (is_sth) _cimg_mp_const_scalar(op(*this)); - if (arg1>0 && opcode._height==4) // Special case with one argument and p>=1 - _cimg_mp_scalar1(mp_abs,opcode[3]); + if (opcode._height==5) { // Single argument + if (arg1) { _cimg_mp_scalar1(mp_abs,opcode[4]); } + else { _cimg_mp_scalar2(mp_neq,opcode[4],0); } + } + opcode[1] = pos = scalar(); opcode.move_to(code); return_new_comp = true; _cimg_mp_return(pos); } break; + case 'o' : + if (!std::strncmp(ss,"o2c(",4)) { // Offset to coordinates + _cimg_mp_op("Function 'o2c()'"); + if (*ss4=='#') { // Index specified + s0 = ss5; while (s0<se1 && (*s0!=',' || level[s0 - expr._data]!=clevel1)) ++s0; + p1 = compile(ss5,s0++,depth1,0,block_flags); + _cimg_mp_check_notnan_index(p1); + _cimg_mp_check_list(); + } else { p1 = ~0U; s0 = ss4; } + arg1 = compile(s0,se1,depth1,0,block_flags); + _cimg_mp_check_type(arg1,1,1,0); + pos = vector(4); + CImg<ulongT>::vector((ulongT)mp_o2c,pos,p1,arg1).move_to(code); + return_new_comp = true; + _cimg_mp_return(pos); + } + break; + case 'p' : if (!std::strncmp(ss,"permut(",7)) { // Number of permutations _cimg_mp_op("Function 'permut()'"); @@ -21359,8 +21539,9 @@ namespace cimg_library { if (*ss8=='#') { // Index specified s0 = ss + 9; while (s0<se1 && (*s0!=',' || level[s0 - expr._data]!=clevel1)) ++s0; p1 = compile(ss + 9,s0++,depth1,0,block_flags); - pos = 2; + _cimg_mp_check_notnan_index(p1); _cimg_mp_check_list(); + pos = 2; } else { p1 = ~0U; s0 = ss8; pos = 1; } if (s0==se1) compile(s0,se1,depth1,0,block_flags); // 'missing' argument error CImg<ulongT>::vector((ulongT)mp_polygon,_cimg_mp_slot_nan,0,p1).move_to(l_opcode); @@ -21391,6 +21572,7 @@ namespace cimg_library { _cimg_mp_op(is_sth?"Function 'prints()'":"Function 'print()'"); if (!is_sth && *s0=='#') { // Image p1 = compile(ss7,se1,depth1,0,block_flags); + _cimg_mp_check_notnan_index(p1); _cimg_mp_check_list(); CImg<ulongT>::vector((ulongT)mp_image_print,_cimg_mp_slot_nan,p1).move_to(code); _cimg_mp_return_nan(); @@ -21735,6 +21917,7 @@ namespace cimg_library { _cimg_mp_op("Function 's()'"); if (*ss2=='#') { // Index specified p1 = compile(ss3,se1,depth1,0,block_flags); + _cimg_mp_check_notnan_index(p1); _cimg_mp_check_list(); } else { if (ss2!=se1) break; p1 = ~0U; } _cimg_mp_scalar1(mp_image_s,p1); @@ -21921,6 +22104,7 @@ namespace cimg_library { _cimg_mp_op("Function 'stats()'"); if (*ss6=='#') { // Index specified p1 = compile(ss7,se1,depth1,0,block_flags); + _cimg_mp_check_notnan_index(p1); _cimg_mp_check_list(); } else { if (ss6!=se1) break; p1 = ~0U; } pos = vector(14); @@ -22280,6 +22464,26 @@ namespace cimg_library { _cimg_mp_scalar1(mp_ui2f,arg1); } + if (!std::strncmp(ss,"unitnorm(",9)) { // Normalize vector to unit norm + _cimg_mp_op("Function 'unitnorm()'"); + s0 = ss + 9; + s1 = s0; while (s1<se1 && (*s1!=',' || level[s1 - expr._data]!=clevel1)) ++s1; + arg1 = compile(s0,s1,depth1,0,block_flags); + arg2 = s1<se1?compile(++s1,se1,depth1,0,block_flags):2; + _cimg_mp_check_type(arg2,0,1,0); + p1 = _cimg_mp_size(arg1); + if (p1>0) pos = is_comp_vector(arg1)?arg1:((return_new_comp = true), vector(p1)); + else { + pos = scalar(); + if (_cimg_mp_is_const_scalar(arg1) && _cimg_mp_is_const_scalar(arg2)) { + val = mem[arg1]; + _cimg_mp_const_scalar(val?(mem[arg2]?1:val):0); + } + } + CImg<ulongT>::vector((ulongT)mp_vector_unitnorm,pos,arg1,p1,arg2).move_to(code); + _cimg_mp_return(pos); + } + if (!std::strncmp(ss,"unref(",6)) { // Un-reference variable _cimg_mp_op("Function 'unref()'"); arg1=~0U; @@ -22445,6 +22649,7 @@ namespace cimg_library { _cimg_mp_op("Function 'w()'"); if (*ss2=='#') { // Index specified p1 = compile(ss3,se1,depth1,0,block_flags); + _cimg_mp_check_notnan_index(p1); _cimg_mp_check_list(); } else { if (ss2!=se1) break; p1 = ~0U; } _cimg_mp_scalar1(mp_image_w,p1); @@ -22454,6 +22659,7 @@ namespace cimg_library { _cimg_mp_op("Function 'wh()'"); if (*ss3=='#') { // Index specified p1 = compile(ss4,se1,depth1,0,block_flags); + _cimg_mp_check_notnan_index(p1); _cimg_mp_check_list(); } else { if (ss3!=se1) break; p1 = ~0U; } _cimg_mp_scalar1(mp_image_wh,p1); @@ -22463,6 +22669,7 @@ namespace cimg_library { _cimg_mp_op("Function 'whd()'"); if (*ss4=='#') { // Index specified p1 = compile(ss5,se1,depth1,0,block_flags); + _cimg_mp_check_notnan_index(p1); _cimg_mp_check_list(); } else { if (ss4!=se1) break; p1 = ~0U; } _cimg_mp_scalar1(mp_image_whd,p1); @@ -22472,6 +22679,7 @@ namespace cimg_library { _cimg_mp_op("Function 'whds()'"); if (*ss5=='#') { // Index specified p1 = compile(ss6,se1,depth1,0,block_flags); + _cimg_mp_check_notnan_index(p1); _cimg_mp_check_list(); } else { if (ss5!=se1) break; p1 = ~0U; } _cimg_mp_scalar1(mp_image_whds,p1); @@ -22547,7 +22755,7 @@ namespace cimg_library { *ss=='v'?mp_var: ss[1]=='i'?(ss[3]=='('?mp_min:mp_minabs): ss[1]=='a'?(ss[3]=='('?mp_max:mp_maxabs): - mp_median; + mp_med; is_sth = true; // Tell if all arguments are constant pos = scalar(); CImg<ulongT>::vector((ulongT)op,pos,0).move_to(l_opcode); @@ -22555,11 +22763,8 @@ namespace cimg_library { ns = s; while (ns<se && (*ns!=',' || level[ns - expr._data]!=clevel1) && (*ns!=')' || level[ns - expr._data]!=clevel)) ++ns; arg2 = compile(s,ns,depth1,0,block_flags); - if (_cimg_mp_is_vector(arg2)) - CImg<ulongT>::sequence(_cimg_mp_size(arg2),arg2 + 1, - arg2 + (ulongT)_cimg_mp_size(arg2)). - move_to(l_opcode); - else CImg<ulongT>::vector(arg2).move_to(l_opcode); + if (_cimg_mp_is_vector(arg2)) CImg<ulongT>::vector(arg2 + 1,_cimg_mp_size(arg2)).move_to(l_opcode); + else CImg<ulongT>::vector(arg2,1).move_to(l_opcode); is_sth&=_cimg_mp_is_const_scalar(arg2); s = ns; } @@ -22752,6 +22957,7 @@ namespace cimg_library { // Variables related to the input list of images. if (*ss1=='#' && ss2<se) { arg1 = compile(ss2,se,depth1,0,block_flags); + _cimg_mp_check_notnan_index(arg1); p1 = (unsigned int)(imglist._width && _cimg_mp_is_const_scalar(arg1)? cimg::mod((int)mem[arg1],imglist.width()):~0U); switch (*ss) { @@ -22807,6 +23013,7 @@ namespace cimg_library { if (*ss1 && *ss2=='#' && ss3<se) { arg1 = compile(ss3,se,depth1,0,block_flags); + _cimg_mp_check_notnan_index(arg1); p1 = (unsigned int)(imglist._width && _cimg_mp_is_const_scalar(arg1)? cimg::mod((int)mem[arg1],imglist.width()):~0U); if (*ss=='w' && *ss1=='h') { // wh#ind @@ -22830,26 +23037,36 @@ namespace cimg_library { if (!list_median[p1]) CImg<doubleT>::vector(imglist[p1].median()).move_to(list_median[p1]); _cimg_mp_const_scalar(*list_median[p1]); } - _cimg_mp_scalar1(mp_list_median,arg1); + _cimg_mp_scalar1(mp_list_id,arg1); + } + + if (*ss1=='d') { // id#ind + if (!imglist) _cimg_mp_return(0); + if (_cimg_mp_is_const_scalar(arg1)) { + if (!list_stats) list_stats.assign(imglist._width); + if (!list_stats[p1]) list_stats[p1].assign(1,14,1,1,0).fill(imglist[p1].get_stats(),false); + _cimg_mp_const_scalar(std::sqrt(list_stats(p1,3))); + } + _cimg_mp_scalar1(mp_list_id,arg1); } if (*ss1=='n') { // in#ind if (!imglist) _cimg_mp_return(0); if (_cimg_mp_is_const_scalar(arg1)) { if (!list_norm) list_norm.assign(imglist._width); - if (!list_norm[p1]) CImg<doubleT>::vector(imglist[p1].magnitude()).move_to(list_norm[p1]); + if (!list_norm[p1]) CImg<doubleT>::vector(imglist[p1].magnitude(2)).move_to(list_norm[p1]); _cimg_mp_const_scalar(*list_norm[p1]); } _cimg_mp_scalar1(mp_list_norm,arg1); } switch (*ss1) { + case 'a' : arg2 = 2; break; // ia#ind case 'm' : arg2 = 0; break; // im#ind case 'M' : arg2 = 1; break; // iM#ind - case 'a' : arg2 = 2; break; // ia#ind - case 'v' : arg2 = 3; break; // iv#ind - case 's' : arg2 = 12; break; // is#ind case 'p' : arg2 = 13; break; // ip#ind + case 's' : arg2 = 12; break; // is#ind + case 'v' : arg2 = 3; break; // iv#ind } } else if (*ss1=='m') switch (*ss) { case 'x' : arg2 = 4; break; // xm#ind @@ -22875,6 +23092,7 @@ namespace cimg_library { if (*ss=='w' && *ss1=='h' && *ss2=='d' && *ss3=='#' && ss4<se) { // whd#ind arg1 = compile(ss4,se,depth1,0,block_flags); + _cimg_mp_check_notnan_index(arg1); if (!imglist) _cimg_mp_return(0); p1 = (unsigned int)(_cimg_mp_is_const_scalar(arg1)?cimg::mod((int)mem[arg1],imglist.width()):~0U); if (p1!=~0U) _cimg_mp_const_scalar(imglist[p1]._width*imglist[p1]._height*imglist[p1]._depth); @@ -22882,6 +23100,7 @@ namespace cimg_library { } if (*ss=='w' && *ss1=='h' && *ss2=='d' && *ss3=='s' && *ss4=='#' && ss5<se) { // whds#ind arg1 = compile(ss5,se,depth1,0,block_flags); + _cimg_mp_check_notnan_index(arg1); if (!imglist) _cimg_mp_return(0); p1 = (unsigned int)(_cimg_mp_is_const_scalar(arg1)?cimg::mod((int)mem[arg1],imglist.width()):~0U); if (p1!=~0U) @@ -23312,6 +23531,18 @@ namespace cimg_library { } } + // Check that specified constant is not nan. + void check_notnan_index(const unsigned int arg, + char *const ss, char *const se, const char saved_char) { + if (arg!=~0U && + (arg==_cimg_mp_slot_nan || (_cimg_mp_is_const_scalar(arg) && cimg::type<double>::is_nan(mem[arg])))) { + char *s0; _cimg_mp_strerr; + throw CImgArgumentException("[" cimg_appname "_math_parser] " + "CImg<%s>::%s: %s%s Specified index is NaN.", + pixel_type(),_cimg_mp_calling_function,s_op,*s_op?":":""); + } + } + // Check a matrix is square. void check_matrix_square(const unsigned int arg, const unsigned int n_arg, char *const ss, char *const se, const char saved_char) { @@ -23736,6 +23967,14 @@ namespace cimg_library { #endif #define _mp_arg(x) mp.mem[mp.opcode[x]] +#ifdef cimg_mp_func_abort + static double mp_abort(_cimg_math_parser& mp) { + cimg::unused(mp); + cimg_mp_func_abort(); + return cimg::type<double>::nan(); + } +#endif + static double mp_abs(_cimg_math_parser& mp) { return cimg::abs(_mp_arg(2)); } @@ -23792,53 +24031,105 @@ namespace cimg_library { static double mp_argkth(_cimg_math_parser& mp) { const unsigned int i_end = (unsigned int)mp.opcode[2]; - const double val = mp_kth(mp); - for (unsigned int i = 4; i<i_end; ++i) if (val==_mp_arg(i)) return i - 3.; - return 1.; + CImg<double> values; + if (i_end==5) values.assign(&_mp_arg(3),(unsigned int)mp.opcode[4],1,1,1,true); // Only a single argument + else { + unsigned int siz = 0; + for (unsigned int i = 4; i<i_end; i+=2) siz+=(unsigned int)mp.opcode[i]; + values.assign(siz); + double *ptr = values; + for (unsigned int i = 3; i<i_end; i+=2) { + const unsigned int len = (unsigned int)mp.opcode[i + 1]; + if (len>1) std::memcpy(ptr,&_mp_arg(i),len*sizeof(double)); + else *ptr = _mp_arg(i); + ptr+=len; + } + } + longT ind = (longT)cimg::round(_mp_arg(3)); + ++values._data; --values._width; // Skip first value + if (ind<0) ind+=values.width() + 1; + ind = cimg::cut(ind,(longT)1,(longT)values.width()); + const double kth = values.kth_smallest((ulongT)(ind - 1)); + --values._data; ++values._width; + for (unsigned int argkth = 1; argkth<values._width; ++argkth) + if (values[argkth]==kth) return argkth; + return cimg::type<double>::nan(); } static double mp_argmin(_cimg_math_parser& mp) { const unsigned int i_end = (unsigned int)mp.opcode[2]; - double val = _mp_arg(3); - unsigned int argval = 0; - for (unsigned int i = 4; i<i_end; ++i) { - const double _val = _mp_arg(i); - if (_val<val) { val = _val; argval = i - 3; } + double val, valmin = cimg::type<double>::inf(); + unsigned int siz = 0, argmin = 0; + for (unsigned int i = 3; i<i_end; i+=2) { + const unsigned int len = (unsigned int)mp.opcode[i + 1]; + if (len>1) { + const double *ptr = &_mp_arg(i); + for (unsigned int k = 0; k<len; ++k) { val = *(ptr++); if (val<valmin) { valmin = val; argmin = siz + k; } } + } else { val = _mp_arg(i); if (val<valmin) { valmin = val; argmin = siz; } } + siz+=len; } - return (double)argval; + return (double)argmin; } static double mp_argminabs(_cimg_math_parser& mp) { const unsigned int i_end = (unsigned int)mp.opcode[2]; - double val = _mp_arg(3), absval = cimg::abs(val); - unsigned int argval = 0; - for (unsigned int i = 4; i<i_end; ++i) { - const double _val = _mp_arg(i), _absval = cimg::abs(_val); - if (_absval<absval) { val = _val; absval = _absval; argval = i - 3; } + double val, abs_val, abs_valminabs = cimg::type<double>::inf(); + unsigned int siz = 0, argminabs = 0; + for (unsigned int i = 3; i<i_end; i+=2) { + const unsigned int len = (unsigned int)mp.opcode[i + 1]; + if (len>1) { + const double *ptr = &_mp_arg(i); + for (unsigned int k = 0; k<len; ++k) { + val = *(ptr++); + abs_val = cimg::abs(val); + if (abs_val<abs_valminabs) { abs_valminabs = abs_val; argminabs = siz + k; } + } + } else { + val = _mp_arg(i); + abs_val = cimg::abs(val); + if (abs_val<abs_valminabs) { abs_valminabs = abs_val; argminabs = siz; } + } + siz+=len; } - return (double)argval; + return (double)argminabs; } static double mp_argmax(_cimg_math_parser& mp) { const unsigned int i_end = (unsigned int)mp.opcode[2]; - double val = _mp_arg(3); - unsigned int argval = 0; - for (unsigned int i = 4; i<i_end; ++i) { - const double _val = _mp_arg(i); - if (_val>val) { val = _val; argval = i - 3; } + double val, valmax = -cimg::type<double>::inf(); + unsigned int siz = 0, argmax = 0; + for (unsigned int i = 3; i<i_end; i+=2) { + const unsigned int len = (unsigned int)mp.opcode[i + 1]; + if (len>1) { + const double *ptr = &_mp_arg(i); + for (unsigned int k = 0; k<len; ++k) { val = *(ptr++); if (val>valmax) { valmax = val; argmax = siz + k; } } + } else { val = _mp_arg(i); if (val>valmax) { valmax = val; argmax = siz; } } + siz+=len; } - return (double)argval; + return (double)argmax; } static double mp_argmaxabs(_cimg_math_parser& mp) { const unsigned int i_end = (unsigned int)mp.opcode[2]; - double val = _mp_arg(3), absval = cimg::abs(val); - unsigned int argval = 0; - for (unsigned int i = 4; i<i_end; ++i) { - const double _val = _mp_arg(i), _absval = cimg::abs(_val); - if (_absval>absval) { val = _val; absval = _absval; argval = i - 3; } + double val, abs_val, abs_valmaxabs = 0; + unsigned int siz = 0, argmaxabs = 0; + for (unsigned int i = 3; i<i_end; i+=2) { + const unsigned int len = (unsigned int)mp.opcode[i + 1]; + if (len>1) { + const double *ptr = &_mp_arg(i); + for (unsigned int k = 0; k<len; ++k) { + val = *(ptr++); + abs_val = cimg::abs(val); + if (abs_val>abs_valmaxabs) { abs_valmaxabs = abs_val; argmaxabs = siz + k; } + } + } else { + val = _mp_arg(i); + abs_val = cimg::abs(val); + if (abs_val>abs_valmaxabs) { abs_valmaxabs = abs_val; argmaxabs = siz; } + } + siz+=len; } - return (double)argval; + return (double)argmaxabs; } static double mp_asin(_cimg_math_parser& mp) { @@ -23855,9 +24146,17 @@ namespace cimg_library { static double mp_avg(_cimg_math_parser& mp) { const unsigned int i_end = (unsigned int)mp.opcode[2]; - double val = _mp_arg(3); - for (unsigned int i = 4; i<i_end; ++i) val+=_mp_arg(i); - return val/(i_end - 3); + unsigned int siz = 0; + double sum = 0; + for (unsigned int i = 3; i<i_end; i+=2) { + const unsigned int len = (unsigned int)mp.opcode[i + 1]; + if (len>1) { + const double *ptr = &_mp_arg(i); + for (unsigned int k = 0; k<len; ++k) sum+=*(ptr++); + } else sum+=_mp_arg(i); + siz+=len; + } + return sum/siz; } static double mp_bitwise_and(_cimg_math_parser& mp) { @@ -23902,30 +24201,18 @@ namespace cimg_library { return cimg::type<double>::nan(); } -#ifdef cimg_mp_func_run - static double mp_run(_cimg_math_parser& mp) { - const unsigned int nb_args = (unsigned int)(mp.opcode[2] - 3)/2; - CImgList<charT> _str; - CImg<charT> it; - for (unsigned int n = 0; n<nb_args; ++n) { - const unsigned int siz = (unsigned int)mp.opcode[4 + 2*n]; - if (siz) { // Vector argument -> string - const double *ptr = &_mp_arg(3 + 2*n) + 1; - unsigned int l = 0; - while (l<siz && ptr[l]) ++l; - CImg<doubleT>(ptr,l,1,1,1,true).move_to(_str); - } else { // Scalar argument -> number - it.assign(24); - cimg_snprintf(it,it._width,"%.17g",_mp_arg(3 + 2*n)); - CImg<charT>::string(it,false,true).move_to(_str); - } - } - CImg(1,1,1,1,0).move_to(_str); - CImg<charT> str = _str>'x'; - cimg_mp_func_run(str._data); - return cimg::type<double>::nan(); + static double mp_c2o(_cimg_math_parser& mp) { + mp_check_list(mp,"c2o"); + unsigned int ind = (unsigned int)mp.opcode[2]; + if (ind!=~0U) ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.imglist.width()); + const CImg<T> &img = ind==~0U?mp.imgin:mp.imglist[ind]; + const int + x = (int)_mp_arg(3), + y = (int)_mp_arg(4), + z = (int)_mp_arg(5), + c = (int)_mp_arg(6); + return (double)img.offset(x,y,z,c); } -#endif static double mp_cbrt(_cimg_math_parser& mp) { return cimg::cbrt(_mp_arg(2)); @@ -24073,6 +24360,17 @@ namespace cimg_library { return cimg::type<double>::nan(); } + static double mp_complex_sqrt(_cimg_math_parser& mp) { + const double + real = _mp_arg(2), imag = _mp_arg(3), + r = std::sqrt(cimg::_hypot(real,imag)), + theta = std::atan2(imag,real)/2; + double *ptrd = &_mp_arg(1) + 1; + ptrd[0] = r*std::cos(theta); + ptrd[1] = r*std::sin(theta); + return cimg::type<double>::nan(); + } + static double mp_complex_tan(_cimg_math_parser& mp) { const double real = _mp_arg(2), imag = _mp_arg(3), denom = std::cos(2*real) + std::cosh(2*imag); double *ptrd = &_mp_arg(1) + 1; @@ -24292,7 +24590,7 @@ namespace cimg_library { mp_check_list(mp,s_op); const unsigned int ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.imglist.width()); CImg<T> &img = mp.imglist[ind]; - int siz = img?(int)img[img._height - 1]:0; + int siz = img?(int)cimg::float2uint((float)img[img._height - 1]):0; if (img && (img._width!=1 || img._depth!=1 || siz<0 || siz>img.height() - 1)) throw CImgArgumentException("[" cimg_appname "_math_parser] CImg<%s>: Function '%s()': " "Specified image #%u of size (%d,%d,%d,%d) cannot be used as dynamic array%s.", @@ -24313,7 +24611,7 @@ namespace cimg_library { ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.imglist.width()); CImg<T> &img = mp.imglist[ind]; const int - siz = img?(int)img[img._height - 1]:0, + siz = img?(int)cimg::float2uint((float)img[img._height - 1]):0, pos0 = mp.opcode[3]==~0U?siz:(int)_mp_arg(3), pos = pos0<0?pos0 + siz:pos0; @@ -24345,7 +24643,7 @@ namespace cimg_library { double *ptrs = &_mp_arg(6 + k) + 1; cimg_forC(img,c) img(0,pos + k,0,c) = ptrs[c]; } - img[img._height - 1] = (T)(siz + nb_elts); + img[img._height - 1] = (T)cimg::uint2float(siz + nb_elts); return cimg::type<double>::nan(); } @@ -24353,7 +24651,7 @@ namespace cimg_library { mp_check_list(mp,"da_remove"); const unsigned int ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.imglist.width()); CImg<T> &img = mp.imglist[ind]; - int siz = img?(int)img[img._height - 1]:0; + int siz = img?(int)cimg::float2uint((float)img[img._height - 1]):0; if (img && (img._width!=1 || img._depth!=1 || siz<0 || siz>img.height() - 1)) throw CImgArgumentException("[" cimg_appname "_math_parser] CImg<%s>: Function 'da_remove()': " "Specified image #%u of size (%d,%d,%d,%d) cannot be used as dynamic array%s.", @@ -24379,7 +24677,7 @@ namespace cimg_library { siz-=end - start + 1; if (img.height()>32 && siz<2*img.height()/3) // Reduce size of dynamic array img.resize(1,std::max(2*siz + 1,32),1,-100,0); - img[img._height - 1] = (T)siz; + img[img._height - 1] = (T)cimg::uint2float(siz); return cimg::type<double>::nan(); } @@ -24387,7 +24685,7 @@ namespace cimg_library { mp_check_list(mp,"da_size"); const unsigned int ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.imglist.width()); CImg<T> &img = mp.imglist[ind]; - const int siz = img?(int)img[img._height - 1]:0; + const int siz = img?(int)cimg::float2uint((float)img[img._height - 1]):0; if (img && (img._width!=1 || img._depth!=1 || siz<0 || siz>img.height() - 1)) throw CImgArgumentException("[" cimg_appname "_math_parser] CImg<%s>: Function 'da_size()': " "Specified image #%u of size (%d,%d,%d,%d) cannot be used as dynamic array%s.", @@ -24618,10 +24916,7 @@ namespace cimg_library { mp_check_list(mp,"ellipse"); const unsigned int i_end = (unsigned int)mp.opcode[2]; unsigned int ind = (unsigned int)mp.opcode[3]; - if (ind!=~0U) { - if (!mp.imglist.width()) return cimg::type<double>::nan(); - ind = (unsigned int)cimg::mod((int)_mp_arg(3),mp.imglist.width()); - } + if (ind!=~0U) ind = (unsigned int)cimg::mod((int)_mp_arg(3),mp.imglist.width()); CImg<T> &img = ind==~0U?mp.imgout:mp.imglist[ind]; CImg<T> color(img._spectrum,1,1,1,0); bool is_invalid_arguments = false, is_outlined = false; @@ -24911,6 +25206,12 @@ namespace cimg_library { return cimg::grand(&mp.rng); } +#if cimg_use_cpp11==1 + static double mp_gamma(_cimg_math_parser& mp) { + return std::tgamma(_mp_arg(2)); + } +#endif + static double mp_gauss(_cimg_math_parser& mp) { const double x = _mp_arg(2), s = _mp_arg(3); return std::exp(-x*x/(2*s*s))/(_mp_arg(4)?std::sqrt(2*s*s*cimg::PI):1); @@ -25081,7 +25382,7 @@ namespace cimg_library { ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.imglist.width()); } const CImg<T> &img = ind==~0U?mp.imgout:mp.imglist[ind]; - return (double)img.magnitude(); + return (double)img.magnitude(2); } static double mp_image_print(_cimg_math_parser& mp) { @@ -25538,13 +25839,27 @@ namespace cimg_library { static double mp_kth(_cimg_math_parser& mp) { const unsigned int i_end = (unsigned int)mp.opcode[2]; - CImg<doubleT> vals(i_end - 4); - double *p = vals.data(); - for (unsigned int i = 4; i<i_end; ++i) *(p++) = _mp_arg(i); - longT ind = (longT)cimg::round(_mp_arg(3)); - if (ind<0) ind+=vals.width() + 1; - ind = cimg::cut(ind,(longT)1,(longT)vals.width()); - return vals.kth_smallest((ulongT)(ind - 1)); + CImg<double> values; + if (i_end==5) values.assign(&_mp_arg(3),(unsigned int)mp.opcode[4],1,1,1,true); // Only a single argument + else { + unsigned int siz = 0; + for (unsigned int i = 4; i<i_end; i+=2) siz+=(unsigned int)mp.opcode[i]; + values.assign(siz); + double *ptr = values; + for (unsigned int i = 3; i<i_end; i+=2) { + const unsigned int len = (unsigned int)mp.opcode[i + 1]; + if (len>1) std::memcpy(ptr,&_mp_arg(i),len*sizeof(double)); + else *ptr = _mp_arg(i); + ptr+=len; + } + } + longT ind = (longT)values[0]; + ++values._data; --values._width; // Skip first value + if (ind<0) ind+=values.width() + 1; + ind = cimg::cut(ind,(longT)1,(longT)values.width()); + const double &kth = values.kth_smallest((ulongT)(ind - 1)); + --values._data; ++values._width; + return kth; } static double mp_lerp(_cimg_math_parser& mp) { @@ -25872,10 +26187,27 @@ namespace cimg_library { static double mp_list_norm(_cimg_math_parser& mp) { const unsigned int ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.imglist.width()); if (!mp.list_norm) mp.list_norm.assign(mp.imglist._width); - if (!mp.list_norm[ind]) CImg<doubleT>::vector(mp.imglist[ind].magnitude()).move_to(mp.list_norm[ind]); + if (!mp.list_norm[ind]) CImg<doubleT>::vector(mp.imglist[ind].magnitude(2)).move_to(mp.list_norm[ind]); return *mp.list_norm[ind]; } + static double mp_list_id(_cimg_math_parser& mp) { + const unsigned int ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.imglist.width()); + bool get_stats = false; + cimg::mutex(13); + if (!mp.list_stats || mp.list_stats.size()!=mp.imglist._width) mp.list_stats.assign(mp.imglist._width); + if (!mp.list_stats[ind]) get_stats = true; + cimg::mutex(13,0); + + if (get_stats) { + CImg<Tdouble> st = mp.imglist[ind].get_stats(); + cimg::mutex(13); + st.move_to(mp.list_stats[ind]); + cimg::mutex(13,0); + } + return std::sqrt(mp.list_stats(ind,3)); + } + static double mp_list_set_ioff(_cimg_math_parser& mp) { if (!mp.imglist.width()) return cimg::type<double>::nan(); const unsigned int ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.imglist.width()); @@ -26497,19 +26829,36 @@ namespace cimg_library { static double mp_max(_cimg_math_parser& mp) { const unsigned int i_end = (unsigned int)mp.opcode[2]; - double val = _mp_arg(3); - for (unsigned int i = 4; i<i_end; ++i) val = std::max(val,_mp_arg(i)); - return val; + double val, valmax = -cimg::type<double>::inf(); + for (unsigned int i = 3; i<i_end; i+=2) { + const unsigned int len = (unsigned int)mp.opcode[i + 1]; + if (len>1) { + const double *ptr = &_mp_arg(i); + for (unsigned int k = 0; k<len; ++k) { val = *(ptr++); if (val>valmax) valmax = val; } + } else { val = _mp_arg(i); if (val>valmax) valmax = val; } + } + return valmax; } static double mp_maxabs(_cimg_math_parser& mp) { const unsigned int i_end = (unsigned int)mp.opcode[2]; - double val = _mp_arg(3), absval = cimg::abs(val); - for (unsigned int i = 4; i<i_end; ++i) { - const double _val = _mp_arg(i), _absval = cimg::abs(_val); - if (_absval>absval) { val = _val; absval = _absval; } + double val, abs_val, valmaxabs = 0, abs_valmaxabs = 0; + for (unsigned int i = 3; i<i_end; i+=2) { + const unsigned int len = (unsigned int)mp.opcode[i + 1]; + if (len>1) { + const double *ptr = &_mp_arg(i); + for (unsigned int k = 0; k<len; ++k) { + val = *(ptr++); + abs_val = cimg::abs(val); + if (abs_val>abs_valmaxabs) { valmaxabs = val; abs_valmaxabs = abs_val; } + } + } else { + val = _mp_arg(i); + abs_val = cimg::abs(val); + if (abs_val>abs_valmaxabs) { valmaxabs = val; abs_valmaxabs = abs_val; } + } } - return val; + return valmaxabs; } static double* _mp_memcopy_double(_cimg_math_parser& mp, const unsigned int ind, const ulongT *const p_ref, @@ -26624,42 +26973,61 @@ namespace cimg_library { static double mp_min(_cimg_math_parser& mp) { const unsigned int i_end = (unsigned int)mp.opcode[2]; - double val = _mp_arg(3); - for (unsigned int i = 4; i<i_end; ++i) val = std::min(val,_mp_arg(i)); - return val; + double val, valmin = cimg::type<double>::inf(); + for (unsigned int i = 3; i<i_end; i+=2) { + const unsigned int len = (unsigned int)mp.opcode[i + 1]; + if (len>1) { + const double *ptr = &_mp_arg(i); + for (unsigned int k = 0; k<len; ++k) { val = *(ptr++); if (val<valmin) valmin = val; } + } else { val = _mp_arg(i); if (val<valmin) valmin = val; } + } + return valmin; } static double mp_minabs(_cimg_math_parser& mp) { const unsigned int i_end = (unsigned int)mp.opcode[2]; - double val = _mp_arg(3), absval = cimg::abs(val); - for (unsigned int i = 4; i<i_end; ++i) { - const double _val = _mp_arg(i), _absval = cimg::abs(_val); - if (_absval<absval) { val = _val; absval = _absval; } + double val, abs_val, valminabs = cimg::type<double>::inf(), abs_valminabs = cimg::type<double>::inf(); + for (unsigned int i = 3; i<i_end; i+=2) { + const unsigned int len = (unsigned int)mp.opcode[i + 1]; + if (len>1) { + const double *ptr = &_mp_arg(i); + for (unsigned int k = 0; k<len; ++k) { + val = *(ptr++); + abs_val = cimg::abs(val); + if (abs_val<abs_valminabs) { valminabs = val; abs_valminabs = abs_val; } + } + } else { + val = _mp_arg(i); + abs_val = cimg::abs(val); + if (abs_val<abs_valminabs) { valminabs = val; abs_valminabs = abs_val; } + } } - return val; + return valminabs; } static double mp_minus(_cimg_math_parser& mp) { return -_mp_arg(2); } - static double mp_median(_cimg_math_parser& mp) { + static double mp_med(_cimg_math_parser& mp) { const unsigned int i_end = (unsigned int)mp.opcode[2]; - switch (i_end - 3) { - case 1 : return _mp_arg(3); - case 2 : return cimg::median(_mp_arg(3),_mp_arg(4)); - case 3 : return cimg::median(_mp_arg(3),_mp_arg(4),_mp_arg(5)); - case 5 : return cimg::median(_mp_arg(3),_mp_arg(4),_mp_arg(5),_mp_arg(6),_mp_arg(7)); - case 7 : return cimg::median(_mp_arg(3),_mp_arg(4),_mp_arg(5),_mp_arg(6),_mp_arg(7),_mp_arg(8),_mp_arg(9)); - case 9 : return cimg::median(_mp_arg(3),_mp_arg(4),_mp_arg(5),_mp_arg(6),_mp_arg(7),_mp_arg(8),_mp_arg(9), - _mp_arg(10),_mp_arg(11)); - case 13 : return cimg::median(_mp_arg(3),_mp_arg(4),_mp_arg(5),_mp_arg(6),_mp_arg(7),_mp_arg(8),_mp_arg(9), - _mp_arg(10),_mp_arg(11),_mp_arg(12),_mp_arg(13),_mp_arg(14),_mp_arg(15)); + CImg<double> values; + if (i_end==5) { // Only a single argument + if ((unsigned)mp.opcode[4]==1) return _mp_arg(3); // Real value + else values.assign(&_mp_arg(3),(unsigned int)mp.opcode[4],1,1,1,true); // Vector value + } else { + unsigned int siz = 0; + for (unsigned int i = 4; i<i_end; i+=2) siz+=(unsigned int)mp.opcode[i]; + values.assign(siz); + double *ptr = values; + for (unsigned int i = 3; i<i_end; i+=2) { + const unsigned int len = (unsigned int)mp.opcode[i + 1]; + if (len>1) std::memcpy(ptr,&_mp_arg(i),len*sizeof(double)); + else *ptr = _mp_arg(i); + ptr+=len; + } } - CImg<doubleT> vals(i_end - 3); - double *p = vals.data(); - for (unsigned int i = 3; i<i_end; ++i) *(p++) = _mp_arg(i); - return vals.median(); + return values.median(); } static double mp_modulo(_cimg_math_parser& mp) { @@ -26707,65 +27075,25 @@ namespace cimg_library { return (double)(_mp_arg(2)!=_mp_arg(3)); } - static double mp_norm0(_cimg_math_parser& mp) { - const unsigned int i_end = (unsigned int)mp.opcode[2]; - switch (i_end - 3) { - case 1 : return _mp_arg(3)!=0; - case 2 : return (_mp_arg(3)!=0) + (_mp_arg(4)!=0); + static double mp_o2c(_cimg_math_parser& mp) { + mp_check_list(mp,"o2c"); + unsigned int ind = (unsigned int)mp.opcode[2]; + if (ind!=~0U) ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.imglist.width()); + const CImg<T> &img = ind==~0U?mp.imgin:mp.imglist[ind]; + longT offset = (longT)_mp_arg(3); + double *ptrd = &_mp_arg(1) + 1; + if (!img) + ptrd[0] = ptrd[1] = ptrd[2] = ptrd[3] = cimg::type<double>::nan(); + else { + *(ptrd++) = (double)(offset%img.width()); + offset/=img.width(); + *(ptrd++) = (double)(offset%img.height()); + offset/=img.height(); + *(ptrd++) = (double)(offset%img.depth()); + offset/=img.depth(); + *ptrd = (double)(offset%img.spectrum()); } - double res = 0; - for (unsigned int i = 3; i<i_end; ++i) - res+=_mp_arg(i)==0?0:1; - return res; - } - - static double mp_norm1(_cimg_math_parser& mp) { - const unsigned int i_end = (unsigned int)mp.opcode[2]; - switch (i_end - 3) { - case 1 : return cimg::abs(_mp_arg(3)); - case 2 : return cimg::abs(_mp_arg(3)) + cimg::abs(_mp_arg(4)); - } - double res = 0; - for (unsigned int i = 3; i<i_end; ++i) - res+=cimg::abs(_mp_arg(i)); - return res; - } - - static double mp_norm2(_cimg_math_parser& mp) { - const unsigned int i_end = (unsigned int)mp.opcode[2]; - switch (i_end - 3) { - case 1 : return cimg::abs(_mp_arg(3)); - case 2 : return cimg::_hypot(_mp_arg(3),_mp_arg(4)); - } - double res = 0; - for (unsigned int i = 3; i<i_end; ++i) - res+=cimg::sqr(_mp_arg(i)); - return std::sqrt(res); - } - - static double mp_norminf(_cimg_math_parser& mp) { - const unsigned int i_end = (unsigned int)mp.opcode[2]; - switch (i_end - 3) { - case 1 : return cimg::abs(_mp_arg(3)); - case 2 : return std::max(cimg::abs(_mp_arg(3)),cimg::abs(_mp_arg(4))); - } - double res = 0; - for (unsigned int i = 3; i<i_end; ++i) { - const double val = cimg::abs(_mp_arg(i)); - if (val>res) res = val; - } - return res; - } - - static double mp_normp(_cimg_math_parser& mp) { - const unsigned int i_end = (unsigned int)mp.opcode[2]; - if (i_end==4) return cimg::abs(_mp_arg(3)); - const double p = (double)mp.opcode[3]; - double res = 0; - for (unsigned int i = 4; i<i_end; ++i) - res+=std::pow(cimg::abs(_mp_arg(i)),p); - res = std::pow(res,1/p); - return res>0?res:0.; + return cimg::type<double>::nan(); } static double mp_permutations(_cimg_math_parser& mp) { @@ -26862,9 +27190,15 @@ namespace cimg_library { static double mp_prod(_cimg_math_parser& mp) { const unsigned int i_end = (unsigned int)mp.opcode[2]; - double val = _mp_arg(3); - for (unsigned int i = 4; i<i_end; ++i) val*=_mp_arg(i); - return val; + double prod = 1; + for (unsigned int i = 3; i<i_end; i+=2) { + const unsigned int len = (unsigned int)mp.opcode[i + 1]; + if (len>1) { + const double *ptr = &_mp_arg(i); + for (unsigned int k = 0; k<len; ++k) prod*=*(ptr++); + } else prod*=_mp_arg(i); + } + return prod; } static double mp_rad2deg(_cimg_math_parser& mp) { @@ -26872,7 +27206,7 @@ namespace cimg_library { } static double mp_repeat(_cimg_math_parser& mp) { - const double nb_it = _mp_arg(2); + const double nb_it = _mp_arg(2), nb_itm1 = nb_it - 1; double *const ptrc = mp.opcode[3]!=~0U?&_mp_arg(3):0, *const ptrs = &_mp_arg(1); @@ -26880,13 +27214,13 @@ namespace cimg_library { *const p_body = ++mp.p_code, *const p_end = p_body + mp.opcode[4]; - if (nb_it>0) { + if (nb_it>=1) { const unsigned int _break_type = mp.break_type; mp.break_type = 0; double it = 0; if (ptrc) { // Version with loop variable (3 arguments) - while (it<nb_it) { + while (it<=nb_itm1) { *ptrc = it; for (mp.p_code = p_body; mp.p_code<p_end; ++mp.p_code) { mp.opcode._data = mp.p_code->_data; @@ -26898,7 +27232,7 @@ namespace cimg_library { } *ptrc = it; } else // Version without loop variable (2 arguments) - while (it<nb_it) { + while (it<=nb_itm1) { for (mp.p_code = p_body; mp.p_code<p_end; ++mp.p_code) { mp.opcode._data = mp.p_code->_data; const ulongT target = mp.opcode[1]; @@ -26950,6 +27284,31 @@ namespace cimg_library { return cimg::round(_mp_arg(2),_mp_arg(3),(int)_mp_arg(4)); } +#ifdef cimg_mp_func_run + static double mp_run(_cimg_math_parser& mp) { + const unsigned int nb_args = (unsigned int)(mp.opcode[2] - 3)/2; + CImgList<charT> _str; + CImg<charT> it; + for (unsigned int n = 0; n<nb_args; ++n) { + const unsigned int siz = (unsigned int)mp.opcode[4 + 2*n]; + if (siz) { // Vector argument -> string + const double *ptr = &_mp_arg(3 + 2*n) + 1; + unsigned int l = 0; + while (l<siz && ptr[l]) ++l; + CImg<doubleT>(ptr,l,1,1,1,true).move_to(_str); + } else { // Scalar argument -> number + it.assign(24); + cimg_snprintf(it,it._width,"%.17g",_mp_arg(3 + 2*n)); + CImg<charT>::string(it,false,true).move_to(_str); + } + } + CImg(1,1,1,1,0).move_to(_str); + CImg<charT> str = _str>'x'; + cimg_mp_func_run(str._data); + return cimg::type<double>::nan(); + } +#endif + static double mp_self_add(_cimg_math_parser& mp) { return _mp_arg(1)+=_mp_arg(2); } @@ -27307,11 +27666,7 @@ namespace cimg_library { } static double mp_std(_cimg_math_parser& mp) { - const unsigned int i_end = (unsigned int)mp.opcode[2]; - CImg<doubleT> vals(i_end - 3); - double *p = vals.data(); - for (unsigned int i = 3; i<i_end; ++i) *(p++) = _mp_arg(i); - return std::sqrt(vals.variance()); + return std::sqrt(mp_var(mp)); } static double mp_string_init(_cimg_math_parser& mp) { @@ -27426,9 +27781,15 @@ namespace cimg_library { static double mp_sum(_cimg_math_parser& mp) { const unsigned int i_end = (unsigned int)mp.opcode[2]; - double val = _mp_arg(3); - for (unsigned int i = 4; i<i_end; ++i) val+=_mp_arg(i); - return val; + double sum = 0; + for (unsigned int i = 3; i<i_end; i+=2) { + const unsigned int len = (unsigned int)mp.opcode[i + 1]; + if (len>1) { + const double *ptr = &_mp_arg(i); + for (unsigned int k = 0; k<len; ++k) sum+=*(ptr++); + } else sum+=_mp_arg(i); + } + return sum; } static double mp_swap(_cimg_math_parser& mp) { @@ -27495,10 +27856,17 @@ namespace cimg_library { static double mp_var(_cimg_math_parser& mp) { const unsigned int i_end = (unsigned int)mp.opcode[2]; - CImg<doubleT> vals(i_end - 3); - double *p = vals.data(); - for (unsigned int i = 3; i<i_end; ++i) *(p++) = _mp_arg(i); - return vals.variance(); + unsigned int siz = 0; + double val, S = 0, S2 = 0; + for (unsigned int i = 3; i<i_end; i+=2) { + const unsigned int len = (unsigned int)mp.opcode[i + 1]; + if (len>1) { + const double *ptr = &_mp_arg(i); + for (unsigned int k = 0; k<len; ++k) { val = *(ptr++); S+=val; S2+=val*val; } + } else { val = _mp_arg(i); S+=val; S2+=val*val; } + siz+=len; + } + return (S2 - S*S/siz)/(siz - 1); } static double mp_vector_copy(_cimg_math_parser& mp) { @@ -27743,6 +28111,75 @@ namespace cimg_library { return !mp_vector_eq(mp); } + static double _mp_vector_norm0(_cimg_math_parser& mp) { + const unsigned int siz = (unsigned int)mp.opcode[2]; + double res = 0; + for (unsigned int i = siz - 1; i>3; --i) res+=(double)(_mp_arg(i)?1:0); + return res; + } + + static double _mp_vector_norm1(_cimg_math_parser& mp) { + const unsigned int siz = (unsigned int)mp.opcode[2]; + double res = 0; + for (unsigned int i = siz - 1; i>3; --i) res+=(double)cimg::abs(_mp_arg(i)); + return res; + } + + static double _mp_vector_norm2(_cimg_math_parser& mp) { + const unsigned int siz = (unsigned int)mp.opcode[2]; + double res = 0; + for (unsigned int i = siz - 1; i>3; --i) res+=(double)cimg::sqr(_mp_arg(i)); + return (double)std::sqrt(res); + } + + static double _mp_vector_norminf(_cimg_math_parser& mp) { + const unsigned int siz = (unsigned int)mp.opcode[2]; + double res = 0; + for (unsigned int i = siz - 1; i>3; --i) { + const double val = (double)cimg::abs(_mp_arg(i)); + if (val>res) res = val; + } + return res; + } + + static double _mp_vector_normp(_cimg_math_parser& mp) { + const unsigned int siz = (unsigned int)mp.opcode[2]; + const double p = _mp_arg(3); + double res = 0; + for (unsigned int i = siz - 1; i>3; --i) res+=(double)std::pow(cimg::abs(_mp_arg(i)),p); + res = (double)std::pow(res,1.0/p); + return res; + } + + static double mp_vector_normp(_cimg_math_parser& mp) { + const unsigned int siz = (unsigned int)mp.opcode[3]; + const double p = _mp_arg(4); + if (siz>0) { // Vector-valued argument + const double *ptrs = &_mp_arg(2) + 1; + double res = 0; + if (p==2) { // L2 + for (unsigned int i = 0; i<siz; ++i) res+=(double)cimg::sqr(*(ptrs++)); + res = (double)std::sqrt(res); + } else if (p==1) // L1 + for (unsigned int i = 0; i<siz; ++i) res+=(double)cimg::abs(*(ptrs++)); + else if (!p) // L0 + for (unsigned int i = 0; i<siz; ++i) res+=(double)(*(ptrs++)?1:0); + else if (cimg::type<float>::is_inf(p)) { // L-inf + for (unsigned int i = 0; i<siz; ++i) { + const double val = (double)cimg::abs(*(ptrs++)); + if (val>res) res = val; + } + } else { // L-p + for (unsigned int i = 0; i<siz; ++i) res+=(double)std::pow(cimg::abs(*(ptrs++)),p); + res = (double)std::pow(res,1.0/p); + } + return res>0?res:0; + } + // Scalar-valued argument. + const double val = _mp_arg(2); + return p?cimg::abs(val):(val!=0); + } + static double mp_vector_print(_cimg_math_parser& mp) { const bool print_string = (bool)mp.opcode[4]; cimg_pragma_openmp(critical(mp_vector_print)) @@ -27847,6 +28284,23 @@ namespace cimg_library { return _mp_arg(1); } + static double mp_vector_unitnorm(_cimg_math_parser& mp) { + const unsigned int siz = (unsigned int)mp.opcode[3]; + const double p = _mp_arg(4); + if (siz>0) { // Vector-valued argument + double *const ptrd = &_mp_arg(1) + 1; + const double *const ptrs = &_mp_arg(2) + 1; + if (ptrd!=ptrs) std::memcpy(ptrd,ptrs,siz*sizeof(double)); + CImg<doubleT> vec(ptrd,siz,1,1,1,true); + const double mag = vec.magnitude(p); + if (mag>0) vec/=mag; + return cimg::type<double>::nan(); + } + // Scalar-valued argument. + const double val = _mp_arg(2); + return val?(_mp_arg(2)?1:val):0; + } + #define _cimg_mp_vfunc(func) \ const longT sizd = (longT)mp.opcode[2];\ const unsigned int nbargs = (unsigned int)(mp.opcode[3] - 4)/2; \ @@ -27942,15 +28396,19 @@ namespace cimg_library { switch (nb_digits) { case -1 : std::strcpy(format,"%g"); break; case 0 : std::strcpy(format,"%.17g"); break; - default : cimg_snprintf(format,format._width,"%%.%dg",nb_digits); + default : + if (nb_digits>=-1) cimg_snprintf(format,format._width,"%%.%dg",nb_digits); + else cimg_snprintf(format,format._width,"%%.%dld",-nb_digits); } CImg<charT> str; if (sizs) { // Vector expression const double *ptrs = &_mp_arg(3) + 1; - CImg<doubleT>(ptrs,sizs,1,1,1,true).value_string(',',sizd + 1,format).move_to(str); + if (nb_digits>=-1) CImg<doubleT>(ptrs,sizs,1,1,1,true).value_string(',',sizd + 1,format).move_to(str); + else CImg<longT>(ptrs,sizs,1,1,1).value_string(',',sizd + 1,format).move_to(str); } else { // Scalar expression str.assign(sizd + 1); - cimg_snprintf(str,sizd + 1,format,_mp_arg(3)); + if (nb_digits>=-1) cimg_snprintf(str,sizd + 1,format,_mp_arg(3)); + else cimg_snprintf(str,sizd + 1,format,(long)_mp_arg(3)); } const unsigned int l = std::min(sizd,(unsigned int)std::strlen(str) + 1); CImg<doubleT>(ptrd,l,1,1,1,true) = str.get_shared_points(0,l - 1); @@ -28635,7 +29093,7 @@ namespace cimg_library { Similar to operator+=(const char*), except it performs a pointwise exponentiation instead of an addition. **/ CImg<T>& pow(const char *const expression) { - return pow((+*this)._fill(expression,true,1,0,0,"pow",this)); + return pow((+*this)._fill(expression,true,3,0,0,"pow",this,0)); } //! Raise each pixel value to a power, specified from an expression \newinstance. @@ -28687,7 +29145,7 @@ namespace cimg_library { Similar to operator<<=(const char*), except that it performs a left rotation instead of a left shift. **/ CImg<T>& rol(const char *const expression) { - return rol((+*this)._fill(expression,true,1,0,0,"rol",this)); + return rol((+*this)._fill(expression,true,3,0,0,"rol",this,0)); } //! Compute the bitwise left rotation of each pixel value \newinstance. @@ -28739,7 +29197,7 @@ namespace cimg_library { Similar to operator>>=(const char*), except that it performs a right rotation instead of a right shift. **/ CImg<T>& ror(const char *const expression) { - return ror((+*this)._fill(expression,true,1,0,0,"ror",this)); + return ror((+*this)._fill(expression,true,3,0,0,"ror",this,0)); } //! Compute the bitwise right rotation of each pixel value \newinstance. @@ -28821,7 +29279,7 @@ namespace cimg_library { \f$\mathrm{min}(I_{(x,y,z,c)},\mathrm{expr}_{(x,y,z,c)})\f$. **/ CImg<T>& min(const char *const expression) { - return min((+*this)._fill(expression,true,1,0,0,"min",this)); + return min((+*this)._fill(expression,true,3,0,0,"min",this,0)); } //! Pointwise min operator between an image and an expression \newinstance. @@ -28879,7 +29337,7 @@ namespace cimg_library { \f$\mathrm{max}(I_{(x,y,z,c)},\mathrm{expr}_{(x,y,z,c)})\f$. **/ CImg<T>& max(const char *const expression) { - return max((+*this)._fill(expression,true,1,0,0,"max",this)); + return max((+*this)._fill(expression,true,3,0,0,"max",this,0)); } //! Pointwise max operator between an image and an expression \newinstance. @@ -28938,7 +29396,7 @@ namespace cimg_library { \f$\mathrm{minabs}(I_{(x,y,z,c)},\mathrm{expr}_{(x,y,z,c)})\f$. **/ CImg<T>& minabs(const char *const expression) { - return minabs((+*this)._fill(expression,true,1,0,0,"minabs",this)); + return minabs((+*this)._fill(expression,true,3,0,0,"minabs",this,0)); } //! Pointwise minabs operator between an image and an expression \newinstance. @@ -28997,7 +29455,7 @@ namespace cimg_library { \f$\mathrm{maxabs}(I_{(x,y,z,c)},\mathrm{expr}_{(x,y,z,c)})\f$. **/ CImg<T>& maxabs(const char *const expression) { - return maxabs((+*this)._fill(expression,true,1,0,0,"maxabs",this)); + return maxabs((+*this)._fill(expression,true,3,0,0,"maxabs",this,0)); } //! Pointwise maxabs operator between an image and an expression \newinstance. @@ -29492,67 +29950,98 @@ namespace cimg_library { // (return 'true' in case of success, and set value of 'res'). template<typename t> bool __eval(const char *const expression, t &res) const { - if (!expression || !*expression) { res = (t)0; return true; } - const char c = *expression; - bool is_success = false; - char sep, end; - double val,val2; - int err; - if ((c>='0' && c<='9') || c=='.') { // Possible value - if (!expression[1]) { // Single digit - res = (t)(c - '0'); - is_success = true; - } else if ((err = std::sscanf(expression,"%lf %c%lf %c",&val,&sep,&val2,&end))==1) { // Single value - res = (t)val; - is_success = true; - } else if (err==3) { // Value1 Operator Value2 - switch (sep) { - case '+' : res = (t)(val + val2); is_success = true; break; - case '-' : res = (t)(val - val2); is_success = true; break; - case '*' : res = (t)(val*val2); is_success = true; break; - case '/' : res = (t)(val/val2); is_success = true; break; - case '%' : res = (t)cimg::mod(val,val2); is_success = true; break; - case '&' : res = (t)((long)val & (long)val2); is_success = true; break; - case '|' : res = (t)((long)val | (long)val2); is_success = true; break; - case '>' : res = (t)(val>val2); is_success = true; break; - case '<' : res = (t)(val<val2); is_success = true; break; - case ';' : res = (t)val2; is_success = true; break; - case '^' : res = (t)std::pow(val,val2); is_success = true; break; + +#define __eval_op(op) if (__eval_get(++ptr,val2) && !*ptr) { res = (t)(op); return true; } else return false; + + double val1, val2; + if (!expression || !*expression || *expression==';' || *expression=='[') return false; + if (!expression[1]) switch (*expression) { + case 'w' : res = (t)_width; return true; + case 'h' : res = (t)_height; return true; + case 'd' : res = (t)_depth; return true; + case 's' : res = (t)_spectrum; return true; + case 'r' : res = (t)_is_shared; return true; + default : if (*expression>='0' && *expression<='9') { res = (t)(*expression - '0'); return true; } + } + if (*expression=='w' && expression[1]=='h') { + if (!expression[2]) { res = (t)(_width*_height); return true; } + if (expression[2]=='d') { + if (!expression[3]) { res = (t)(_width*_height*_depth); return true; } + if (expression[3]=='s' && !expression[4]) { res = (t)(_width*_height*_depth*_spectrum); return true; } + } + if (expression[2]=='s' && !expression[3]) { res = (t)(_width*_height*_spectrum); return true; } + } + const char *ptr = expression; + while (*ptr && cimg::is_blank(*ptr)) ++ptr; + if (*ptr=='\'' && *(++ptr)) { // Detect 'stringA' op 'stringB' (op='==' or '!=') + const char *ptr2 = std::strchr(ptr,'\''); + if (ptr2) { + const char *ptr3 = ptr2 + 1; + while (*ptr3 && cimg::is_blank(*ptr3)) ++ptr3; + const char *ptr4 = ptr3; + if ((*ptr3=='!' || *ptr3=='=') && *(++ptr4)=='=') { + ++ptr4; + while (*ptr4 && cimg::is_blank(*ptr4)) ++ptr4; + if (*ptr4=='\'' && *(++ptr4)) { + const char *const ptr5 = std::strchr(ptr4,'\''); + if (ptr5) { + const char *ptr6 = ptr5 + 1; + while (*ptr6 && cimg::is_blank(*ptr6)) ++ptr6; + if (!*ptr6) { + CImg<charT> str1(ptr,ptr2 - ptr,1,1,1,true), str2(ptr4,ptr5 - ptr4,1,1,1,true); + if (*ptr3=='!') res = (t)!(str1==str2); else res = (t)(str1==str2); + return true; + } + } + } } } - } else if ((c=='+' || c=='-' || c=='!') && // +Value, -Value or !Value - (((sep = expression[1])>='0' && sep<='9') || sep=='.')) { - if (!expression[2]) { // [+-!] + Single digit - const int ival = sep - '0'; - res = (t)(c=='+'?ival:c=='-'?-ival:!ival); - is_success = true; - } else if ((err = std::sscanf(expression + 1,"%lf %c%lf %c",&val,&sep,&val2,&end))==1) { // [+-!] Single value - res = (t)(c=='+'?val:c=='-'?-val:(double)!val); - is_success = true; - } else if (err==3) { // [+-!] Value1 Operator Value2 - const double val1 = c=='+'?val:c=='-'?-val:(double)!val; - switch (sep) { - case '+' : res = (t)(val1 + val2); is_success = true; break; - case '-' : res = (t)(val1 - val2); is_success = true; break; - case '*' : res = (t)(val1*val2); is_success = true; break; - case '/' : res = (t)(val1/val2); is_success = true; break; - case '%' : res = (t)cimg::mod(val1,val2); is_success = true; break; - case '&' : res = (t)((long)val1 & (long)val2); is_success = true; break; - case '|' : res = (t)((long)val1 | (long)val2); is_success = true; break; - case '>' : res = (t)(val1>val2); is_success = true; break; - case '<' : res = (t)(val1<val2); is_success = true; break; - case ';' : res = (t)val2; is_success = true; break; - case '^' : val = std::pow(val,val2); res = (t)(c=='+'?val:c=='-'?-val:!val); is_success = true; break; + return false; + } + if (__eval_get(ptr,val1)) { // Detect 'value1' op 'value2' + switch (*ptr) { + case 0 : res = (t)val1; return true; + case '+' : __eval_op(val1 + val2); + case '-' : __eval_op(val1 - val2); + case '*' : __eval_op(val1 * val2); + case '/' : __eval_op(val1 / val2); + case '%' : __eval_op(cimg::mod(val1,val2)); + case '&' : if (ptr[1]=='&') { ++ptr; __eval_op(val1 && val2); } else { __eval_op((long)val1 & (long)val2); } + case '|' : if (ptr[1]=='|') { ++ptr; __eval_op(val1 || val2); } else { __eval_op((long)val1 | (long)val2); } + case '>' : if (ptr[1]=='=') { ++ptr; __eval_op(val1>=val2); } else { __eval_op(val1>val2); } + case '<' : if (ptr[1]=='=') { ++ptr; __eval_op(val1<=val2); } else { __eval_op(val1<val2); } + case ';' : __eval_op(val2); + case '^' : __eval_op(std::pow(val1,val2)); + case '=' : if (*++ptr=='=') { __eval_op(val1==val2); } else return false; + case '!' : if (*++ptr=='=') { __eval_op(val1!=val2); } else return false; + } + } + return false; + } + + // Return 'true' is a single 'value' or '!value' has been succesfully read ('value' being a double or { w,h,d,s }). + bool __eval_get(const char* &ptr, double &value) const { + int n = 0; + while (*ptr && cimg::is_blank(*ptr)) ++ptr; + + bool is_not = false; // Detect preceding '!' operator + if (*ptr=='!') { is_not = true; ++ptr; while (*ptr && cimg::is_blank(*ptr)) ++ptr; } + + if ((*ptr=='w' || *ptr=='h' || *ptr=='d' || *ptr=='s' || *ptr=='r') || std::sscanf(ptr,"%lf %n",&value,&n)==1) { + if (!n) { + switch (*ptr) { + case 'w': value = (double)_width; break; + case 'h': value = (double)_height; break; + case 'd': value = (double)_depth; break; + case 's': value = (double)_spectrum; break; + case 'r': value = (double)_is_shared; break; } - } - } else if (!expression[1]) switch (*expression) { // Other common single-char expressions - case 'w' : res = (t)_width; is_success = true; break; - case 'h' : res = (t)_height; is_success = true; break; - case 'd' : res = (t)_depth; is_success = true; break; - case 's' : res = (t)_spectrum; is_success = true; break; - case 'r' : res = (t)_is_shared; is_success = true; break; - } - return is_success; + ++ptr; while (*ptr && cimg::is_blank(*ptr)) ++ptr; + } else ptr+=n; + if (is_not) value = (double)!value; + return true; + } + return false; } double _eval(CImg<T> *const img_output, const char *const expression, @@ -29737,32 +30226,36 @@ namespace cimg_library { //! Compute norm of the image, viewed as a matrix. /** - \param magnitude_type Norm type. Can be: - - \c -1: Linf-norm + \param magnitude_type Can be: - \c 0: L0-norm - \c 1: L1-norm - \c 2: L2-norm + - \c p>2 : Lp-norm + - \c ~0U: Linf-norm **/ - double magnitude(const int magnitude_type=2) const { + double magnitude(const float magnitude_type=2) const { if (is_empty()) throw CImgInstanceException(_cimg_instance "magnitude(): Empty instance.", cimg_instance); const ulongT siz = size(); double res = 0; - switch (magnitude_type) { - case -1 : { - cimg_for(*this,ptrs,T) { const double val = (double)cimg::abs(*ptrs); if (val>res) res = val; } - } break; - case 1 : { - cimg_pragma_openmp(parallel for reduction(+:res) cimg_openmp_if_size(size(),8192)) - for (longT off = 0; off<(longT)siz; ++off) res+=(double)cimg::abs(_data[off]); - } break; - default : { + if (magnitude_type==2) { // L2 cimg_pragma_openmp(parallel for reduction(+:res) cimg_openmp_if_size(size(),8192)) for (longT off = 0; off<(longT)siz; ++off) res+=(double)cimg::sqr(_data[off]); res = (double)std::sqrt(res); - } + } else if (magnitude_type==1) { // L1 + cimg_pragma_openmp(parallel for reduction(+:res) cimg_openmp_if_size(size(),8192)) + for (longT off = 0; off<(longT)siz; ++off) res+=(double)cimg::abs(_data[off]); + } else if (!magnitude_type) { // L0 + cimg_pragma_openmp(parallel for reduction(+:res) cimg_openmp_if_size(size(),8192)) + for (longT off = 0; off<(longT)siz; ++off) res+=(double)(_data[off]?1:0); + } else if (cimg::type<float>::is_inf(magnitude_type)) { // L-inf + cimg_for(*this,ptrs,T) { const double val = (double)cimg::abs(*ptrs); if (val>res) res = val; } + } else { // L-p + cimg_pragma_openmp(parallel for reduction(+:res) cimg_openmp_if_size(size(),8192)) + for (longT off = 0; off<(longT)siz; ++off) res+=(double)std::pow(cimg::abs(_data[off]),magnitude_type); + res = (double)std::pow(res,1.0/magnitude_type); } return res; } @@ -30033,7 +30526,7 @@ namespace cimg_library { /** If the instance matrix is not square, the Moore-Penrose pseudo-inverse is computed instead. \param use_LU Choose the inverting algorithm. Can be: - - \c true: LU solver (faster but less precise). + - \c true: LU solver (faster but sometimes less precise). - \c false: SVD solver (more precise but slower). \param lambda is used only in the Moore-Penrose pseudoinverse for estimating A^t.(A^t.A + lambda.Id)^-1. **/ @@ -30972,7 +31465,7 @@ namespace cimg_library { cimg_forX(*this,x) { CImg<Tfloat> S = get_column(x); const CImg<Tfloat> S0 = method<2?CImg<Tfloat>():S; - Tfloat residual = S.magnitude()/S._height; + Tfloat residual = S.magnitude(2)/S._height; const unsigned int nmax = max_iter?max_iter:D._width; for (unsigned int n = 0; n<nmax && residual>max_residual; ++n) { @@ -31027,7 +31520,7 @@ namespace cimg_library { W(x,ind) = weight; cimg_forY(S,y) S[y]-=weight*D(ind,y); } - residual = S.magnitude()/S._height; + residual = S.magnitude(2)/S._height; is_orthoproj = true; } } @@ -32187,103 +32680,108 @@ namespace cimg_library { **/ CImg<T>& fill(const char *const expression, const bool repeat_values, const bool allow_formula=true, CImgList<T> *const list_images=0) { - return _fill(expression,repeat_values,allow_formula?1:0,list_images,"fill",0); + return _fill(expression,repeat_values,allow_formula?3:1,list_images,"fill",0,0); } - // 'formula_mode' = { 0 = does not allow formula | 1 = allow formula | - // 2 = allow formula and do not fill image values }. - CImg<T>& _fill(const char *const expression, const bool repeat_values, const unsigned int formula_mode, - CImgList<T> *const list_images, const char *const calling_function, const CImg<T> *provides_copy) { + // bits of 'mode' can enable/disable these properties: + // . 1 = Allow list of values. + // . 2 = Allow formula. + // . 4 = Evaluate but does not fill image values. + CImg<T>& _fill(const char *const expression, const bool repeat_values, const unsigned int mode, + CImgList<T> *const list_images, const char *const calling_function, + const CImg<T> *provides_copy, CImg<doubleT> *const result_end) { if (is_empty() || !expression || !*expression) return *this; - const unsigned int omode = cimg::exception_mode(); + const unsigned int excmode = cimg::exception_mode(); cimg::exception_mode(0); CImg<charT> is_error_expr; - bool is_error_seq = false, is_value_sequence = false; + bool is_done = false, is_value_sequence = false; cimg_abort_init; + if (result_end) result_end->assign(); - if (formula_mode) { - - // Try to pre-detect regular value sequence to avoid exception thrown by _cimg_math_parser. + // Detect value sequence. + if (mode&1) { double value; char sep; const int err = cimg_sscanf(expression,"%lf %c",&value,&sep); if (err==1 || (err==2 && sep==',')) { - if (err==1) { if (formula_mode==2) return *this; return fill((T)value); } + if (err==1) { if (mode&4) return *this; return fill((T)value); } else is_value_sequence = true; } + } - // Try to fill values according to a formula. + // Try to fill values according to a formula. + if (mode&2 && !is_value_sequence) { _cimg_abort_init_openmp; - if (!is_value_sequence) try { - CImg<T> base = provides_copy?provides_copy->get_shared():get_shared(); - _cimg_math_parser mp(expression + (*expression=='>' || *expression=='<' || - *expression=='*' || *expression==':'), - calling_function,base,this,list_images,true); - if (!provides_copy && expression && *expression!='>' && *expression!='<' && *expression!=':' && - mp.need_input_copy) - base.assign().assign(*this,false); // Needs input copy + try { + CImg<T> base = provides_copy?provides_copy->get_shared():get_shared(); + _cimg_math_parser mp(expression + (*expression=='>' || *expression=='<' || + *expression=='*' || *expression==':'), + calling_function,base,this,list_images,true); + if (!provides_copy && expression && *expression!='>' && *expression!='<' && *expression!=':' && + mp.need_input_copy) + base.assign().assign(*this,false); // Needs input copy - // Determine 2nd largest image dimension (used as axis for inner loop in parallelized evaluation). - unsigned int M; - if (mp.result_dim) { - M = cimg::max(_width,_height,_depth); - M = M==_width?std::max(_height,_depth):M==_height?std::max(_width,_depth):std::max(_width,_height); - } else { - M = cimg::max(_width,_height,_depth,_spectrum); - M = M==_width?cimg::max(_height,_depth,_spectrum): - M==_height?cimg::max(_width,_depth,_spectrum): - M==_depth?cimg::max(_width,_height,_spectrum):cimg::max(_width,_height,_depth); - } + // Determine M2, smallest image dimension (used as axis for the most inner loop in parallelized iterations). + // M1 is the total number of parallelized iterations. + unsigned int M1 = 0, M2 = 0; + cimg::unused(M1,M2); + if (mp.result_dim) { + M2 = cimg::min(_width,_height,_depth); + M1 = M2==_width?_height*_depth:M2==_height?_width*_depth:_width*_height; + } else { + M2 = cimg::min(_width,_height,_depth,_spectrum); + M1 = M2==_width?_height*_depth*_spectrum:M2==_height?_width*_depth*_spectrum: + M2==_depth?_width*_height*_spectrum:_width*_height*_depth; + } - bool do_in_parallel = false; + bool do_in_parallel = false; #if cimg_use_openmp!=0 - if (mp.is_noncritical_run && (*expression=='*' || *expression==':')) - throw CImgArgumentException(_cimg_instance - "%s(): Cannot evaluate expression '%s' in parallel, " - "as 'run()' is used outside a 'critical()' section.", - cimg_instance,calling_function,expression); - cimg_openmp_if(!mp.is_noncritical_run && - (*expression=='*' || *expression==':' || - (mp.is_parallelizable && M>=(cimg_openmp_sizefactor)*320 && size()/M>=2))) - do_in_parallel = true; + if (mp.is_noncritical_run && (*expression=='*' || *expression==':')) + throw CImgArgumentException(_cimg_instance + "%s(): Cannot evaluate expression '%s' in parallel, " + "as 'run()' is used outside a 'critical()' section.", + cimg_instance,calling_function,expression); + cimg_openmp_if(!mp.is_noncritical_run && + (*expression=='*' || *expression==':' || (mp.is_parallelizable && M1>=2 && M1*M2>=16))) + do_in_parallel = true; #endif - if (mp.result_dim) { // Vector-valued expression - const unsigned int N = std::min(mp.result_dim,_spectrum); - const ulongT whd = (ulongT)_width*_height*_depth; - T *ptrd = *expression=='<'?_data + _width*_height*_depth - 1:_data; + if (mp.result_dim) { // Vector-valued expression + const unsigned int N = std::min(mp.result_dim,_spectrum); + const ulongT whd = (ulongT)_width*_height*_depth; + T *ptrd = *expression=='<'?_data + _width*_height*_depth - 1:_data; - if (*expression=='<') { - CImg<doubleT> res(1,mp.result_dim); - mp.begin_t(); - cimg_rofYZ(*this,y,z) { - cimg_abort_test; - if (formula_mode==2) cimg_rofX(*this,x) mp(x,y,z,0); - else cimg_rofX(*this,x) { - mp(x,y,z,0,res._data); - const double *ptrs = res._data; - T *_ptrd = ptrd--; for (unsigned int n = N; n>0; --n) { *_ptrd = (T)(*ptrs++); _ptrd+=whd; } - } - } - mp.end_t(); + if (*expression=='<') { + CImg<doubleT> res(1,mp.result_dim); + mp.begin_t(); + cimg_rofYZ(*this,y,z) { + cimg_abort_test; + if (mode&4) cimg_rofX(*this,x) mp(x,y,z,0); + else cimg_rofX(*this,x) { + mp(x,y,z,0,res._data); + const double *ptrs = res._data; + T *_ptrd = ptrd--; for (unsigned int n = N; n>0; --n) { *_ptrd = (T)(*ptrs++); _ptrd+=whd; } + } + } + mp.end_t(); - } else if (*expression=='>' || !do_in_parallel) { - CImg<doubleT> res(1,mp.result_dim); - mp.begin_t(); - cimg_forYZ(*this,y,z) { - cimg_abort_test; - if (formula_mode==2) cimg_forX(*this,x) mp(x,y,z,0); - else cimg_forX(*this,x) { - mp(x,y,z,0,res._data); - const double *ptrs = res._data; - T *_ptrd = ptrd++; for (unsigned int n = N; n>0; --n) { *_ptrd = (T)(*ptrs++); _ptrd+=whd; } - } - } - mp.end_t(); + } else if (*expression=='>' || !do_in_parallel) { + CImg<doubleT> res(1,mp.result_dim); + mp.begin_t(); + cimg_forYZ(*this,y,z) { + cimg_abort_test; + if (mode&4) cimg_forX(*this,x) mp(x,y,z,0); + else cimg_forX(*this,x) { + mp(x,y,z,0,res._data); + const double *ptrs = res._data; + T *_ptrd = ptrd++; for (unsigned int n = N; n>0; --n) { *_ptrd = (T)(*ptrs++); _ptrd+=whd; } + } + } + mp.end_t(); - } else { + } else { #if cimg_use_openmp!=0 - cimg_pragma_openmp(parallel) + cimg_pragma_openmp(parallel) { _cimg_math_parser *const _mp = omp_get_thread_num()?new _cimg_math_parser(mp):&mp, @@ -32296,7 +32794,7 @@ namespace cimg_library { cimg_pragma_openmp(for cimg_openmp_collapse(2)) \ cimg_for##_YZ(*this,_y,_z) _cimg_abort_try_openmp { \ cimg_abort_test; \ - if (formula_mode==2) cimg_for##_X(*this,_x) lmp(x,y,z,0); \ + if (mode&4) cimg_for##_X(*this,_x) lmp(x,y,z,0); \ else { \ CImg<doubleT> res(1,lmp.result_dim); \ T *__ptrd = data(_sx,_sy,_sz,0); \ @@ -32311,8 +32809,8 @@ namespace cimg_library { } \ } _cimg_abort_catch_openmp _cimg_abort_catch_fill_openmp - if (M==_width) { _cimg_fill_openmp_vector(YZ,y,z,X,x,0,y,z,1) } - else if (M==_height) { _cimg_fill_openmp_vector(XZ,x,z,Y,y,x,0,z,_width) } + if (M2==_width) { _cimg_fill_openmp_vector(YZ,y,z,X,x,0,y,z,1) } + else if (M2==_height) { _cimg_fill_openmp_vector(XZ,x,z,Y,y,x,0,z,_width) } else { _cimg_fill_openmp_vector(XY,x,y,Z,z,x,y,0,_width*_height) } lmp.end_t(); @@ -32320,26 +32818,26 @@ namespace cimg_library { if (&lmp!=&mp) delete &lmp; } #endif - } + } - } else { // Scalar-valued expression - T *ptrd = *expression=='<'?end() - 1:_data; - if (*expression=='<') { - mp.begin_t(); - if (formula_mode==2) cimg_rofYZC(*this,y,z,c) { cimg_abort_test; cimg_rofX(*this,x) mp(x,y,z,c); } + } else { // Scalar-valued expression + T *ptrd = *expression=='<'?end() - 1:_data; + if (*expression=='<') { + mp.begin_t(); + if (mode&4) cimg_rofYZC(*this,y,z,c) { cimg_abort_test; cimg_rofX(*this,x) mp(x,y,z,c); } else cimg_rofYZC(*this,y,z,c) { cimg_abort_test; cimg_rofX(*this,x) *(ptrd--) = (T)mp(x,y,z,c); } - mp.end_t(); + mp.end_t(); - } else if (*expression=='>' || !do_in_parallel) { - mp.begin_t(); - if (formula_mode==2) cimg_forYZC(*this,y,z,c) { cimg_abort_test; cimg_forX(*this,x) mp(x,y,z,c); } - else cimg_forYZC(*this,y,z,c) { cimg_abort_test; cimg_forX(*this,x) *(ptrd++) = (T)mp(x,y,z,c); } - mp.end_t(); + } else if (*expression=='>' || !do_in_parallel) { + mp.begin_t(); + if (mode&4) cimg_forYZC(*this,y,z,c) { cimg_abort_test; cimg_forX(*this,x) mp(x,y,z,c); } + else cimg_forYZC(*this,y,z,c) { cimg_abort_test; cimg_forX(*this,x) *(ptrd++) = (T)mp(x,y,z,c); } + mp.end_t(); - } else { + } else { #if cimg_use_openmp!=0 - cimg_pragma_openmp(parallel) + cimg_pragma_openmp(parallel) { _cimg_math_parser *const _mp = omp_get_thread_num()?new _cimg_math_parser(mp):&mp, @@ -32352,7 +32850,7 @@ namespace cimg_library { cimg_pragma_openmp(for cimg_openmp_collapse(3)) \ cimg_for##_YZC(*this,_y,_z,_c) _cimg_abort_try_openmp { \ cimg_abort_test; \ - if (formula_mode==2) cimg_for##_X(*this,_x) lmp(x,y,z,c); \ + if (mode&4) cimg_for##_X(*this,_x) lmp(x,y,z,c); \ else { \ T *_ptrd = data(_sx,_sy,_sz,_sc); \ const ulongT off = (ulongT)_off; \ @@ -32360,9 +32858,9 @@ namespace cimg_library { } \ } _cimg_abort_catch_openmp _cimg_abort_catch_fill_openmp - if (M==_width) { _cimg_fill_openmp_scalar(YZC,y,z,c,X,x,0,y,z,c,1) } - else if (M==_height) { _cimg_fill_openmp_scalar(XZC,x,z,c,Y,y,x,0,z,c,_width) } - else if (M==_depth) { _cimg_fill_openmp_scalar(XYC,x,y,c,Z,z,x,y,0,c,_width*_height) } + if (M2==_width) { _cimg_fill_openmp_scalar(YZC,y,z,c,X,x,0,y,z,c,1) } + else if (M2==_height) { _cimg_fill_openmp_scalar(XZC,x,z,c,Y,y,x,0,z,c,_width) } + else if (M2==_depth) { _cimg_fill_openmp_scalar(XYC,x,y,c,Z,z,x,y,0,c,_width*_height) } else { _cimg_fill_openmp_scalar(XYZ,x,y,z,C,c,x,y,z,0,_width*_height*_depth) } lmp.end_t(); @@ -32370,24 +32868,30 @@ namespace cimg_library { if (&lmp!=&mp) delete &lmp; } #endif - } } - mp.end(); - } catch (CImgException& e) { CImg<charT>::string(e._message).move_to(is_error_expr); } + } + mp.end(); + + if (result_end && mp.result_end) // Transfer result of the end() block if requested. + result_end->assign(mp.result_end + (mp.result_end_dim?1:0),std::max(1U,mp.result_end_dim)); + + is_done = true; + } catch (CImgException& e) { CImg<charT>::string(e._message).move_to(is_error_expr); } } // Try to fill values according to a value sequence. - if (!formula_mode || is_value_sequence || is_error_expr) { - is_error_seq = _fill_from_values(expression,repeat_values); - cimg::exception_mode(omode); - if (is_error_seq) { - if (is_error_expr) throw CImgArgumentException("%s",is_error_expr._data); - else throw CImgArgumentException(_cimg_instance - "%s(): Invalid sequence of filling values '%s'.", - cimg_instance,calling_function,expression); - } + if (!is_done && mode&1) is_done = !_fill_from_values(expression,repeat_values); + + if (!is_done) { + cimg::exception_mode(excmode); + if (is_error_expr) throw CImgArgumentException(_cimg_instance + "%s", + cimg_instance,is_error_expr._data); + else throw CImgArgumentException(_cimg_instance + "%s(): Invalid sequence of filling values '%s'.", + cimg_instance,calling_function,expression); } - cimg::exception_mode(omode); + cimg::exception_mode(excmode); cimg_abort_test; return *this; } @@ -32417,7 +32921,7 @@ namespace cimg_library { } // Fill image according to a value sequence, given as a string. - // Return 'true' if an error occured. + // Return 'true' if an error occured, 'false' otherwise. bool _fill_from_values(const char *const values, const bool repeat_values) { CImg<charT> item(256); const char *nvalues = values; @@ -46691,7 +47195,7 @@ namespace cimg_library { *(ptrd++) = (float)color._spectrum; cimg_foroff(color,l) *(ptrd++) = (float)*(ptrc++); } else { - *(ptrd++) = (float)shared_ind; + *(ptrd++) = (float)cimg::uint2float((unsigned int)shared_ind); *(ptrd++) = 0; *(ptrd++) = 0; } @@ -46723,7 +47227,7 @@ namespace cimg_library { *(ptrd++) = (float)opacity._spectrum; cimg_foroff(opacity,l) *(ptrd++) = (float)*(ptro++); } else { - *(ptrd++) = (float)shared_ind; + *(ptrd++) = (float)cimg::uint2float((unsigned int)shared_ind); *(ptrd++) = 0; *(ptrd++) = 0; } @@ -46835,24 +47339,32 @@ namespace cimg_library { const unsigned int nb_inds = (unsigned int)*(ptrs++); primitives[p].assign(1,nb_inds); tp *ptrp = primitives[p]._data; - for (unsigned int i = 0; i<nb_inds; ++i) *(ptrp++) = (tp)cimg::float2uint((float)*(ptrs++)); + for (unsigned int i = 0; i<nb_inds; ++i) + *(ptrp++) = (tp)cimg::float2uint((float)*(ptrs++)); } colors.assign(nb_primitives); + cimglist_for(colors,c) { if (*ptrs==(T)-128) { ++ptrs; - const unsigned int w = (unsigned int)*(ptrs++), h = (unsigned int)*(ptrs++), s = (unsigned int)*(ptrs++); + const unsigned int + w = (unsigned int)cimg::float2uint((float)*(ptrs++)), + h = (unsigned int)*(ptrs++), + s = (unsigned int)*(ptrs++); if (!h && !s) colors[c].assign(colors[w],true); - else { colors[c].assign(ptrs,w,h,1,s,false); ptrs+=w*h*s; } + else { colors[c].assign(ptrs,w,h,1,s,false); ptrs+=(ulongT)w*h*s; } } else { colors[c].assign(ptrs,1,1,1,3,false); ptrs+=3; } } opacities.assign(nb_primitives); cimglist_for(opacities,o) { if (*ptrs==(T)-128) { ++ptrs; - const unsigned int w = (unsigned int)*(ptrs++), h = (unsigned int)*(ptrs++), s = (unsigned int)*(ptrs++); + const unsigned int + w = (unsigned int)cimg::float2uint((float)*(ptrs++)), + h = (unsigned int)*(ptrs++), + s = (unsigned int)*(ptrs++); if (!h && !s) opacities[o].assign(opacities[w],true); - else { opacities[o].assign(ptrs,w,h,1,s,false); ptrs+=w*h*s; } + else { opacities[o].assign(ptrs,w,h,1,s,false); ptrs+=(ulongT)w*h*s; } } else opacities[o].assign(1,1,1,1,*(ptrs++)); } return points; @@ -47040,24 +47552,22 @@ namespace cimg_library { if (is_empty() || !opacity || !pattern || std::min(y0,y1)>=height() || std::max(y0,y1)<0 || std::min(x0,x1)>=width() || std::max(x0,x1)<0) return *this; - int w1 = width() - 1, h1 = height() - 1, dx01 = x1 - x0, dy01 = y1 - y0; const bool is_horizontal = cimg::abs(dx01)>cimg::abs(dy01); if (is_horizontal) cimg::swap(x0,y0,x1,y1,w1,h1,dx01,dy01); - if (pattern==~0U && y0>y1) { - cimg::swap(x0,x1,y0,y1); - dx01*=-1; dy01*=-1; - } + if (pattern==~0U && y0>y1) { cimg::swap(x0,x1,y0,y1); dx01*=-1; dy01*=-1; } static unsigned int hatch = ~0U - (~0U>>1); if (init_hatch) hatch = ~0U - (~0U>>1); cimg_init_scanline(opacity); const int - step = y0<=y1?1:-1,hdy01 = dy01*cimg::sign(dx01)/2, - cy0 = cimg::cut(y0,0,h1), cy1 = cimg::cut(y1,0,h1) + step; + step = y0<=y1?1:-1, + hdy01 = dy01*cimg::sign(dx01)/2, + cy0 = cimg::cut(y0,0,h1), + cy1 = cimg::cut(y1,0,h1) + step; dy01+=dy01?0:1; for (int y = cy0; y!=cy1; y+=step) { @@ -47432,29 +47942,32 @@ namespace cimg_library { - This function uses several call to the single CImg::draw_line() procedure, depending on the vectors size in \p points. **/ - template<typename t, typename tc> - CImg<T>& draw_line(const CImg<t>& points, + template<typename tp, typename tc> + CImg<T>& draw_line(const CImg<tp>& points, const tc *const color, const float opacity=1, const unsigned int pattern=~0U, const bool init_hatch=true) { - if (is_empty() || !points || points._width<2) return *this; - bool ninit_hatch = init_hatch; - switch (points._height) { - case 0 : case 1 : + if (is_empty() || !points) return *this; + if (!color) throw CImgArgumentException(_cimg_instance - "draw_line(): Invalid specified point set (%u,%u,%u,%u,%p).", + "draw_line(): Specified color is (null).", + cimg_instance); + if (points.height()!=2) + throw CImgArgumentException(_cimg_instance + "draw_line(): Invalid specified point set (%u,%u,%u,%u).", cimg_instance, - points._width,points._height,points._depth,points._spectrum,points._data); + points._width,points._height,points._depth,points._spectrum); + CImg<intT> ipoints; + if (cimg::type<tp>::is_float()) ipoints = points.get_round(); + else ipoints.assign(points,cimg::type<tp>::string()==cimg::type<int>::string()); - default : { - const int x0 = (int)points(0,0), y0 = (int)points(0,1); - int ox = x0, oy = y0; - for (unsigned int i = 1; i<points._width; ++i) { - const int x = (int)points(i,0), y = (int)points(i,1); - draw_line(ox,oy,x,y,color,opacity,pattern,ninit_hatch); - ninit_hatch = false; - ox = x; oy = y; - } - } + bool ninit_hatch = init_hatch; + const int x0 = ipoints(0,0), y0 = ipoints(0,1); + int ox = x0, oy = y0; + for (unsigned int i = 1; i<ipoints._width; ++i) { + const int x = ipoints(i,0), y = ipoints(i,1); + draw_line(ox,oy,x,y,color,opacity,pattern,ninit_hatch); + ninit_hatch = false; + ox = x; oy = y; } return *this; } @@ -49410,45 +49923,43 @@ namespace cimg_library { "draw_polygon(): Invalid specified point set (%u,%u,%u,%u).", cimg_instance, points._width,points._height,points._depth,points._spectrum); - if (points._width==1) return draw_point(cimg::uiround(points(0,0)),cimg::uiround(points(0,1)),color,opacity); - if (points._width==2) return draw_line(cimg::uiround(points(0,0)),cimg::uiround(points(0,1)), - cimg::uiround(points(1,0)),cimg::uiround(points(1,1)),color,opacity); - if (points._width==3) return draw_triangle(cimg::uiround(points(0,0)),cimg::uiround(points(0,1)), - cimg::uiround(points(1,0)),cimg::uiround(points(1,1)), - cimg::uiround(points(2,0)),cimg::uiround(points(2,1)),color,opacity); + CImg<intT> ipoints; + if (cimg::type<tp>::is_float()) ipoints = points.get_round(); + else ipoints.assign(points,cimg::type<tp>::string()==cimg::type<int>::string()); + + if (ipoints._width==1) return draw_point(ipoints(0,0),ipoints(0,1),color,opacity); + if (ipoints._width==2) return draw_line(ipoints(0,0),ipoints(0,1),ipoints(1,0),ipoints(1,1),color,opacity); + if (ipoints._width==3) return draw_triangle(ipoints(0,0),ipoints(0,1),ipoints(1,0),ipoints(1,1), + ipoints(2,0),ipoints(2,1),color,opacity); cimg_init_scanline(opacity); int xmin = 0, ymin = 0, - xmax = points.get_shared_row(0).max_min(xmin), - ymax = points.get_shared_row(1).max_min(ymin); + xmax = ipoints.get_shared_row(0).max_min(xmin), + ymax = ipoints.get_shared_row(1).max_min(ymin); if (xmax<0 || xmin>=width() || ymax<0 || ymin>=height()) return *this; if (ymin==ymax) return draw_line(xmin,ymin,xmax,ymax,color,opacity); ymin = std::max(0,ymin); ymax = std::min(height() - 1,ymax); - CImg<intT> Xs(points._width,ymax - ymin + 1); + CImg<intT> Xs(ipoints._width,ymax - ymin + 1); CImg<uintT> count(Xs._height,1,1,1,0); unsigned int n = 0, nn = 1; bool go_on = true; while (go_on) { - unsigned int an = (nn + 1)%points._width; - const int - x0 = cimg::uiround(points(n,0)), - y0 = cimg::uiround(points(n,1)); - if (points(nn,1)==y0) while (points(an,1)==y0) { nn = an; (an+=1)%=points._width; } - const int - x1 = cimg::uiround(points(nn,0)), - y1 = cimg::uiround(points(nn,1)); + unsigned int an = (nn + 1)%ipoints._width; + const int x0 = ipoints(n,0), y0 = ipoints(n,1); + if (ipoints(nn,1)==y0) while (ipoints(an,1)==y0) { nn = an; (an+=1)%=ipoints._width; } + const int x1 = ipoints(nn,0), y1 = ipoints(nn,1); unsigned int tn = an; - while (points(tn,1)==y1) (tn+=1)%=points._width; - + while (ipoints(tn,1)==y1) (tn+=1)%=ipoints._width; if (y0!=y1) { const int - y2 = cimg::uiround(points(tn,1)), + y2 = ipoints(tn,1), x01 = x1 - x0, y01 = y1 - y0, y12 = y2 - y1, step = cimg::sign(y01), - tmax = std::max(1,cimg::abs(y01)), htmax = tmax*cimg::sign(x01)/2, + tmax = std::max(1,cimg::abs(y01)), + htmax = tmax*cimg::sign(x01)/2, tend = tmax - (step==cimg::sign(y12)); unsigned int y = (unsigned int)y0 - ymin; for (int t = 0; t<=tend; ++t, y+=step) @@ -49475,43 +49986,38 @@ namespace cimg_library { } //! Draw a outlined 2D or 3D polygon \overloading. - template<typename t, typename tc> - CImg<T>& draw_polygon(const CImg<t>& points, + template<typename tp, typename tc> + CImg<T>& draw_polygon(const CImg<tp>& points, const tc *const color, const float opacity, const unsigned int pattern) { if (is_empty() || !points) return *this; if (!color) throw CImgArgumentException(_cimg_instance "draw_polygon(): Specified color is (null).", cimg_instance); - if (points._width==1) return draw_point((int)points(0,0),(int)points(0,1),color,opacity); - if (points._width==2) return draw_line((int)points(0,0),(int)points(0,1), - (int)points(1,0),(int)points(1,1),color,opacity,pattern); - bool ninit_hatch = true; - switch (points._height) { - case 0 : case 1 : + if (points.height()!=2) throw CImgArgumentException(_cimg_instance "draw_polygon(): Invalid specified point set (%u,%u,%u,%u).", cimg_instance, points._width,points._height,points._depth,points._spectrum); - default : { - CImg<intT> npoints(points._width,2); - int x = npoints(0,0) = (int)points(0,0), y = npoints(0,1) = (int)points(0,1); - unsigned int nb_points = 1; - for (unsigned int p = 1; p<points._width; ++p) { - const int nx = (int)points(p,0), ny = (int)points(p,1); - if (nx!=x || ny!=y) { npoints(nb_points,0) = nx; npoints(nb_points++,1) = ny; x = nx; y = ny; } - } - const int x0 = (int)npoints(0,0), y0 = (int)npoints(0,1); - int ox = x0, oy = y0; - for (unsigned int i = 1; i<nb_points; ++i) { - const int _x = (int)npoints(i,0), _y = (int)npoints(i,1); - draw_line(ox,oy,_x,_y,color,opacity,pattern,ninit_hatch); - ninit_hatch = false; - ox = _x; oy = _y; - } - draw_line(ox,oy,x0,y0,color,opacity,pattern,false); - } + CImg<intT> ipoints; + if (cimg::type<tp>::is_float()) ipoints = points.get_round(); + else ipoints.assign(points,cimg::type<tp>::string()==cimg::type<int>::string()); + + if (ipoints._width==1) return draw_point(ipoints(0,0),ipoints(0,1),color,opacity); + if (ipoints._width==2) return draw_line(ipoints(0,0),ipoints(0,1),ipoints(1,0),ipoints(1,1), + color,opacity,pattern); + if (ipoints._width==3) return draw_triangle(ipoints(0,0),ipoints(0,1),ipoints(1,0),ipoints(1,1), + ipoints(2,0),ipoints(2,1),color,opacity,pattern); + bool ninit_hatch = true; + const int x0 = ipoints(0,0), y0 = ipoints(0,1); + int ox = x0, oy = y0; + for (unsigned int i = 1; i<ipoints._width; ++i) { + const int x = ipoints(i,0), y = ipoints(i,1); + draw_line(ox,oy,x,y,color,opacity,pattern,ninit_hatch); + ninit_hatch = false; + ox = x; oy = y; } + draw_line(ox,oy,x0,y0,color,opacity,pattern,false); return *this; } @@ -50125,7 +50631,7 @@ namespace cimg_library { const unsigned int cmin = std::min(_spectrum,letter._spectrum); if (foreground_color) for (unsigned int c = 0; c<cmin; ++c) - if (foreground_color[c]!=1) letter.get_shared_channel(c)*=foreground_color[c]/255; + if (foreground_color[c]!=255) letter.get_shared_channel(c)*=foreground_color[c]/255.0f; if (mask) { // Letter has mask if (background_color) for (unsigned int c = 0; c<cmin; ++c) @@ -52708,7 +53214,13 @@ namespace cimg_library { static unsigned int snap_number = 0; std::FILE *file; do { - cimg_snprintf(filename,filename._width,cimg_appname "_%.4u.bmp",snap_number++); + cimg_snprintf(filename,filename._width,cimg_appname "_%.6u." +#ifdef cimg_use_png + "png", +#else + "bmp", +#endif + snap_number++); if ((file=cimg::std_fopen(filename,"r"))!=0) cimg::fclose(file); } while (file); if (visu0) { @@ -52724,9 +53236,9 @@ namespace cimg_library { do { #ifdef cimg_use_zlib - cimg_snprintf(filename,filename._width,cimg_appname "_%.4u.cimgz",snap_number++); + cimg_snprintf(filename,filename._width,cimg_appname "_%.6u.cimgz",snap_number++); #else - cimg_snprintf(filename,filename._width,cimg_appname "_%.4u.cimg",snap_number++); + cimg_snprintf(filename,filename._width,cimg_appname "_%.6u.cimg",snap_number++); #endif if ((file=cimg::std_fopen(filename,"r"))!=0) cimg::fclose(file); } while (file); @@ -53220,7 +53732,7 @@ namespace cimg_library { return res; } - // Return a visualizable uchar8 image for display routines. + // Return a visualizable 'uchar8' image for display routines. CImg<ucharT> _get_select(const CImgDisplay& disp, const int normalization, const int x, const int y, const int z) const { if (is_empty()) return CImg<ucharT>(1,1,1,1,0); @@ -53420,7 +53932,7 @@ namespace cimg_library { unsigned int len = (unsigned int)std::strlen(message); cimg_forC(*this,c) cimg_snprintf(message._data + len,message._width - len,"%g ",(double)(*this)(x,0,0,c)); - len = std::strlen(message); + len = (unsigned int)std::strlen(message); cimg_snprintf(message._data + len,message._width - len,")"); } if (x0>=0 && x1>=0) { @@ -53483,7 +53995,13 @@ namespace cimg_library { CImg<ucharT> &screen = visu?visu:visu0; std::FILE *file; do { - cimg_snprintf(filename,filename._width,cimg_appname "_%.4u.bmp",snap_number++); + cimg_snprintf(filename,filename._width,cimg_appname "_%.6u." +#ifdef cimg_use_png + "png", +#else + "bmp", +#endif + snap_number++); if ((file=cimg::std_fopen(filename,"r"))!=0) cimg::fclose(file); } while (file); (+screen).__draw_text(" Saving snapshot... ",font_size,0).display(disp); @@ -53500,9 +54018,9 @@ namespace cimg_library { do { #ifdef cimg_use_zlib - cimg_snprintf(filename,filename._width,cimg_appname "_%.4u.cimgz",snap_number++); + cimg_snprintf(filename,filename._width,cimg_appname "_%.6u.cimgz",snap_number++); #else - cimg_snprintf(filename,filename._width,cimg_appname "_%.4u.cimg",snap_number++); + cimg_snprintf(filename,filename._width,cimg_appname "_%.6u.cimg",snap_number++); #endif if ((file=cimg::std_fopen(filename,"r"))!=0) cimg::fclose(file); } while (file); @@ -54891,7 +55409,7 @@ namespace cimg_library { \param[out] description Description, as stored in the filename. \note - libtiff support is enabled by defining the precompilation - directive \c cimg_use_tif. + directive \c cimg_use_tiff. - When libtiff is enabled, 2D and 3D (multipage) several channel per pixel are supported for <tt>char,uchar,short,ushort,float</tt> and \c double pixel types. @@ -57074,7 +57592,7 @@ namespace cimg_library { else std::fprintf(cimg::output()," (%s) = [ ",_is_shared?"shared":"non-shared"); if (!is_empty()) cimg_foroff(*this,off) { - std::fprintf(cimg::output(),"%g",(double)_data[off]); + std::fprintf(cimg::output(),cimg::type<T>::format_s(),cimg::type<T>::format(_data[off])); if (off!=siz1) std::fprintf(cimg::output(),"%s",off%_width==width1?" ; ":" "); if (off==7 && siz>16) { off = siz1 - 8; std::fprintf(cimg::output(),"... "); } } @@ -57867,7 +58385,13 @@ namespace cimg_library { static unsigned int snap_number = 0; std::FILE *file; do { - cimg_snprintf(filename,filename._width,cimg_appname "_%.4u.bmp",snap_number++); + cimg_snprintf(filename,filename._width,cimg_appname "_%.6u." +#ifdef cimg_use_png + "png", +#else + "bmp", +#endif + snap_number++); if ((file=cimg::std_fopen(filename,"r"))!=0) cimg::fclose(file); } while (file); (+visu).__draw_text(" Saving snapshot... ",font_size,0).display(disp); @@ -57879,7 +58403,7 @@ namespace cimg_library { static unsigned int snap_number = 0; std::FILE *file; do { - cimg_snprintf(filename,filename._width,cimg_appname "_%.4u.off",snap_number++); + cimg_snprintf(filename,filename._width,cimg_appname "_%.6u.off",snap_number++); if ((file=cimg::std_fopen(filename,"r"))!=0) cimg::fclose(file); } while (file); (+visu).__draw_text(" Saving object... ",font_size,0).display(disp); @@ -57893,9 +58417,9 @@ namespace cimg_library { do { #ifdef cimg_use_zlib - cimg_snprintf(filename,filename._width,cimg_appname "_%.4u.cimgz",snap_number++); + cimg_snprintf(filename,filename._width,cimg_appname "_%.6u.cimgz",snap_number++); #else - cimg_snprintf(filename,filename._width,cimg_appname "_%.4u.cimg",snap_number++); + cimg_snprintf(filename,filename._width,cimg_appname "_%.6u.cimg",snap_number++); #endif if ((file=cimg::std_fopen(filename,"r"))!=0) cimg::fclose(file); } while (file); @@ -57911,7 +58435,7 @@ namespace cimg_library { static unsigned int snap_number = 0; std::FILE *file; do { - cimg_snprintf(filename,filename._width,cimg_appname "_%.4u.eps",snap_number++); + cimg_snprintf(filename,filename._width,cimg_appname "_%.6u.eps",snap_number++); if ((file=cimg::std_fopen(filename,"r"))!=0) cimg::fclose(file); } while (file); (+visu).__draw_text(" Saving EPS snapshot... ",font_size,0).display(disp); @@ -57932,7 +58456,7 @@ namespace cimg_library { static unsigned int snap_number = 0; std::FILE *file; do { - cimg_snprintf(filename,filename._width,cimg_appname "_%.4u.svg",snap_number++); + cimg_snprintf(filename,filename._width,cimg_appname "_%.6u.svg",snap_number++); if ((file=cimg::std_fopen(filename,"r"))!=0) cimg::fclose(file); } while (file); (+visu).__draw_text(" Saving SVG snapshot... ",font_size,0).display(disp); @@ -59306,7 +59830,7 @@ namespace cimg_library { \param use_bigtiff Allow to save big tiff files (>4Gb). \note - libtiff support is enabled by defining the precompilation - directive \c cimg_use_tif. + directive \c cimg_use_tiff. - When libtiff is enabled, 2D and 3D (multipage) several channel per pixel are supported for <tt>char,uchar,short,ushort,float</tt> and \c double pixel types. @@ -63080,7 +63604,13 @@ namespace cimg_library { static unsigned int snap_number = 0; std::FILE *file; do { - cimg_snprintf(filename,filename._width,cimg_appname "_%.4u.bmp",snap_number++); + cimg_snprintf(filename,filename._width,cimg_appname "_%.6u." +#ifdef cimg_use_png + "png", +#else + "bmp", +#endif + snap_number++); if ((file=cimg::std_fopen(filename,"r"))!=0) cimg::fclose(file); } while (file); if (visu0) { @@ -63096,9 +63626,9 @@ namespace cimg_library { std::FILE *file; do { #ifdef cimg_use_zlib - cimg_snprintf(filename,filename._width,cimg_appname "_%.4u.cimgz",snap_number++); + cimg_snprintf(filename,filename._width,cimg_appname "_%.6u.cimgz",snap_number++); #else - cimg_snprintf(filename,filename._width,cimg_appname "_%.4u.cimg",snap_number++); + cimg_snprintf(filename,filename._width,cimg_appname "_%.6u.cimg",snap_number++); #endif if ((file=cimg::std_fopen(filename,"r"))!=0) cimg::fclose(file); } while (file); @@ -65112,7 +65642,12 @@ namespace cimg_library { const char *codec=0, const bool keep_open=false) const { #ifndef cimg_use_opencv cimg::unused(codec,keep_open); - return save_ffmpeg_external(filename,fps); + if (keep_open) cimg::warn(_cimglist_instance + "save_video(): Cannot output streamed video, as this requires features from the " + "OpenCV library ('-Dcimg_use_opencv') must be defined).", + cimglist_instance); + if (!is_empty()) return save_ffmpeg_external(filename,fps); + return *this; #else try { static cv::VideoWriter *writers[32] = {}; diff --git a/examples/CImg_demo.cpp b/examples/CImg_demo.cpp index 4a03db80e..ad2993276 100644 --- a/examples/CImg_demo.cpp +++ b/examples/CImg_demo.cpp @@ -1288,7 +1288,7 @@ void* item_breakout() { board.fill(0); visu0 = background; cimg_forXY(board,x,y) if (0.2f + cimg::rand(-1,1)>=0) { CImg<float> cbrick = CImg<double>::vector(100 + cimg::rand()*155,100 + cimg::rand()*155,100 + cimg::rand()*155). - unroll('v').resize(brick.width(),brick.height()); + unroll('c').resize(brick.width(),brick.height()); cimg_forC(cbrick,k) (cbrick.get_shared_channel(k).mul(brick))/=255; visu0.draw_image(x*32,y*16,cbrick); board(x,y) = 1; diff --git a/examples/image_surface3d.cpp b/examples/image_surface3d.cpp index 292125975..cba7cef87 100644 --- a/examples/image_surface3d.cpp +++ b/examples/image_surface3d.cpp @@ -119,9 +119,9 @@ int main(int argc,char **argv) { (rtype==4?"Gouraud-shaded faces":(rtype==5?"Phong-shaded faces":"Isophotes")))))); static bool first_time = true; if (rtype==6) visu.display_object3d(disp,isopoints,isoprimitives,isocolors,first_time,1,-1,true, - 500.0f,0.0f,0.0f,-5000.0f,0.0f,0.0f,true,pose.data()); + 500.0f,0.0f,0.0f,-5000.0f,0.0f,0.0f,true,pose.data(),true); else visu.display_object3d(disp,points,primitives,colors,first_time,rtype,-1,true, - 500.0f,0.0f,0.0f,-5000.0f,0.0f,0.0f,true,pose.data()); + 500.0f,0.0f,0.0f,-5000.0f,0.0f,0.0f,true,pose.data(),true); first_time = false; switch (disp.key()) { case 0: break; diff --git a/html/header.html b/html/header.html index b04d1d934..6e382d21a 100644 --- a/html/header.html +++ b/html/header.html @@ -23,7 +23,7 @@ <div class="header"> <a href="index.html"><img alt="Logo" src="img/logo_header.jpg" class="center_image" style="margin-top:1em;"/></a> <h2 style="padding-bottom: 1em"> - Latest stable version: <b><a href="http://cimg.eu/files/CImg_3.1.6.zip">3.1.6</a></b> Current pre-release: <b><a href="http://cimg.eu/files/CImg_latest.zip">3.2.0</a></b> + Latest stable version: <b><a href="http://cimg.eu/files/CImg_3.2.5.zip">3.2.5</a></b> </h2> <hr/> @@ -55,9 +55,9 @@ <li><a href='links.html'><span> <img alt="Links" src='img/menu_links.png' /> Links</span></a></li> - <li><a target="_blank" href='https://www.amazon.fr/dp/B08WRCZRR3/ref=cm_sw_em_r_mt_dp_Y4VV7GNQSBQDZ4XQ95YR'><span style="background-color:khaki"> - <img alt="Book_fr" src='img/menu_tutorial.png' /> - <span style="color: forestgreen">Book (Fr)</span></span></a> + <li><a target="_blank" href='https://www.taylorfrancis.com/books/mono/10.1201/9781003323693/digital-image-processing-vincent-barra-christophe-tilmant-david-tschumperle'><span style="background-color:khaki"> + <img alt="Book" src='img/menu_tutorial.png' /> + <span style="color: forestgreen">Book</span></span></a></li> </ul> </div> <hr/> diff --git a/html/header_doxygen.html b/html/header_doxygen.html index 42a695d03..c15a8fe95 100644 --- a/html/header_doxygen.html +++ b/html/header_doxygen.html @@ -26,7 +26,7 @@ <div class="header"> <a href="../index.html"><img alt="Logo" src="../img/logo_header.jpg" class="center_image" style="margin-top:1em;"/></a> <h2 style="padding-bottom: 1em"> - Latest stable version: <b><a href="http://cimg.eu/files/CImg_3.1.6.zip">3.1.6</a></b> Current pre-release: <b><a href="http://cimg.eu/files/CImg_latest.zip">3.2.0</a></b> + Latest stable version: <b><a href="http://cimg.eu/files/CImg_3.2.5.zip">3.2.5</a></b> </h2> <hr/> @@ -58,9 +58,9 @@ <li><a href='../links.html'><span> <img alt="Links" src='../img/menu_links.png' /> Links</span></a></li> - <li><a target="_blank" href='https://www.amazon.fr/dp/B08WRCZRR3/ref=cm_sw_em_r_mt_dp_Y4VV7GNQSBQDZ4XQ95YR'><span style="background-color:khaki"> - <img alt="Book_fr" src='../img/menu_tutorial.png' /> - <span style="color: forestgreen">Book (Fr)</span></span></a> + <li><a target="_blank" href='https://www.taylorfrancis.com/books/mono/10.1201/9781003323693/digital-image-processing-vincent-barra-christophe-tilmant-david-tschumperle'><span style="background-color:khaki"> + <img alt="Book" src='img/menu_tutorial.png' /> + <span style="color: forestgreen">Book</span></span></a></li> </ul> </div> <hr/> diff --git a/html/img/book_cimg_en.jpg b/html/img/book_cimg_en.jpg new file mode 100644 index 0000000000000000000000000000000000000000..ac3b55ecfa4f47db4a96d7d8d8d28f8aaa29a64b GIT binary patch literal 36326 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%0Ai&)qRgNs=s1{q= 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 literal 0 HcmV?d00001 diff --git a/html/index.html b/html/index.html index 39518763b..422a8a881 100644 --- a/html/index.html +++ b/html/index.html @@ -106,15 +106,20 @@ </div><div class="section_end"></div> <!-- ************* --> - <!-- Book (Fr) --> + <!-- Book (En/Fr) --> <!-- ************* --> - <div class="section_title"><p>Book (Fr)</div><div class="section_content"> + <div class="section_title"><p>Book (En/Fr)</div><div class="section_content"> <ul> - <li>If you understand French, you may be interested by <a href="https://www.amazon.fr/dp/B08WRCZRR3/ref=cm_sw_em_r_mt_dp_Y4VV7GNQSBQDZ4XQ95YR">the nice book we wrote</a> - on how to use the <span class="gmd_cimg"></span> Library to develop image processing algorithms, from scratch. - In these 318 pages, we review the important concepts of the library and address a wide variety of applications in image processing + <li>We have a comprehensive book, written in English, on how to use the <span class="gmd_cimg"></span> Library + to develop various image processing algorithms, from scratch + (published by <a href="https://www.taylorfrancis.com/books/mono/10.1201/9781003323693/digital-image-processing-vincent-barra-christophe-tilmant-david-tschumperle">Taylor & Francis Group</a>). + In this 308 pages book, we review the important concepts of the library and address a wide variety of applications in image processing (<i>Filtering, Mathematical Morphology, Feature Extraction, Segmentation, Multispectral Approaches, 3D Visualization, etc.</i>).<br/><br/> + <a target="_blank" href="https://www.taylorfrancis.com/books/mono/10.1201/9781003323693/digital-image-processing-vincent-barra-christophe-tilmant-david-tschumperle"><img class="center_image" src="img/book_cimg_en.jpg" /></a><br/> + </li> + <li> + If you understand French, you may be interested by the <a href="https://www.amazon.fr/dp/B08WRCZRR3/ref=cm_sw_em_r_mt_dp_Y4VV7GNQSBQDZ4XQ95YR">French version of this book</a><br/><br/> <a target="_blank" href="https://www.amazon.fr/dp/B08WRCZRR3/ref=cm_sw_em_r_mt_dp_Y4VV7GNQSBQDZ4XQ95YR"><img class="center_image" src="img/book_cimg.jpg" /></a> </li> </ul> diff --git a/resources/compile_win_visualcpp.bat b/resources/compile_win_visualcpp.bat index f553071b4..619901731 100644 --- a/resources/compile_win_visualcpp.bat +++ b/resources/compile_win_visualcpp.bat @@ -10,6 +10,6 @@ REM ---------------------------------------------------------------- CD ..\examples\ SET CPPFILE=CImg_demo captcha curve_editor2d dtmri_view3d edge_explorer2d fade_images gaussian_fit1d generate_loop_macros hough_transform2d image2ascii image_registration2d image_surface3d jawbreaker mcf_levelsets2d mcf_levelsets3d odykill pde_heatflow2d pde_TschumperleDeriche2d plotter1d radon_transform2d scene3d spherical_function3d tetris tron tutorial wavelet_atrous use_chlpca use_draw_gradient use_nlmeans use_RGBclass use_skeleton FOR %%F IN (%CPPFILE%) DO ( - cl /W4 /wd"4127" /wd"4311" /wd"4312" /wd"4512" /wd"4571" /wd"4640" /wd"4706" /wd"4710" /wd"4800" /wd"4804" /wd"4820" /wd"4996" /Ox /Ob2 /Oi /Ot /c /EHsc /D "_CRT_SECURE_NO_WARNINGS" /I"%SDKPATH%\Include" /I"..\\" %%F.cpp + cl /W4 /wd"4127" /wd"4311" /wd"4312" /wd"4512" /wd"4571" /wd"4640" /wd"4706" /wd"4710" /wd"4800" /wd"4804" /wd"4820" /wd"4996" /c /EHsc /D "_CRT_SECURE_NO_WARNINGS" /I"%SDKPATH%\Include" /I"..\\" %%F.cpp link /LIBPATH:"%SDKPATH%\Lib" %%F.obj user32.lib gdi32.lib shell32.lib )