1 /*
2  #
3  #  File        : gmic.cpp
4  #                ( C++ source file )
5  #
6  #  Description : GREYC's Magic for Image Computing - G'MIC core
7  #                ( https://gmic.eu )
8  #
9  #  Copyright   : David Tschumperle
10  #                ( https://tschumperle.users.greyc.fr/ )
11  #
12  #  Licenses    : This file is 'dual-licensed', you have to choose one
13  #                of the two licenses below to apply.
14  #
15  #                CeCILL-C
16  #                The CeCILL-C license is close to the GNU LGPL.
17  #                ( http://cecill.info/licences/Licence_CeCILL-C_V1-en.html )
18  #
19  #            or  CeCILL v2.1
20  #                The CeCILL license is compatible with the GNU GPL.
21  #                ( http://cecill.info/licences/Licence_CeCILL_V2.1-en.html )
22  #
23  #  This software is governed either by the CeCILL or the CeCILL-C license
24  #  under French law and abiding by the rules of distribution of free software.
25  #  You can  use, modify and or redistribute the software under the terms of
26  #  the CeCILL or CeCILL-C licenses as circulated by CEA, CNRS and INRIA
27  #  at the following URL: "http://cecill.info".
28  #
29  #  As a counterpart to the access to the source code and  rights to copy,
30  #  modify and redistribute granted by the license, users are provided only
31  #  with a limited warranty  and the software's author,  the holder of the
32  #  economic rights,  and the successive licensors  have only  limited
33  #  liability.
34  #
35  #  In this respect, the user's attention is drawn to the risks associated
36  #  with loading,  using,  modifying and/or developing or reproducing the
37  #  software by the user in light of its specific status of free software,
38  #  that may mean  that it is complicated to manipulate,  and  that  also
39  #  therefore means  that it is reserved for developers  and  experienced
40  #  professionals having in-depth computer knowledge. Users are therefore
41  #  encouraged to load and test the software's suitability as regards their
42  #  requirements in conditions enabling the security of their systems and/or
43  #  data to be ensured and,  more generally, to use and operate it in the
44  #  same conditions as regards security.
45  #
46  #  The fact that you are presently reading this means that you have had
47  #  knowledge of the CeCILL and CeCILL-C licenses and that you accept its terms.
48  #
49 */
50 
51 // Add G'MIC-specific methods to the CImg<T> class of the CImg library.
52 //----------------------------------------------------------------------
53 #if defined(cimg_plugin)
54 
55 template<typename t>
copy_rounded(const CImg<t> & img)56 static CImg<T> copy_rounded(const CImg<t>& img) {
57   if (!cimg::type<t>::is_float() || cimg::type<T>::is_float()) return img;
58   CImg<T> res(img._width,img._height,img._depth,img._spectrum);
59   const t *ptrs = img._data;
60   cimg_for(res,ptrd,T) *ptrd = (T)cimg::round(*(ptrs++));
61   return res;
62 }
63 
copy_rounded(const CImg<T> & img)64 static CImg<T> copy_rounded(const CImg<T>& img) {
65   return CImg<T>(img,true);
66 }
67 
storage_type(const CImgList<T> & images)68 static const char *storage_type(const CImgList<T>& images) {
69   T im = cimg::type<T>::max(), iM = cimg::type<T>::min();
70   bool is_int = true;
71   for (unsigned int l = 0; l<images.size() && is_int; ++l) {
72     cimg_for(images[l],p,T) {
73       const T val = *p;
74       if (!(val==(T)(int)val)) { is_int = false; break; }
75       if (val<im) im = val;
76       if (val>iM) iM = val;
77     }
78   }
79   if (is_int) {
80     if (im>=0) {
81       if (iM<(1U<<8)) return "uchar";
82       else if (iM<(1U<<16)) return "ushort";
83       else if (iM<((cimg_uint64)1<<32)) return "uint";
84     } else {
85       if (im>=-(1<<7) && iM<(1<<7)) return "char";
86       else if (im>=-(1<<15) && iM<(1<<15)) return "short";
87       else if (im>=-((cimg_int64)1<<31) && iM<((cimg_int64)1<<31)) return "int";
88     }
89   }
90   return cimg::type<T>::string();
91 }
92 
append_CImg3d(const CImgList<T> & images)93 static CImg<T> append_CImg3d(const CImgList<T>& images) {
94   if (!images) return CImg<T>();
95   if (images.size()==1) return +images[0];
96   CImg<charT> error_message(1024);
97   unsigned int g_nbv = 0, g_nbp = 0;
98   ulongT siz = 0;
99   cimglist_for(images,l) {
100     const CImg<T>& img = images[l];
101     if (!img.is_CImg3d(false,error_message))
102       throw CImgArgumentException("append_CImg3d(): image [%d] (%u,%u,%u,%u,%p) "
103                                   "is not a CImg3d (%s).",
104                                   l,img._width,img._height,img._depth,img._spectrum,img._data,
105                                   error_message.data());
106     siz+=img.size() - 8;
107     g_nbv+=cimg::float2uint((float)img[6]);
108     g_nbp+=cimg::float2uint((float)img[7]);
109   }
110 
111   CImg<T> res(1,siz + 8);
112   const T **const ptrs = new const T*[images.size()];
113   T *ptrd = res._data;
114   *(ptrd++) = (T)('C' + 0.5f); *(ptrd++) = (T)('I' + 0.5f); // Create object header
115   *(ptrd++) = (T)('m' + 0.5f); *(ptrd++) = (T)('g' + 0.5f);
116   *(ptrd++) = (T)('3' + 0.5f); *(ptrd++) = (T)('d' + 0.5f);
117   *(ptrd++) = (T)cimg::uint2float(g_nbv);
118   *(ptrd++) = (T)cimg::uint2float(g_nbp);
119   cimglist_for(images,l) { // Merge object points
120     const CImg<T>& img = images[l];
121     const unsigned int nbv = cimg::float2uint((float)img[6]);
122     std::memcpy(ptrd,img._data + 8,3*nbv*sizeof(T));
123     ptrd+=3*nbv;
124     ptrs[l] = img._data + 8 + 3*nbv;
125   }
126   ulongT poff = 0;
127   cimglist_for(images,l) { // Merge object primitives
128     const unsigned int
129       nbv = cimg::float2uint((float)images[l][6]),
130       nbp = cimg::float2uint((float)images[l][7]);
131     for (unsigned int p = 0; p<nbp; ++p) {
132       const unsigned int
133         nbi = cimg::float2uint((float)*(ptrs[l]++)),
134         _nbi = nbi<5?nbi:nbi==5?2:nbi/3;
135       *(ptrd++) = (T)cimg::uint2float(nbi);
136       for (unsigned int i = 0; i<_nbi; ++i) *(ptrd++) = (T)cimg::uint2float(cimg::float2uint(*(ptrs[l]++)) + poff);
137       for (unsigned int i = nbi - _nbi; i; --i) *(ptrd++) = *(ptrs[l]++);
138     }
139     poff+=nbv;
140   }
141   ulongT voff = 0;
142   cimglist_for(images,l) { // Merge object colors
143     const unsigned int nbc = cimg::float2uint((float)images[l][7]);
144     for (unsigned int c = 0; c<nbc; ++c)
145       if (*(ptrs[l])==(T)-128) {
146         *(ptrd++) = *(ptrs[l]++);
147         const unsigned int
148           w = (unsigned int)*(ptrs[l]++),
149           h = (unsigned int)*(ptrs[l]++),
150           s = (unsigned int)*(ptrs[l]++);
151         if (!h && !s) { *(ptrd++) = (T)(w + voff); *(ptrd++) = 0; *(ptrd++) = 0; }
152         else {
153           *(ptrd++) = (T)w; *(ptrd++) = (T)h; *(ptrd++) = (T)s;
154           const ulongT whs = (ulongT)w*h*s;
155           std::memcpy(ptrd,ptrs[l],whs*sizeof(T));
156           ptrs[l]+=whs; ptrd+=whs;
157         }
158       } else { *(ptrd++) = *(ptrs[l]++); *(ptrd++) = *(ptrs[l]++); *(ptrd++) = *(ptrs[l]++); }
159     voff+=nbc;
160   }
161   voff = 0;
162   cimglist_for(images,l) { // Merge object opacities
163     const unsigned int nbo = cimg::float2uint((float)images[l][7]);
164     for (unsigned int o = 0; o<nbo; ++o)
165       if (*(ptrs[l])==(T)-128) {
166         *(ptrd++) = *(ptrs[l]++);
167         const unsigned int
168           w = (unsigned int)*(ptrs[l]++),
169           h = (unsigned int)*(ptrs[l]++),
170           s = (unsigned int)*(ptrs[l]++);
171         if (!h && !s) { *(ptrd++) = (T)(w + voff); *(ptrd++) = 0; *(ptrd++) = 0; }
172         else {
173           *(ptrd++) = (T)w; *(ptrd++) = (T)h; *(ptrd++) = (T)s;
174           const ulongT whs = (ulongT)w*h*s;
175           std::memcpy(ptrd,ptrs[l],whs*sizeof(T));
176           ptrs[l]+=whs; ptrd+=whs;
177         }
178       } else *(ptrd++) = *(ptrs[l]++);
179     voff+=nbo;
180   }
181   delete[] ptrs;
182   return res;
183 }
184 
append_string_to(CImg<T> & img,T * & ptrd) const185 CImg<T>& append_string_to(CImg<T>& img, T* &ptrd) const {
186   if (!_width) return img;
187   if (ptrd + _width>=img.end()) {
188     CImg<T> tmp(3*img._width/2 + _width + 1);
189     std::memcpy(tmp,img,img._width*sizeof(T));
190     ptrd = tmp._data + (ptrd - img._data);
191     tmp.move_to(img);
192   }
193   std::memcpy(ptrd,_data,_width*sizeof(T));
194   ptrd+=_width;
195   return img;
196 }
197 
append_string_to(const char c,CImg<T> & img,T * & ptrd)198 static CImg<T>& append_string_to(const char c, CImg<T>& img, T* &ptrd) {
199   if (ptrd + 1>=img.end()) {
200     CImg<T> tmp(3*img._width/2 + 2);
201     std::memcpy(tmp,img,img._width*sizeof(T));
202     ptrd = tmp._data + (ptrd - img._data);
203     tmp.move_to(img);
204   }
205   *(ptrd++) = c;
206   return img;
207 }
208 
color_CImg3d(const float R,const float G,const float B,const float opacity,const bool set_RGB,const bool set_opacity)209 CImg<T>& color_CImg3d(const float R, const float G, const float B, const float opacity,
210                       const bool set_RGB, const bool set_opacity) {
211   CImg<char> error_message(1024);
212   if (!is_CImg3d(false,error_message))
213     throw CImgInstanceException(_cimg_instance
214                                 "color_CImg3d(): image instance is not a CImg3d (%s).",
215                                 cimg_instance,error_message.data());
216   T *ptrd = data() + 6;
217   const unsigned int
218     nbv = cimg::float2uint((float)*(ptrd++)),
219     nbp = cimg::float2uint((float)*(ptrd++));
220   ptrd+=3*nbv;
221   for (unsigned int i = 0; i<nbp; ++i) { const unsigned int N = (unsigned int)*(ptrd++); ptrd+=N; }
222   for (unsigned int c = 0; c<nbp; ++c)
223     if (*ptrd==(T)-128) {
224       ++ptrd;
225       const unsigned int
226         w = (unsigned int)*(ptrd++),
227         h = (unsigned int)*(ptrd++),
228         s = (unsigned int)*(ptrd++);
229       ptrd+=w*h*s;
230     } else if (set_RGB) { *(ptrd++) = (T)R; *(ptrd++) = (T)G; *(ptrd++) = (T)B; } else ptrd+=3;
231   if (set_opacity)
232     for (unsigned int o = 0; o<nbp; ++o) {
233       if (*ptrd==(T)-128) {
234         ++ptrd;
235         const unsigned int
236           w = (unsigned int)*(ptrd++),
237           h = (unsigned int)*(ptrd++),
238           s = (unsigned int)*(ptrd++);
239         ptrd+=w*h*s;
240       } else *(ptrd++) = (T)opacity;
241     }
242   return *this;
243 }
244 
get_color_CImg3d(const float R,const float G,const float B,const float opacity,const bool set_RGB,const bool set_opacity) const245 CImg<T> get_color_CImg3d(const float R, const float G, const float B,
246                          const float opacity, const bool set_RGB, const bool set_opacity) const {
247   return (+*this).color_CImg3d(R,G,B,opacity,set_RGB,set_opacity);
248 }
249 
copymark()250 CImg<T>& copymark() {
251   return get_copymark().move_to(*this);
252 }
253 
get_copymark() const254 CImg<T> get_copymark() const {
255   if (is_empty() || !*_data) return CImg<T>::string("_c1");
256   const char *pe = _data + _width - 1, *ext = cimg::split_filename(_data);
257   if (*ext) pe = --ext;
258   unsigned int num = 0, fact = 1, baselength = _width;
259   if (pe>_data+2) { // Try to find ending number if any
260     const char *npe = pe - 1;
261     while (npe>_data && *npe>='0' && *npe<='9') { num+=fact*(*(npe--) - '0'); fact*=10; }
262     if (npe>_data && npe!=pe - 1 && *(npe-1)=='_' && *npe=='c' && npe[1]!='0') {
263       pe = npe - 1;
264       baselength = pe + _width - ext;
265     }
266     else num = 0;
267   }
268   ++num;
269   const unsigned int ndigits = (unsigned int)std::max(1.,std::ceil(std::log10(num + 1.)));
270   CImg<T> res(baselength + ndigits + 2);
271   std::memcpy(res,_data,pe - _data);
272   std::sprintf(res._data + (pe - _data),"_c%u%s",num,ext);
273   return res;
274 }
275 
get_draw_ellipse(const int x,const int y,const float r0,const float r1,const float angle,const T * const col,const float opacity) const276 CImg<T> get_draw_ellipse(const int x, const int y, const float r0, const float r1,
277                          const float angle, const T *const col, const float opacity) const {
278   return (+*this).draw_ellipse(x,y,r0,r1,angle,col,opacity);
279 }
280 
get_draw_ellipse(const int x,const int y,const float r0,const float r1,const float angle,const T * const col,const float opacity,const unsigned int pattern) const281 CImg<T> get_draw_ellipse(const int x, const int y, const float r0, const float r1,
282                          const float angle, const T *const col, const float opacity,
283                          const unsigned int pattern) const {
284   return (+*this).draw_ellipse(x,y,r0,r1,angle,col,opacity,pattern);
285 }
286 
get_draw_fill(const int x,const int y,const int z,const T * const col,const float opacity,const float tolerance,const bool is_high_connectivity) const287 CImg<T> get_draw_fill(const int x, const int y, const int z,
288                       const T *const col, const float opacity,
289                       const float tolerance, const bool is_high_connectivity) const {
290   return (+*this).draw_fill(x,y,z,col,opacity,tolerance,is_high_connectivity);
291 }
292 
293 template<typename t, typename tc>
get_draw_graph(const CImg<t> & data,const tc * const color,const float opacity,const unsigned int plot_type,const int vertex_type,const double ymin,const double ymax,const unsigned int pattern) const294 CImg<T> get_draw_graph(const CImg<t>& data,
295                        const tc *const color, const float opacity,
296                        const unsigned int plot_type, const int vertex_type,
297                        const double ymin, const double ymax,
298                        const unsigned int pattern) const {
299   return (+*this).draw_graph(data,color,opacity,plot_type,vertex_type,ymin,ymax,pattern);
300 }
301 
gmic_draw_image(const float x,const float y,const float z,const float c,const char sepx,const char sepy,const char sepz,const char sepc,const CImg<T> & sprite,const CImg<T> & mask,const float opacity,const float max_opacity_mask)302 CImg<T>& gmic_draw_image(const float x, const float y, const float z, const float c,
303                          const char sepx, const char sepy, const char sepz, const char sepc,
304                          const CImg<T>& sprite, const CImg<T>& mask, const float opacity,
305                          const float max_opacity_mask) {
306   const float
307     fx = sepx=='~'?x*(width() - sprite.width()):sepx=='%'?x*(width() - 1)/100:x,
308     fy = sepy=='~'?y*(height() - sprite.height()):sepy=='%'?y*(height() - 1)/100:y,
309     fz = sepz=='~'?y*(depth() - sprite.depth()):sepz=='%'?z*(depth() - 1)/100:z,
310     fc = sepc=='~'?c*(spectrum() - sprite.spectrum()):sepc=='%'?c*(spectrum() - 1)/100:c;
311   return draw_image((int)cimg::round(fx),(int)cimg::round(fy),
312                     (int)cimg::round(fz),(int)cimg::round(fc),
313                     sprite,mask,opacity,max_opacity_mask);
314 }
315 
get_gmic_draw_image(const float x,const float y,const float z,const float c,const char sepx,const char sepy,const char sepz,const char sepc,const CImg<T> & sprite,const CImg<T> & mask,const float opacity,const float max_opacity_mask) const316 CImg<T> get_gmic_draw_image(const float x, const float y, const float z, const float c,
317                             const char sepx, const char sepy, const char sepz, const char sepc,
318                             const CImg<T>& sprite, const CImg<T>& mask, const float opacity,
319                             const float max_opacity_mask) const {
320   return (+*this).gmic_draw_image(x,y,z,c,sepx,sepy,sepz,sepc,sprite,mask,opacity,max_opacity_mask);
321 }
322 
gmic_draw_image(const float x,const float y,const float z,const float c,const char sepx,const char sepy,const char sepz,const char sepc,const CImg<T> & sprite,const float opacity)323 CImg<T>& gmic_draw_image(const float x, const float y, const float z, const float c,
324                          const char sepx, const char sepy, const char sepz, const char sepc,
325                          const CImg<T>& sprite, const float opacity) {
326   const float
327     fx = sepx=='~'?x*(width() - sprite.width()):sepx=='%'?x*(width() - 1)/100:x,
328     fy = sepy=='~'?y*(height() - sprite.height()):sepy=='%'?y*(height() - 1)/100:y,
329     fz = sepz=='~'?y*(depth() - sprite.depth()):sepz=='%'?z*(depth() - 1)/100:z,
330     fc = sepc=='~'?c*(spectrum() - sprite.spectrum()):sepc=='%'?c*(spectrum() - 1)/100:c;
331   return draw_image((int)cimg::round(fx),(int)cimg::round(fy),
332                     (int)cimg::round(fz),(int)cimg::round(fc),
333                     sprite,opacity);
334 }
335 
get_gmic_draw_image(const float x,const float y,const float z,const float c,const char sepx,const char sepy,const char sepz,const char sepc,const CImg<T> & sprite,const float opacity) const336 CImg<T> get_gmic_draw_image(const float x, const float y, const float z, const float c,
337                             const char sepx, const char sepy, const char sepz, const char sepc,
338                             const CImg<T>& sprite, const float opacity) const {
339   return (+*this).gmic_draw_image(x,y,z,c,sepx,sepy,sepz,sepc,sprite,opacity);
340 }
341 
get_draw_line(const int x0,const int y0,const int x1,const int y1,const T * const col,const float opacity,const unsigned int pattern) const342 CImg<T> get_draw_line(const int x0, const int y0, const int x1, const int y1, const T *const col,
343                       const float opacity, const unsigned int pattern) const {
344   return (+*this).draw_line(x0,y0,x1,y1,col,opacity,pattern);
345 }
346 
get_draw_mandelbrot(const CImg<T> & color_palette,const float opacity,const double z0r,const double z0i,const double z1r,const double z1i,const unsigned int itermax,const bool normalized_iteration,const bool julia_set,const double paramr,const double parami) const347 CImg<T> get_draw_mandelbrot(const CImg<T>& color_palette, const float opacity,
348                             const double z0r, const double z0i, const double z1r, const double z1i,
349                             const unsigned int itermax, const bool normalized_iteration,
350                             const bool julia_set, const double paramr, const double parami) const {
351   return (+*this).draw_mandelbrot(color_palette,opacity,z0r,z0i,z1r,z1i,itermax,
352                                   normalized_iteration,julia_set,paramr,parami);
353 }
354 
355 template<typename tp, typename tf, typename tc, typename to>
get_draw_object3d(const float x0,const float y0,const float z0,const CImg<tp> & vertices,const CImgList<tf> & primitives,const CImgList<tc> & colors,const CImgList<to> & opacities,const unsigned int render_mode,const bool double_sided,const float focale,const float light_x,const float light_y,const float light_z,const float specular_lightness,const float specular_shininess,const float g_opacity,CImg<floatT> & zbuffer) const356 CImg<T> get_draw_object3d(const float x0, const float y0, const float z0,
357                           const CImg<tp>& vertices, const CImgList<tf>& primitives,
358                           const CImgList<tc>& colors, const CImgList<to>& opacities,
359                           const unsigned int render_mode, const bool double_sided,
360                           const float focale,
361                           const float light_x, const float light_y,const float light_z,
362                           const float specular_lightness, const float specular_shininess,
363                           const float g_opacity, CImg<floatT>& zbuffer) const {
364   return (+*this).draw_object3d(x0,y0,z0,vertices,primitives,colors,opacities,render_mode,
365                                 double_sided,focale,light_x,light_y,light_z,specular_lightness,
366                                 specular_shininess,g_opacity,zbuffer);
367 }
368 
get_draw_plasma(const float alpha,const float beta,const unsigned int scale) const369 CImg<T> get_draw_plasma(const float alpha, const float beta, const unsigned int scale) const {
370   return (+*this).draw_plasma(alpha,beta,scale);
371 }
372 
get_draw_point(const int x,const int y,const int z,const T * const col,const float opacity) const373 CImg<T> get_draw_point(const int x, const int y, const int z, const T *const col,
374                        const float opacity) const {
375   return (+*this).draw_point(x,y,z,col,opacity);
376 }
377 
378 template<typename t>
get_draw_polygon(const CImg<t> & pts,const T * const col,const float opacity) const379 CImg<T> get_draw_polygon(const CImg<t>& pts, const T *const col, const float opacity) const {
380   return (+*this).draw_polygon(pts,col,opacity);
381 }
382 
383 template<typename t>
get_draw_polygon(const CImg<t> & pts,const T * const col,const float opacity,const unsigned int pattern) const384 CImg<T> get_draw_polygon(const CImg<t>& pts, const T *const col, const float opacity,
385                          const unsigned int pattern) const {
386   return (+*this).draw_polygon(pts,col,opacity,pattern);
387 }
388 
gmic_autocrop(const CImg<T> & color=CImg<T>::empty ())389 CImg<T>& gmic_autocrop(const CImg<T>& color=CImg<T>::empty()) {
390   if (color.width()==1) autocrop(*color);
391   else autocrop(color);
392   return *this;
393 }
394 
get_gmic_autocrop(const CImg<T> & color=CImg<T>::empty ())395 CImg<T> get_gmic_autocrop(const CImg<T>& color=CImg<T>::empty()) {
396   return (+*this).gmic_autocrop(color);
397 }
398 
gmic_blur(const float sigma_x,const float sigma_y,const float sigma_z,const float sigma_c,const bool boundary_conditions,const bool is_gaussian)399 CImg<T>& gmic_blur(const float sigma_x, const float sigma_y, const float sigma_z, const float sigma_c,
400                    const bool boundary_conditions, const bool is_gaussian) {
401   if (is_empty()) return *this;
402   if (is_gaussian) {
403     if (_width>1) vanvliet(sigma_x,0,'x',boundary_conditions);
404     if (_height>1) vanvliet(sigma_y,0,'y',boundary_conditions);
405     if (_depth>1) vanvliet(sigma_z,0,'z',boundary_conditions);
406     if (_spectrum>1) vanvliet(sigma_c,0,'c',boundary_conditions);
407   } else {
408     if (_width>1) deriche(sigma_x,0,'x',boundary_conditions);
409     if (_height>1) deriche(sigma_y,0,'y',boundary_conditions);
410     if (_depth>1) deriche(sigma_z,0,'z',boundary_conditions);
411     if (_spectrum>1) deriche(sigma_c,0,'c',boundary_conditions);
412   }
413   return *this;
414 }
415 
get_gmic_blur(const float sigma_x,const float sigma_y,const float sigma_z,const float sigma_c,const bool boundary_conditions,const bool is_gaussian) const416 CImg<Tfloat> get_gmic_blur(const float sigma_x, const float sigma_y, const float sigma_z, const float sigma_c,
417                            const bool boundary_conditions, const bool is_gaussian) const {
418   return CImg<Tfloat>(*this,false).gmic_blur(sigma_x,sigma_y,sigma_z,sigma_c,boundary_conditions,is_gaussian);
419 }
420 
gmic_blur_box(const float sigma_x,const float sigma_y,const float sigma_z,const float sigma_c,const unsigned int order,const bool boundary_conditions,const unsigned int nb_iter)421 CImg<T>& gmic_blur_box(const float sigma_x, const float sigma_y, const float sigma_z, const float sigma_c,
422                        const unsigned int order, const bool boundary_conditions,
423                        const unsigned int nb_iter) {
424   if (is_empty()) return *this;
425   if (_width>1) boxfilter(sigma_x,order,'x',boundary_conditions,nb_iter);
426   if (_height>1) boxfilter(sigma_y,order,'y',boundary_conditions,nb_iter);
427   if (_depth>1) boxfilter(sigma_z,order,'z',boundary_conditions,nb_iter);
428   if (_spectrum>1) boxfilter(sigma_c,order,'c',boundary_conditions,nb_iter);
429   return *this;
430 }
431 
get_gmic_blur_box(const float sigma_x,const float sigma_y,const float sigma_z,const float sigma_c,const unsigned int order,const bool boundary_conditions,const unsigned int nb_iter) const432 CImg<Tfloat> get_gmic_blur_box(const float sigma_x, const float sigma_y, const float sigma_z, const float sigma_c,
433                                const unsigned int order, const bool boundary_conditions,
434                                const unsigned int nb_iter) const {
435   return CImg<Tfloat>(*this,false).gmic_blur_box(sigma_x,sigma_y,sigma_z,sigma_c,order,boundary_conditions,nb_iter);
436 }
437 
gmic_blur_box(const float sigma,const unsigned int order,const bool boundary_conditions,const unsigned int nb_iter)438 CImg<T>& gmic_blur_box(const float sigma, const unsigned int order, const bool boundary_conditions,
439                        const unsigned int nb_iter) {
440   const float nsigma = sigma>=0?sigma:-sigma*cimg::max(_width,_height,_depth)/100;
441   return gmic_blur_box(nsigma,nsigma,nsigma,0,order,boundary_conditions,nb_iter);
442 }
443 
get_gmic_blur_box(const float sigma,const unsigned int order,const bool boundary_conditions,const unsigned int nb_iter) const444 CImg<Tfloat> get_gmic_blur_box(const float sigma, const unsigned int order, const bool boundary_conditions,
445                                const unsigned int nb_iter) const {
446   return CImg<Tfloat>(*this,false).gmic_blur_box(sigma,order,boundary_conditions,nb_iter);
447 }
448 
gmic_discard(const char * const axes)449 CImg<T>& gmic_discard(const char *const axes) {
450   for (const char *s = axes; *s; ++s) discard(*s);
451   return *this;
452 }
453 
get_gmic_discard(const char * const axes) const454 CImg<T> get_gmic_discard(const char *const axes) const {
455   return (+*this).gmic_discard(axes);
456 }
457 
458 template<typename t>
gmic_discard(const CImg<t> & values,const char * const axes)459 CImg<T>& gmic_discard(const CImg<t>& values, const char *const axes) {
460   if (is_empty() || !values || !axes || !*axes) return *this;
461   for (const char *s = axes; *s; ++s) discard(values,*s);
462   return *this;
463 }
464 
465 template<typename t>
get_gmic_discard(const CImg<t> & values,const char * const axes) const466 CImg<T> get_gmic_discard(const CImg<t>& values, const char *const axes) const {
467   return (+*this).gmic_discard(values,axes);
468 }
469 
gmic_draw_text(const float x,const float y,const char sepx,const char sepy,const char * const text,const T * const col,const int bg,const float opacity,const unsigned int siz,const unsigned int nb_cols)470 CImg<T>& gmic_draw_text(const float x, const float y,
471                         const char sepx, const char sepy,
472                         const char *const text, const T *const col,
473                         const int bg, const float opacity, const unsigned int siz,
474                         const unsigned int nb_cols) {
475   float fx = 0, fy = 0;
476   if (is_empty()) {
477     const T one[] = { (T)1 };
478     fx = sepx=='%' || sepx=='~'?0:x;
479     fy = sepy=='%' || sepy=='~'?0:y;
480     draw_text((int)cimg::round(fx),(int)cimg::round(fy),"%s",one,0,opacity,siz,text).resize(-100,-100,1,nb_cols);
481     cimg_forC(*this,c) get_shared_channel(c)*=col[c];
482     return *this;
483   }
484   if (sepx=='~' || sepy=='~') {
485     const char one[] = { 1 };
486     CImg<ucharT> foo;
487     foo.draw_text(0,0,"%s",one,0,1,siz,text);
488     fx = sepx=='~'?x*(width() - foo.width()):sepx=='%'?x*(width() - 1)/100:x;
489     fy = sepy=='~'?y*(height() - foo.height()):sepy=='%'?y*(height() - 1)/100:y;
490   } else {
491     fx = sepx=='%'?x*(width() - 1)/100:x;
492     fy = sepy=='%'?y*(height() - 1)/100:y;
493   }
494   return draw_text((int)cimg::round(fx),(int)cimg::round(fy),"%s",col,bg,opacity,siz,text);
495 }
496 
get_gmic_draw_text(const float x,const float y,const char sepx,const char sepy,const char * const text,const T * const col,const int bg,const float opacity,const unsigned int siz,const unsigned int nb_cols) const497 CImg<T> get_gmic_draw_text(const float x, const float y,
498                            const char sepx, const char sepy,
499                            const char *const text, const T *const col,
500                            const int bg, const float opacity, const unsigned int siz,
501                            const unsigned int nb_cols) const {
502   return (+*this).gmic_draw_text(x,y,sepx,sepy,text,col,bg,opacity,siz,nb_cols);
503 }
504 
gmic_invert_endianness(const char * const stype)505 CImg<T>& gmic_invert_endianness(const char *const stype) {
506 
507 #define _gmic_invert_endianness(value_type,svalue_type) \
508   if (!std::strcmp(stype,svalue_type)) \
509     if (cimg::type<T>::string()==cimg::type<value_type>::string()) invert_endianness(); \
510     else CImg<value_type>(*this).invert_endianness().move_to(*this);
511   _gmic_invert_endianness(unsigned char,"uchar")
512   else _gmic_invert_endianness(unsigned char,"unsigned char")
513     else _gmic_invert_endianness(char,"char")
514       else _gmic_invert_endianness(unsigned short,"ushort")
515         else _gmic_invert_endianness(unsigned short,"unsigned short")
516           else _gmic_invert_endianness(short,"short")
517             else _gmic_invert_endianness(unsigned int,"uint")
518               else _gmic_invert_endianness(unsigned int,"unsigned int")
519                 else _gmic_invert_endianness(int,"int")
520                   else _gmic_invert_endianness(uint64T,"uint64")
521                     else _gmic_invert_endianness(uint64T,"unsigned int64")
522                       else _gmic_invert_endianness(int64T,"int64")
523                         else _gmic_invert_endianness(float,"float")
524                           else _gmic_invert_endianness(double,"double")
525                             else invert_endianness();
526   return *this;
527 }
528 
get_gmic_invert_endianness(const char * const stype) const529 CImg<T> get_gmic_invert_endianness(const char *const stype) const {
530   return (+*this).gmic_invert_endianness(stype);
531 }
532 
gmic_matchpatch(const CImg<T> & patch_image,const unsigned int patch_width,const unsigned int patch_height,const unsigned int patch_depth,const unsigned int nb_iterations,const unsigned int nb_randoms,const float occ_penalization,const bool is_score,const CImg<T> * const initialization)533 CImg<T>& gmic_matchpatch(const CImg<T>& patch_image,
534                          const unsigned int patch_width,
535                          const unsigned int patch_height,
536                          const unsigned int patch_depth,
537                          const unsigned int nb_iterations,
538                          const unsigned int nb_randoms,
539                          const float occ_penalization,
540                          const bool is_score,
541                          const CImg<T> *const initialization) {
542   return get_gmic_matchpatch(patch_image,patch_width,patch_height,patch_depth,
543                              nb_iterations,nb_randoms,occ_penalization,is_score,initialization).move_to(*this);
544 }
545 
get_gmic_matchpatch(const CImg<T> & patch_image,const unsigned int patch_width,const unsigned int patch_height,const unsigned int patch_depth,const unsigned int nb_iterations,const unsigned int nb_randoms,const float occ_penalization,const bool is_score,const CImg<T> * const initialization) const546 CImg<T> get_gmic_matchpatch(const CImg<T>& patch_image,
547                             const unsigned int patch_width,
548                             const unsigned int patch_height,
549                             const unsigned int patch_depth,
550                             const unsigned int nb_iterations,
551                             const unsigned int nb_randoms,
552                             const float occ_penalization,
553                             const bool is_score,
554                             const CImg<T> *const initialization) const {
555   CImg<floatT> score, res;
556   res = _matchpatch(patch_image,patch_width,patch_height,patch_depth,
557                     nb_iterations,nb_randoms,occ_penalization,
558                     initialization?*initialization:CImg<T>::const_empty(),
559                     is_score,is_score?score:CImg<floatT>::empty());
560   const unsigned int s = res._spectrum;
561   if (score) res.resize(-100,-100,-100,s + 1,0).draw_image(0,0,0,s,score);
562   return res;
563 }
564 
gmic_print(const char * const title,const bool is_debug,const bool is_valid) const565 const CImg<T>& gmic_print(const char *const title, const bool is_debug,
566                           const bool is_valid) const {
567   cimg::mutex(29);
568   CImg<doubleT> st;
569   if (is_valid && !is_empty()) get_stats().move_to(st);
570   const ulongT siz = size(), msiz = siz*sizeof(T), siz1 = siz - 1,
571     mdisp = msiz<8*1024?0U:msiz<8*1024*1024?1U:2U,
572     wh = _width*_height, whd = _width*_height*_depth,
573     w1 = _width - 1, wh1 = _width*_height - 1, whd1 = _width*_height*_depth - 1;
574 
575   std::fprintf(cimg::output(),"%s%s%s%s:\n  %ssize%s = (%u,%u,%u,%u) [%lu %s of %s%ss].\n  %sdata%s = %s",
576                cimg::t_magenta,cimg::t_bold,title,cimg::t_normal,
577                cimg::t_bold,cimg::t_normal,_width,_height,_depth,_spectrum,
578                (unsigned long)(mdisp==0?msiz:(mdisp==1?(msiz>>10):(msiz>>20))),
579                mdisp==0?"b":(mdisp==1?"Kio":"Mio"),
580                _is_shared?"shared ":"",
581                cimg::type<T>::string(),
582                cimg::t_bold,cimg::t_normal,
583                is_debug?"":"(");
584   if (is_debug) std::fprintf(cimg::output(),"%p = (",(void*)_data);
585   if (is_valid) {
586     if (is_empty()) std::fprintf(cimg::output(),") [%s].\n",
587                                  pixel_type());
588     else {
589       cimg_foroff(*this,off) {
590         std::fprintf(cimg::output(),cimg::type<T>::format_s(),cimg::type<T>::format(_data[off]));
591         if (off!=siz1) std::fprintf(cimg::output(),"%s",
592                                     off%whd==whd1?"^":
593                                     off%wh==wh1?"\\":
594                                     off%_width==w1?";":",");
595         if (off==11 && siz>24) { off = siz1 - 12; std::fprintf(cimg::output(),"(...),"); }
596       }
597       std::fprintf(cimg::output(),")%s.\n  %smin%s = %g, %smax%s = %g, %smean%s = %g, "
598                    "%sstd%s = %g, %scoords_min%s = (%u,%u,%u,%u), "
599                    "%scoords_max%s = (%u,%u,%u,%u).\n",
600                    _is_shared?" [shared]":"",
601                    cimg::t_bold,cimg::t_normal,st[0],
602                    cimg::t_bold,cimg::t_normal,st[1],
603                    cimg::t_bold,cimg::t_normal,st[2],
604                    cimg::t_bold,cimg::t_normal,std::sqrt(st[3]),
605                    cimg::t_bold,cimg::t_normal,(int)st[4],(int)st[5],(int)st[6],(int)st[7],
606                    cimg::t_bold,cimg::t_normal,(int)st[8],(int)st[9],(int)st[10],(int)st[11]);
607     }
608   } else std::fprintf(cimg::output(),"%s%sinvalid pointer%s) [shared %s].\n",
609                       cimg::t_red,cimg::t_bold,cimg::t_normal,
610                       pixel_type());
611   std::fflush(cimg::output());
612   cimg::mutex(29,0);
613   return *this;
614 }
615 
gmic_set(const double value,const int x,const int y,const int z,const int v)616 CImg<T>& gmic_set(const double value,
617                   const int x, const int y, const int z, const int v) {
618   (*this).atXYZC(x,y,z,v,(T)0) = (T)value;
619   return *this;
620 }
621 
get_gmic_set(const double value,const int x,const int y,const int z,const int v) const622 CImg<T> get_gmic_set(const double value,
623                      const int x, const int y, const int z, const int v) const {
624   return (+*this).gmic_set(value,x,y,z,v);
625 }
626 
gmic_shift(const float delta_x,const float delta_y=0,const float delta_z=0,const float delta_c=0,const unsigned int boundary_conditions=0,const bool interpolation=false)627 CImg<T>& gmic_shift(const float delta_x, const float delta_y=0, const float delta_z=0, const float delta_c=0,
628                     const unsigned int boundary_conditions=0, const bool interpolation=false) {
629   if (is_empty()) return *this;
630   const int
631     idelta_x = (int)cimg::round(delta_x),
632     idelta_y = (int)cimg::round(delta_y),
633     idelta_z = (int)cimg::round(delta_z),
634     idelta_c = (int)cimg::round(delta_c);
635   if (!interpolation ||
636       (delta_x==(float)idelta_x && delta_y==(float)idelta_y && delta_z==(float)idelta_z && delta_c==(float)idelta_c))
637     return shift(idelta_x,idelta_y,idelta_z,idelta_c,boundary_conditions); // Integer displacement
638   return _gmic_shift(delta_x,delta_y,delta_z,delta_c,boundary_conditions).move_to(*this);
639 }
640 
get_gmic_shift(const float delta_x,const float delta_y=0,const float delta_z=0,const float delta_c=0,const unsigned int boundary_conditions=0,const bool interpolation=false) const641 CImg<T> get_gmic_shift(const float delta_x, const float delta_y=0, const float delta_z=0, const float delta_c=0,
642                        const unsigned int boundary_conditions=0, const bool interpolation=false) const {
643   if (is_empty()) return CImg<T>::empty();
644   const int
645     idelta_x = (int)cimg::round(delta_x),
646     idelta_y = (int)cimg::round(delta_y),
647     idelta_z = (int)cimg::round(delta_z),
648     idelta_c = (int)cimg::round(delta_c);
649   if (!interpolation ||
650       (delta_x==(float)idelta_x && delta_y==(float)idelta_y && delta_z==(float)idelta_z && delta_c==(float)idelta_c))
651     return (+*this).shift(idelta_x,idelta_y,idelta_z,idelta_c,boundary_conditions); // Integer displacement
652   return _gmic_shift(delta_x,delta_y,delta_z,delta_c,boundary_conditions);
653 }
654 
_gmic_shift(const float delta_x,const float delta_y=0,const float delta_z=0,const float delta_c=0,const unsigned int boundary_conditions=0) const655 CImg<T> _gmic_shift(const float delta_x, const float delta_y=0, const float delta_z=0, const float delta_c=0,
656                     const unsigned int boundary_conditions=0) const {
657   CImg<T> res(_width,_height,_depth,_spectrum);
658   if (delta_c!=0) // 4D shift
659     switch (boundary_conditions) {
660     case 3 : { // Mirror
661       const float w2 = 2.f*width(), h2 = 2.f*height(), d2 = 2.f*depth(), s2 = 2.f*spectrum();
662       cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if_size(res.size(),4096))
663       cimg_forXYZC(res,x,y,z,c) {
664         const float
665           mx = cimg::mod(x - delta_x,w2),
666           my = cimg::mod(y - delta_y,h2),
667           mz = cimg::mod(z - delta_z,d2),
668           mc = cimg::mod(c - delta_c,s2);
669         res(x,y,z,c) = _linear_atXYZC(mx<width()?mx:w2 - mx - 1,
670                                       my<height()?my:h2 - my - 1,
671                                       mz<depth()?mz:d2 - mz - 1,
672                                       mc<spectrum()?mc:s2 - mc - 1);
673       }
674     } break;
675     case 2 : // Periodic
676       cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if_size(res.size(),4096))
677       cimg_forXYZC(res,x,y,z,c) res(x,y,z,c) = _linear_atXYZC_p(x - delta_x,y - delta_y,z - delta_z,c - delta_c);
678       break;
679     case 1 : // Neumann
680       cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if_size(res.size(),4096))
681       cimg_forXYZC(res,x,y,z,c) res(x,y,z,c) = _linear_atXYZC(x - delta_x,y - delta_y,z - delta_z,c - delta_c);
682       break;
683     default : // Dirichlet
684       cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if_size(res.size(),4096))
685       cimg_forXYZC(res,x,y,z,c) res(x,y,z,c) = linear_atXYZC(x - delta_x,y - delta_y,z - delta_z,c - delta_c,(T)0);
686     }
687   else if (delta_z!=0) // 3D shift
688     switch (boundary_conditions) {
689     case 3 : { // Mirror
690       const float w2 = 2.f*width(), h2 = 2.f*height(), d2 = 2.f*depth();
691       cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if_size(res.size(),4096))
692       cimg_forC(res,c) cimg_forXYZ(res,x,y,z) {
693         const float
694           mx = cimg::mod(x - delta_x,w2),
695           my = cimg::mod(y - delta_y,h2),
696           mz = cimg::mod(z - delta_z,d2);
697         res(x,y,z,c) = _linear_atXYZ(mx<width()?mx:w2 - mx - 1,
698                                      my<height()?my:h2 - my - 1,
699                                      mz<depth()?mz:d2 - mz - 1,c);
700       }
701     } break;
702     case 2 : // Periodic
703       cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if_size(res.size(),4096))
704       cimg_forC(res,c) cimg_forXYZ(res,x,y,z) res(x,y,z,c) = _linear_atXYZ_p(x - delta_x,y - delta_y,z - delta_z,c);
705     break;
706     case 1 : // Neumann
707       cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if_size(res.size(),4096))
708       cimg_forC(res,c) cimg_forXYZ(res,x,y,z) res(x,y,z,c) = _linear_atXYZ(x - delta_x,y - delta_y,z - delta_z,c);
709       break;
710     default : // Dirichlet
711       cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if_size(res.size(),4096))
712       cimg_forC(res,c) cimg_forXYZ(res,x,y,z) res(x,y,z,c) = linear_atXYZ(x - delta_x,y - delta_y,z - delta_z,c,(T)0);
713     }
714   else if (delta_y!=0) // 2D shift
715     switch (boundary_conditions) {
716     case 3 : { // Mirror
717       const float w2 = 2.f*width(), h2 = 2.f*height();
718       cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if_size(res.size(),4096))
719       cimg_forZC(res,z,c) cimg_forXY(res,x,y) {
720         const float
721           mx = cimg::mod(x - delta_x,w2),
722           my = cimg::mod(y - delta_y,h2);
723         res(x,y,z,c) = _linear_atXY(mx<width()?mx:w2 - mx - 1,
724                                     my<height()?my:h2 - my - 1,z,c);
725       }
726     } break;
727     case 2 : // Periodic
728       cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if_size(res.size(),4096))
729       cimg_forZC(res,z,c) cimg_forXY(res,x,y) res(x,y,z,c) = _linear_atXY_p(x - delta_x,y - delta_y,z,c);
730       break;
731     case 1 : // Neumann
732       cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if_size(res.size(),4096))
733       cimg_forZC(res,z,c) cimg_forXY(res,x,y) res(x,y,z,c) = _linear_atXY(x - delta_x,y - delta_y,z,c);
734       break;
735     default : // Dirichlet
736       cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if_size(res.size(),4096))
737       cimg_forZC(res,z,c) cimg_forXY(res,x,y) res(x,y,z,c) = linear_atXY(x - delta_x,y - delta_y,z,c,(T)0);
738     }
739   else // 1D shift
740     switch (boundary_conditions) {
741     case 3 : { // Mirror
742       const float w2 = 2.f*width();
743       cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if_size(res.size(),4096))
744       cimg_forYZC(res,y,z,c) cimg_forX(res,x) {
745         const float mx = cimg::mod(x - delta_x,w2);
746         res(x,y,z,c) = _linear_atX(mx<width()?mx:w2 - mx - 1,y,z,c);
747       }
748     } break;
749     case 2 : // Periodic
750       cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if_size(res.size(),4096))
751       cimg_forYZC(res,y,z,c) cimg_forX(res,x) res(x,y,z,c) = _linear_atX_p(x - delta_x,y,z,c);
752       break;
753     case 1 : // Neumann
754       cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if_size(res.size(),4096))
755       cimg_forYZC(res,y,z,c) cimg_forX(res,x) res(x,y,z,c) = _linear_atX(x - delta_x,y,z,c);
756       break;
757     default : // Dirichlet
758       cimg_pragma_openmp(parallel for cimg_openmp_collapse(3) cimg_openmp_if_size(res.size(),4096))
759       cimg_forYZC(res,y,z,c) cimg_forX(res,x) res(x,y,z,c) = linear_atX(x - delta_x,y,z,c,(T)0);
760     }
761   return res;
762 }
763 
764 template<typename t>
gmic_symmetric_eigen(CImg<t> & val,CImg<t> & vec) const765 const CImg<T>& gmic_symmetric_eigen(CImg<t>& val, CImg<t>& vec) const {
766   if (spectrum()!=3 && spectrum()!=6) return symmetric_eigen(val,vec);
767   val.assign(width(),height(),depth(),spectrum()==3?2:3);
768   vec.assign(width(),height(),depth(),spectrum()==3?2:6);
769   CImg<t> _val, _vec;
770   cimg_forXYZ(*this,x,y,z) {
771     get_tensor_at(x,y,z).symmetric_eigen(_val,_vec);
772     val.set_vector_at(_val,x,y,z);
773     if (spectrum()==3) {
774       vec(x,y,z,0) = _vec(0,0);
775       vec(x,y,z,1) = _vec(0,1);
776     } else {
777       vec(x,y,z,0) = _vec(0,0);
778       vec(x,y,z,1) = _vec(0,1);
779       vec(x,y,z,2) = _vec(0,2);
780 
781       vec(x,y,z,3) = _vec(1,0);
782       vec(x,y,z,4) = _vec(1,1);
783       vec(x,y,z,5) = _vec(1,2);
784     }
785   }
786   return *this;
787 }
788 
789 template<typename t>
inpaint(const CImg<t> & mask,const unsigned int method)790 CImg<T>& inpaint(const CImg<t>& mask, const unsigned int method) {
791   if (!is_sameXYZ(mask))
792     throw CImgArgumentException("CImg<%s>::inpaint(): Invalid mask (%u,%u,%u,%u,%p) for "
793                                 "instance image (%u,%u,%u,%u,%p).",
794                                 pixel_type(),mask._width,mask._height,mask._depth,
795                                 mask._spectrum,mask._data,
796                                 _width,_height,_depth,_spectrum,_data);
797   CImg<t> _mask(mask,false), _nmask(mask,false);
798   bool is_pixel = false;
799 
800   do {
801     is_pixel = false;
802 
803     if (depth()==1) { // 2D image
804       CImg_3x3(M,t);
805       CImg_3x3(I,T);
806 
807       switch (method) {
808       case 0: // Average 2D (low-connectivity)
809         cimg_for3x3(_mask,x,y,0,0,M,t) if (Mcc && (!Mcp || !Mpc || !Mnc || !Mcn)) {
810           is_pixel = true;
811           const unsigned int wcp = Mcp?0U:1U, wpc = Mpc?0U:1U, wnc = Mnc?0U:1U, wcn = Mcn?0U:1U,
812             sumw = wcp + wpc + wnc + wcn;
813           cimg_forC(*this,k) {
814             cimg_get3x3(*this,x,y,0,k,I,T);
815             (*this)(x,y,k) = (T)((wcp*Icp + wpc*Ipc + wnc*Inc + wcn*Icn)/(float)sumw);
816           }
817           _nmask(x,y) = 0;
818         }
819         break;
820 
821       case 1: // Average 2D (high-connectivity)
822         cimg_for3x3(_mask,x,y,0,0,M,t) if (Mcc && (!Mpp || !Mcp || !Mnp || !Mpc || !Mnc || !Mpn || !Mcn || !Mnn)) {
823           is_pixel = true;
824           const unsigned int
825             wpp = Mpp?0U:1U, wcp = Mcp?0U:2U, wnp = Mnp?0U:1U,
826             wpc = Mpc?0U:2U, wnc = Mnc?0U:2U,
827             wpn = Mpn?0U:1U, wcn = Mcn?0U:2U, wnn = Mnn?0U:1U,
828             sumw = wpp + wcp + wnp + wpc + wnc + wpn + wcn + wnn;
829           cimg_forC(*this,k) {
830             cimg_get3x3(*this,x,y,0,k,I,T);
831             (*this)(x,y,k) = (T)((wpp*Ipp + wcp*Icp + wnp*Inp + wpc*Ipc +
832                                   wnc*Inc + wpn*Ipn + wcn*Icn + wnn*Inn)/(float)sumw);
833           }
834           _nmask(x,y) = 0;
835         }
836         break;
837 
838       case 2: { // Median 2D (low-connectivity)
839         T J[4];
840         cimg_for3x3(_mask,x,y,0,0,M,t)
841           if (Mcc && (!Mcp || !Mpc || !Mnc || !Mcn)) {
842             is_pixel = true;
843             cimg_forC(*this,k) {
844               cimg_get3x3(*this,x,y,0,k,I,T);
845               unsigned int ind = 0;
846               if (!Mcp) J[ind++] = Icp;
847               if (!Mpc) J[ind++] = Ipc;
848               if (!Mnc) J[ind++] = Inc;
849               if (!Mcn) J[ind++] = Icn;
850               (*this)(x,y,k) = CImg<T>(J,ind,1,1,1,true).kth_smallest(ind>>1);
851             }
852             _nmask(x,y) = 0;
853           }
854       } break;
855 
856       default: // Median 2D (high-connectivity)
857         T J[8];
858         cimg_for3x3(_mask,x,y,0,0,M,t)
859           if (Mcc && (!Mpp || !Mcp || !Mnp || !Mpc || !Mnc || !Mpn || !Mcn || !Mnn)) {
860             is_pixel = true;
861             cimg_forC(*this,k) {
862               cimg_get3x3(*this,x,y,0,k,I,T);
863               unsigned int ind = 0;
864               if (!Mpp) J[ind++] = Ipp;
865               if (!Mcp) J[ind++] = Icp;
866               if (!Mnp) J[ind++] = Inp;
867               if (!Mpc) J[ind++] = Ipc;
868               if (!Mnc) J[ind++] = Inc;
869               if (!Mpn) J[ind++] = Ipn;
870               if (!Mcn) J[ind++] = Icn;
871               if (!Mnn) J[ind++] = Inn;
872               (*this)(x,y,k) = CImg<T>(J,ind,1,1,1,true).kth_smallest(ind>>1);
873             }
874             _nmask(x,y) = 0;
875           }
876       }
877 
878     } else { // 3D image
879       CImg_3x3x3(M,t);
880       CImg_3x3x3(I,T);
881 
882       switch (method) {
883       case 0: // Average 3D (low-connectivity)
884         cimg_for3x3x3(_mask,x,y,z,0,M,t)
885           if (Mccc && (!Mccp || !Mcpc || !Mpcc || !Mncc || !Mcnc || !Mccn)) {
886             is_pixel = true;
887             const unsigned int
888               wccp = Mccp?0U:1U, wcpc = Mcpc?0U:1U, wpcc = Mpcc?0U:1U,
889               wncc = Mncc?0U:1U, wcnc = Mcnc?0U:1U, wccn = Mccn?0U:1U,
890               sumw = wcpc + wpcc + wccp + wncc + wcnc + wccn;
891             cimg_forC(*this,k) {
892               cimg_get3x3x3(*this,x,y,z,k,I,T);
893               (*this)(x,y,z,k) = (T)((wccp*Iccp + wcpc*Icpc + wpcc*Ipcc +
894                                       wncc*Incc + wcnc*Icnc + wccn*Iccn)/(float)sumw);
895             }
896             _nmask(x,y,z) = 0;
897           }
898         break;
899 
900       case 1: // Average 3D (high-connectivity)
901         cimg_for3x3x3(_mask,x,y,z,0,M,t)
902           if (Mccc && (!Mppp || !Mcpp || !Mnpp || !Mpcp || !Mccp || !Mncp || !Mpnp || !Mcnp ||
903                        !Mnnp || !Mppc || !Mcpc || !Mnpc || !Mpcc || !Mncc || !Mpnc || !Mcnc ||
904                        !Mnnc || !Mppn || !Mcpn || !Mnpn || !Mpcn || !Mccn || !Mncn || !Mpnn ||
905                        !Mcnn || !Mnnn)) {
906             is_pixel = true;
907             const unsigned int
908               wppp = Mppp?0U:1U, wcpp = Mcpp?0U:2U, wnpp = Mnpp?0U:1U,
909               wpcp = Mpcp?0U:2U, wccp = Mccp?0U:4U, wncp = Mncp?0U:2U,
910               wpnp = Mpnp?0U:1U, wcnp = Mcnp?0U:2U, wnnp = Mnnp?0U:1U,
911               wppc = Mppc?0U:2U, wcpc = Mcpc?0U:4U, wnpc = Mnpc?0U:2U,
912               wpcc = Mpcc?0U:4U, wncc = Mncc?0U:4U,
913               wpnc = Mpnc?0U:2U, wcnc = Mcnc?0U:4U, wnnc = Mnnc?0U:2U,
914               wppn = Mppn?0U:1U, wcpn = Mcpn?0U:2U, wnpn = Mnpn?0U:1U,
915               wpcn = Mpcn?0U:2U, wccn = Mccn?0U:4U, wncn = Mncn?0U:2U,
916               wpnn = Mpnn?0U:1U, wcnn = Mcnn?0U:2U, wnnn = Mnnn?0U:1U,
917               sumw = wppp + wcpp + wnpp + wpcp + wccp + wncp + wpnp + wcnp + wnnp +
918               wppc + wcpc + wnpc + wpcc + wncc + wpnc + wcnc + wnnc +
919               wppn + wcpn + wnpn + wpcn + wccn + wncn + wpnn + wcnn + wnnn;
920             cimg_forC(*this,k) {
921               cimg_get3x3x3(*this,x,y,z,k,I,T);
922               (*this)(x,y,z,k) = (T)((wppp*Ippp + wcpp*Icpp + wnpp*Inpp +
923                                       wpcp*Ipcp + wccp*Iccp + wncp*Incp +
924                                       wpnp*Ipnp + wcnp*Icnp + wnnp*Innp +
925                                       wppc*Ippc + wcpc*Icpc + wnpc*Inpc +
926                                       wpcc*Ipcc + wncc*Incc +
927                                       wpnc*Ipnc + wcnc*Icnc + wnnc*Innc +
928                                       wppn*Ippn + wcpn*Icpn + wnpn*Inpn +
929                                       wpcn*Ipcn + wccn*Iccn + wncn*Incn +
930                                       wpnn*Ipnn + wcnn*Icnn + wnnn*Innn)/(float)sumw);
931             }
932             _nmask(x,y,z) = 0;
933           }
934         break;
935 
936       case 2: { // Median 3D (low-connectivity)
937         T J[6];
938         cimg_for3x3x3(_mask,x,y,z,0,M,t)
939           if (Mccc && (!Mccp || !Mcpc || !Mpcc || !Mncc || !Mcnc || !Mccn)) {
940             is_pixel = true;
941             cimg_forC(*this,k) {
942               cimg_get3x3x3(*this,x,y,z,k,I,T);
943               unsigned int ind = 0;
944               if (!Mccp) J[ind++] = Iccp;
945               if (!Mcpc) J[ind++] = Icpc;
946               if (!Mpcc) J[ind++] = Ipcc;
947               if (!Mncc) J[ind++] = Incc;
948               if (!Mcnc) J[ind++] = Icnc;
949               if (!Mccn) J[ind++] = Iccn;
950               (*this)(x,y,z,k) = CImg<T>(J,ind,1,1,1,true).kth_smallest(ind>>1);
951             }
952             _nmask(x,y,z) = 0;
953           }
954       } break;
955 
956       default: { // Median 3D (high-connectivity)
957         T J[26];
958         cimg_for3x3x3(_mask,x,y,z,0,M,t)
959           if (Mccc && (!Mppp || !Mcpp || !Mnpp || !Mpcp || !Mccp || !Mncp || !Mpnp || !Mcnp ||
960                        !Mnnp || !Mppc || !Mcpc || !Mnpc || !Mpcc || !Mncc || !Mpnc || !Mcnc ||
961                        !Mnnc || !Mppn || !Mcpn || !Mnpn || !Mpcn || !Mccn || !Mncn || !Mpnn ||
962                        !Mcnn || !Mnnn)) {
963             is_pixel = true;
964             cimg_forC(*this,k) {
965               cimg_get3x3x3(*this,x,y,z,k,I,T);
966               unsigned int ind = 0;
967               if (!Mppp) J[ind++] = Ippp;
968               if (!Mcpp) J[ind++] = Icpp;
969               if (!Mnpp) J[ind++] = Inpp;
970               if (!Mpcp) J[ind++] = Ipcp;
971               if (!Mccp) J[ind++] = Iccp;
972               if (!Mncp) J[ind++] = Incp;
973               if (!Mpnp) J[ind++] = Ipnp;
974               if (!Mcnp) J[ind++] = Icnp;
975               if (!Mnnp) J[ind++] = Innp;
976               if (!Mppc) J[ind++] = Ippc;
977               if (!Mcpc) J[ind++] = Icpc;
978               if (!Mnpc) J[ind++] = Inpc;
979               if (!Mpcc) J[ind++] = Ipcc;
980               if (!Mncc) J[ind++] = Incc;
981               if (!Mpnc) J[ind++] = Ipnc;
982               if (!Mcnc) J[ind++] = Icnc;
983               if (!Mnnc) J[ind++] = Innc;
984               if (!Mppn) J[ind++] = Ippn;
985               if (!Mcpn) J[ind++] = Icpn;
986               if (!Mnpn) J[ind++] = Inpn;
987               if (!Mpcn) J[ind++] = Ipcn;
988               if (!Mccn) J[ind++] = Iccn;
989               if (!Mncn) J[ind++] = Incn;
990               if (!Mpnn) J[ind++] = Ipnn;
991               if (!Mcnn) J[ind++] = Icnn;
992               if (!Mnnn) J[ind++] = Innn;
993               (*this)(x,y,z,k) = CImg<T>(J,ind,1,1,1,true).kth_smallest(ind>>1);
994             }
995             _nmask(x,y,z) = 0;
996           }
997       } break;
998       }
999     }
1000 
1001     _mask = _nmask;
1002   } while (is_pixel);
1003   return *this;
1004 }
1005 
1006 template<typename t>
get_inpaint(const CImg<t> & mask,const unsigned int method) const1007 CImg<T> get_inpaint(const CImg<t>& mask, const unsigned int method) const {
1008   return (+*this).inpaint(mask,method);
1009 }
1010 
1011 template<typename t>
inpaint_patch(const CImg<t> & mask,const unsigned int patch_size=11,const unsigned int lookup_size=22,const float lookup_factor=1,const int lookup_increment=1,const unsigned int blend_size=0,const float blend_threshold=0.5f,const float blend_decay=0.02f,const unsigned int blend_scales=10,const bool is_blend_outer=false)1012 CImg<T>& inpaint_patch(const CImg<t>& mask, const unsigned int patch_size=11,
1013                        const unsigned int lookup_size=22, const float lookup_factor=1,
1014                        const int lookup_increment=1,
1015                        const unsigned int blend_size=0, const float blend_threshold=0.5f,
1016                        const float blend_decay=0.02f, const unsigned int blend_scales=10,
1017                        const bool is_blend_outer=false) {
1018   if (depth()>1)
1019     throw CImgInstanceException(_cimg_instance
1020                                 "inpaint_patch(): Instance image is volumetric (should be 2D).",
1021                                 cimg_instance);
1022   if (!is_sameXYZ(mask))
1023     throw CImgArgumentException(_cimg_instance
1024                                 "inpaint_patch() : Sizes of instance image and specified mask "
1025                                 "(%u,%u,%u,%u) do not match.",
1026                                 cimg_instance,
1027                                 mask._width,mask._height,mask._depth,mask._spectrum);
1028   if (!patch_size)
1029     throw CImgArgumentException(_cimg_instance
1030                                 "inpaint_patch() : Specified patch size is 0, must be strictly "
1031                                 "positive.",
1032                                 cimg_instance);
1033   if (!lookup_size)
1034     throw CImgArgumentException(_cimg_instance
1035                                 "inpaint_patch() : Specified lookup size is 0, must be strictly "
1036                                 "positive.",
1037                                 cimg_instance);
1038   if (lookup_factor<0)
1039     throw CImgArgumentException(_cimg_instance
1040                                 "inpaint_patch() : Specified lookup factor %g is negative, must be "
1041                                 "positive.",
1042                                 cimg_instance,
1043                                 lookup_factor);
1044   if (!lookup_increment)
1045     throw CImgArgumentException(_cimg_instance
1046                                 "inpaint_patch() : Specified lookup increment is 0, must be "
1047                                 "strictly positive.",
1048                                 cimg_instance);
1049   if (blend_decay<0)
1050     throw CImgArgumentException(_cimg_instance
1051                                 "inpaint_patch() : Specified blend decay %g is negative, must be "
1052                                 "positive.",
1053                                 cimg_instance,
1054                                 blend_decay);
1055 
1056   // Find (dilated by 2) bounding box for the inpainting mask.
1057   unsigned int xm0 = _width, ym0 = _height, xm1 = 0, ym1 = 0;
1058   bool is_mask_found = false;
1059   cimg_forXY(mask,x,y) if (mask(x,y)) {
1060     is_mask_found = true;
1061     if (x<(int)xm0) xm0 = (unsigned int)x;
1062     if (x>(int)xm1) xm1 = (unsigned int)x;
1063     if (y<(int)ym0) ym0 = (unsigned int)y;
1064     if (y>(int)ym1) ym1 = (unsigned int)y;
1065   }
1066   if (!is_mask_found) return *this;
1067   xm0 = xm0>2?xm0 - 2:0;
1068   ym0 = ym0>2?ym0 - 2:0;
1069   xm1 = xm1<_width - 3?xm1 + 2:_width - 1;
1070   ym1 = ym1<_height - 3?ym1 + 2:_height - 1;
1071   int ox = (int)xm0, oy = (int)ym0;
1072   unsigned int dx = xm1 - xm0 + 1U, dy = ym1 - ym0 + 1U;
1073 
1074   // Construct normalized version of the mask.
1075   CImg<ucharT> nmask(dx,dy);
1076   {
1077     unsigned char *ptrM = nmask.data();
1078     cimg_for_inXY(mask,xm0,ym0,xm1,ym1,x,y) *(ptrM++) = mask(x,y)?0U:1U;
1079   }
1080   xm0 = ym0 = 0; xm1 = dx - 1; ym1 = dy - 1;
1081 
1082   // Start patch filling algorithm.
1083   const int p2 = (int)patch_size/2, p1 = (int)patch_size - p2 - 1;
1084   const unsigned int patch_size2 = patch_size*patch_size;
1085   unsigned int _lookup_size = lookup_size, nb_lookups = 0, nb_fails = 0, nb_saved_patches = 0;
1086   bool is_strict_search = true;
1087   const float one = 1;
1088 
1089   CImg<floatT> confidences(nmask), priorities(dx,dy,1,2,-1), pC;
1090   CImg<unsigned int> saved_patches(4,256), is_visited(width(),height(),1,1,0);
1091   CImg<ucharT> pM, pN;  // Pre-declare patch variables (avoid iterative memory alloc/dealloc)
1092   CImg<T> pP, pbest;
1093   CImg<floatT> weights(patch_size,patch_size,1,1,0);
1094   weights.draw_gaussian((float)p1,(float)p1,patch_size/15.f,&one)/=patch_size2;
1095   unsigned int target_index = 0;
1096 
1097   while (true) {
1098 
1099     // Extract mask border points and compute priorities to find target point.
1100     unsigned int nb_border_points = 0;
1101     float target_confidence = -1, target_priority = -1;
1102     int target_x = -1, target_y = -1;
1103     CImg_5x5(M,unsigned char);
1104 
1105     cimg_for_in5x5(nmask,xm0,ym0,xm1,ym1,x,y,0,0,M,unsigned char)
1106       if (!Mcc && (Mcp || Mcn || Mpc || Mnc)) { // Found mask border point
1107 
1108         float confidence_term = -1, data_term = -1;
1109         if (priorities(x,y)>=0) { // If priority has already been computed
1110           confidence_term = priorities(x,y,0);
1111           data_term = priorities(x,y,1);
1112         } else { // If priority must be computed/updated
1113 
1114           // Compute smoothed normal vector.
1115           const float
1116             // N = smoothed 3x3 neighborhood of M.
1117             Npc = (4.f*Mpc + 2.f*Mbc + 2.f*Mcc + 2.f*Mpp + 2.f*Mpn + Mbp + Mbn + Mcp + Mcn)/16,
1118             Nnc = (4.f*Mnc + 2.f*Mac + 2.f*Mcc + 2.f*Mnp + 2.f*Mnn + Map + Man + Mcp + Mcn)/16,
1119             Ncp = (4.f*Mcp + 2.f*Mcb + 2.f*Mcc + 2.f*Mpp + 2.f*Mnp + Mpb + Mnb + Mpc + Mnc)/16,
1120             Ncn = (4.f*Mcn + 2.f*Mca + 2.f*Mcc + 2.f*Mpn + 2.f*Mnn + Mpa + Mna + Mpc + Mnc)/16,
1121             _nx = 0.5f*(Nnc - Npc),
1122             _ny = 0.5f*(Ncn - Ncp),
1123             nn = std::sqrt(1e-8f + _nx*_nx + _ny*_ny),
1124             nx = _nx/nn,
1125             ny = _ny/nn;
1126 
1127           // Compute confidence term.
1128           nmask._inpaint_patch_crop(x - p1,y - p1,x + p2,y + p2,1).move_to(pM);
1129           confidences._inpaint_patch_crop(x - p1,y - p1,x + p2,y + p2,1).move_to(pC);
1130           confidence_term = 0;
1131           const unsigned char *ptrM = pM.data();
1132           cimg_for(pC,ptrC,float) confidence_term+=*ptrC**(ptrM++);
1133           confidence_term/=patch_size2;
1134           priorities(x,y,0) = confidence_term;
1135 
1136           // Compute data term.
1137           _inpaint_patch_crop(ox + x - p1,oy + y - p1,ox + x + p2,oy + y + p2,2).move_to(pP);
1138           float mean_ix2 = 0, mean_ixiy = 0, mean_iy2 = 0;
1139 
1140           CImg_3x3(I,T);
1141           CImg_3x3(_M, unsigned char);
1142           cimg_forC(pP,c) cimg_for3x3(pP,p,q,0,c,I,T) {
1143             // Compute weight-mean of structure tensor inside patch.
1144             cimg_get3x3(pM,p,q,0,0,_M,unsigned char);
1145             const float
1146               ixf = (float)(_Mnc*_Mcc*(Inc - Icc)),
1147               iyf = (float)(_Mcn*_Mcc*(Icn - Icc)),
1148               ixb = (float)(_Mcc*_Mpc*(Icc - Ipc)),
1149               iyb = (float)(_Mcc*_Mcp*(Icc - Icp)),
1150               ix = cimg::abs(ixf)>cimg::abs(ixb)?ixf:ixb,
1151               iy = cimg::abs(iyf)>cimg::abs(iyb)?iyf:iyb,
1152               w = weights(p,q);
1153             mean_ix2 += w*ix*ix;
1154             mean_ixiy += w*ix*iy;
1155             mean_iy2 += w*iy*iy;
1156           }
1157           const float // Compute tensor-directed data term
1158             ux = mean_ix2*(-ny) + mean_ixiy*nx,
1159             uy = mean_ixiy*(-ny) + mean_iy2*nx;
1160           data_term = std::sqrt(ux*ux + uy*uy);
1161           priorities(x,y,1) = data_term;
1162         }
1163         const float priority = confidence_term*data_term;
1164         if (priority>target_priority) {
1165           target_priority = priority; target_confidence = confidence_term;
1166           target_x = ox + x; target_y = oy + y;
1167         }
1168         ++nb_border_points;
1169       }
1170     if (!nb_border_points) break; // No more mask border points to inpaint!
1171 
1172     // Locate already reconstructed neighbors (if any), to get good origins for patch lookup.
1173     CImg<unsigned int> lookup_candidates(2,256);
1174     unsigned int nb_lookup_candidates = 0, *ptr_lookup_candidates = lookup_candidates.data();
1175     {
1176       const unsigned int *ptr_saved_patches = saved_patches.data();
1177       const int
1178         x0 = target_x - (int)patch_size, y0 = target_y - (int)patch_size,
1179         x1 = target_x + (int)patch_size, y1 = target_y + (int)patch_size;
1180       for (unsigned int k = 0; k<nb_saved_patches; ++k) {
1181         const unsigned int
1182           src_x = *(ptr_saved_patches++), src_y = *(ptr_saved_patches++),
1183           dest_x = *(ptr_saved_patches++), dest_y = *(ptr_saved_patches++);
1184         if ((int)dest_x>=x0 && (int)dest_y>=y0 && (int)dest_x<=x1 && (int)dest_y<=y1) {
1185           const int off_x = target_x - (int)dest_x, off_y = target_y - (int)dest_y;
1186           *(ptr_lookup_candidates++) = src_x + off_x;
1187           *(ptr_lookup_candidates++) = src_y + off_y;
1188           if (++nb_lookup_candidates>=lookup_candidates._height) {
1189             lookup_candidates.resize(2,-200,1,1,0);
1190             ptr_lookup_candidates = lookup_candidates.data(0,nb_lookup_candidates);
1191           }
1192         }
1193       }
1194     }
1195     // Add also target point as a center for the patch lookup.
1196     if (++nb_lookup_candidates>=lookup_candidates._height) {
1197       lookup_candidates.resize(2,-200,1,1,0);
1198       ptr_lookup_candidates = lookup_candidates.data(0,nb_lookup_candidates);
1199     }
1200     *(ptr_lookup_candidates++) = (unsigned int)target_x;
1201     *(ptr_lookup_candidates++) = (unsigned int)target_y;
1202 
1203     // Divide size of lookup regions if several lookup sources have been detected.
1204     unsigned int final_lookup_size = _lookup_size;
1205     if (nb_lookup_candidates>1) {
1206       const unsigned int
1207         _final_lookup_size = std::max(5U,(unsigned int)cimg::round(_lookup_size*lookup_factor/
1208                                                                     std::sqrt((float)nb_lookup_candidates),1,1));
1209       final_lookup_size = _final_lookup_size + 1 - (_final_lookup_size%2);
1210     }
1211     const int l2 = (int)final_lookup_size/2, l1 = (int)final_lookup_size - l2 - 1;
1212 
1213 #ifdef gmic_debug
1214     CImg<ucharT> visu(*this,false);
1215     for (unsigned int C = 0; C<nb_lookup_candidates; ++C) {
1216       const int
1217         xl = lookup_candidates(0,C),
1218         yl = lookup_candidates(1,C);
1219       visu.draw_rectangle(xl - l1,yl - l1,xl + l2,yl + l2,CImg<ucharT>::vector(0,255,0).data(),0.2f);
1220     }
1221     visu.draw_rectangle(target_x - p1,target_y - p1,target_x + p2,target_y + p2,
1222                         CImg<ucharT>::vector(255,0,0).data(),0.5f);
1223     static int foo = 0;
1224     if (!(foo%1)) {
1225       static CImgDisplay disp_debug;
1226       disp_debug.display(visu).set_title("DEBUG");
1227     }
1228     ++foo;
1229 #endif // #ifdef gmic_debug
1230 
1231     // Find best patch candidate to fill target point.
1232     _inpaint_patch_crop(target_x - p1,target_y - p1,target_x + p2,target_y + p2,0).move_to(pP);
1233     nmask._inpaint_patch_crop(target_x - ox - p1,target_y - oy - p1,target_x - ox + p2,target_y - oy + p2,0).
1234       move_to(pM);
1235     ++target_index;
1236     const unsigned int
1237       _lookup_increment = (unsigned int)(lookup_increment>0?lookup_increment:
1238                                          nb_lookup_candidates>1?1:-lookup_increment);
1239     float best_ssd = cimg::type<float>::max();
1240     int best_x = -1, best_y = -1;
1241     for (unsigned int C = 0; C<nb_lookup_candidates; ++C) {
1242       const int
1243         xl = (int)lookup_candidates(0,C),
1244         yl = (int)lookup_candidates(1,C),
1245         xl0 = std::max(p1,xl - l1), yl0 = std::max(p1,yl - l1),
1246         xl1 = std::min(width() - 1 - p2,xl + l2), yl1 = std::min(height() - 1 - p2,yl + l2);
1247       for (int y = yl0; y<=yl1; y+=_lookup_increment)
1248         for (int x = xl0; x<=xl1; x+=_lookup_increment) if (is_visited(x,y)!=target_index) {
1249             if (is_strict_search) mask._inpaint_patch_crop(x - p1,y - p1,x + p2,y + p2,1).move_to(pN);
1250             else nmask._inpaint_patch_crop(x - ox - p1,y - oy - p1,x - ox + p2,y - oy + p2,0).move_to(pN);
1251             if ((is_strict_search && pN.sum()==0) || (!is_strict_search && pN.sum()==patch_size2)) {
1252               _inpaint_patch_crop(x - p1,y - p1,x + p2,y + p2,0).move_to(pC);
1253               float ssd = 0;
1254               const T *_pP = pP._data;
1255               const float *_pC = pC._data;
1256               cimg_for(pM,_pM,unsigned char) { if (*_pM) {
1257                   cimg_forC(pC,c) {
1258                     ssd+=cimg::sqr((Tfloat)*_pC - (Tfloat)*_pP); _pC+=patch_size2; _pP+=patch_size2;
1259                   }
1260                   if (ssd>=best_ssd) break;
1261                   _pC-=pC._spectrum*patch_size2;
1262                   _pP-=pC._spectrum*patch_size2;
1263                 }
1264                 ++_pC; ++_pP;
1265               }
1266               if (ssd<best_ssd) { best_ssd = ssd; best_x = x; best_y = y; }
1267             }
1268             is_visited(x,y) = target_index;
1269           }
1270     }
1271 
1272     if (best_x<0) { // If no best patch found
1273       priorities(target_x - ox,target_y - oy,0)/=10; // Reduce its priority (lower data_term)
1274       if (++nb_fails>=4) { // If too much consecutive fails :
1275         nb_fails = 0;
1276         _lookup_size+=_lookup_size/2; // Try to expand the lookup size
1277         if (++nb_lookups>=3) {
1278           if (is_strict_search) { // If still fails, switch to non-strict search mode
1279             is_strict_search = false;
1280             _lookup_size = lookup_size;
1281             nb_lookups = 0;
1282           }
1283           else return *this; // Pathological case, probably a weird mask
1284         }
1285       }
1286     } else { // Best patch found -> reconstruct missing part on the target patch
1287       _lookup_size = lookup_size;
1288       nb_lookups = nb_fails = 0;
1289       _inpaint_patch_crop(best_x - p1,best_y - p1,best_x + p2,best_y + p2,0).move_to(pbest);
1290       nmask._inpaint_patch_crop(target_x - ox - p1,target_y - oy - p1,target_x - ox + p2,target_y - oy + p2,1).
1291         move_to(pM);
1292       cimg_for(pM,ptr,unsigned char) *ptr = (unsigned char)(1 - *ptr);
1293       draw_image(target_x - p1,target_y - p1,pbest,pM,1,1);
1294       confidences.draw_image(target_x - ox - p1,target_y - oy - p1,pC.fill(target_confidence),pM,1,1);
1295       nmask.draw_rectangle(target_x - ox - p1,target_y - oy - p1,0,0,target_x - ox + p2,target_y - oy + p2,0,0,1);
1296       priorities.draw_rectangle(target_x - ox - (int)patch_size,
1297                                 target_y - oy - (int)patch_size,0,0,
1298                                 target_x - ox + 3*p2/2,
1299                                 target_y - oy + 3*p2/2,0,0,-1);
1300       // Remember patch positions.
1301       unsigned int *ptr_saved_patches = saved_patches.data(0,nb_saved_patches);
1302       *(ptr_saved_patches++) = (unsigned int)best_x;
1303       *(ptr_saved_patches++) = (unsigned int)best_y;
1304       *(ptr_saved_patches++) = (unsigned int)target_x;
1305       *ptr_saved_patches = (unsigned int)target_y;
1306       if (++nb_saved_patches>=saved_patches._height) saved_patches.resize(4,-200,1,1,0);
1307     }
1308   }
1309   nmask.assign(); // Free some unused memory resources
1310   priorities.assign();
1311   confidences.assign();
1312   is_visited.assign();
1313 
1314   // Blend inpainting result (if requested), using multi-scale blending algorithm.
1315   if (blend_size && blend_scales) {
1316     const float _blend_threshold = std::max(0.f,std::min(1.f,blend_threshold));
1317     saved_patches._height = nb_saved_patches;
1318 
1319     // Re-crop image and mask if outer blending is activated.
1320     if (is_blend_outer) {
1321       const int
1322         b2 = (int)blend_size/2, b1 = (int)blend_size - b2 - 1,
1323         xb0 = std::max(0,ox - b1),
1324         yb0 = std::max(0,oy - b1),
1325         xb1 = (int)std::min(_width - 1,xb0 + dx + b1 + b2),
1326         yb1 = (int)std::min(_height - 1,yb0 + dy + b1 + b2);
1327       ox = xb0; oy = yb0; dx = xb1 - xb0 + 1U, dy = yb1 - yb0 + 1U;
1328     }
1329 
1330     // Generate map of source offsets.
1331     CImg<unsigned int> offsets(dx,dy,1,2);
1332     {
1333       unsigned int *ptr = saved_patches.end();
1334       cimg_forY(saved_patches,i) {
1335         const unsigned int yd = *(--ptr), xd = *(--ptr), ys = *(--ptr), xs = *(--ptr);
1336         for (int l = -p1; l<=p2; ++l)
1337           for (int k = -p1; k<=p2; ++k) {
1338             const int xdk = (int)xd + k, ydl = (int)yd + l;
1339             if (xdk>=0 && xdk<=width() - 1 && ydl>=0 && ydl<=height() - 1 && mask(xd + k,yd + l)) {
1340               offsets(xd - ox + k,yd - oy + l,0) = xs + k;
1341               offsets(xd - ox + k,yd - oy + l,1) = ys + l;
1342             }
1343           }
1344       }
1345     }
1346     unsigned int *ptrx = offsets.data(0,0,0,0), *ptry = offsets.data(0,0,0,1);
1347     cimg_forXY(offsets,x,y) {
1348       if (!mask(x + ox,y + oy)) { *ptrx = (unsigned int)(x + ox); *ptry = (unsigned int)(y + oy); }
1349       ++ptrx; ++ptry;
1350     }
1351 
1352     // Generate map of local blending amplitudes.
1353     CImg<floatT> blend_map(dx,dy,1,1,0);
1354     CImg_3x3(I,float);
1355     cimg_for3XY(offsets,x,y) if (mask(x + ox,y + oy)) {
1356       const float
1357         iox = std::max((float)offsets(_n1x,y,0) - offsets(x,y,0),
1358                         (float)offsets(x,y,0) - offsets(_p1x,y,0)),
1359         ioy = std::max((float)offsets(x,_n1y,1) - offsets(x,y,1),
1360                         (float)offsets(x,y,1) - offsets(x,_p1y,1)),
1361         ion = std::sqrt(iox*iox + ioy*ioy);
1362       float iin = 0;
1363       cimg_forC(*this,c) {
1364         cimg_get3x3(*this,x,y,0,c,I,float);
1365         const float
1366           iix = (float)std::max(Inc - Icc,Icc - Ipc),
1367           iiy = (float)std::max(Icn - Icc,Icc - Icp);
1368         iin+=std::log(1 + iix*iix + iiy*iiy);
1369       }
1370       iin/=_spectrum;
1371       blend_map(x,y) = ion*iin;
1372     }
1373     blend_map.threshold(blend_map.max()*_blend_threshold).distance(1);
1374     cimg_forXY(blend_map,x,y) blend_map(x,y) = 1/(1 + blend_decay*blend_map(x,y));
1375     blend_map.quantize(blend_scales + 1,false);
1376     float bm, bM = blend_map.max_min(bm);
1377     if (bm==bM) blend_map.fill((float)blend_scales);
1378 
1379     // Generate blending scales.
1380     CImg<T> result = _inpaint_patch_crop(ox,oy,ox + dx - 1,oy + dy - 1,0);
1381     for (unsigned int blend_iter = 1; blend_iter<=blend_scales; ++blend_iter) {
1382       const unsigned int
1383         _blend_width = blend_iter*blend_size/blend_scales,
1384         blend_width = _blend_width?_blend_width + 1 - (_blend_width%2):0;
1385       if (!blend_width) continue;
1386       const int b2 = (int)blend_width/2, b1 = (int)blend_width - b2 - 1;
1387       CImg<floatT>
1388         blended = _inpaint_patch_crop(ox,oy,ox + dx - 1,oy + dy - 1,0),
1389         cumul(dx,dy,1,1);
1390       weights.assign(blend_width,blend_width,1,1,0).
1391         draw_gaussian((float)b1,(float)b1,blend_width/4.f,&one);
1392       cimg_forXY(cumul,x,y) cumul(x,y) = mask(x + ox,y + oy)?0.f:1.f;
1393       blended.mul(cumul);
1394 
1395       cimg_forY(saved_patches,l) {
1396         const unsigned int *ptr = saved_patches.data(0,l);
1397         const int
1398           xs = (int)*(ptr++),
1399           ys = (int)*(ptr++),
1400           xd = (int)*(ptr++),
1401           yd = (int)*(ptr++);
1402         if (xs - b1<0 || ys - b1<0 || xs + b2>=width() || ys + b2>=height()) { // Blend with partial patch
1403           const int
1404             xs0 = std::max(0,xs - b1),
1405             ys0 = std::max(0,ys - b1),
1406             xs1 = std::min(width() - 1,xs + b2),
1407             ys1 = std::min(height() - 1,ys + b2);
1408           _inpaint_patch_crop(xs0,ys0,xs1,ys1,0).move_to(pP);
1409           weights._inpaint_patch_crop(xs0 - xs + b1,ys0 - ys + b1,xs1 - xs + b1,ys1 - ys + b1,0).move_to(pC);
1410           blended.draw_image(xd + xs0 - xs - ox,yd + ys0 - ys - oy,pP,pC,-1);
1411           cumul.draw_image(xd + xs0 - xs - ox,yd + ys0 - ys - oy,pC,-1);
1412         } else { // Blend with full-size patch
1413           _inpaint_patch_crop(xs - b1,ys - b1,xs + b2,ys + b2,0).move_to(pP);
1414           blended.draw_image(xd - b1 - ox,yd - b1 - oy,pP,weights,-1);
1415           cumul.draw_image(xd - b1 - ox,yd - b1 - oy,weights,-1);
1416         }
1417       }
1418 
1419       if (is_blend_outer) {
1420         cimg_forXY(blended,x,y) if (blend_map(x,y)==blend_iter) {
1421           const float cum = cumul(x,y);
1422           if (cum>0) cimg_forC(*this,c) result(x,y,c) = (T)(blended(x,y,c)/cum);
1423         }
1424       } else { cimg_forXY(blended,x,y) if (mask(x + ox,y + oy) && blend_map(x,y)==blend_iter) {
1425           const float cum = cumul(x,y);
1426           if (cum>0) cimg_forC(*this,c) result(x,y,c) = (T)(blended(x,y,c)/cum);
1427         }
1428       }
1429     }
1430     if (is_blend_outer) draw_image(ox,oy,result);
1431     else cimg_forXY(result,x,y) if (mask(x + ox,y + oy))
1432            cimg_forC(*this,c) (*this)(x + ox,y + oy,c) = (T)result(x,y,c);
1433   }
1434   return *this;
1435 }
1436 
1437 // Special crop function that supports more boundary conditions :
1438 // 0=dirichlet (with value 0), 1=dirichlet (with value 1) and 2=neumann.
_inpaint_patch_crop(const int x0,const int y0,const int x1,const int y1,const unsigned int boundary=0) const1439 CImg<T> _inpaint_patch_crop(const int x0, const int y0, const int x1, const int y1,
1440                             const unsigned int boundary=0) const {
1441   const int
1442     nx0 = x0<x1?x0:x1, nx1 = x0^x1^nx0,
1443     ny0 = y0<y1?y0:y1, ny1 = y0^y1^ny0;
1444   CImg<T> res(1U + nx1 - nx0,1U + ny1 - ny0,1,_spectrum);
1445   if (nx0<0 || nx1>=width() || ny0<0 || ny1>=height()) {
1446     if (boundary>=2) cimg_forXYZC(res,x,y,z,c) res(x,y,z,c) = _atXY(nx0 + x,ny0 + y,z,c);
1447     else res.fill((T)boundary).draw_image(-nx0,-ny0,*this);
1448   } else res.draw_image(-nx0,-ny0,*this);
1449   return res;
1450 }
1451 
1452 template<typename t>
get_inpaint_patch(const CImg<t> & mask,const unsigned int patch_size=11,const unsigned int lookup_size=22,const float lookup_factor=1,const int lookup_increment=1,const unsigned int blend_size=0,const float blend_threshold=0.5,const float blend_decay=0.02f,const unsigned int blend_scales=10,const bool is_blend_outer=false) const1453 CImg<T> get_inpaint_patch(const CImg<t>& mask, const unsigned int patch_size=11,
1454                           const unsigned int lookup_size=22, const float lookup_factor=1,
1455                           const int lookup_increment=1,
1456                           const unsigned int blend_size=0, const float blend_threshold=0.5,
1457                           const float blend_decay=0.02f, const unsigned int blend_scales=10,
1458                           const bool is_blend_outer=false) const {
1459   return (+*this).inpaint_patch(mask,patch_size,lookup_size,lookup_factor,lookup_increment,
1460                                 blend_size,blend_threshold,blend_decay,blend_scales,is_blend_outer);
1461 }
1462 
max(const char * const expression,CImgList<T> & images)1463 CImg<T>& max(const char *const expression, CImgList<T> &images) {
1464   return max((+*this)._fill(expression,true,1,&images,&images,"max",this));
1465 }
1466 
min(const char * const expression,CImgList<T> & images)1467 CImg<T>& min(const char *const expression, CImgList<T> &images) {
1468   return min((+*this)._fill(expression,true,1,&images,&images,"min",this));
1469 }
1470 
operator_andeq(const char * const expression,CImgList<T> & images)1471 CImg<T>& operator_andeq(const char *const expression, CImgList<T> &images) {
1472   return operator&=((+*this)._fill(expression,true,1,&images,&images,"operator&=",this));
1473 }
1474 
operator_brseq(const char * const expression,CImgList<T> & images)1475 CImg<T>& operator_brseq(const char *const expression, CImgList<T> &images) {
1476   return operator<<=((+*this)._fill(expression,true,1,&images,&images,"operator<<=",this));
1477 }
1478 
operator_blseq(const char * const expression,CImgList<T> & images)1479 CImg<T>& operator_blseq(const char *const expression, CImgList<T> &images) {
1480   return operator>>=((+*this)._fill(expression,true,1,&images,&images,"operator>>=",this));
1481 }
1482 
operator_diveq(const char * const expression,CImgList<T> & images)1483 CImg<T>& operator_diveq(const char *const expression, CImgList<T> &images) {
1484   return div((+*this)._fill(expression,true,1,&images,&images,"operator/=",this));
1485 }
1486 
1487 template<typename t>
operator_eq(const t value)1488 CImg<T>& operator_eq(const t value) {
1489   if (is_empty()) return *this;
1490   cimg_openmp_for(*this,*ptr == (T)value,131072);
1491   return *this;
1492 }
1493 
operator_eq(const char * const expression,CImgList<T> & images)1494 CImg<T>& operator_eq(const char *const expression, CImgList<T> &images) {
1495   return operator_eq((+*this)._fill(expression,true,1,&images,&images,"operator_eq",this));
1496 }
1497 
1498 template<typename t>
operator_eq(const CImg<t> & img)1499 CImg<T>& operator_eq(const CImg<t>& img) {
1500   const ulongT siz = size(), isiz = img.size();
1501   if (siz && isiz) {
1502     if (is_overlapped(img)) return operator_eq(+img);
1503     T *ptrd = _data, *const ptre = _data + siz;
1504     if (siz>isiz)
1505       for (ulongT n = siz/isiz; n; --n)
1506         for (const t *ptrs = img._data, *ptrs_end = ptrs + isiz; ptrs<ptrs_end; ++ptrd)
1507           *ptrd = (T)(*ptrd == (T)*(ptrs++));
1508     for (const t *ptrs = img._data; ptrd<ptre; ++ptrd) *ptrd = (T)(*ptrd == (T)*(ptrs++));
1509   }
1510   return *this;
1511 }
1512 
1513 template<typename t>
operator_ge(const t value)1514 CImg<T>& operator_ge(const t value) {
1515   if (is_empty()) return *this;
1516   cimg_openmp_for(*this,*ptr >= (T)value,131072);
1517   return *this;
1518 }
1519 
operator_ge(const char * const expression,CImgList<T> & images)1520 CImg<T>& operator_ge(const char *const expression, CImgList<T> &images) {
1521   return operator_ge((+*this)._fill(expression,true,1,&images,&images,"operator_ge",this));
1522 }
1523 
1524 template<typename t>
operator_ge(const CImg<t> & img)1525 CImg<T>& operator_ge(const CImg<t>& img) {
1526   const ulongT siz = size(), isiz = img.size();
1527   if (siz && isiz) {
1528     if (is_overlapped(img)) return operator_ge(+img);
1529     T *ptrd = _data, *const ptre = _data + siz;
1530     if (siz>isiz)
1531       for (ulongT n = siz/isiz; n; --n)
1532         for (const t *ptrs = img._data, *ptrs_end = ptrs + isiz; ptrs<ptrs_end; ++ptrd)
1533           *ptrd = (T)(*ptrd >= (T)*(ptrs++));
1534     for (const t *ptrs = img._data; ptrd<ptre; ++ptrd) *ptrd = (T)(*ptrd >= (T)*(ptrs++));
1535   }
1536   return *this;
1537 }
1538 
1539 template<typename t>
operator_gt(const t value)1540 CImg<T>& operator_gt(const t value) {
1541   if (is_empty()) return *this;
1542   cimg_openmp_for(*this,*ptr > (T)value,131072);
1543   return *this;
1544 }
1545 
operator_gt(const char * const expression,CImgList<T> & images)1546 CImg<T>& operator_gt(const char *const expression, CImgList<T> &images) {
1547   return operator_gt((+*this)._fill(expression,true,1,&images,&images,"operator_gt",this));
1548 }
1549 
1550 template<typename t>
operator_gt(const CImg<t> & img)1551 CImg<T>& operator_gt(const CImg<t>& img) {
1552   const ulongT siz = size(), isiz = img.size();
1553   if (siz && isiz) {
1554     if (is_overlapped(img)) return operator_gt(+img);
1555     T *ptrd = _data, *const ptre = _data + siz;
1556     if (siz>isiz)
1557       for (ulongT n = siz/isiz; n; --n)
1558         for (const t *ptrs = img._data, *ptrs_end = ptrs + isiz; ptrs<ptrs_end; ++ptrd)
1559           *ptrd = (T)(*ptrd > (T)*(ptrs++));
1560     for (const t *ptrs = img._data; ptrd<ptre; ++ptrd) *ptrd = (T)(*ptrd > (T)*(ptrs++));
1561   }
1562   return *this;
1563 }
1564 
1565 template<typename t>
operator_le(const t value)1566 CImg<T>& operator_le(const t value) {
1567   if (is_empty()) return *this;
1568   cimg_openmp_for(*this,*ptr <= (T)value,131072);
1569   return *this;
1570 }
1571 
operator_le(const char * const expression,CImgList<T> & images)1572 CImg<T>& operator_le(const char *const expression, CImgList<T> &images) {
1573   return operator_le((+*this)._fill(expression,true,1,&images,&images,"operator_le",this));
1574 }
1575 
1576 template<typename t>
operator_le(const CImg<t> & img)1577 CImg<T>& operator_le(const CImg<t>& img) {
1578   const ulongT siz = size(), isiz = img.size();
1579   if (siz && isiz) {
1580     if (is_overlapped(img)) return operator_le(+img);
1581     T *ptrd = _data, *const ptre = _data + siz;
1582     if (siz>isiz)
1583       for (ulongT n = siz/isiz; n; --n)
1584         for (const t *ptrs = img._data, *ptrs_end = ptrs + isiz; ptrs<ptrs_end; ++ptrd)
1585           *ptrd = (T)(*ptrd <= (T)*(ptrs++));
1586     for (const t *ptrs = img._data; ptrd<ptre; ++ptrd) *ptrd = (T)(*ptrd <= (T)*(ptrs++));
1587   }
1588   return *this;
1589 }
1590 
1591 template<typename t>
operator_lt(const t value)1592 CImg<T>& operator_lt(const t value) {
1593   if (is_empty()) return *this;
1594   cimg_openmp_for(*this,*ptr < (T)value,131072);
1595   return *this;
1596 }
1597 
operator_lt(const char * const expression,CImgList<T> & images)1598 CImg<T>& operator_lt(const char *const expression, CImgList<T> &images) {
1599   return operator_lt((+*this)._fill(expression,true,1,&images,&images,"operator_lt",this));
1600 }
1601 
1602 template<typename t>
operator_lt(const CImg<t> & img)1603 CImg<T>& operator_lt(const CImg<t>& img) {
1604   const ulongT siz = size(), isiz = img.size();
1605   if (siz && isiz) {
1606     if (is_overlapped(img)) return operator_lt(+img);
1607     T *ptrd = _data, *const ptre = _data + siz;
1608     if (siz>isiz)
1609       for (ulongT n = siz/isiz; n; --n)
1610         for (const t *ptrs = img._data, *ptrs_end = ptrs + isiz; ptrs<ptrs_end; ++ptrd)
1611           *ptrd = (T)(*ptrd < (T)*(ptrs++));
1612     for (const t *ptrs = img._data; ptrd<ptre; ++ptrd) *ptrd = (T)(*ptrd < (T)*(ptrs++));
1613   }
1614   return *this;
1615 }
1616 
operator_minuseq(const char * const expression,CImgList<T> & images)1617 CImg<T>& operator_minuseq(const char *const expression, CImgList<T> &images) {
1618   return operator-=((+*this)._fill(expression,true,1,&images,&images,"operator-=",this));
1619 }
1620 
operator_modeq(const char * const expression,CImgList<T> & images)1621 CImg<T>& operator_modeq(const char *const expression, CImgList<T> &images) {
1622   return operator%=((+*this)._fill(expression,true,1,&images,&images,"operator%=",this));
1623 }
1624 
operator_muleq(const char * const expression,CImgList<T> & images)1625 CImg<T>& operator_muleq(const char *const expression, CImgList<T> &images) {
1626   return mul((+*this)._fill(expression,true,1,&images,&images,"operator*=",this));
1627 }
1628 
1629 template<typename t>
operator_neq(const t value)1630 CImg<T>& operator_neq(const t value) {
1631   if (is_empty()) return *this;
1632   cimg_openmp_for(*this,*ptr != (T)value,131072);
1633   return *this;
1634 }
1635 
operator_neq(const char * const expression,CImgList<T> & images)1636 CImg<T>& operator_neq(const char *const expression, CImgList<T> &images) {
1637   return operator_neq((+*this)._fill(expression,true,1,&images,&images,"operator_neq",this));
1638 }
1639 
1640 template<typename t>
operator_neq(const CImg<t> & img)1641 CImg<T>& operator_neq(const CImg<t>& img) {
1642   const ulongT siz = size(), isiz = img.size();
1643   if (siz && isiz) {
1644     if (is_overlapped(img)) return operator_neq(+img);
1645     T *ptrd = _data, *const ptre = _data + siz;
1646     if (siz>isiz)
1647       for (ulongT n = siz/isiz; n; --n)
1648         for (const t *ptrs = img._data, *ptrs_end = ptrs + isiz; ptrs<ptrs_end; ++ptrd)
1649           *ptrd = (T)(*ptrd != (T)*(ptrs++));
1650     for (const t *ptrs = img._data; ptrd<ptre; ++ptrd) *ptrd = (T)(*ptrd != (T)*(ptrs++));
1651   }
1652   return *this;
1653 }
1654 
operator_oreq(const char * const expression,CImgList<T> & images)1655 CImg<T>& operator_oreq(const char *const expression, CImgList<T> &images) {
1656   return operator|=((+*this)._fill(expression,true,1,&images,&images,"operator|=",this));
1657 }
1658 
operator_pluseq(const char * const expression,CImgList<T> & images)1659 CImg<T>& operator_pluseq(const char *const expression, CImgList<T> &images) {
1660   return operator+=((+*this)._fill(expression,true,1,&images,&images,"operator+=",this));
1661 }
1662 
operator_xoreq(const char * const expression,CImgList<T> & images)1663 CImg<T>& operator_xoreq(const char *const expression, CImgList<T> &images) {
1664   return operator^=((+*this)._fill(expression,true,1,&images,&images,"operator^=",this));
1665 }
1666 
pow(const char * const expression,CImgList<T> & images)1667 CImg<T>& pow(const char *const expression, CImgList<T> &images) {
1668   return pow((+*this)._fill(expression,true,1,&images,&images,"pow",this));
1669 }
1670 
1671 template<typename t>
replace(CImg<t> & img)1672 CImg<T>& replace(CImg<t>& img) {
1673   return img.move_to(*this);
1674 }
1675 
1676 template<typename t>
get_replace(const CImg<t> & img) const1677 CImg<T> get_replace(const CImg<t>& img) const {
1678   return +img;
1679 }
1680 
gmic_eval(const char * const expression,CImgList<T> & images)1681 CImg<T>& gmic_eval(const char *const expression, CImgList<T> &images) {
1682   return _fill(expression,true,2,&images,&images,"eval",0);
1683 }
1684 
get_gmic_eval(const char * const expression,CImgList<T> & images) const1685 CImg<T> get_gmic_eval(const char *const expression, CImgList<T> &images) const {
1686   return (+*this).gmic_eval(expression,images);
1687 }
1688 
reverse_CImg3d()1689 CImg<T>& reverse_CImg3d() {
1690   CImg<char> error_message(1024);
1691   if (!is_CImg3d(false,error_message))
1692     throw CImgInstanceException(_cimg_instance
1693                                 "reverse_CImg3d(): image instance is not a CImg3d (%s).",
1694                                 cimg_instance,error_message.data());
1695   T *p = _data + 6;
1696   const unsigned int
1697     nbv = cimg::float2uint(*(p++)),
1698     nbp = cimg::float2uint(*(p++));
1699   p+=3*nbv;
1700   for (unsigned int i = 0; i<nbp; ++i) {
1701     const unsigned int nb = (unsigned int)*(p++);
1702     switch(nb) {
1703     case 2: case 3: cimg::swap(p[0],p[1]); break;
1704     case 6: cimg::swap(p[0],p[1],p[2],p[4],p[3],p[5]); break;
1705     case 9: cimg::swap(p[0],p[1],p[3],p[5],p[4],p[6]); break;
1706     case 4: cimg::swap(p[0],p[1],p[2],p[3]); break;
1707     case 12: cimg::swap(p[0],p[1],p[2],p[3],p[4],p[6],p[5],p[7],p[8],p[10],p[9],p[11]); break;
1708     }
1709     p+=nb;
1710   }
1711   return *this;
1712 }
1713 
get_reverse_CImg3d() const1714 CImg<T> get_reverse_CImg3d() const {
1715   return (+*this).reverse_CImg3d();
1716 }
1717 
rol(const char * const expression,CImgList<T> & images)1718 CImg<T>& rol(const char *const expression, CImgList<T> &images) {
1719   return rol((+*this)._fill(expression,true,1,&images,&images,"rol",this));
1720 }
1721 
ror(const char * const expression,CImgList<T> & images)1722 CImg<T>& ror(const char *const expression, CImgList<T> &images) {
1723   return ror((+*this)._fill(expression,true,1,&images,&images,"ror",this));
1724 }
1725 
1726 template<typename t>
rotate_CImg3d(const CImg<t> & rot)1727 CImg<T>& rotate_CImg3d(const CImg<t>& rot) {
1728   CImg<charT> error_message(1024);
1729   if (!is_CImg3d(false,error_message))
1730     throw CImgInstanceException(_cimg_instance
1731                                 "rotate_CImg3d(): image instance is not a CImg3d (%s).",
1732                                 cimg_instance,error_message.data());
1733   const unsigned int nbv = cimg::float2uint((float)(*this)[6]);
1734   const T *ptrs = data() + 8;
1735   const float
1736     a = (float)rot(0,0), b = (float)rot(1,0), c = (float)rot(2,0),
1737     d = (float)rot(0,1), e = (float)rot(1,1), f = (float)rot(2,1),
1738     g = (float)rot(0,2), h = (float)rot(1,2), i = (float)rot(2,2);
1739   T *ptrd = data() + 8;
1740   for (unsigned int j = 0; j<nbv; ++j) {
1741     const float
1742       x = (float)ptrs[0],
1743       y = (float)ptrs[1],
1744       z = (float)ptrs[2];
1745     ptrs+=3;
1746     ptrd[0] = (T)(a*x + b*y + c*z);
1747     ptrd[1] = (T)(d*x + e*y + f*z);
1748     ptrd[2] = (T)(g*x + h*y + i*z);
1749     ptrd+=3;
1750   }
1751   return *this;
1752 }
1753 
1754 template<typename t>
get_rotate_CImg3d(const CImg<t> & rot) const1755 CImg<T> get_rotate_CImg3d(const CImg<t>& rot) const {
1756   return (+*this).rotate_CImg3d(rot);
1757 }
1758 
scale_CImg3d(const float sx,const float sy,const float sz)1759 CImg<T>& scale_CImg3d(const float sx, const float sy, const float sz) {
1760   CImg<charT> error_message(1024);
1761   if (!is_CImg3d(false,error_message))
1762     throw CImgInstanceException(_cimg_instance
1763                                 "scale_CImg3d(): image instance is not a CImg3d (%s).",
1764                                 cimg_instance,error_message.data());
1765   const unsigned int nbv = cimg::float2uint((float)(*this)[6]);
1766   T *ptrd = data() + 8;
1767   for (unsigned int j = 0; j<nbv; ++j) { *(ptrd++)*=(T)sx; *(ptrd++)*=(T)sy; *(ptrd++)*=(T)sz; }
1768   return *this;
1769 }
1770 
get_scale_CImg3d(const float sx,const float sy,const float sz) const1771 CImg<T> get_scale_CImg3d(const float sx, const float sy, const float sz) const {
1772   return (+*this).scale_CImg3d(sx,sy,sz);
1773 }
1774 
shift_CImg3d(const float tx,const float ty,const float tz)1775 CImg<T>& shift_CImg3d(const float tx, const float ty, const float tz) {
1776   CImg<charT> error_message(1024);
1777   if (!is_CImg3d(false,error_message))
1778     throw CImgInstanceException(_cimg_instance
1779                                 "shift_CImg3d(): image instance is not a CImg3d (%s).",
1780                                 cimg_instance,error_message.data());
1781   const unsigned int nbv = cimg::float2uint((float)(*this)[6]);
1782   T *ptrd = data() + 8;
1783   for (unsigned int j = 0; j<nbv; ++j) { *(ptrd++)+=(T)tx; *(ptrd++)+=(T)ty; *(ptrd++)+=(T)tz; }
1784   return *this;
1785 }
1786 
get_shift_CImg3d(const float tx,const float ty,const float tz) const1787 CImg<T> get_shift_CImg3d(const float tx, const float ty, const float tz) const {
1788   return (+*this).shift_CImg3d(tx,ty,tz);
1789 }
1790 
get_split_CImg3d() const1791 CImgList<T> get_split_CImg3d() const {
1792   CImg<charT> error_message(1024);
1793   if (!is_CImg3d(false,error_message))
1794     throw CImgInstanceException(_cimg_instance
1795                                 "get_split_CImg3d(): image instance is not a CImg3d (%s).",
1796                                 cimg_instance,error_message.data());
1797   CImgList<T> res;
1798   const T *ptr0 = _data, *ptr = ptr0 + 6;
1799   CImg<T>(ptr0,1,(unsigned int)(ptr - ptr0),1,1).move_to(res); // Header
1800   ptr0 = ptr;
1801   const unsigned int
1802     nbv = cimg::float2uint(*(ptr++)),
1803     nbp = cimg::float2uint(*(ptr++));
1804   CImg<T>(ptr0,1,(unsigned int)(ptr - ptr0),1,1).move_to(res); // Nb vertices and primitives
1805   ptr0 = ptr; ptr+=3*nbv;
1806   CImg<T>(ptr0,1,(unsigned int)(ptr - ptr0),1,1).move_to(res); // Vertices
1807   ptr0 = ptr;
1808   for (unsigned int i = 0; i<nbp; ++i) ptr+=(unsigned int)(*ptr) + 1;
1809   CImg<T>(ptr0,1,(unsigned int)(ptr - ptr0),1,1).move_to(res); // Primitives
1810   ptr0 = ptr;
1811   for (unsigned int i = 0; i<nbp; ++i) {
1812     const T val = *(ptr++);
1813     if (val!=-128) ptr+=2;
1814     else {
1815       const unsigned int
1816         w = cimg::float2uint(ptr[0]),
1817         h = cimg::float2uint(ptr[1]),
1818         s = cimg::float2uint(ptr[2]);
1819       ptr+=3;
1820       if (w*h*s!=0) ptr+=w*h*s;
1821     }
1822   }
1823   CImg<T>(ptr0,1,(unsigned int)(ptr - ptr0),1,1).move_to(res); // Colors/Textures
1824   ptr0 = ptr;
1825   for (unsigned int i = 0; i<nbp; ++i) {
1826     const T val = *(ptr++);
1827     if (val==-128) {
1828       const unsigned int
1829         w = cimg::float2uint(ptr[0]),
1830         h = cimg::float2uint(ptr[1]),
1831         s = cimg::float2uint(ptr[2]);
1832       ptr+=3;
1833       if (w*h*s!=0) ptr+=w*h*s;
1834     }
1835   }
1836   CImg<T>(ptr0,1,(unsigned int)(ptr - ptr0),1,1).move_to(res); // Opacities
1837   return res;
1838 }
1839 
save_gmz(const char * filename,const CImgList<T> & images,const CImgList<charT> & names)1840 static const CImgList<T>& save_gmz(const char *filename, const CImgList<T>& images, const CImgList<charT>& names) {
1841   CImgList<T> gmz(images.size() + 1);
1842   cimglist_for(images,l) gmz[l].assign(images[l],true);
1843   CImg<charT> gmz_info = CImg<charT>::string("GMZ");
1844   gmz_info.append((names>'x'),'x').unroll('y').move_to(gmz.back());
1845   gmz.save_cimg(filename,true);
1846   return images;
1847 }
1848 
1849 //--------------- End of CImg<T> plug-in ----------------------------
1850 
1851 // Add G'MIC-specific methods to the CImgList<T> class of the CImg library.
1852 //-------------------------------------------------------------------------
1853 #undef cimg_plugin
1854 #elif defined(cimglist_plugin)
1855 
1856 template<typename t>
copy_rounded(const CImgList<t> & list)1857 static CImgList<T> copy_rounded(const CImgList<t>& list) {
1858   if (!cimg::type<t>::is_float() || cimg::type<T>::is_float()) return list;
1859   CImgList<T> res(list.size());
1860   cimglist_for(res,l) CImg<T>::copy_rounded(list[l]).move_to(res[l]);
1861   return res;
1862 }
1863 
copy_rounded(const CImg<T> & list)1864 static CImg<T> copy_rounded(const CImg<T>& list) {
1865   return CImgList<T>(list,true);
1866 }
1867 
1868 // The method below is a variant of the method 'CImgList<T>::_display()', where
1869 // G'MIC command 'display2d' is used in place of the native method 'CImg<T>::display()',
1870 // for displaying 2d images only.
1871 template<typename t>
_gmic_display(CImgDisplay & disp,const char * const title,const CImgList<charT> * const titles,const bool display_info,const char axis,const float align,unsigned int * const XYZ,const bool exit_on_anykey,const unsigned int orig,const bool is_first_call,bool & is_exit,t & gmic_instance0,CImgList<T> & images,CImgList<charT> & images_names) const1872 const CImgList<T>& _gmic_display(CImgDisplay &disp, const char *const title, const CImgList<charT> *const titles,
1873                                  const bool display_info, const char axis, const float align, unsigned int *const XYZ,
1874                                  const bool exit_on_anykey, const unsigned int orig, const bool is_first_call,
1875                                  bool &is_exit,
1876                                  t& gmic_instance0, CImgList<T>& images, CImgList<charT>& images_names) const {
1877   if (is_empty())
1878     throw CImgInstanceException(_cimglist_instance
1879                                 "display(): Empty instance.",
1880                                 cimglist_instance);
1881   if (!disp) {
1882     if (axis=='x') {
1883       unsigned int sum_width = 0, max_height = 0;
1884       cimglist_for(*this,l) {
1885         const CImg<T> &img = _data[l];
1886         const unsigned int
1887           w = CImgDisplay::_fitscreen(img._width,img._height,img._depth,128,-85,false),
1888           h = CImgDisplay::_fitscreen(img._width,img._height,img._depth,128,-85,true);
1889         sum_width+=w;
1890         if (h>max_height) max_height = h;
1891       }
1892       disp.assign(cimg_fitscreen(sum_width,max_height,1),title?title:titles?titles->__display()._data:0,1);
1893     } else {
1894       unsigned int max_width = 0, sum_height = 0;
1895       cimglist_for(*this,l) {
1896         const CImg<T> &img = _data[l];
1897         const unsigned int
1898           w = CImgDisplay::_fitscreen(img._width,img._height,img._depth,128,-85,false),
1899           h = CImgDisplay::_fitscreen(img._width,img._height,img._depth,128,-85,true);
1900         if (w>max_width) max_width = w;
1901         sum_height+=h;
1902       }
1903       disp.assign(cimg_fitscreen(max_width,sum_height,1),title?title:titles?titles->__display()._data:0,1);
1904     }
1905     if (!title && !titles) disp.set_title("CImgList<%s> (%u)",pixel_type(),_width);
1906   } else if (title) disp.set_title("%s",title);
1907   else if (titles) disp.set_title("%s",titles->__display()._data);
1908   const CImg<char> dtitle = CImg<char>::string(disp.title());
1909   if (display_info) print(disp.title());
1910   disp.show().flush();
1911 
1912   if (_width==1) {
1913     const unsigned int dw = disp._width, dh = disp._height;
1914     if (!is_first_call)
1915       disp.resize(cimg_fitscreen(_data[0]._width,_data[0]._height,_data[0]._depth),false);
1916     disp.set_title("%s (%ux%ux%ux%u)",
1917                    dtitle.data(),_data[0]._width,_data[0]._height,_data[0]._depth,_data[0]._spectrum);
1918     if (_data[0]._depth==1) { // Use custom command 'display2d' for 2D images.
1919       CImgList<T> _images(_data[0],true);
1920       CImgList<charT> _images_names(dtitle,true);
1921       CImg<charT> com(128);
1922       bool is_exception = false;
1923       std::sprintf(com,"_d2d_core %d",(int)!is_first_call);
1924       t gmic_instance;
1925       cimg::swap(gmic_instance.commands,gmic_instance0.commands);
1926       cimg::swap(gmic_instance.commands_names,gmic_instance0.commands_names);
1927       cimg::swap(gmic_instance.commands_has_arguments,gmic_instance0.commands_has_arguments);
1928       void *const _display_window0 = gmic_instance.display_windows[0];
1929       gmic_instance.display_windows[0] = &disp;
1930       try { gmic_instance.run(com.data(),_images,_images_names); }
1931       catch (...) { is_exception = true; }
1932       cimg::swap(gmic_instance.commands,gmic_instance0.commands);
1933       cimg::swap(gmic_instance.commands_names,gmic_instance0.commands_names);
1934       cimg::swap(gmic_instance.commands_has_arguments,gmic_instance0.commands_has_arguments);
1935       gmic_instance.display_windows[0] = _display_window0;
1936       if (is_exception) throw CImgDisplayException("");
1937     } else _data[0]._display(disp,0,false,XYZ,exit_on_anykey,!is_first_call); // Otherwise, use standard display()
1938     if (disp.key()) is_exit = true;
1939     disp.resize(cimg_fitscreen(dw,dh,1),false).set_title("%s",dtitle.data());
1940   } else {
1941     bool disp_resize = !is_first_call;
1942     while (!disp.is_closed() && !is_exit) {
1943       const CImg<intT> s = _select(disp,0,true,axis,align,exit_on_anykey,orig,disp_resize,!is_first_call,true);
1944       disp_resize = true;
1945       if (s[0]<0 && !disp.wheel()) { // No selections done
1946         if (disp.button()&2) { disp.flush(); break; }
1947         is_exit = true;
1948       } else if (disp.wheel()) { // Zoom in/out
1949         const int wheel = disp.wheel();
1950         disp.set_wheel();
1951         if (!is_first_call && wheel<0) break;
1952         if (wheel>0 && _width>=4) {
1953           const unsigned int
1954             delta = std::max(1U,(unsigned int)cimg::round(0.3*_width)),
1955             ind0 = (unsigned int)std::max(0,s[0] - (int)delta),
1956             ind1 = (unsigned int)std::min(width() - 1,s[0] + (int)delta);
1957           if ((ind0!=0 || ind1!=_width - 1) && ind1 - ind0>=3) {
1958             const CImgList<T> sublist = get_shared_images(ind0,ind1);
1959             CImgList<charT> t_sublist;
1960             if (titles) t_sublist = titles->get_shared_images(ind0,ind1);
1961             sublist._gmic_display(disp,0,titles?&t_sublist:0,false,axis,align,XYZ,exit_on_anykey,
1962                                   orig + ind0,false,is_exit,
1963                                   gmic_instance0,images,images_names);
1964           }
1965         }
1966       } else if (s[0]!=0 || s[1]!=width() - 1) {
1967         const CImgList<T> sublist = get_shared_images(s[0],s[1]);
1968         CImgList<charT> t_sublist;
1969         if (titles) t_sublist = titles->get_shared_images(s[0],s[1]);
1970         sublist._gmic_display(disp,0,titles?&t_sublist:0,false,axis,align,XYZ,exit_on_anykey,
1971                               orig + s[0],false,is_exit,
1972                               gmic_instance0,images,images_names);
1973       }
1974       disp.set_title("%s",dtitle.data());
1975     }
1976   }
1977   return *this;
1978 }
1979 
1980 #undef cimglist_plugin
1981 
1982 //--------------- End of CImgList<T> plug-in ------------------------
1983 
1984 #else // #if defined(cimg_plugin) .. #elif defined(cimglist_plugin)
1985 
1986 #include "gmic.h"
1987 using namespace cimg_library;
1988 
1989 #include "gmic_stdlib.h"
1990 
1991 // Define convenience macros, variables and functions.
1992 //----------------------------------------------------
1993 
1994 #undef min
1995 #undef max
1996 
1997 // Define number of hash slots to store variables and commands.
1998 #ifndef gmic_varslots
1999 #define gmic_varslots 128
2000 #endif
2001 #ifndef gmic_comslots
2002 #define gmic_comslots 128
2003 #endif
2004 #ifndef gmic_winslots
2005 #define gmic_winslots 10
2006 #endif
2007 
2008 // Macro to force stringifying selection for error messages.
2009 #define gmic_selection_err selection2string(selection,images_names,1,gmic_selection)
2010 
2011 // Return image argument as a shared or non-shared copy of one existing image.
_gmic_image_arg(const unsigned int ind,const CImg<unsigned int> & selection)2012 inline bool _gmic_image_arg(const unsigned int ind, const CImg<unsigned int>& selection) {
2013   cimg_forY(selection,l) if (selection[l]==ind) return true;
2014   return false;
2015 }
2016 #define gmic_image_arg(ind) gmic_check(_gmic_image_arg(ind,selection)?images[ind]:\
2017                                        images[ind].get_shared())
2018 
2019 // Macro to manage argument substitutions from a command.
2020 template<typename T>
_gmic_substitute_args(const char * const argument,const char * const argument0,const char * const command,const char * const item,const CImgList<T> & images)2021 void gmic::_gmic_substitute_args(const char *const argument, const char *const argument0,
2022                                  const char *const command, const char *const item,
2023                                  const CImgList<T>& images) {
2024   if (is_debug) {
2025     if (std::strcmp(argument,argument0))
2026       debug(images,"Command '%s': arguments = '%s' -> '%s'.",
2027             *command?command:item,argument0,argument);
2028     else
2029       debug(images,"Command '%s': arguments = '%s'.",
2030             *command?command:item,argument0);
2031   }
2032 }
2033 
2034 #define gmic_substitute_args(is_image_expr) { \
2035   const char *const argument0 = argument; \
2036   substitute_item(argument,images,images_names,parent_images,parent_images_names,variables_sizes,\
2037                   command_selection,is_image_expr).move_to(_argument); \
2038   _gmic_substitute_args(argument = _argument,argument0,command,item,images); \
2039 }
2040 
2041 // Macro for computing a readable version of a command argument.
_gmic_argument_text(const char * const argument,CImg<char> & argument_text,const bool is_verbose)2042 inline char *_gmic_argument_text(const char *const argument, CImg<char>& argument_text, const bool is_verbose) {
2043   if (is_verbose) return cimg::strellipsize(argument,argument_text,80,false);
2044   else return &(*argument_text=0);
2045 }
2046 
2047 #define gmic_argument_text_printed() _gmic_argument_text(argument,argument_text,is_verbose)
2048 #define gmic_argument_text() _gmic_argument_text(argument,argument_text,true)
2049 
2050 // Macro for having 'get' or 'non-get' versions of G'MIC commands.
2051 #define gmic_apply(function) { \
2052     __ind = (unsigned int)selection[l]; \
2053     gmic_check(images[__ind]); \
2054     if (is_get) { \
2055       images[__ind].get_##function.move_to(images); \
2056       images_names[__ind].get_copymark().move_to(images_names); \
2057     } else images[__ind].function; \
2058   }
2059 
2060 // Macro for simple commands that has no arguments and act on images.
2061 #define gmic_simple_command(command_name,function,description) \
2062   if (!std::strcmp(command_name,command)) { \
2063     print(images,0,description,gmic_selection.data()); \
2064     cimg_forY(selection,l) gmic_apply(function()); \
2065     is_change = true; continue; \
2066 }
2067 
2068 // Macro for G'MIC arithmetic commands.
2069 #define gmic_arithmetic_command(command_name,\
2070                                 function1,description1,arg1_1,arg1_2,arg1_3,value_type1, \
2071                                 function2,description2,arg2_1,arg2_2, \
2072                                 function3,description3,arg3_1,arg3_2, \
2073                                 description4) \
2074  if (!std::strcmp(command_name,command)) { \
2075    gmic_substitute_args(true); \
2076    sep = *indices = *formula = 0; value = 0; \
2077    if (cimg_sscanf(argument,"%lf%c",&value,&end)==1 || \
2078        (cimg_sscanf(argument,"%lf%c%c",&value,&sep,&end)==2 && sep=='%')) { \
2079      const char *const ssep = sep=='%'?"%":""; \
2080      print(images,0,description1 ".",arg1_1,arg1_2,arg1_3); \
2081      cimg_forY(selection,l) { \
2082        CImg<T>& img = gmic_check(images[selection[l]]); \
2083        nvalue = value; \
2084        if (sep=='%' && img) { \
2085          vmax = (double)img.max_min(vmin); \
2086          nvalue = vmin + (vmax - vmin)*value/100; \
2087        } \
2088        if (is_get) { \
2089          g_img.assign(img,false).function1((value_type1)nvalue).move_to(images); \
2090          images_names.insert(images_names[selection[l]].get_copymark()); \
2091        } else img.function1((value_type1)nvalue); \
2092      } \
2093      ++position; \
2094    } else if (cimg_sscanf(argument,"[%255[a-zA-Z0-9_.%+-]%c%c",indices,&sep,&end)==2 && sep==']' && \
2095               (ind=selection2cimg(indices,images.size(),images_names,command_name)).height()==1) { \
2096      print(images,0,description2 ".",arg2_1,arg2_2); \
2097      const CImg<T> img0 = gmic_image_arg(*ind); \
2098      cimg_forY(selection,l) { \
2099        CImg<T>& img = gmic_check(images[selection[l]]); \
2100        if (is_get) { \
2101          g_img.assign(img,false).function2(img0).move_to(images); \
2102          images_names.insert(images_names[selection[l]].get_copymark()); \
2103        } else img.function2(img0); \
2104      } \
2105      ++position; \
2106    } else if (cimg_sscanf(argument,"'%4095[^']%c%c",formula,&sep,&end)==2 && sep=='\'') { \
2107      strreplace_fw(formula); print(images,0,description3 ".",arg3_1,arg3_2); \
2108      cimg_forY(selection,l) { \
2109        CImg<T>& img = gmic_check(images[selection[l]]); \
2110        if (is_get) { \
2111          g_img.assign(img,false).function3((const char*)formula,images).move_to(images); \
2112          images_names.insert(images_names[selection[l]].get_copymark()); \
2113        } else img.function3((const char*)formula,images); \
2114      } \
2115      ++position; \
2116    } else { \
2117      print(images,0,description4 ".",gmic_selection.data()); \
2118      if (images && selection) { \
2119        if (is_get) { \
2120          g_img.assign(gmic_check(images[selection[0]]),false); \
2121          for (unsigned int l = 1; l<(unsigned int)selection.height(); ++l) \
2122            g_img.function2(gmic_check(images[selection[l]])); \
2123          images_names.insert(images_names[selection[0]].get_copymark()); \
2124          g_img.move_to(images); \
2125        } else if (selection.height()>=2) { \
2126        CImg<T>& img = gmic_check(images[selection[0]]); \
2127        for (unsigned int l = 1; l<(unsigned int)selection.height(); ++l) \
2128          img.function2(gmic_check(images[selection[l]])); \
2129        remove_images(images,images_names,selection,1,selection.height() - 1); \
2130        }}} is_change = true; continue; \
2131    }
2132 
2133 // Manage list of all gmic runs (for CImg math parser 'ext()').
gmic_runs()2134 inline gmic_list<void*>& gmic_runs() { static gmic_list<void*> val; return val; }
2135 
2136 template<typename T>
mp_call(char * const str,void * const p_list,const T & pixel_type)2137 double gmic::mp_call(char *const str, void *const p_list, const T& pixel_type) {
2138   cimg::unused(pixel_type);
2139   double res = cimg::type<double>::nan();
2140   char sep;
2141   cimg_pragma_openmp(critical(mp_call))
2142   {
2143     // Retrieve current gmic instance.
2144     cimg::mutex(24);
2145     CImgList<void*> &grl = gmic_runs();
2146     int ind;
2147     for (ind = grl.width() - 1; ind>=0; --ind) {
2148       CImg<void*> &gr = grl[ind];
2149       if (gr[1]==(void*)p_list) break;
2150     }
2151     if (ind<0) { cimg::mutex(24,0); res = cimg::type<double>::nan(); } // Instance not found
2152     else {
2153       CImg<void*> &gr = grl[ind];
2154       gmic &gmic_instance = *(gmic*)gr[0];
2155       cimg::mutex(24,0);
2156 
2157       // Run given command line.
2158       CImgList<T> &images = *(CImgList<T>*)gr[1];
2159       CImgList<char> &images_names = *(CImgList<char>*)gr[2];
2160       CImgList<T> &parent_images = *(CImgList<T>*)gr[3];
2161       CImgList<char> &parent_images_names = *(CImgList<char>*)gr[4];
2162       const unsigned int *const variables_sizes = (const unsigned int*)gr[5];
2163       const CImg<unsigned int> *const command_selection = (const CImg<unsigned int>*)gr[6];
2164 
2165       if (gmic_instance.is_debug_info && gmic_instance.debug_line!=~0U) {
2166         CImg<char> title(32);
2167         cimg_snprintf(title,title.width(),"*ext#%u",gmic_instance.debug_line);
2168         CImg<char>::string(title).move_to(gmic_instance.callstack);
2169       } else CImg<char>::string("*ext").move_to(gmic_instance.callstack);
2170       unsigned int pos = 0;
2171       try {
2172         gmic_instance._run(gmic_instance.commands_line_to_CImgList(gmic::strreplace_fw(str)),pos,images,images_names,
2173                            parent_images,parent_images_names,variables_sizes,0,0,command_selection);
2174       } catch (gmic_exception&) {
2175         res = cimg::type<double>::nan();
2176       }
2177       gmic_instance.callstack.remove();
2178       if (!gmic_instance.status || !*gmic_instance.status || cimg_sscanf(gmic_instance.status,"%lf%c",&res,&sep)!=1)
2179         res = cimg::type<double>::nan();
2180     }
2181   }
2182   return res;
2183 }
2184 
2185 template<typename Ts, typename T>
mp_store(const Ts * const ptr,const unsigned int w,const unsigned int h,const unsigned d,const unsigned int s,const char * const str,void * const p_list,const T & pixel_type)2186 double gmic::mp_store(const Ts *const ptr,
2187                       const unsigned int w, const unsigned int h, const unsigned d, const unsigned int s,
2188                       const char *const str, void *const p_list, const T& pixel_type) {
2189   cimg::unused(pixel_type);
2190 
2191   // Retrieve current gmic instance.
2192   cimg::mutex(24);
2193   CImgList<void*> &grl = gmic_runs();
2194   int ind;
2195   for (ind = grl.width() - 1; ind>=0; --ind) {
2196     CImg<void*> &gr = grl[ind];
2197     if (gr[1]==(void*)p_list) break;
2198   }
2199   if (ind<0) cimg::mutex(24,0); // Instance not found
2200   else {
2201     CImg<void*> &gr = grl[ind];
2202     gmic &gmic_instance = *(gmic*)gr[0];
2203     const unsigned int *const variables_sizes = (const unsigned int*)gr[5];
2204     CImg<char> _varname(256);
2205     char *const varname = _varname.data(), end;
2206 
2207     if (cimg_sscanf(str,"%255[a-zA-Z0-9_]%c",&(*varname=0),&end)==1 &&
2208         (*varname<'0' || *varname>'9')) {
2209       CImgList<T> g_list;
2210       CImg<T>(ptr,w,h,d,s).move_to(g_list);
2211       CImg<char> name = CImg<char>::string(varname);
2212       name.resize(name.width() + 4,1,1,1,0,0,1);
2213       name[0] = 'G'; name[1] = 'M'; name[2] = 'Z'; name[3] = 0;
2214       name.unroll('y').move_to(g_list);
2215 
2216       g_list.get_serialize(false).unroll('x').move_to(name);
2217       name.resize(name.width() + 9 + std::strlen(varname),1,1,1,0,0,1);
2218       std::sprintf(name,"%c*store/%s",gmic_store,_varname.data());
2219       gmic_instance.set_variable(_varname.data(),name,variables_sizes);
2220       cimg::mutex(24,0);
2221     } else {
2222       cimg::mutex(24,0);
2223       throw CImgArgumentException("[" cimg_appname "_math_parser] CImg<%s>: Function 'store()': "
2224                                   "Invalid variable name '%s' specified.",
2225                                   cimg::type<T>::string(),str);
2226     }
2227   }
2228   return cimg::type<double>::nan();
2229 }
2230 
2231 // Manage correspondence between abort pointers and thread ids.
2232 CImgList<void*> gmic::list_p_is_abort = CImgList<void*>();
abort_ptr(bool * const p_is_abort)2233 bool *gmic::abort_ptr(bool *const p_is_abort) {
2234 #if defined(__MACOSX__) || defined(__APPLE__)
2235   void* tid = (void*)(cimg_ulong)getpid();
2236 #elif cimg_OS==1
2237   void* tid = (void*)(cimg_ulong)syscall(SYS_lwp_gettid);
2238 #elif cimg_OS==2
2239   void* tid = (void*)(cimg_ulong)GetCurrentThreadId();
2240 #else
2241   void* tid = (void*)0;
2242 #endif
2243   cimg::mutex(21);
2244   bool *res = p_is_abort;
2245   int ind = -1;
2246   cimglist_for(list_p_is_abort,l)
2247     if (list_p_is_abort(l,0)==tid) { ind = l; break; }
2248   if (p_is_abort) { // Set pointer
2249     if (ind>=0) list_p_is_abort(ind,1) = (void*)p_is_abort;
2250     else CImg<void*>::vector(tid,(void*)p_is_abort).move_to(list_p_is_abort);
2251   } else { // Get pointer
2252     static bool _is_abort;
2253     res = ind<0?&_is_abort:(bool*)list_p_is_abort(ind,1);
2254   }
2255   cimg::mutex(21,0);
2256   return res;
2257 }
2258 
2259 // Manage mutexes.
2260 struct _gmic_mutex {
2261 #ifdef _PTHREAD_H
2262   pthread_mutex_t mutex[256];
_gmic_mutex_gmic_mutex2263   _gmic_mutex() { for (unsigned int i = 0; i<256; ++i) pthread_mutex_init(&mutex[i],0); }
lock_gmic_mutex2264   void lock(const unsigned int n) { pthread_mutex_lock(&mutex[n]); }
unlock_gmic_mutex2265   void unlock(const unsigned int n) { pthread_mutex_unlock(&mutex[n]); }
2266 #elif cimg_OS==2 // #ifdef _PTHREAD_H
2267   HANDLE mutex[256];
2268   _gmic_mutex() { for (unsigned int i = 0; i<256; ++i) mutex[i] = CreateMutex(0,FALSE,0); }
2269   void lock(const unsigned int n) { WaitForSingleObject(mutex[n],INFINITE); }
2270   void unlock(const unsigned int n) { ReleaseMutex(mutex[n]); }
2271 #else // #ifdef _PTHREAD_H
2272   _gmic_mutex() {}
2273   void lock(const unsigned int) {}
2274   void unlock(const unsigned int) {}
2275 #endif // #if cimg_OS==2
2276 };
gmic_mutex()2277 inline _gmic_mutex& gmic_mutex() { static _gmic_mutex val; return val; }
2278 
2279 // Thread structure and routine for command 'parallel'.
2280 template<typename T>
2281 struct _gmic_parallel {
2282   CImgList<char> *images_names, *parent_images_names, commands_line;
2283   CImgList<_gmic_parallel<T> > *gmic_threads;
2284   CImgList<T> *images, *parent_images;
2285   CImg<unsigned int> variables_sizes;
2286   const CImg<unsigned int> *command_selection;
2287   bool is_thread_running;
2288   gmic_exception exception;
2289   gmic gmic_instance;
2290 #ifdef gmic_is_parallel
2291 #ifdef _PTHREAD_H
2292   pthread_t thread_id;
2293 #elif cimg_OS==2
2294   HANDLE thread_id;
2295 #endif // #ifdef _PTHREAD_H
2296 #endif // #ifdef gmic_is_parallel
_gmic_parallel_gmic_parallel2297   _gmic_parallel() { variables_sizes.assign(gmic_varslots); }
2298 };
2299 
2300 template<typename T>
2301 #if cimg_OS!=2
gmic_parallel(void * arg)2302 static void *gmic_parallel(void *arg)
2303 #else // #if cimg_OS!=2
2304 static DWORD WINAPI gmic_parallel(void *arg)
2305 #endif // #if cimg_OS!=2
2306 {
2307   _gmic_parallel<T> &st = *(_gmic_parallel<T>*)arg;
2308   try {
2309     unsigned int pos = 0;
2310     st.gmic_instance.abort_ptr(st.gmic_instance.is_abort);
2311     st.gmic_instance.is_debug_info = false;
2312     st.gmic_instance._run(st.commands_line,pos,*st.images,*st.images_names,
2313                           *st.parent_images,*st.parent_images_names,
2314                           st.variables_sizes,0,0,st.command_selection);
2315   } catch (gmic_exception &e) {
2316     st.exception._command.assign(e._command);
2317     st.exception._message.assign(e._message);
2318   }
2319 #if defined(gmic_is_parallel) && defined(_PTHREAD_H)
2320   pthread_exit(0);
2321 #endif // #if defined(gmic_is_parallel) && defined(_PTHREAD_H)
2322   return 0;
2323 }
2324 
2325 // Array of G'MIC builtin commands (must be sorted in lexicographic order!).
2326 const char *gmic::builtin_commands_names[] = {
2327   "!=","%","&","*","*3d","+","+3d","-","-3d","/","/3d","<","<<","<=","=","==",">",">=",">>",
2328   "a","abs","acos","acosh","add","add3d","and","append","asin","asinh","atan","atan2","atanh","autocrop","axes",
2329   "b","bilateral","blur","boxfilter","break","bsl","bsr",
2330   "c","camera","channels","check","check3d","col3d","color3d","columns","command","continue","convolve","correlate",
2331     "cos","cosh","crop","cumulate","cursor","cut",
2332   "d","d3d","db3d","debug","denoise","deriche","dijkstra","dilate","discard","displacement","display","display3d",
2333     "distance","div","div3d","divide","do","done","double3d",
2334   "e","echo","eigen","eikonal","elevation3d","elif","ellipse","else","endian","endif","endl","endlocal","eq",
2335     "equalize","erode","error","eval","exec","exp",
2336   "f","f3d","fft","fi","files","fill","flood","focale3d","for",
2337   "g","ge","gradient","graph","gt","guided",
2338   "h","hessian","histogram",
2339   "i","if","ifft","image","index","inpaint","input","invert","isoline3d","isosurface3d",
2340   "j","j3d",
2341   "k","keep",
2342   "l","l3d","label","le","light3d","line","local","log","log10","log2","lt",
2343   "m","m*","m/","m3d","mandelbrot","map","matchpatch","max","md3d","mdiv","median","min","mirror","mmul","mod",
2344     "mode3d","moded3d","move","mse","mul","mul3d","mutex","mv",
2345   "n","name","named","neq","network","nm","nmd","noarg","noise","normalize",
2346   "o","o3d","object3d","onfail","opacity3d","or","output",
2347   "p","parallel","pass","permute","plasma","plot","point","polygon","pow","print","progress",
2348   "q","quit",
2349   "r","r3d","rand","remove","repeat","resize","return","reverse","reverse3d",
2350     "rm","rol","ror","rotate","rotate3d","round","rows","rv","rv3d",
2351   "s","s3d","screen","select","serialize","set","sh","shared","sharpen","shift","sign","sin","sinc","sinh","skip",
2352     "sl3d","slices","smooth","solve","sort","specl3d","specs3d","sphere3d","split","split3d","sqr","sqrt","srand",
2353     "ss3d","status","store","streamline3d","structuretensors","sub","sub3d","svd",
2354   "t","tan","tanh","text","trisolve",
2355   "u","uncommand","unroll","unserialize",
2356   "v","vanvliet","verbose",
2357   "w","w0","w1","w2","w3","w4","w5","w6","w7","w8","w9","wait","warn","warp","watershed","while","window",
2358   "x","xor","y","z","^","|" };
2359 
2360 CImg<int> gmic::builtin_commands_inds = CImg<int>::empty();
2361 
2362 // Perform a dichotomic search in a lexicographic ordered 'CImgList<char>' or 'char**'.
2363 // Return false or true if search succeeded.
2364 template<typename T>
search_sorted(const char * const str,const T & list,const unsigned int length,unsigned int & out_ind)2365 bool gmic::search_sorted(const char *const str, const T& list, const unsigned int length, unsigned int &out_ind) {
2366   if (!length) { out_ind = 0; return false; }
2367   int err, pos, posm = 0, posM = length - 1;
2368   do {
2369     pos = (posm + posM)/2;
2370     err = std::strcmp(list[pos],str);
2371     if (!err) { posm = pos; break; }
2372     if (err>0) posM = pos - 1; else posm = pos + 1;
2373   } while (posm<=posM);
2374   out_ind = posm;
2375   return !err;
2376 }
2377 
2378 // Return Levenshtein distance between two strings.
2379 // (adapted from http://rosettacode.org/wiki/Levenshtein_distance#C)
_levenshtein(const char * const s,const char * const t,CImg<int> & d,const int i,const int j)2380 int gmic::_levenshtein(const char *const s, const char *const t,
2381                        CImg<int>& d, const int i, const int j) {
2382   const int ls = d.width() - 1, lt = d.height() - 1;
2383   if (d(i,j)>=0) return d(i,j);
2384   int x;
2385   if (i==ls) x = lt - j;
2386   else if (j==lt) x = ls - i;
2387   else if (s[i]==t[j]) x = _levenshtein(s,t,d,i + 1,j + 1);
2388   else {
2389     x = _levenshtein(s,t,d,i + 1,j + 1);
2390     int y;
2391     if ((y=_levenshtein(s,t,d,i,j + 1))<x) x = y;
2392     if ((y=_levenshtein(s,t,d,i + 1,j))<x) x = y;
2393     ++x;
2394   }
2395   return d(i,j) = x;
2396 }
2397 
levenshtein(const char * const s,const char * const t)2398 int gmic::levenshtein(const char *const s, const char *const t) {
2399   const char *const ns = s?s:"", *const nt = t?t:"";
2400   const int ls = (int)std::strlen(ns), lt = (int)std::strlen(nt);
2401   if (!ls) return lt; else if (!lt) return ls;
2402   CImg<int> d(1 + ls,1 + lt,1,1,-1);
2403   return _levenshtein(ns,nt,d,0,0);
2404 }
2405 
2406 // Return true if specified filename corresponds to an existing file or directory.
check_filename(const char * const filename)2407 bool gmic::check_filename(const char *const filename) {
2408   bool res = false;
2409 #if cimg_OS==2
2410   const unsigned int attr = (unsigned int)GetFileAttributesA(filename);
2411   res = (attr!=~0U);
2412 #else // #if cimg_OS==2
2413   std::FILE *const file = cimg::std_fopen(filename,"r");
2414   if (file) { res = true; cimg::fclose(file); }
2415 #endif // #if cimg_OS==2
2416   return res;
2417 }
2418 
2419 // Wait for threads to finish.
2420 template<typename T>
wait_threads(void * const p_gmic_threads,const bool try_abort,const T & pixel_type)2421 void gmic::wait_threads(void *const p_gmic_threads, const bool try_abort, const T& pixel_type) {
2422   cimg::unused(pixel_type);
2423   CImg<_gmic_parallel<T> > &gmic_threads = *(CImg<_gmic_parallel<T> >*)p_gmic_threads;
2424 #ifdef gmic_is_parallel
2425   cimg_forY(gmic_threads,l) {
2426     if (try_abort && !gmic_threads[l].is_thread_running)
2427       gmic_threads[l].gmic_instance.is_abort_thread = true;
2428 #ifdef _PTHREAD_H
2429     pthread_join(gmic_threads[l].thread_id,0);
2430 #elif cimg_OS==2 // #ifdef _PTHREAD_H
2431     WaitForSingleObject(gmic_threads[l].thread_id,INFINITE);
2432     CloseHandle(gmic_threads[l].thread_id);
2433 #endif // #ifdef _PTHREAD_H
2434     is_change|=gmic_threads[l].gmic_instance.is_change;
2435     gmic_threads[l].is_thread_running = false;
2436   }
2437 #endif // #ifdef gmic_is_parallel
2438 }
2439 
2440 // Return a hashcode from a string.
hashcode(const char * const str,const bool is_variable)2441 unsigned int gmic::hashcode(const char *const str, const bool is_variable) {
2442   if (!str) return 0U;
2443   unsigned int hash = 0U;
2444   if (is_variable) {
2445     if (*str=='_') return str[1]=='_'?(gmic_varslots - 1):(gmic_varslots - 2);
2446     for (const char *s = str; *s; ++s) (hash*=31)+=*s;
2447     return hash%(gmic_varslots - 2);
2448   }
2449   for (const char *s = str; *s; ++s) (hash*=31)+=*s;
2450   return hash&(gmic_comslots - 1);
2451 }
2452 
2453 // Tells if the implementation of a G'MIC command contains arguments.
command_has_arguments(const char * const command)2454 bool gmic::command_has_arguments(const char *const command) {
2455   if (!command || !*command) return false;
2456   for (const char *s = std::strchr(command,'$'); s; s = std::strchr(s,'$')) {
2457     const char c = *(++s);
2458     if (c=='#' ||
2459         c=='*' ||
2460         c=='=' ||
2461         (c>'0' && c<='9') ||
2462         (c=='-' && *(s + 1)>'0' && *(s + 1)<='9') ||
2463         (c=='\"' && *(s + 1)=='*' && *(s + 2)=='\"') ||
2464         (c=='{' && (*(s + 1)=='^' ||
2465                     (*(s + 1)>'0' && *(s + 1)<='9') ||
2466                     (*(s + 1)=='-' && *(s + 2)>'0' && *(s + 2)<='9')))) return true;
2467   }
2468   return false;
2469 }
2470 
2471 // Compute the basename of a filename.
basename(const char * const str)2472 const char* gmic::basename(const char *const str)  {
2473   if (!str || !*str) return "";
2474   const unsigned int l = (unsigned int)std::strlen(str);
2475   unsigned int ll = l - 1; // 'Last' character to check
2476   while (ll>=3 && str[ll]>='0' && str[ll]<='9') --ll;
2477   if (ll>=3 && ll!=l - 1 && str[ll - 1]=='_' && str[ll]=='c' && str[ll + 1]!='0') ll-=2; // Ignore copy mark
2478   else ll = l - 1;
2479   if (*str=='[' && (str[ll]==']' || str[ll]=='.')) return str;
2480   const char *p = 0, *np = str;
2481   while (np>=str && (p=np)) np = std::strchr(np,'/') + 1;
2482   np = p;
2483   while (np>=str && (p=np)) np = std::strchr(np,'\\') + 1;
2484   return p;
2485 }
2486 
2487 // Replace special characters in a string.
strreplace_fw(char * const str)2488 char *gmic::strreplace_fw(char *const str) {
2489   if (str) for (char *s = str ; *s; ++s) {
2490       const char c = *s;
2491       if (c<' ')
2492         *s = c==gmic_dollar?'$':c==gmic_lbrace?'{':c==gmic_rbrace?'}':c==gmic_comma?',':
2493           c==gmic_dquote?'\"':c;
2494     }
2495   return str;
2496 }
2497 
strreplace_bw(char * const str)2498 char *gmic::strreplace_bw(char *const str) {
2499   if (str) for (char *s = str ; *s; ++s) {
2500       const char c = *s;
2501       *s = c=='$'?gmic_dollar:c=='{'?gmic_lbrace:c=='}'?gmic_rbrace:c==','?gmic_comma:
2502         c=='\"'?gmic_dquote:c;
2503     }
2504   return str;
2505 }
2506 
2507 //! Escape a string.
2508 // 'res' must be a C-string large enough ('4*strlen(str) + 1' is always safe).
strescape(const char * const str,char * const res)2509 unsigned int gmic::strescape(const char *const str, char *const res) {
2510   const char *const esc = "abtnvfr";
2511   char *ptrd = res;
2512   for (const char *ptrs = str; *ptrs; ++ptrs) {
2513     const unsigned char c = *ptrs;
2514     if (c=='\\' || c=='\'' || c=='\"') { *(ptrd++) = '\\'; *(ptrd++) = c; }
2515     else if (c>=7 && c<=13) { *(ptrd++) = '\\'; *(ptrd++) = esc[c - 7]; }
2516     else if (c>=32 && c<=126) *(ptrd++) = c;
2517     else if (c<gmic_dollar || c>gmic_dquote) {
2518       *(ptrd++) = '\\';
2519       *(ptrd++) = 'x';
2520       unsigned char d = c>>4;
2521       *(ptrd++) = (char)(d + (d<10?'0':'a'-10));
2522       d = c&15;
2523       *(ptrd++) = (char)(d + (d<10?'0':'a'-10));
2524     } else *(ptrd++) = c;
2525   }
2526   *ptrd = 0;
2527   return (unsigned int)(ptrd - res);
2528 }
2529 
2530 // Constructors / destructors.
2531 //----------------------------
2532 #define gmic_new_attr commands(new CImgList<char>[gmic_comslots]), commands_names(new CImgList<char>[gmic_comslots]), \
2533     commands_has_arguments(new CImgList<char>[gmic_comslots]), \
2534     _variables(new CImgList<char>[gmic_varslots]), _variables_names(new CImgList<char>[gmic_varslots]), \
2535     variables(new CImgList<char>*[gmic_varslots]), variables_names(new CImgList<char>*[gmic_varslots]), \
2536     is_running(false)
2537 
2538 #define display_window(n) (*(CImgDisplay*)display_windows[n])
2539 
2540 CImg<char> gmic::stdlib = CImg<char>::empty();
2541 
gmic()2542 gmic::gmic():gmic_new_attr {
2543   CImgList<gmic_pixel_type> images;
2544   CImgList<char> images_names;
2545   _gmic(0,images,images_names,0,true,0,0);
2546 }
2547 
2548 template<typename T>
2549 gmic::gmic(const char *const commands_line, const char *const custom_commands,
2550            const bool include_stdlib, float *const p_progress, bool *const p_is_abort,
2551            const T& pixel_type):
2552   gmic_new_attr {
2553   cimg::unused(pixel_type);
2554   CImgList<T> images;
2555   CImgList<char> images_names;
2556   _gmic(commands_line,
2557         images,images_names,custom_commands,
2558         include_stdlib,p_progress,p_is_abort);
2559 }
2560 
2561 gmic::~gmic() {
2562   cimg::exception_mode(cimg_exception_mode);
2563   cimg_forX(display_windows,l) delete &display_window(l);
2564   cimg::mutex(21);
2565 #if defined(__MACOSX__) || defined(__APPLE__)
2566   void* tid = (void*)(cimg_ulong)getpid();
2567 #elif cimg_OS==1
2568   void* tid = (void*)(cimg_ulong)syscall(SYS_lwp_gettid);
2569 #elif cimg_OS==2
2570   void* tid = (void*)(cimg_ulong)GetCurrentThreadId();
2571 #else
2572   void* tid = (void*)0;
2573 #endif
2574   int ind = -1;
2575   cimglist_for(list_p_is_abort,l)
2576     if (list_p_is_abort(l,0)==tid) { ind = l; break; }
2577   if (ind>=0) list_p_is_abort.remove(ind);
2578   cimg::mutex(21,0);
2579 
2580   delete[] commands;
2581   delete[] commands_names;
2582   delete[] commands_has_arguments;
2583   delete[] _variables;
2584   delete[] _variables_names;
2585   delete[] variables;
2586   delete[] variables_names;
2587 }
2588 
2589 // Decompress G'MIC standard library commands.
2590 //---------------------------------------------
decompress_stdlib()2591 const CImg<char>& gmic::decompress_stdlib() {
2592   if (!stdlib) try {
2593       CImgList<char>::get_unserialize(CImg<unsigned char>(data_gmic_stdlib,1,size_data_gmic_stdlib,1,1,true))[0].
2594         move_to(stdlib);
2595     } catch (...) {
2596       cimg::mutex(29);
2597       std::fprintf(cimg::output(),
2598                    "[gmic] %s*** Warning *** Could not decompress G'MIC standard library, ignoring it.%s\n",
2599                    cimg::t_red,cimg::t_normal);
2600       std::fflush(cimg::output());
2601       cimg::mutex(29,0);
2602       stdlib.assign(1,1,1,1,0);
2603     }
2604   return stdlib;
2605 }
2606 
2607 // Get path to .gmic user file.
2608 //-----------------------------
path_user(const char * const custom_path)2609 const char* gmic::path_user(const char *const custom_path) {
2610   static CImg<char> path_user;
2611   if (path_user) return path_user;
2612   cimg::mutex(28);
2613   const char *_path_user = 0;
2614   if (custom_path && cimg::is_directory(custom_path)) _path_user = custom_path;
2615   if (!_path_user) _path_user = getenv("GMIC_PATH");
2616   if (!_path_user) _path_user = getenv("GMIC_GIMP_PATH");
2617   if (!_path_user) {
2618 #if cimg_OS!=2
2619     _path_user = getenv("HOME");
2620 #else
2621     _path_user = getenv("APPDATA");
2622 #endif
2623   }
2624   if (!_path_user) _path_user = getenv("TMP");
2625   if (!_path_user) _path_user = getenv("TEMP");
2626   if (!_path_user) _path_user = getenv("TMPDIR");
2627   if (!_path_user) _path_user = "";
2628   path_user.assign(1024);
2629 #if cimg_OS!=2
2630   cimg_snprintf(path_user,path_user.width(),"%s%c.gmic",
2631                 _path_user,cimg_file_separator);
2632 #else
2633   cimg_snprintf(path_user,path_user.width(),"%s%cuser.gmic",
2634                 _path_user,cimg_file_separator);
2635 #endif
2636   CImg<char>::string(path_user).move_to(path_user); // Optimize length
2637   cimg::mutex(28,0);
2638   return path_user;
2639 }
2640 
2641 // Get path to the resource directory.
2642 //------------------------------------
path_rc(const char * const custom_path)2643 const char* gmic::path_rc(const char *const custom_path) {
2644   static CImg<char> path_rc;
2645   CImg<char> path_tmp;
2646   if (path_rc) return path_rc;
2647   cimg::mutex(28);
2648   const char *_path_rc = 0;
2649   if (custom_path && cimg::is_directory(custom_path)) _path_rc = custom_path;
2650   if (!_path_rc) _path_rc = getenv("GMIC_PATH");
2651   if (!_path_rc) _path_rc = getenv("GMIC_GIMP_PATH");
2652   if (!_path_rc) _path_rc = getenv("XDG_CONFIG_HOME");
2653   if (!_path_rc) {
2654 #if cimg_OS!=2
2655     _path_rc = getenv("HOME");
2656     if (_path_rc) {
2657       path_tmp.assign(std::strlen(_path_rc) + 10);
2658       cimg_sprintf(path_tmp,"%s/.config",_path_rc);
2659       if (cimg::is_directory(path_tmp)) _path_rc = path_tmp;
2660     }
2661 #else
2662     _path_rc = getenv("APPDATA");
2663 #endif
2664   }
2665   if (!_path_rc) _path_rc = getenv("TMP");
2666   if (!_path_rc) _path_rc = getenv("TEMP");
2667   if (!_path_rc) _path_rc = getenv("TMPDIR");
2668   if (!_path_rc) _path_rc = "";
2669   path_rc.assign(1024);
2670   cimg_snprintf(path_rc,path_rc.width(),"%s%cgmic%c",
2671                 _path_rc,cimg_file_separator,cimg_file_separator);
2672   CImg<char>::string(path_rc).move_to(path_rc); // Optimize length
2673   cimg::mutex(28,0);
2674   return path_rc;
2675 }
2676 
2677 // Create resources directory.
2678 //----------------------------
init_rc(const char * const custom_path)2679 bool gmic::init_rc(const char *const custom_path) {
2680   CImg<char> dirname = CImg<char>::string(path_rc(custom_path));
2681   if (dirname.width()>=2) dirname[dirname.width() - 2] = 0;
2682   if (!cimg::is_directory(dirname)) {
2683     std::remove(dirname); // In case 'dirname' is already a file
2684 #if cimg_OS==2
2685     return (bool)CreateDirectoryA(dirname,0);
2686 #else
2687     return !(bool)mkdir(dirname,0777);
2688 #endif
2689   }
2690   return true;
2691 }
2692 
2693 // Get current call stack as a string.
2694 //-------------------------------------
callstack2string(const CImg<unsigned int> * const callstack_selection,const bool _is_debug) const2695 CImg<char> gmic::callstack2string(const CImg<unsigned int> *const callstack_selection, const bool _is_debug) const {
2696   if (callstack_selection && !*callstack_selection) return CImg<char>("./",3);
2697   CImgList<char> input_callstack;
2698   if (!callstack_selection) input_callstack.assign(callstack,true);
2699   else cimg_forY(*callstack_selection,l) input_callstack.insert(callstack[(*callstack_selection)[l]],~0U,true);
2700   CImgList<char> res;
2701   const unsigned int siz = (unsigned int)input_callstack.size();
2702   if (siz<=9 || _is_debug) res.assign(input_callstack,false);
2703   else {
2704     res.assign(9);
2705     res[0].assign(input_callstack[0],false);
2706     res[1].assign(input_callstack[1],false);
2707     res[2].assign(input_callstack[2],false);
2708     res[3].assign(input_callstack[3],false);
2709     res[4].assign("(...)",6);
2710     res[5].assign(input_callstack[siz - 4],false);
2711     res[6].assign(input_callstack[siz - 3],false);
2712     res[7].assign(input_callstack[siz - 2],false);
2713     res[8].assign(input_callstack[siz - 1],false);
2714   }
2715   cimglist_for(res,l) if (res(l,0)) res[l].back() = '/'; else res.remove(l--);
2716   CImg<char>::vector(0).move_to(res);
2717   return res>'x';
2718 }
2719 
callstack2string(const bool _is_debug) const2720 CImg<char> gmic::callstack2string(const bool _is_debug) const {
2721   return callstack2string(0,_is_debug);
2722 }
2723 
callstack2string(const CImg<unsigned int> & callstack_selection,const bool _is_debug) const2724 CImg<char> gmic::callstack2string(const CImg<unsigned int>& callstack_selection, const bool _is_debug) const {
2725   return callstack2string(&callstack_selection,_is_debug);
2726 }
2727 
2728 // Parse items from a G'MIC command line.
2729 //---------------------------------------
commands_line_to_CImgList(const char * const commands_line)2730 CImgList<char> gmic::commands_line_to_CImgList(const char *const commands_line) {
2731   if (!commands_line || !*commands_line) return CImgList<char>();
2732   bool is_dquoted = false;
2733   const char *ptrs0 = commands_line;
2734   while (*ptrs0==' ') ++ptrs0;  // Remove leading spaces to first item
2735   CImg<char> item((unsigned int)std::strlen(ptrs0) + 1);
2736   CImgList<char> items;
2737   char *ptrd = item.data(), c = 0;
2738 
2739   for (const char *ptrs = ptrs0; *ptrs; ++ptrs) {
2740     c = *ptrs;
2741     if (c=='\\') { // If escaped character
2742       c = *(++ptrs);
2743       if (!c) { c = '\\'; --ptrs; }
2744       else if (c=='$') c = gmic_dollar;
2745       else if (c=='{') c = gmic_lbrace;
2746       else if (c=='}') c = gmic_rbrace;
2747       else if (c==',') c = gmic_comma;
2748       else if (c=='\"') c = gmic_dquote;
2749       else if (c==' ') c = ' ';
2750       else *(ptrd++) = '\\';
2751       *(ptrd++) = c;
2752     } else if (is_dquoted) { // If non-escaped character inside string
2753       if (c=='\"') is_dquoted = false;
2754       else if (c==1) { while (c && c!=' ') c = *(++ptrs); if (!c) break; } // Discard debug info inside string
2755       else *(ptrd++) = (c=='$' && ptrs[1]!='?')?gmic_dollar:c=='{'?gmic_lbrace:c=='}'?gmic_rbrace:
2756              c==','?gmic_comma:c;
2757     } else { // Non-escaped character outside string
2758       if (c=='\"') is_dquoted = true;
2759       else if (c==' ') {
2760         *ptrd = 0; CImg<char>(item.data(),(unsigned int)(ptrd - item.data() + 1)).move_to(items);
2761         ptrd = item.data();
2762         ++ptrs; while (*ptrs==' ') ++ptrs; ptrs0 = ptrs--; // Remove trailing spaces to next item
2763       } else *(ptrd++) = c;
2764     }
2765   }
2766   if (is_dquoted) {
2767     CImg<char> str; CImg<char>::string(commands_line).move_to(str); // Discard debug info inside string
2768     ptrd = str;
2769     c = 0;
2770     bool _is_debug_info = false;
2771     cimg_for(str,ptrs,char) {
2772       c = *ptrs;
2773       if (c && c!=1) *(ptrd++) = c;
2774       else { // Try to retrieve first debug line when discarding debug info
2775         unsigned int _debug_filename = ~0U, _debug_line = ~0U;
2776         if (!_is_debug_info && cimg_sscanf(ptrs + 1,"%x,%x",&_debug_line,&(_debug_filename=0))) {
2777           debug_filename = _debug_filename;
2778           debug_line = _debug_line;
2779           _is_debug_info = is_debug_info = true;
2780         }
2781         while (c && c!=' ') c = *(++ptrs);
2782       }
2783     } *ptrd = 0;
2784     error(true,"Invalid command line: Double quotes are not closed, in expression '%s'.",
2785           str.data());
2786   }
2787   if (ptrd!=item.data() && c!=' ') {
2788     *ptrd = 0; CImg<char>(item.data(),(unsigned int)(ptrd - item.data() + 1)).move_to(items);
2789   }
2790   if (is_debug) {
2791     debug("Decompose command line into %u items: ",items.size());
2792     cimglist_for(items,l) {
2793       if (items(l,0)==1) {
2794         if (items(l,1)) debug("  item[%u] = (debug info 0x%s)",l,items[l].data() + 1);
2795         else debug("  item[%u] = (undefined debug info)",l);
2796       } else debug("  item[%u] = '%s'",l,items[l].data());
2797     }
2798   }
2799   return items;
2800 }
2801 
2802 // Print log message.
2803 //-------------------
print(const char * format,...)2804 gmic& gmic::print(const char *format, ...) {
2805   if (verbosity<1 && !is_debug) return *this;
2806   va_list ap;
2807   va_start(ap,format);
2808   CImg<char> message(65536);
2809   message[message.width() - 2] = 0;
2810   cimg_vsnprintf(message,message.width(),format,ap);
2811   strreplace_fw(message);
2812   if (message[message.width() - 2]) cimg::strellipsize(message,message.width() - 2);
2813   va_end(ap);
2814 
2815   // Display message.
2816   cimg::mutex(29);
2817   if (*message!='\r')
2818     for (unsigned int i = 0; i<nb_carriages; ++i) std::fputc('\n',cimg::output());
2819   nb_carriages = 1;
2820   std::fprintf(cimg::output(),
2821                "[gmic]%s %s",
2822                callstack2string().data(),message.data());
2823   std::fflush(cimg::output());
2824   cimg::mutex(29,0);
2825   return *this;
2826 }
2827 
2828 // Print error message, and quit interpreter.
2829 //-------------------------------------------
error(const bool output_header,const char * const format,...)2830 gmic& gmic::error(const bool output_header, const char *const format, ...) {
2831   va_list ap;
2832   va_start(ap,format);
2833   CImg<char> message(1024);
2834   message[message.width() - 2] = 0;
2835   cimg_vsnprintf(message,message.width(),format,ap);
2836   strreplace_fw(message);
2837   if (message[message.width() - 2]) cimg::strellipsize(message,message.width() - 2);
2838   va_end(ap);
2839 
2840   // Display message.
2841   const CImg<char> s_callstack = callstack2string();
2842   if (verbosity>=1 || is_debug) {
2843     cimg::mutex(29);
2844     if (*message!='\r')
2845       for (unsigned int i = 0; i<nb_carriages; ++i) std::fputc('\n',cimg::output());
2846     nb_carriages = 1;
2847     if (output_header) {
2848       if (is_debug_info && debug_filename<commands_files.size() && debug_line!=~0U)
2849         std::fprintf(cimg::output(),"[gmic]%s %s%s*** Error (file '%s', %sline #%u) *** %s%s",
2850                      s_callstack.data(),cimg::t_red,cimg::t_bold,
2851                      commands_files[debug_filename].data(),
2852                      is_debug_info?"":"call from ",debug_line,message.data(),
2853                      cimg::t_normal);
2854       else
2855         std::fprintf(cimg::output(),"[gmic]%s %s%s*** Error *** %s%s",
2856                      s_callstack.data(),cimg::t_red,cimg::t_bold,
2857                      message.data(),cimg::t_normal);
2858     } else
2859       std::fprintf(cimg::output(),"[gmic]%s %s%s%s%s",
2860                    s_callstack.data(),cimg::t_red,cimg::t_bold,
2861                    message.data(),cimg::t_normal);
2862     std::fflush(cimg::output());
2863     cimg::mutex(29,0);
2864   }
2865 
2866   // Store detailed error message for interpreter.
2867   CImg<char> full_message(512 + message.width());
2868   if (debug_filename<commands_files.size() && debug_line!=~0U)
2869     cimg_snprintf(full_message,full_message.width(),
2870                   "*** Error in %s (file '%s', %sline #%u) *** %s",
2871                   s_callstack.data(),
2872                   commands_files[debug_filename].data(),
2873                   is_debug_info?"":"call from ",debug_line,message.data());
2874   else cimg_snprintf(full_message,full_message.width(),
2875                      "*** Error in %s *** %s",
2876                      s_callstack.data(),message.data());
2877   CImg<char>::string(full_message).move_to(status);
2878   message.assign();
2879   is_running = false;
2880   throw gmic_exception(0,status);
2881 }
2882 
2883 // Print debug message.
2884 //---------------------
debug(const char * format,...)2885 gmic& gmic::debug(const char *format, ...) {
2886   if (!is_debug) return *this;
2887   va_list ap;
2888   va_start(ap,format);
2889   CImg<char> message(1024);
2890   message[message.width() - 2] = 0;
2891   cimg_vsnprintf(message,message.width(),format,ap);
2892   if (message[message.width() - 2]) cimg::strellipsize(message,message.width() - 2);
2893   va_end(ap);
2894 
2895   // Display message.
2896   cimg::mutex(29);
2897   if (*message!='\r')
2898     for (unsigned int i = 0; i<nb_carriages; ++i) std::fputc('\n',cimg::output());
2899   nb_carriages = 1;
2900 
2901   if (is_debug_info && debug_filename<commands_files.size() && debug_line!=~0U)
2902     std::fprintf(cimg::output(),
2903                  "%s<gmic>%s#%u ",
2904                  cimg::t_green,callstack2string(true).data(),debug_line);
2905   else
2906     std::fprintf(cimg::output(),
2907                  "%s<gmic>%s ",
2908                  cimg::t_green,callstack2string(true).data());
2909 
2910   for (char *s = message; *s; ++s) {
2911     char c = *s;
2912     if (c<' ') switch (c) {
2913       case gmic_dollar : std::fprintf(cimg::output(),"\\$"); break;
2914       case gmic_lbrace : std::fprintf(cimg::output(),"\\{"); break;
2915       case gmic_rbrace : std::fprintf(cimg::output(),"\\}"); break;
2916       case gmic_comma : std::fprintf(cimg::output(),"\\,"); break;
2917       case gmic_dquote : std::fprintf(cimg::output(),"\\\""); break;
2918       default : std::fputc(c,cimg::output());
2919       }
2920     else std::fputc(c,cimg::output());
2921   }
2922   std::fprintf(cimg::output(),
2923                "%s",
2924                cimg::t_normal);
2925   std::fflush(cimg::output());
2926   cimg::mutex(29,0);
2927   return *this;
2928 }
2929 
2930 // Set variable in the interpreter environment.
2931 //---------------------------------------------
2932 // 'operation' can be { 0 (add new variable), '=' (replace or add),'.','+','-','*','/','%','&','|','^','<','>' }
2933 // Return the variable value.
set_variable(const char * const name,const char * const value,const char operation,const unsigned int * const variables_sizes)2934 const char *gmic::set_variable(const char *const name, const char *const value,
2935                                const char operation,
2936                                const unsigned int *const variables_sizes) {
2937   if (!name || !value) return "";
2938   char _operation = operation, end;
2939   bool is_name_found = false;
2940   double lvalue, rvalue;
2941   CImg<char> s_value;
2942   int ind = 0;
2943   const bool
2944     is_global = *name=='_',
2945     is_thread_global = is_global && name[1]=='_';
2946   if (is_thread_global) cimg::mutex(30);
2947   const unsigned int hash = hashcode(name,true);
2948   const int lind = is_global || !variables_sizes?0:(int)variables_sizes[hash];
2949   CImgList<char>
2950     &__variables = *variables[hash],
2951     &__variables_names = *variables_names[hash];
2952 
2953   if ((!operation || operation=='=') && *value==gmic_store &&
2954       !std::strncmp(value + 1,"*store/",7) && value[8]) { // Get value from image-encoded variable.
2955     const char *const cname = value + 8;
2956     const bool is_cglobal = *cname=='_';
2957     const unsigned int chash = hashcode(cname,true);
2958     const int clind = is_cglobal || !variables_sizes?0:(int)variables_sizes[chash];
2959     CImgList<char>
2960       &__cvariables = *variables[chash],
2961       &__cvariables_names = *variables_names[chash];
2962     for (int l = __cvariables.width() - 1; l>=clind; --l) if (!std::strcmp(__cvariables_names[l],cname)) {
2963         is_name_found = true; ind = l; break;
2964       }
2965     if (is_name_found) {
2966       __cvariables[ind].get_resize(__cvariables[ind].width() + std::strlen(name) - std::strlen(cname),1,1,1,0,0,1).
2967         move_to(s_value);
2968       std::sprintf(s_value,"%c*store/%s",gmic_store,name);
2969     } else s_value.assign(1,1,1,1,0);
2970     is_name_found = false;
2971   } else if (!operation || operation=='=' || operation=='.') s_value.assign(value,std::strlen(value) + 1,1,1,1,true);
2972   else s_value.assign(24);
2973 
2974   if (operation) {
2975     // Retrieve index of current definition.
2976     for (int l = __variables.width() - 1; l>=lind; --l) if (!std::strcmp(__variables_names[l],name)) {
2977         is_name_found = true; ind = l; break;
2978       }
2979     if (operation=='=') {
2980       if (!is_name_found) _operation = 0; // New variable
2981       else s_value.move_to(__variables[ind]);
2982     } else if (operation=='.') {
2983       if (!is_name_found) _operation = 0; // New variable
2984       else if (*value) {
2985         --__variables[ind]._width;
2986         __variables[ind].append(CImg<char>::string(value,true,true),'x');
2987       }
2988     } else {
2989       const char *const s_operation = operation=='+'?"+":operation=='-'?"-":operation=='*'?"*":operation=='/'?"/":
2990         operation=='%'?"%":operation=='&'?"&":operation=='|'?"|":operation=='^'?"^":
2991         operation=='<'?"<<":">>";
2992       if (!is_name_found)
2993         error(true,"Operation '%s=' requested on undefined variable '%s'.",
2994               s_operation,name);
2995       if (cimg_sscanf(__variables[ind],"%lf%c",&lvalue,&end)!=1)
2996         error(true,"Operation '%s=' requested on non-numerical variable '%s=%s'.",
2997               s_operation,name,__variables[ind].data());
2998       if (cimg_sscanf(value,"%lf%c",&rvalue,&end)!=1)
2999         error(true,"Operation '%s=' requested on variable '%s', with non-numerical argument '%s'.",
3000               s_operation,name,value);
3001       *s_value = 0;
3002       cimg_snprintf(s_value,s_value.width(),"%.17g",
3003                     operation=='+'?lvalue + rvalue:
3004                     operation=='-'?lvalue - rvalue:
3005                     operation=='*'?lvalue*rvalue:
3006                     operation=='/'?lvalue/rvalue:
3007                     operation=='%'?cimg::mod(lvalue,rvalue):
3008                     operation=='&'?(double)((cimg_ulong)lvalue & (cimg_ulong)rvalue):
3009                     operation=='|'?(double)((cimg_ulong)lvalue | (cimg_ulong)rvalue):
3010                     operation=='^'?std::pow(lvalue,rvalue):
3011                     operation=='<'?(double)((cimg_long)lvalue << (unsigned int)rvalue):
3012                     (double)((cimg_long)lvalue >> (unsigned int)rvalue));
3013       CImg<char>::string(s_value).move_to(__variables[ind]);
3014     }
3015   }
3016   if (!_operation) { // New variable
3017     ind = __variables.width();
3018     CImg<char>::string(name).move_to(__variables_names);
3019     s_value.move_to(__variables);
3020   }
3021   if (is_thread_global) cimg::mutex(30,0);
3022   return __variables[ind].data();
3023 }
3024 
set_variable(const char * const name,const CImg<unsigned char> & value,const unsigned int * const variables_sizes)3025 const char *gmic::set_variable(const char *const name, const CImg<unsigned char>& value,
3026                                const unsigned int *const variables_sizes) {
3027   if (!name || !value) return "";
3028   bool is_name_found = false;
3029   CImg<char> s_value((char*)value.data(),value.width(),value.height(),value.depth(),value.spectrum(),true);
3030   int ind = 0;
3031   const bool
3032     is_global = *name=='_',
3033     is_thread_global = is_global && name[1]=='_';
3034   if (is_thread_global) cimg::mutex(30);
3035   const unsigned int hash = hashcode(name,true);
3036   const int lind = is_global || !variables_sizes?0:(int)variables_sizes[hash];
3037   CImgList<char>
3038     &__variables = *variables[hash],
3039     &__variables_names = *variables_names[hash];
3040 
3041   // Retrieve index of current definition.
3042   for (int l = __variables.width() - 1; l>=lind; --l) if (!std::strcmp(__variables_names[l],name)) {
3043       is_name_found = true; ind = l; break;
3044     }
3045   if (is_name_found) s_value.move_to(__variables[ind]); // Update variable
3046   else  { // New variable
3047     ind = __variables.width();
3048     CImg<char>::string(name).move_to(__variables_names);
3049     s_value.move_to(__variables);
3050   }
3051   if (is_thread_global) cimg::mutex(30,0);
3052   return __variables[ind].data();
3053 }
3054 
3055 // Add custom commands from a char* buffer.
3056 //------------------------------------------
add_commands(const char * const data_commands,const char * const commands_file,unsigned int * count_new,unsigned int * count_replaced,bool * const is_entrypoint)3057 gmic& gmic::add_commands(const char *const data_commands, const char *const commands_file,
3058                          unsigned int *count_new, unsigned int *count_replaced,
3059                          bool *const is_entrypoint) {
3060   if (!data_commands || !*data_commands) return *this;
3061   cimg::mutex(23);
3062   CImg<char> s_body(256*1024), s_line(256*1024), s_name(256), debug_info(32);
3063   unsigned int line_number = 1, pos = 0;
3064   bool is_last_slash = false, _is_last_slash = false, is_newline = false;
3065   int hash = -1, l_debug_info = 0;
3066   char sep = 0;
3067   if (commands_file) CImg<char>::string(commands_file).move_to(commands_files);
3068   if (count_new) *count_new = 0;
3069   if (count_replaced) *count_replaced = 0;
3070 
3071   for (const char *data = data_commands; *data; is_last_slash = _is_last_slash,
3072          line_number+=is_newline?1:0) {
3073 
3074     // Read new line.
3075     char *_line = s_line, *const line_end = s_line.end();
3076     while (*data!='\n' && *data && _line<line_end) *(_line++) = *(data++);
3077     if (_line<line_end) *_line = 0; else *(line_end - 1) = 0;
3078     if (*data=='\n') { is_newline = true; ++data; } else is_newline = false; // Skip next '\n'
3079 
3080     // Replace/remove unusual characters.
3081     char *__line = s_line;
3082     for (_line = s_line; *_line; ++_line) if (*_line!=13) *(__line++) = (unsigned char)*_line<' '?' ':*_line;
3083     *__line = 0;
3084     _line = s_line; if (*_line=='#') *_line = 0; else do { // Remove comments
3085         if ((_line=std::strchr(_line,'#')) && *(_line - 1)==' ') { *--_line = 0; break; }
3086       } while (_line++);
3087 
3088     // Remove useless trailing spaces.
3089     char *linee = s_line.data() + std::strlen(s_line) - 1;
3090     while (linee>=s_line && *linee==' ') --linee;
3091     *(linee + 1) = 0;
3092     char *lines = s_line; while (*lines==' ') ++lines; // Remove useless leading spaces
3093     if (!*lines) continue; // Empty line
3094 
3095     // Check if last character is a '\'...
3096     _is_last_slash = false;
3097     for (_line = linee; *_line=='\\' && _line>=lines; --_line) _is_last_slash = !_is_last_slash;
3098     if (_is_last_slash) *(linee--) = 0; // ... and remove it if necessary
3099     if (!*lines) continue; // Empty line found
3100     *s_name = *s_body = 0;
3101 
3102     if ((!is_last_slash && std::strchr(lines,':') && // Check for a command definition (or implicit '__init__')
3103          cimg_sscanf(lines,"%255[a-zA-Z0-9_] %c %262143[^\n]",s_name.data(),&sep,s_body.data())>=2 &&
3104          (*lines<'0' || *lines>'9') && sep==':') || hash<0) {
3105       CImg<char> body = CImg<char>::string(hash<0 && !*s_name?lines:s_body);
3106       if (hash<0 && !*s_name) { std::strcpy(s_name,"_main_"); if (is_entrypoint) *is_entrypoint = true; }
3107       hash = (int)hashcode(s_name,false);
3108 
3109       if (commands_file) { // Insert debug info code in body
3110         if (commands_files.width()<2)
3111           l_debug_info = cimg_snprintf(debug_info.data() + 1,debug_info.width() - 2,"%x",line_number);
3112         else
3113           l_debug_info = cimg_snprintf(debug_info.data() + 1,debug_info.width() - 2,"%x,%x",
3114                                             line_number,commands_files.width() - 1);
3115         if (l_debug_info>=debug_info.width() - 1) l_debug_info = debug_info.width() - 2;
3116         debug_info[0] = 1; debug_info[l_debug_info + 1] = ' ';
3117         ((CImg<char>(debug_info,l_debug_info + 2,1,1,1,true),body)>'x').move_to(body);
3118       }
3119       if (!search_sorted(s_name,commands_names[hash],commands_names[hash].size(),pos)) {
3120         commands_names[hash].insert(1,pos);
3121         commands[hash].insert(1,pos);
3122         commands_has_arguments[hash].insert(1,pos);
3123         if (count_new) ++*count_new;
3124       } else if (count_replaced) ++*count_replaced;
3125       CImg<char>::string(s_name).move_to(commands_names[hash][pos]);
3126       CImg<char>::vector((char)command_has_arguments(body)).
3127         move_to(commands_has_arguments[hash][pos]);
3128       body.move_to(commands[hash][pos]);
3129 
3130     } else { // Continuation of a previous line
3131       if (!is_last_slash) commands[hash][pos].back() = ' ';
3132       else --(commands[hash][pos]._width);
3133       const CImg<char> body = CImg<char>(lines,(unsigned int)(linee - lines + 2));
3134       commands_has_arguments[hash](pos,0) |= (char)command_has_arguments(body);
3135       if (commands_file && !is_last_slash) { // Insert code with debug info
3136         if (commands_files.width()<2)
3137           l_debug_info = cimg_snprintf(debug_info.data() + 1,debug_info.width() - 2,"%x",line_number);
3138         else
3139           l_debug_info = cimg_snprintf(debug_info.data() + 1,debug_info.width() - 2,"%x,%x",
3140                                        line_number,commands_files.width() - 1);
3141         if (l_debug_info>=debug_info.width() - 1) l_debug_info = debug_info.width() - 2;
3142         debug_info[0] = 1; debug_info[l_debug_info + 1] = ' ';
3143         ((commands[hash][pos],CImg<char>(debug_info,l_debug_info + 2,1,1,1,true),body)>'x').
3144           move_to(commands[hash][pos]);
3145       } else commands[hash][pos].append(body,'x'); // Insert code without debug info
3146     }
3147   }
3148 
3149   if (is_debug) {
3150     CImg<unsigned int> hdist(gmic_comslots);
3151     cimg_forX(hdist,i) hdist[i] = commands[i].size();
3152     const CImg<double> st = hdist.get_stats();
3153     cimg_snprintf(s_body,s_body.width(),
3154                   "Distribution of command hashes: [ %s ], min = %u, max = %u, mean = %g, std = %g.",
3155                   hdist.value_string().data(),(unsigned int)st[0],(unsigned int)st[1],st[2],
3156                   std::sqrt(st[3]));
3157     cimg::strellipsize(s_body,512,false);
3158     debug("%s",s_body.data());
3159   }
3160   cimg::mutex(23,0);
3161   return *this;
3162 }
3163 
3164 // Add commands from a file.
3165 //---------------------------
add_commands(std::FILE * const file,const char * const filename,unsigned int * count_new,unsigned int * count_replaced,bool * const is_entrypoint)3166 gmic& gmic::add_commands(std::FILE *const file, const char *const filename,
3167                          unsigned int *count_new, unsigned int *count_replaced,
3168                          bool *const is_entrypoint) {
3169   if (!file) return *this;
3170 
3171   // Try reading it first as a .cimg file.
3172   try {
3173     CImg<char> buffer;
3174     buffer.load_cimg(file).unroll('x');
3175     buffer.resize(buffer.width() + 1,1,1,1,0);
3176     add_commands(buffer.data(),filename,count_new,count_replaced,is_entrypoint);
3177   } catch (...) {
3178     std::rewind(file);
3179     std::fseek(file,0,SEEK_END);
3180     const cimg_long siz = std::ftell(file);
3181     std::rewind(file);
3182     if (siz>0) {
3183       CImg<char> buffer((unsigned int)siz + 1);
3184       if (std::fread(buffer.data(),sizeof(char),siz,file)) {
3185         buffer[siz] = 0;
3186         add_commands(buffer.data(),filename,count_new,count_replaced,is_entrypoint);
3187       }
3188     }
3189   }
3190   return *this;
3191 }
3192 
3193 // Return subset indices from a selection string.
3194 //-----------------------------------------------
selection2cimg(const char * const string,const unsigned int index_max,const CImgList<char> & names,const char * const command,const bool is_selection)3195 CImg<unsigned int> gmic::selection2cimg(const char *const string, const unsigned int index_max,
3196                                         const CImgList<char>& names,
3197                                         const char *const command, const bool is_selection) {
3198 
3199   // First, try to detect the most common cases.
3200   if (string && !*string) return CImg<unsigned int>(); // Empty selection
3201   if (!string || (*string=='^' && !string[1])) { // Whole selection
3202     CImg<unsigned int> res(1,index_max); cimg_forY(res,y) res[y] = (unsigned int)y; return res;
3203   } else if (*string>='0' && *string<='9' && !string[1]) { // Single positive digit
3204     const unsigned int ind = *string - '0';
3205     if (ind<index_max) return CImg<unsigned int>::vector(ind);
3206   } else if (*string=='-' && string[1]>='0' && string[2]<='9' && !string[2]) { // Single negative digit
3207     const unsigned int ind = index_max - string[1] + '0';
3208     if (ind<index_max) return CImg<unsigned int>::vector(ind);
3209   }
3210 
3211   // Manage remaining cases.
3212   const char *const stype = is_selection?"selection":"subset";
3213   const int
3214     ctypel = is_selection?'[':'\'',
3215     ctyper = is_selection?']':'\'';
3216 
3217   CImg<bool> is_selected(1,index_max,1,1,false);
3218   CImg<char> name, item;
3219   bool is_inverse = *string=='^';
3220   const char *it = string + (is_inverse?1:0);
3221   for (bool stopflag = false; !stopflag; ) {
3222     float ind0 = 0, ind1 = 0, step = 1;
3223     int iind0 = 0, iind1 = 0, istep = 1;
3224     bool is_label = false;
3225     char sep = 0;
3226 
3227     const char *const it_comma = std::strchr(it,',');
3228     if (it_comma) { item.assign(it,(unsigned int)(it_comma - it + 1)); item.back() = 0; it = it_comma + 1; }
3229     else { CImg<char>::string(it).move_to(item); stopflag = true; }
3230 
3231     char end, *it_colon = std::strchr(item,':');
3232     if (it_colon) {
3233       *(it_colon++) = sep = 0;
3234       if ((cimg_sscanf(it_colon,"%f%c",&step,&end)==1 ||
3235            cimg_sscanf(it_colon,"%f%c%c",&step,&sep,&end)==2) &&
3236           (!sep || sep=='%') && step>0) {
3237         if (sep=='%') step*=index_max/100.0f;
3238       } else step = 0;
3239       istep = (int)cimg::round(step);
3240       if (istep<=0)
3241         error(true,"Command '%s': Invalid %s %c%s%c (syntax error after colon ':').",
3242               command,stype,ctypel,string,ctyper);
3243     }
3244 
3245     if (!*item) { // Particular cases [:N] or [^:N]
3246       if (is_inverse) { iind0 = 0; iind1 = -1; is_inverse = false; }
3247       else continue;
3248     } else if (cimg_sscanf(item,"%f%c",&ind0,&end)==1) { // Single index
3249       iind1 = iind0 = (int)cimg::round(ind0);
3250     } else if (cimg_sscanf(item,"%f-%f%c",&ind0,&ind1,&end)==2) { // Sequence between 2 indices
3251       iind0 = (int)cimg::round(ind0);
3252       iind1 = (int)cimg::round(ind1);
3253     } else if (cimg_sscanf(item,"%255[a-zA-Z0-9_]%c",name.assign(256).data(),&end)==1 && // Label
3254                (*name<'0' || *name>'9')) {
3255       cimglist_for(names,l) if (names[l] && !std::strcmp(names[l],name)) {
3256         is_selected(l) = true; is_label = true;
3257       }
3258       if (!is_label)
3259         error(true,"Command '%s': Invalid %s %c%s%c (undefined label '%s').",
3260               command,stype,ctypel,string,ctyper,name.data());
3261     } else if (cimg_sscanf(item,"%f%c%c",&ind0,&sep,&end)==2 && sep=='%') { // Single percent
3262       iind1 = iind0 = (int)cimg::round(ind0*((int)index_max - 1)/100) - (ind0<0?1:0);
3263     } else if (cimg_sscanf(item,"%f%%-%f%c%c",&ind0,&ind1,&sep,&end)==3 && sep=='%') {
3264       // Sequence between 2 percents.
3265       iind0 = (int)cimg::round(ind0*((int)index_max - 1)/100) - (ind0<0?1:0);
3266       iind1 = (int)cimg::round(ind1*((int)index_max - 1)/100) - (ind1<0?1:0);
3267     } else if (cimg_sscanf(item,"%f%%-%f%c",&ind0,&ind1,&end)==2) {
3268       // Sequence between a percent and an index.
3269       iind0 = (int)cimg::round(ind0*((int)index_max - 1)/100) - (ind0<0?1:0);
3270       iind1 = (int)cimg::round(ind1);
3271     } else if (cimg_sscanf(item,"%f-%f%c%c",&ind0,&ind1,&sep,&end)==3 && sep=='%') {
3272       // Sequence between an index and a percent.
3273       iind0 = (int)cimg::round(ind0);
3274       iind1 = (int)cimg::round(ind1*((int)index_max - 1)/100) - (ind1<0?1:0);
3275     } else error(true,"Command '%s': Invalid %s %c%s%c.",
3276                  command,stype,ctypel,string,ctyper);
3277 
3278     if (!index_max) error(true,"Command '%s': Invalid %s %c%s%c (no data available).",
3279                           command,stype,ctypel,string,ctyper);
3280     if (!is_label) {
3281       int
3282         uind0 = (int)(iind0<0?iind0 + index_max:iind0),
3283         uind1 = (int)(iind1<0?iind1 + index_max:iind1);
3284       if (uind0>uind1) { cimg::swap(uind0,uind1); cimg::swap(iind0,iind1); }
3285       if (uind0<0 || uind0>=(int)index_max)
3286         error(true,"Command '%s': Invalid %s %c%s%c (contains index '%d', "
3287               "not in range -%u...%u).",
3288               command,stype,ctypel,string,ctyper,iind0,index_max,index_max - 1);
3289       if (uind1<0 || uind1>=(int)index_max)
3290         error(true,"Command '%s': Invalid %s %c%s%c (contains index '%d', "
3291               "not in range -%u...%u).",
3292               command,stype,ctypel,string,ctyper,iind1,index_max,index_max - 1);
3293       for (int l = uind0; l<=uind1; l+=istep) is_selected[l] = true;
3294     }
3295   }
3296   unsigned int index = 0;
3297   cimg_for(is_selected,p,bool) if (*p) ++index;
3298   CImg<unsigned int> selection(1,is_inverse?index_max - index:index);
3299   index = 0;
3300   if (is_inverse) { cimg_forY(is_selected,l) if (!is_selected[l]) selection[index++] = (unsigned int)l; }
3301   else cimg_forY(is_selected,l) if (is_selected[l]) selection[index++] = (unsigned int)l;
3302   return selection;
3303 }
3304 
3305 // Return selection or filename strings from a set of indices.
3306 //------------------------------------------------------------
3307 // output_type can be { 0=display indices without brackets | 1=display indices with brackets | 2=display image names }
selection2string(const CImg<unsigned int> & selection,const CImgList<char> & images_names,const unsigned int output_type,CImg<char> & res) const3308 CImg<char>& gmic::selection2string(const CImg<unsigned int>& selection,
3309                                    const CImgList<char>& images_names,
3310                                    const unsigned int output_type,
3311                                    CImg<char>& res) const {
3312   res.assign(256);
3313   if (output_type<2) {
3314     const char *const bl = output_type?"[":"", *const br = output_type?"]":"";
3315     switch (selection.height()) {
3316     case 0:
3317       cimg_snprintf(res.data(),res.width()," %s%s",bl,br);
3318       break;
3319     case 1:
3320       cimg_snprintf(res.data(),res.width()," %s%u%s",
3321                     bl,selection[0],br);
3322       break;
3323     case 2:
3324       cimg_snprintf(res.data(),res.width(),"s %s%u,%u%s",
3325                     bl,selection[0],selection[1],br);
3326       break;
3327     case 3:
3328       cimg_snprintf(res.data(),res.width(),"s %s%u,%u,%u%s",
3329                     bl,selection[0],selection[1],selection[2],br);
3330       break;
3331     case 4:
3332       cimg_snprintf(res.data(),res.width(),"s %s%u,%u,%u,%u%s",
3333                     bl,selection[0],selection[1],selection[2],selection[3],br);
3334       break;
3335     case 5:
3336       cimg_snprintf(res.data(),res.width(),"s %s%u,%u,%u,%u,%u%s",
3337                     bl,selection[0],selection[1],selection[2],selection[3],selection[4],br);
3338       break;
3339     case 6:
3340       cimg_snprintf(res.data(),res.width(),"s %s%u,%u,%u,%u,%u,%u%s",
3341                     bl,selection[0],selection[1],selection[2],
3342                     selection[3],selection[4],selection[5],br);
3343       break;
3344     case 7:
3345       cimg_snprintf(res.data(),res.width(),"s %s%u,%u,%u,%u,%u,%u,%u%s",
3346                     bl,selection[0],selection[1],selection[2],selection[3],
3347                     selection[4],selection[5],selection[6],br);
3348       break;
3349     default:
3350       cimg_snprintf(res.data(),res.width(),"s %s%u,%u,%u,(...),%u,%u,%u%s",
3351                     bl,selection[0],selection[1],selection[2],
3352                     selection[selection.height() - 3],
3353                     selection[selection.height() - 2],
3354                     selection[selection.height() - 1],br);
3355     }
3356     return res;
3357   }
3358 
3359   switch (selection.height()) {
3360   case 0:
3361     *res = 0;
3362     break;
3363   case 1:
3364     cimg_snprintf(res.data(),res.width(),"%s",
3365                   basename(images_names[selection[0]]));
3366     break;
3367   case 2:
3368     cimg_snprintf(res.data(),res.width(),"%s, %s",
3369                   basename(images_names[selection[0]]),
3370                   basename(images_names[selection[1]]));
3371     break;
3372   case 3:
3373     cimg_snprintf(res.data(),res.width(),"%s, %s, %s",
3374                   basename(images_names[selection[0]]),
3375                   basename(images_names[selection[1]]),
3376                   basename(images_names[selection[2]]));
3377     break;
3378   case 4:
3379     cimg_snprintf(res.data(),res.width(),"%s, %s, %s, %s",
3380                   basename(images_names[selection[0]]),
3381                   basename(images_names[selection[1]]),
3382                   basename(images_names[selection[2]]),
3383                   basename(images_names[selection[3]]));
3384     break;
3385   default:
3386     cimg_snprintf(res.data(),res.width(),"%s, (...), %s",
3387                   basename(images_names[selection[0]]),
3388                   basename(images_names[selection.back()]));
3389   }
3390   return res;
3391 }
3392 
3393 // Print log message.
3394 //-------------------
3395 template<typename T>
print(const CImgList<T> & list,const CImg<unsigned int> * const callstack_selection,const char * format,...)3396 gmic& gmic::print(const CImgList<T>& list, const CImg<unsigned int> *const callstack_selection,
3397                   const char *format, ...) {
3398   if (verbosity<1 && !is_debug) return *this;
3399   va_list ap;
3400   va_start(ap,format);
3401   CImg<char> message(65536);
3402   message[message.width() - 2] = 0;
3403   cimg_vsnprintf(message,message.width(),format,ap);
3404   strreplace_fw(message);
3405   if (message[message.width() - 2]) cimg::strellipsize(message,message.width() - 2);
3406   va_end(ap);
3407 
3408   // Display message.
3409   cimg::mutex(29);
3410   if (*message!='\r')
3411     for (unsigned int i = 0; i<nb_carriages; ++i) std::fputc('\n',cimg::output());
3412   nb_carriages = 1;
3413   if (!callstack_selection || *callstack_selection)
3414     std::fprintf(cimg::output(),
3415                  "[gmic]-%u%s %s",
3416                  list.size(),callstack2string(callstack_selection).data(),message.data());
3417   else std::fprintf(cimg::output(),"%s",message.data());
3418   std::fflush(cimg::output());
3419   cimg::mutex(29,0);
3420   return *this;
3421 }
3422 
3423 // Print warning message.
3424 //-----------------------
3425 template<typename T>
warn(const CImgList<T> & list,const CImg<unsigned int> * const callstack_selection,const bool force_visible,const char * const format,...)3426 gmic& gmic::warn(const CImgList<T>& list, const CImg<unsigned int> *const callstack_selection,
3427                  const bool force_visible, const char *const format, ...) {
3428   if (!force_visible && verbosity<1 && !is_debug) return *this;
3429   va_list ap;
3430   va_start(ap,format);
3431   CImg<char> message(1024);
3432   message[message.width() - 2] = 0;
3433   cimg_vsnprintf(message,message.width(),format,ap);
3434   strreplace_fw(message);
3435   if (message[message.width() - 2]) cimg::strellipsize(message,message.width() - 2);
3436   va_end(ap);
3437 
3438   // Display message.
3439   const CImg<char> s_callstack = callstack2string(callstack_selection);
3440   cimg::mutex(29);
3441   if (*message!='\r')
3442     for (unsigned int i = 0; i<nb_carriages; ++i) std::fputc('\n',cimg::output());
3443   nb_carriages = 1;
3444   if (!callstack_selection || *callstack_selection) {
3445     if (debug_filename<commands_files.size() && debug_line!=~0U)
3446       std::fprintf(cimg::output(),
3447                    "[gmic]-%u%s %s%s*** Warning (file '%s', %sline #%u) *** %s%s",
3448                    list.size(),s_callstack.data(),cimg::t_magenta,cimg::t_bold,
3449                    commands_files[debug_filename].data(),
3450                    is_debug_info?"":"call from ",debug_line,message.data(),
3451                    cimg::t_normal);
3452     else
3453       std::fprintf(cimg::output(),
3454                    "[gmic]-%u%s %s%s*** Warning *** %s%s",
3455                    list.size(),s_callstack.data(),cimg::t_magenta,cimg::t_bold,
3456                    message.data(),cimg::t_normal);
3457   } else std::fprintf(cimg::output(),"%s%s%s%s",
3458                       cimg::t_magenta,cimg::t_bold,message.data(),cimg::t_normal);
3459   std::fflush(cimg::output());
3460   cimg::mutex(29,0);
3461   return *this;
3462 }
3463 
3464 // Print error message, and quit interpreter.
3465 //-------------------------------------------
3466 template<typename T>
error(const bool output_header,const CImgList<T> & list,const CImg<unsigned int> * const callstack_selection,const char * const command,const char * const format,...)3467 gmic& gmic::error(const bool output_header, const CImgList<T>& list,
3468                   const CImg<unsigned int> *const callstack_selection,
3469                   const char *const command, const char *const format, ...) {
3470   va_list ap;
3471   va_start(ap,format);
3472   CImg<char> message(1024);
3473   message[message.width() - 2] = 0;
3474   cimg_vsnprintf(message,message.width(),format,ap);
3475   strreplace_fw(message);
3476   if (message[message.width() - 2]) cimg::strellipsize(message,message.width() - 2);
3477   va_end(ap);
3478 
3479   // Display message.
3480   const CImg<char> s_callstack = callstack2string(callstack_selection);
3481   if (verbosity>=1 || is_debug) {
3482     cimg::mutex(29);
3483     if (*message!='\r')
3484       for (unsigned int i = 0; i<nb_carriages; ++i) std::fputc('\n',cimg::output());
3485     nb_carriages = 1;
3486     if (!callstack_selection || *callstack_selection) {
3487       if (output_header) {
3488         if (debug_filename<commands_files.size() && debug_line!=~0U)
3489           std::fprintf(cimg::output(),
3490                        "[gmic]-%u%s %s%s*** Error (file '%s', %sline #%u) *** %s%s",
3491                        list.size(),s_callstack.data(),cimg::t_red,cimg::t_bold,
3492                        commands_files[debug_filename].data(),
3493                        is_debug_info?"":"call from ",debug_line,message.data(),
3494                        cimg::t_normal);
3495         else
3496           std::fprintf(cimg::output(),
3497                        "[gmic]-%u%s %s%s*** Error *** %s%s",
3498                        list.size(),s_callstack.data(),cimg::t_red,cimg::t_bold,
3499                        message.data(),cimg::t_normal);
3500       } else
3501         std::fprintf(cimg::output(),
3502                      "[gmic]-%u%s %s%s%s%s",
3503                      list.size(),s_callstack.data(),cimg::t_red,cimg::t_bold,
3504                      message.data(),cimg::t_normal);
3505     } else std::fprintf(cimg::output(),"%s",message.data());
3506     std::fflush(cimg::output());
3507     cimg::mutex(29,0);
3508   }
3509 
3510   // Store detailed error message for interpreter.
3511   CImg<char> full_message(512 + message.width());
3512   if (debug_filename<commands_files.size() && debug_line!=~0U)
3513     cimg_snprintf(full_message,full_message.width(),
3514                   "*** Error in %s (file '%s', %sline #%u) *** %s",
3515                   s_callstack.data(),
3516                   commands_files[debug_filename].data(),
3517                   is_debug_info?"":"call from ",debug_line,message.data());
3518   else cimg_snprintf(full_message,full_message.width(),
3519                      "*** Error in %s *** %s",
3520                      s_callstack.data(),message.data());
3521   CImg<char>::string(full_message).move_to(status);
3522   message.assign();
3523   is_running = false;
3524   throw gmic_exception(command,status);
3525 }
3526 
3527 template<typename T>
check_cond(const char * const expr,CImgList<T> & images,const char * const command)3528 bool gmic::check_cond(const char *const expr, CImgList<T>& images, const char *const command) {
3529   bool res = false;
3530   float _res = 0;
3531   char end;
3532   if (cimg_sscanf(expr,"%f%c",&_res,&end)==1) res = (bool)_res;
3533   else {
3534     CImg<char> _expr(expr,(unsigned int)std::strlen(expr) + 1);
3535     strreplace_fw(_expr);
3536     CImg<T> &img = images.size()?images.back():CImg<T>::empty();
3537     try { if (img.eval(_expr,0,0,0,0,&images,&images)) res = true; }
3538     catch (CImgException &e) {
3539       const char *const e_ptr = std::strstr(e.what(),": ");
3540       error(true,images,0,command,
3541             "Command '%s': Invalid argument '%s': %s",
3542             command,cimg::strellipsize(_expr,64,false),e_ptr?e_ptr + 2:e.what());
3543     }
3544   }
3545   return res;
3546 }
3547 
3548 #define arg_error(command) gmic::error(true,images,0,command,"Command '%s': Invalid argument '%s'.",\
3549                                        command,gmic_argument_text())
3550 
3551 // Print debug message.
3552 //---------------------
3553 template<typename T>
debug(const CImgList<T> & list,const char * format,...)3554 gmic& gmic::debug(const CImgList<T>& list, const char *format, ...) {
3555   if (!is_debug) return *this;
3556   va_list ap;
3557   va_start(ap,format);
3558   CImg<char> message(1024);
3559   message[message.width() - 2] = 0;
3560   cimg_vsnprintf(message,message.width(),format,ap);
3561   if (message[message.width() - 2]) cimg::strellipsize(message,message.width() - 2);
3562   va_end(ap);
3563 
3564   // Display message.
3565   cimg::mutex(29);
3566   if (*message!='\r')
3567     for (unsigned int i = 0; i<nb_carriages; ++i) std::fputc('\n',cimg::output());
3568   nb_carriages = 1;
3569   if (is_debug_info && debug_filename!=~0U && debug_line!=~0U)
3570     std::fprintf(cimg::output(),
3571                  "%s<gmic>-%u%s#%u ",
3572                  cimg::t_green,list.size(),callstack2string(true).data(),debug_line);
3573   else
3574     std::fprintf(cimg::output(),
3575                  "%s<gmic>-%u%s ",
3576                  cimg::t_green,list.size(),callstack2string(true).data());
3577   for (char *s = message; *s; ++s) {
3578     char c = *s;
3579     if (c<' ') {
3580       switch (c) {
3581       case gmic_dollar : std::fprintf(cimg::output(),"\\$"); break;
3582       case gmic_lbrace : std::fprintf(cimg::output(),"\\{"); break;
3583       case gmic_rbrace : std::fprintf(cimg::output(),"\\}"); break;
3584       case gmic_comma : std::fprintf(cimg::output(),"\\,"); break;
3585       case gmic_dquote : std::fprintf(cimg::output(),"\\\""); break;
3586       default : std::fputc(c,cimg::output());
3587       }
3588     } else std::fputc(c,cimg::output());
3589   }
3590   std::fprintf(cimg::output(),
3591                "%s",
3592                cimg::t_normal);
3593   std::fflush(cimg::output());
3594   cimg::mutex(29,0);
3595   return *this;
3596 }
3597 
3598 // Check if a shared image of the image list is safe or not.
3599 //----------------------------------------------------------
3600 template<typename T>
gmic_is_valid_pointer(const T * const ptr)3601 inline bool gmic_is_valid_pointer(const T *const ptr) {
3602 #if cimg_OS==1
3603   const int result = access((const char*)ptr,F_OK);
3604   if (result==-1 && errno==EFAULT) return false;
3605 #elif cimg_OS==2 // #if cimg_OS==1
3606   return !IsBadReadPtr((void*)ptr,1);
3607 #endif // #if cimg_OS==1
3608   return true;
3609 }
3610 
3611 template<typename T>
check_image(const CImgList<T> & list,CImg<T> & img)3612 CImg<T>& gmic::check_image(const CImgList<T>& list, CImg<T>& img) {
3613   check_image(list,(const CImg<T>&)img);
3614   return img;
3615 }
3616 
3617 template<typename T>
check_image(const CImgList<T> & list,const CImg<T> & img)3618 const CImg<T>& gmic::check_image(const CImgList<T>& list, const CImg<T>& img) {
3619 #ifdef gmic_check_image
3620   if (!img.is_shared() || gmic_is_valid_pointer(img.data())) return img;
3621   if (is_debug) error(true,list,0,0,"Image list contains an invalid shared image (%p,%d,%d,%d,%d) "
3622                       "(references a deallocated buffer).",
3623                       img.data(),img.width(),img.height(),img.depth(),img.spectrum());
3624   else error(true,list,0,0,"Image list contains an invalid shared image (%d,%d,%d,%d) "
3625              "(references a deallocated buffer).",
3626              img.width(),img.height(),img.depth(),img.spectrum());
3627 #else // #ifdef gmic_check_image
3628   cimg::unused(list);
3629 #endif // #ifdef gmic_check_image
3630   return img;
3631 }
3632 
3633 #define gmic_check(img) check_image(images,img)
3634 
3635 // Remove list of images in a selection.
3636 //---------------------------------------
3637 template<typename T>
remove_images(CImgList<T> & images,CImgList<char> & images_names,const CImg<unsigned int> & selection,const unsigned int start,const unsigned int end)3638 gmic& gmic::remove_images(CImgList<T> &images, CImgList<char> &images_names,
3639                           const CImg<unsigned int>& selection,
3640                           const unsigned int start, const unsigned int end) {
3641   if (start==0 && end==(unsigned int)selection.height() - 1 && selection.height()==images.width()) {
3642     images.assign();
3643     images_names.assign();
3644   } else for (int l = (int)end; l>=(int)start; ) {
3645       unsigned int eind = selection[l--], ind = eind;
3646       while (l>=(int)start && selection[l]==ind - 1) ind = selection[l--];
3647       images.remove(ind,eind); images_names.remove(ind,eind);
3648     }
3649   return *this;
3650 }
3651 
3652 // Template constructor.
3653 //----------------------
3654 template<typename T>
gmic(const char * const commands_line,CImgList<T> & images,CImgList<char> & images_names,const char * const custom_commands,const bool include_stdlib,float * const p_progress,bool * const p_is_abort)3655 gmic::gmic(const char *const commands_line, CImgList<T>& images, CImgList<char>& images_names,
3656            const char *const custom_commands, const bool include_stdlib,
3657            float *const p_progress, bool *const p_is_abort):gmic_new_attr {
3658   _gmic(commands_line,
3659         images,images_names,
3660         custom_commands,include_stdlib,
3661         p_progress,p_is_abort);
3662 }
3663 
3664 // This method is shared by all constructors. It initializes all the interpreter environment.
3665 template<typename T>
3666 void gmic::_gmic(const char *const commands_line,
3667                  CImgList<T>& images, CImgList<char>& images_names,
3668                  const char *const custom_commands, const bool include_stdlib,
3669                  float *const p_progress, bool *const p_is_abort) {
3670   static bool is_first = true;
3671 
3672   // Initialize class variables and default G'MIC environment.
3673   cimg::mutex(22);
3674   if (!builtin_commands_inds) {
3675     builtin_commands_inds.assign(128,2,1,1,-1);
3676     for (unsigned int i = 0; i<sizeof(builtin_commands_names)/sizeof(char*); ++i) {
3677       const int c = *builtin_commands_names[i];
3678       if (builtin_commands_inds[c]<0) builtin_commands_inds[c] = (int)i;
3679       builtin_commands_inds(c,1) = (int)i;
3680     }
3681   }
3682   cimg::mutex(22,0);
3683 
3684   cimg::srand();
3685   setlocale(LC_NUMERIC,"C");
3686   cimg_exception_mode = cimg::exception_mode();
3687   cimg::exception_mode(0);
3688   allow_entrypoint = false;
3689   is_debug = false;
3690   is_double3d = true;
3691   nb_carriages = 0;
3692   verbosity = 0;
3693   render3d = 4;
3694   renderd3d = -1;
3695   focale3d = 700;
3696   light3d.assign();
3697   light3d_x = light3d_y = 0;
3698   light3d_z = -5e8f;
3699   specular_lightness3d = 0.15f;
3700   specular_shininess3d = 0.8f;
3701   starting_commands_line = commands_line;
3702   reference_time = (unsigned long)cimg::time();
3703   if (is_first) {
3704     try { is_display_available = (bool)CImgDisplay::screen_width(); } catch (CImgDisplayException&) { }
3705     is_first = false;
3706   }
3707   if (is_display_available) {
3708     display_windows.assign(gmic_winslots);
3709     cimg_forX(display_windows,l) display_windows[l] = new CImgDisplay;
3710   }
3711   for (unsigned int l = 0; l<gmic_comslots; ++l) {
3712     commands_names[l].assign();
3713     commands[l].assign();
3714     commands_has_arguments[l].assign();
3715   }
3716   for (unsigned int l = 0; l<gmic_varslots; ++l) {
3717     _variables[l].assign();
3718     variables[l] = &_variables[l];
3719     _variables_names[l].assign();
3720     variables_names[l] = &_variables_names[l];
3721   }
3722   if (include_stdlib) add_commands(gmic::decompress_stdlib().data());
3723   add_commands(custom_commands);
3724 
3725   // Set pre-defined global variables.
3726   CImg<char> str(8);
3727 
3728   set_variable("_path_rc",gmic::path_rc(),0);
3729   set_variable("_path_user",gmic::path_user(),0);
3730 
3731   cimg_snprintf(str,str.width(),"%u",cimg::nb_cpus());
3732   set_variable("_cpus",str.data(),0);
3733 
3734   cimg_snprintf(str,str.width(),"%u",gmic_version);
3735   set_variable("_version",str.data(),0);
3736 
3737 #if cimg_OS==1
3738   cimg_snprintf(str,str.width(),"%u",(unsigned int)getpid());
3739 #elif cimg_OS==2 // #if cimg_OS==1
3740   cimg_snprintf(str,str.width(),"%u",(unsigned int)_getpid());
3741 #else // #if cimg_OS==1
3742   cimg_snprintf(str,str.width(),"0");
3743 #endif // #if cimg_OS==1
3744   set_variable("_pid",str.data(),0);
3745 
3746 #ifdef cimg_use_vt100
3747   set_variable("_vt100","1",0);
3748 #else
3749   set_variable("_vt100","0",0);
3750 #endif // # if cimg_use_vt100
3751 
3752 #ifdef gmic_prerelease
3753   set_variable("_prerelease",gmic_prerelease,0);
3754 #else
3755   set_variable("_prerelease","0",0);
3756 #endif // #ifdef gmic_prerelease
3757 
3758   // Launch the G'MIC interpreter.
3759   const CImgList<char> items = commands_line?commands_line_to_CImgList(commands_line):CImgList<char>::empty();
3760   try {
3761     _run(items,images,images_names,p_progress,p_is_abort);
3762   } catch (gmic_exception&) {
3763     print(images,0,"Abort G'MIC interpreter (caught exception).\n");
3764     throw;
3765   }
3766 }
3767 bool gmic::is_display_available = false;
3768 
3769 // Print info on selected images.
3770 //--------------------------------
3771 template<typename T>
print_images(const CImgList<T> & images,const CImgList<char> & images_names,const CImg<unsigned int> & selection,const bool is_header)3772 gmic& gmic::print_images(const CImgList<T>& images, const CImgList<char>& images_names,
3773                          const CImg<unsigned int>& selection, const bool is_header) {
3774   if (!images || !images_names || !selection) {
3775     if (is_header) print(images,0,"Print image [].");
3776     return *this;
3777   }
3778   const bool is_verbose = verbosity>=1 || is_debug;
3779   CImg<char> title(256);
3780   if (is_header) {
3781     CImg<char> gmic_selection, gmic_names;
3782     if (is_verbose) {
3783       selection2string(selection,images_names,1,gmic_selection);
3784       selection2string(selection,images_names,2,gmic_names);
3785     }
3786     cimg::strellipsize(gmic_names,80,false);
3787     print(images,0,"Print image%s = '%s'.\n",
3788           gmic_selection.data(),gmic_names.data());
3789   }
3790 
3791   if (is_verbose) {
3792     cimg_forY(selection,l) {
3793       const unsigned int uind = selection[l];
3794       const CImg<T>& img = images[uind];
3795       const int o_verbosity = verbosity;
3796       const bool o_is_debug = is_debug;
3797       bool is_valid = true;
3798       verbosity = 0;
3799       is_debug = false;
3800 
3801       try { gmic_check(img); } catch (gmic_exception&) { is_valid = false; }
3802       is_debug = o_is_debug;
3803       verbosity = o_verbosity;
3804       cimg_snprintf(title,title.width(),"[%u] = '%s'",
3805                     uind,images_names[uind].data());
3806       cimg::strellipsize(title,80,false);
3807       img.gmic_print(title,is_debug,is_valid);
3808     }
3809     nb_carriages = 0;
3810   }
3811   return *this;
3812 }
3813 
3814 // Display selected images.
3815 //-------------------------
3816 template<typename T>
display_images(const CImgList<T> & images,const CImgList<char> & images_names,const CImg<unsigned int> & selection,unsigned int * const XYZ,const bool exit_on_anykey)3817 gmic& gmic::display_images(const CImgList<T>& images, const CImgList<char>& images_names,
3818                            const CImg<unsigned int>& selection, unsigned int *const XYZ,
3819                            const bool exit_on_anykey) {
3820   if (!images || !images_names || !selection) { print(images,0,"Display image []."); return *this; }
3821   const bool is_verbose = verbosity>=1 || is_debug;
3822   CImg<char> gmic_selection;
3823   if (is_verbose) selection2string(selection,images_names,1,gmic_selection);
3824 
3825   // Check for available display.
3826   if (!is_display_available) {
3827     cimg::unused(exit_on_anykey);
3828     print(images,0,"Display image%s",gmic_selection.data());
3829     if (is_verbose) {
3830       cimg::mutex(29);
3831       if (XYZ) std::fprintf(cimg::output(),", from point (%u,%u,%u)",XYZ[0],XYZ[1],XYZ[2]);
3832       std::fprintf(cimg::output()," (console output only, no display %s).\n",
3833                    cimg_display?"available":"support");
3834       std::fflush(cimg::output());
3835       cimg::mutex(29,0);
3836       print_images(images,images_names,selection,false);
3837     }
3838     return *this;
3839   }
3840 
3841   CImgList<T> visu;
3842   CImgList<char> t_visu;
3843   CImg<bool> is_valid(1,selection.height(),1,1,true);
3844   cimg_forY(selection,l) {
3845     const CImg<T>& img = images[selection[l]];
3846     const int o_verbosity = verbosity;
3847     const bool o_is_debug = is_debug;
3848     verbosity = 0;
3849     is_debug = false;
3850     try { gmic_check(img); } catch (gmic_exception&) { is_valid[l] = false; }
3851     is_debug = o_is_debug;
3852     verbosity = o_verbosity;
3853   }
3854 
3855   CImg<char> s_tmp;
3856   cimg_forY(selection,l) {
3857     const unsigned int uind = selection[l];
3858     const CImg<T>& img = images[uind];
3859     if (img && is_valid[l]) visu.insert(img,~0U,true);
3860     else visu.insert(1);
3861     const char *const ext = cimg::split_filename(images_names[uind]);
3862     const CImg<char> str = CImg<char>::string(std::strlen(ext)>6?
3863                                               images_names[uind].data():
3864                                               basename(images_names[uind]),true,true);
3865     s_tmp.assign(str.width() + 16);
3866     cimg_snprintf(s_tmp,s_tmp.width(),"[%u] %s",uind,str.data());
3867     s_tmp.move_to(t_visu);
3868   }
3869 
3870   CImg<char> gmic_names;
3871   if (visu) selection2string(selection,images_names,2,gmic_names);
3872   cimg::strellipsize(gmic_names,80,false);
3873 
3874   print(images,0,"Display image%s = '%s'",gmic_selection.data(),gmic_names.data());
3875   if (is_verbose) {
3876     cimg::mutex(29);
3877     if (XYZ) std::fprintf(cimg::output(),", from point (%u,%u,%u).\n",XYZ[0],XYZ[1],XYZ[2]);
3878     else std::fprintf(cimg::output(),".\n");
3879     std::fflush(cimg::output());
3880     nb_carriages = 0;
3881     cimg::mutex(29,0);
3882   }
3883 
3884   if (visu) {
3885     CImgDisplay _disp, &disp = display_window(0)?display_window(0):_disp;
3886     CImg<char> title(256);
3887     if (visu.size()==1)
3888       cimg_snprintf(title,title.width(),"%s (%dx%dx%dx%d)",
3889                     gmic_names.data(),
3890                     visu[0].width(),visu[0].height(),visu[0].depth(),visu[0].spectrum());
3891     else
3892       cimg_snprintf(title,title.width(),"%s (%u)",
3893                     gmic_names.data(),visu.size());
3894     cimg::strellipsize(title,80,false);
3895     CImg<bool> is_shared(visu.size());
3896     cimglist_for(visu,l) {
3897       is_shared[l] = visu[l].is_shared();
3898       visu[l]._is_shared = images[selection[l]].is_shared();
3899     }
3900     print_images(images,images_names,selection,false);
3901     bool is_exit = false;
3902     try {
3903       visu._gmic_display(disp,0,&t_visu,false,'x',0.5f,XYZ,exit_on_anykey,0,true,is_exit,
3904                          *this,visu,t_visu);
3905     } catch (CImgDisplayException&) {
3906       try { error(true,images,0,"display","Unable to display image '%s'.",gmic_names.data()); }
3907       catch (gmic_exception&) {}
3908     }
3909     cimglist_for(visu,l) visu[l]._is_shared = is_shared(l);
3910   }
3911   return *this;
3912 }
3913 
3914 // Display plots of selected images.
3915 //----------------------------------
3916 template<typename T>
display_plots(const CImgList<T> & images,const CImgList<char> & images_names,const CImg<unsigned int> & selection,const unsigned int plot_type,const unsigned int vertex_type,const double xmin,const double xmax,const double ymin,const double ymax,const bool exit_on_anykey)3917 gmic& gmic::display_plots(const CImgList<T>& images, const CImgList<char>& images_names,
3918                           const CImg<unsigned int>& selection,
3919                           const unsigned int plot_type, const unsigned int vertex_type,
3920                           const double xmin, const double xmax,
3921                           const double ymin, const double ymax,
3922                           const bool exit_on_anykey) {
3923   if (!images || !images_names || !selection) { print(images,0,"Plot image []."); return *this; }
3924   const bool is_verbose = verbosity>=1 || is_debug;
3925   CImg<char> gmic_selection;
3926   if (is_verbose) selection2string(selection,images_names,1,gmic_selection);
3927 
3928   // Check for available display.
3929   if (!is_display_available) {
3930     cimg::unused(plot_type,vertex_type,xmin,xmax,ymin,ymax,exit_on_anykey);
3931     print(images,0,"Plot image%s (console output only, no display %s).\n",
3932           gmic_selection.data(),cimg_display?"available":"support");
3933     print_images(images,images_names,selection,false);
3934     return *this;
3935   }
3936 
3937   CImgList<unsigned int> empty_indices;
3938   cimg_forY(selection,l) if (!gmic_check(images[selection(l)]))
3939     CImg<unsigned int>::vector(selection(l)).move_to(empty_indices);
3940   if (empty_indices && is_verbose) {
3941     CImg<char> eselec;
3942     selection2string(empty_indices>'y',images_names,1,eselec);
3943     warn(images,0,false,
3944          "Command 'plot': Image%s %s empty.",
3945          eselec.data(),empty_indices.size()>1?"are":"is");
3946   }
3947 
3948   CImg<char> gmic_names;
3949   if (is_verbose) selection2string(selection,images_names,2,gmic_names);
3950   print(images,0,"Plot image%s = '%s'.",
3951         gmic_selection.data(),gmic_names.data());
3952 
3953   CImgDisplay _disp, &disp = display_window(0)?display_window(0):_disp;
3954   bool is_first_line = false;
3955   cimg_forY(selection,l) {
3956     const unsigned int uind = selection[l];
3957     const CImg<T>& img = images[uind];
3958     if (img) {
3959       if (is_verbose && !is_first_line) {
3960         cimg::mutex(29);
3961         std::fputc('\n',cimg::output());
3962         std::fflush(cimg::output());
3963         cimg::mutex(29,0);
3964         is_first_line = true;
3965       }
3966       img.print(images_names[uind].data());
3967       if (!disp) disp.assign(cimg_fitscreen(CImgDisplay::screen_width()/2,CImgDisplay::screen_height()/2,1),0,0);
3968       img.display_graph(disp.set_title("%s (%dx%dx%dx%d)",
3969                                        basename(images_names[uind]),
3970                                        img.width(),img.height(),img.depth(),img.spectrum()),
3971                         plot_type,vertex_type,0,xmin,xmax,0,ymin,ymax,exit_on_anykey);
3972       if (is_verbose) nb_carriages = 0;
3973     }
3974   }
3975   return *this;
3976 }
3977 
3978 // Display selected 3D objects.
3979 //-----------------------------
3980 template<typename T>
display_objects3d(const CImgList<T> & images,const CImgList<char> & images_names,const CImg<unsigned int> & selection,const CImg<unsigned char> & background3d,const bool exit_on_anykey)3981 gmic& gmic::display_objects3d(const CImgList<T>& images, const CImgList<char>& images_names,
3982                               const CImg<unsigned int>& selection,
3983                               const CImg<unsigned char>& background3d,
3984                               const bool exit_on_anykey) {
3985   if (!images || !images_names || !selection) {
3986     print(images,0,"Display 3D object [].");
3987     return *this;
3988   }
3989   const bool is_verbose = verbosity>=1 || is_debug;
3990   CImg<char> gmic_selection;
3991   if (is_verbose) selection2string(selection,images_names,1,gmic_selection);
3992   CImg<char> error_message(1024);
3993   cimg_forY(selection,l) if (!gmic_check(images[selection[l]]).is_CImg3d(true,error_message))
3994     error(true,images,0,0,
3995           "Command 'display3d': Invalid 3D object [%d] in selected image%s (%s).",
3996           selection[l],gmic_selection_err.data(),error_message.data());
3997 
3998   // Check for available display.
3999   if (!is_display_available) {
4000     cimg::unused(background3d,exit_on_anykey);
4001     print(images,0,"Display 3D object%s (skipped, no display %s).",
4002           gmic_selection.data(),cimg_display?"available":"support");
4003     return *this;
4004   }
4005 
4006   CImgDisplay _disp, &disp = display_window(0)?display_window(0):_disp;
4007   cimg_forY(selection,l) {
4008     const unsigned int uind = selection[l];
4009     const CImg<T>& img = images[uind];
4010     if (!disp) {
4011       if (background3d) disp.assign(cimg_fitscreen(background3d.width(),background3d.height(),1),0,0);
4012       else disp.assign(cimg_fitscreen(CImgDisplay::screen_width()/2,CImgDisplay::screen_height()/2,1),0,0);
4013     }
4014 
4015     CImg<unsigned char> background;
4016     if (background3d) background = background3d.get_resize(disp.width(),disp.height(),1,3);
4017     else background.assign(1,2,1,3).fill(32,64,32,116,64,96).resize(1,256,1,3,3).
4018            resize(disp.width(),disp.height(),1,3);
4019     background.display(disp);
4020 
4021     CImgList<unsigned int> primitives;
4022     CImgList<unsigned char> colors;
4023     CImgList<float> opacities;
4024     CImg<float> vertices(img,false), pose3d(4,4,1,1,0);
4025     pose3d(0,0) = pose3d(1,1) = pose3d(2,2) = pose3d(3,3) = 1;
4026     vertices.CImg3dtoobject3d(primitives,colors,opacities,false);
4027     print(images,0,"Display 3D object [%u] = '%s' (%d vertices, %u primitives).",
4028           uind,images_names[uind].data(),
4029           vertices.width(),primitives.size());
4030     disp.set_title("%s (%d vertices, %u primitives)",
4031                    basename(images_names[uind]),
4032                    vertices.width(),primitives.size());
4033     if (light3d) colors.insert(light3d,~0U,true);
4034     background.display_object3d(disp,vertices,primitives,colors,opacities,
4035                                 true,render3d,renderd3d,is_double3d,focale3d,
4036                                 light3d_x,light3d_y,light3d_z,
4037                                 specular_lightness3d,specular_shininess3d,
4038                                 true,pose3d,exit_on_anykey);
4039     print(images,0,"Selected 3D pose = [ %g,%g,%g,%g,%g,%g,%g,%g,%g,%g,%g,%g,%g,%g,%g,%g ].",
4040           pose3d[0],pose3d[1],pose3d[2],pose3d[3],
4041           pose3d[4],pose3d[5],pose3d[6],pose3d[7],
4042           pose3d[8],pose3d[9],pose3d[10],pose3d[11],
4043           pose3d[12],pose3d[13],pose3d[14],pose3d[15]);
4044     if (disp.is_closed()) break;
4045   }
4046   return *this;
4047 }
4048 
4049 // Substitute '{}' and '$' expressions in a string.
4050 //--------------------------------------------------
4051 template<typename T>
substitute_item(const char * const source,CImgList<T> & images,CImgList<char> & images_names,CImgList<T> & parent_images,CImgList<char> & parent_images_names,const unsigned int * const variables_sizes,const CImg<unsigned int> * const command_selection,const bool is_image_expr)4052 CImg<char> gmic::substitute_item(const char *const source,
4053                                  CImgList<T>& images, CImgList<char>& images_names,
4054                                  CImgList<T>& parent_images, CImgList<char>& parent_images_names,
4055                                  const unsigned int *const variables_sizes,
4056                                  const CImg<unsigned int> *const command_selection,
4057                                  const bool is_image_expr) {
4058   if (!source) return CImg<char>();
4059   CImg<char> substituted_items(64), inbraces, substr(40), vs;
4060   char *ptr_sub = substituted_items.data();
4061   CImg<unsigned int> _ind;
4062   const char dot = is_image_expr?'.':0;
4063 
4064   for (const char *nsource = source; *nsource; )
4065     if (*nsource!='{' && *nsource!='$' && *nsource!=dot) {
4066 
4067       // If not starting with '{', '.' or '$'.
4068       const char *const nsource0 = nsource;
4069       do { ++nsource; } while (*nsource && *nsource!='{' && *nsource!='$' && *nsource!=dot);
4070       CImg<char>(nsource0,(unsigned int)(nsource - nsource0),1,1,1,true).
4071         append_string_to(substituted_items,ptr_sub);
4072     } else { // '{...}', '...' or '${...}' expression found
4073       bool is_2dollars = false, is_braces = false, is_substituted = false;
4074       int ind = 0, l_inbraces = 0;
4075       char sep = 0;
4076       _ind.assign();
4077       *substr = 0;
4078       if (inbraces) *inbraces = 0; else inbraces.assign(1,1,1,1,0);
4079 
4080       // '.' expression ('.', '..' or '...').
4081       if (*nsource=='.') {
4082         const char *str = 0;
4083         unsigned int p = 0, N = 0;
4084         if (nsource==source || *(nsource - 1)==',') {
4085           if (!nsource[1] || nsource[1]==',' ||
4086               (nsource==source && nsource[1]=='x' && nsource[2]>='0' && nsource[2]<='9' &&
4087                cimg_sscanf(nsource + 2,"%u%c",&p,&(sep=0))==1)) { str = "[-1]"; N = 1; }
4088           else if (nsource[1]=='.') {
4089             if (!nsource[2] || nsource[2]==',' ||
4090                 (nsource==source && nsource[2]=='x' && nsource[3]>='0' && nsource[3]<='9' &&
4091                  cimg_sscanf(nsource + 3,"%u%c",&p,&(sep=0))==1)) { str = "[-2]"; N = 2; }
4092             else if (nsource[2]=='.') {
4093               if (!nsource[3] || nsource[3]==',' ||
4094                   (nsource==source && nsource[3]=='x' && nsource[4]>='0' && nsource[4]<='9' &&
4095                    cimg_sscanf(nsource + 4,"%u%c",&p,&(sep=0))==1)) { str = "[-3]"; N = 3; }
4096             }
4097           }
4098         }
4099         if (N) { CImg<char>::string(str,false,true).append_string_to(substituted_items,ptr_sub); nsource+=N; }
4100         else CImg<char>::append_string_to(*(nsource++),substituted_items,ptr_sub);
4101         continue;
4102 
4103         // '{...}' expression.
4104       } else if (*nsource=='{') {
4105         const char *const ptr_beg = nsource + 1, *ptr_end = ptr_beg;
4106         unsigned int p = 0;
4107         for (p = 1; p>0 && *ptr_end; ++ptr_end) { if (*ptr_end=='{') ++p; if (*ptr_end=='}') --p; }
4108         if (p) {
4109           CImg<char>::append_string_to(*(nsource++),substituted_items,ptr_sub);
4110           continue;
4111         }
4112         l_inbraces = (int)(ptr_end - ptr_beg - 1);
4113 
4114         if (l_inbraces>0) {
4115           inbraces.assign(ptr_beg,l_inbraces + 1).back() = 0;
4116           substitute_item(inbraces,images,images_names,parent_images,parent_images_names,variables_sizes,
4117                           command_selection,false).move_to(inbraces);
4118           strreplace_fw(inbraces);
4119         }
4120         nsource+=l_inbraces + 2;
4121         if (!*inbraces)
4122           error(true,images,0,0,
4123                 "Item substitution '{}': Empty braces.");
4124 
4125         // Display window features.
4126         if (!is_substituted && *inbraces=='*' &&
4127             (!inbraces[1] ||
4128              (inbraces[1]>='0' && inbraces[1]<='9' && !inbraces[2]) ||
4129              (inbraces[1]==',' && inbraces[2]) ||
4130              (inbraces[1]>='0' && inbraces[1]<='9' && inbraces[2]==',' && inbraces[3]))) {
4131 
4132           char *feature = inbraces.data() + 1;
4133           unsigned int wind = 0;
4134           if (*feature>='0' && *feature<='9') wind = (unsigned int)(*(feature++) - '0');
4135           char *e_feature = 0;
4136           if (!*feature) {
4137             if (!is_display_available) { *substr = '0'; substr[1] = 0; }
4138             else cimg_snprintf(substr,substr.width(),"%d",
4139                                (int)(display_window(wind) && !display_window(wind).is_closed()));
4140             is_substituted = true;
4141           } else if (*(feature++)==',') {
4142             do {
4143               is_substituted = false;
4144               e_feature = std::strchr(feature,',');
4145               if (e_feature) *e_feature = 0;
4146               if (!is_display_available) { *substr = '0'; substr[1] = 0; is_substituted = true; }
4147               else {
4148                 CImgDisplay &disp = display_window(wind);
4149                 bool flush_request = false;
4150                 if (*feature=='-' &&
4151                     feature[1]!='w' && feature[1]!='h' && feature[1]!='d' && feature[1]!='e' &&
4152                     feature[1]!='u' && feature[1]!='v' && feature[1]!='n' && feature[1]!='t') {
4153                   flush_request = true; ++feature;
4154                 }
4155                 if (!*feature) { // Empty feature
4156                   cimg_snprintf(substr,substr.width(),"%d",disp?(disp.is_closed()?0:1):0);
4157                   is_substituted = true;
4158                 } else if (*feature && !feature[1]) switch (*feature) { // Single-char features
4159                   case 'w' : // Display width
4160                     cimg_snprintf(substr,substr.width(),"%d",disp.width());
4161                     is_substituted = true;
4162                     break;
4163                   case 'h' : // Display height
4164                     cimg_snprintf(substr,substr.width(),"%d",disp.height());
4165                     is_substituted = true;
4166                     break;
4167                   case 'd' : // Window width
4168                     cimg_snprintf(substr,substr.width(),"%d",disp.window_width());
4169                     is_substituted = true;
4170                     break;
4171                   case 'e' : // Window height
4172                     cimg_snprintf(substr,substr.width(),"%d",disp.window_height());
4173                     is_substituted = true;
4174                     break;
4175                   case 'u' : // Screen width
4176                     try {
4177                       cimg_snprintf(substr,substr.width(),"%d",CImgDisplay::screen_width());
4178                     } catch (CImgDisplayException&) {
4179                       *substr = '0'; substr[1] = 0;
4180                     }
4181                     is_substituted = true;
4182                     break;
4183                   case 'v' : // Screen height
4184                     try {
4185                       cimg_snprintf(substr,substr.width(),"%d",CImgDisplay::screen_height());
4186                     } catch (CImgDisplayException&) {
4187                       *substr = '0'; substr[1] = 0;
4188                     }
4189                     is_substituted = true;
4190                     break;
4191                   case 'n' : // Normalization type
4192                     cimg_snprintf(substr,substr.width(),"%d",disp.normalization());
4193                     is_substituted = true;
4194                     break;
4195                   case 't' : // Window title
4196                     cimg_snprintf(substr,substr.width(),"%s",disp.title());
4197                     is_substituted = true;
4198                     break;
4199                   case 'x' : // X-coordinate of mouse pointer
4200                     cimg_snprintf(substr,substr.width(),"%d",disp.mouse_x());
4201                     is_substituted = true;
4202                     if (flush_request) { disp._mouse_x = -1; disp._mouse_y = -1; }
4203                     break;
4204                   case 'y' : // Y-coordinate of mouse pointer
4205                     cimg_snprintf(substr,substr.width(),"%d",disp.mouse_y());
4206                     is_substituted = true;
4207                     if (flush_request) { disp._mouse_x = -1; disp._mouse_y = -1; }
4208                     break;
4209                   case 'b' : // State of mouse buttons
4210                     cimg_snprintf(substr,substr.width(),"%d",disp.button());
4211                     is_substituted = true;
4212                     if (flush_request) disp._button = 0;
4213                     break;
4214                   case 'o' : // State of mouse wheel
4215                     cimg_snprintf(substr,substr.width(),"%d",disp.wheel());
4216                     is_substituted = true;
4217                     if (flush_request) disp._wheel = 0;
4218                     break;
4219                   case 'c' : // Closed state of display window
4220                     cimg_snprintf(substr,substr.width(),"%d",(int)disp.is_closed());
4221                     is_substituted = true;
4222                     if (flush_request) disp._is_closed = false;
4223                     break;
4224                   case 'r' : // Resize event
4225                     cimg_snprintf(substr,substr.width(),"%d",(int)disp.is_resized());
4226                     is_substituted = true;
4227                     if (flush_request) disp._is_resized = false;
4228                     break;
4229                   case 'm' : // Move event
4230                     cimg_snprintf(substr,substr.width(),"%d",(int)disp.is_moved());
4231                     is_substituted = true;
4232                     if (flush_request) disp._is_moved = false;
4233                     break;
4234                   case 'k' : // Key event
4235                     cimg_snprintf(substr,substr.width(),"%u",disp.key());
4236                     is_substituted = true;
4237                     if (flush_request) disp._keys[0] = 0;
4238                     break;
4239                   } else if (*feature=='w' && feature[1]=='h' && !feature[2]) { // Display width*height
4240                   cimg_snprintf(substr,substr.width(),"%lu",
4241                                 (unsigned long)disp.width()*disp.height());
4242                   is_substituted = true;
4243                 } else if (*feature=='d' && feature[1]=='e' && !feature[2]) { // Window width*height
4244                   cimg_snprintf(substr,substr.width(),"%lu",
4245                                 (unsigned long)disp.window_width()*disp.window_height());
4246                   is_substituted = true;
4247                 } else if (*feature=='u' && feature[1]=='v' && !feature[2]) { // Screen width*height
4248                   try {
4249                     cimg_snprintf(substr,substr.width(),"%lu",
4250                                   (unsigned long)CImgDisplay::screen_width()*CImgDisplay::screen_height());
4251                   } catch (CImgDisplayException&) {
4252                     *substr = '0'; substr[1] = 0;
4253                   }
4254                   is_substituted = true;
4255                 }
4256                 if (*feature && !is_substituted) { // Pressed state of specified key
4257                   bool &ik = disp.is_key(feature);
4258                   cimg_snprintf(substr,substr.width(),"%d",(int)ik);
4259                   is_substituted = true;
4260                   if (flush_request) ik = false;
4261                 }
4262               }
4263               if (is_substituted)
4264                 CImg<char>::string(substr,false,true).append_string_to(substituted_items,ptr_sub);
4265               if (e_feature) {
4266                 *e_feature = ','; feature = e_feature + 1;
4267                 CImg<char>::append_string_to(',',substituted_items,ptr_sub);
4268               } else feature+=std::strlen(feature);
4269             } while (*feature || e_feature);
4270             *substr = 0; is_substituted = true;
4271           }
4272         }
4273 
4274         // Double-backquoted string.
4275         if (!is_substituted && inbraces.width()>=3 && *inbraces=='`' && inbraces[1]=='`') {
4276           strreplace_bw(inbraces.data() + 2);
4277           CImg<char>(inbraces.data() + 2,inbraces.width() - 3,1,1,1,true).
4278             append_string_to(substituted_items,ptr_sub);
4279           *substr = 0; is_substituted = true;
4280         }
4281 
4282         // Escaped string.
4283         if (!is_substituted && inbraces.width()>=2 && *inbraces=='/') {
4284           const char *s = inbraces.data() + 1;
4285           vs.assign(inbraces.width()*4);
4286           const unsigned int l = strescape(s,vs);
4287           CImg<char>(vs,l,1,1,1,true).
4288             append_string_to(substituted_items,ptr_sub);
4289           *substr = 0; is_substituted = true;
4290         }
4291 
4292         // Sequence of ascii characters.
4293         if (!is_substituted && inbraces.width()>=3 && *inbraces=='\'' &&
4294             inbraces[inbraces.width() - 2]=='\'') {
4295           const char *s = inbraces.data() + 1;
4296           if (inbraces.width()>3) {
4297             inbraces[inbraces.width() - 2] = 0;
4298             cimg::strunescape(inbraces);
4299             for (*substr = 0; *s; ++s) {
4300               cimg_snprintf(substr,substr.width(),"%d,",(int)(unsigned char)*s);
4301               CImg<char>(substr.data(),(unsigned int)std::strlen(substr),1,1,1,true).
4302                 append_string_to(substituted_items,ptr_sub);
4303             }
4304             if (*substr) --ptr_sub;
4305           }
4306           *substr = 0; is_substituted = true;
4307         }
4308 
4309         // Image feature.
4310         if (!is_substituted) {
4311           const char *feature = inbraces;
4312           if (l_inbraces<=2) ind = images.width() - 1; // Single-char case
4313           else if (cimg_sscanf(inbraces,"%d%c",&ind,&(sep=0))==2 && sep==',') {
4314             if (ind<0) ind+=images.width();
4315             if (ind<0 || ind>=images.width()) {
4316               if (images.width())
4317                 error(true,images,0,0,
4318                       "Item substitution '{%s}': Invalid selection [%d] (not in range -%u...%u).",
4319                       cimg::strellipsize(inbraces,64,false),ind,images.size(),images.size() - 1);
4320               else
4321                 error(true,images,0,0,
4322                       "Item substitution '{%s}': Invalid selection [%d] (no image data available).",
4323                       cimg::strellipsize(inbraces,64,false),ind);
4324             }
4325             while (*feature!=',') ++feature;
4326             ++feature;
4327           } else if (cimg_sscanf(inbraces,"%255[a-zA-Z0-9_]%c",substr.assign(256).data(),&(sep=0))==2 && sep==',') {
4328             selection2cimg(substr,images.size(),images_names,"Item substitution '{name,feature}'").move_to(_ind);
4329             if (_ind.height()!=1)
4330               error(true,images,0,0,
4331                     "Item substitution '{%s}': Invalid selection [%s], specifies multiple images.",
4332                     cimg::strellipsize(inbraces,64,false),substr.data());
4333             ind = (int)*_ind;
4334             while (*feature!=',') ++feature;
4335             ++feature;
4336           } else ind = images.width() - 1;
4337 
4338           CImg<T> &img = ind>=0?gmic_check(images[ind]):CImg<T>::empty();
4339           *substr = 0;
4340           if (!*feature)
4341             error(true,images,0,0,
4342                   "Item substitution '{%s}': Request for empty feature.",
4343                   cimg::strellipsize(inbraces,64,false));
4344 
4345           if (!feature[1]) switch (*feature) { // Single-char feature
4346             case 'b' : { // Image basename
4347               if (ind>=0) {
4348                 substr.assign(std::max(substr.width(),images_names[ind].width()));
4349                 cimg::split_filename(images_names[ind].data(),substr);
4350                 const char *const basename = gmic::basename(substr);
4351                 std::memmove(substr,basename,std::strlen(basename) + 1);
4352                 strreplace_bw(substr);
4353               }
4354               is_substituted = true;
4355             } break;
4356             case 'd' : // Image depth
4357               cimg_snprintf(substr,substr.width(),"%d",img.depth());
4358               is_substituted = true;
4359               break;
4360             case 'f' : { // Image folder name
4361               if (ind>=0) {
4362                 substr.assign(std::max(substr.width(),images_names[ind].width()));
4363                 std::strcpy(substr,images_names[ind]);
4364                 const char *const basename = gmic::basename(substr);
4365                 substr[basename - substr.data()] = 0;
4366                 strreplace_bw(substr);
4367               }
4368               is_substituted = true;
4369             } break;
4370             case 'h' : // Image height
4371               cimg_snprintf(substr,substr.width(),"%d",img.height());
4372               is_substituted = true;
4373               break;
4374             case 'n' : // Image name
4375               if (ind>=0) {
4376                 substr.assign(std::max(substr.width(),images_names[ind].width()));
4377                 cimg_snprintf(substr,substr.width(),"%s",images_names[ind].data());
4378                 strreplace_bw(substr);
4379               }
4380               is_substituted = true;
4381               break;
4382             case 's' : // Number of image channels
4383               cimg_snprintf(substr,substr.width(),"%d",img.spectrum());
4384               is_substituted = true;
4385               break;
4386             case 't' : { // Ascii string from image values
4387               const unsigned int siz = (unsigned int)img.size();
4388               if (siz) {
4389                 unsigned int strsiz = 0;
4390                 cimg_for(img,ptr,T) if ((unsigned char)*ptr) ++strsiz; else break;
4391                 if (strsiz) {
4392                   CImg<char> text(strsiz + 1), _text = text.get_shared_points(0,strsiz - 1,0,0,0);
4393                   _text = CImg<T>(img.data(),strsiz,1,1,1,true);
4394                   text.back() = 0;
4395                   strreplace_bw(text);
4396                   _text.append_string_to(substituted_items,ptr_sub);
4397                 }
4398               }
4399               *substr = 0; is_substituted = true;
4400             } break;
4401             case 'x' : // Image extension
4402               if (ind>=0) {
4403                 substr.assign(std::max(substr.width(),images_names[ind].width()));
4404                 cimg_snprintf(substr,substr.width(),"%s",
4405                               cimg::split_filename(images_names[ind].data()));
4406                 strreplace_bw(substr);
4407               }
4408               is_substituted = true;
4409               break;
4410             case 'w' : // Image width
4411               cimg_snprintf(substr,substr.width(),"%d",img.width());
4412               is_substituted = true;
4413               break;
4414             case '^' : { // Sequence of all pixel values
4415               img.value_string(',').move_to(vs);
4416               if (vs && *vs) { --vs._width; vs.append_string_to(substituted_items,ptr_sub); }
4417               *substr = 0; is_substituted = true;
4418             } break;
4419             }
4420 
4421           const unsigned int l_feature = (unsigned int)std::strlen(feature);
4422           if (!is_substituted && *feature=='@') { // Subset of values
4423             if (l_feature>=2) {
4424               if (feature[1]=='^' && !feature[2]) { // All pixel values
4425                 img.value_string(',').move_to(vs);
4426                 if (vs && *vs) { --vs._width; vs.append_string_to(substituted_items,ptr_sub); }
4427                 *substr = 0; is_substituted = true;
4428               } else {
4429                 CImg<char> subset(feature + 1,l_feature);
4430                 subset.back() = 0;
4431                 CImg<T> values;
4432                 ++feature;
4433                 CImg<char> o_status;
4434                 status.move_to(o_status); // Save status because 'selection2cimg' may change it
4435                 const int o_verbosity = verbosity;
4436                 const bool o_is_debug = is_debug;
4437                 verbosity = 0;
4438                 is_debug = false;
4439                 try {
4440                   const CImg<unsigned int> inds = selection2cimg(subset,(unsigned int)img.size(),
4441                                                                  CImgList<char>::empty(),"",false);
4442                   values.assign(1,inds.height());
4443                   cimg_foroff(inds,q) values[q] = img[inds[q]];
4444                 } catch (gmic_exception &e) {
4445                   const char *const e_ptr = std::strstr(e.what(),": ");
4446                   error(true,images,0,0,
4447                         "Item substitution '{%s}': %s",
4448                         cimg::strellipsize(inbraces,64,false),e_ptr?e_ptr + 2:e.what());
4449                 }
4450                 is_debug = o_is_debug;
4451                 verbosity = o_verbosity;
4452                 o_status.move_to(status);
4453                 cimg_foroff(values,q) {
4454                   cimg_snprintf(substr,substr.width(),"%.17g",(double)values[q]);
4455                   CImg<char>::string(substr,true,true).
4456                     append_string_to(substituted_items,ptr_sub);
4457                   *(ptr_sub - 1) = ',';
4458                 }
4459                 if (values) --ptr_sub;
4460               }
4461             }
4462             *substr = 0; is_substituted = true;
4463           }
4464 
4465           if (!is_substituted && l_feature==2 && *feature=='\'' && feature[1]=='\'') { // Empty string
4466             *substr = 0; is_substituted = true;
4467           }
4468 
4469           if (!is_substituted) { // Other mathematical expression
4470             const bool is_string = l_feature>=3 && *feature=='`' && inbraces[inbraces.width() - 2]=='`';
4471             if (is_string) { ++feature; inbraces[inbraces.width() - 2] = 0; }
4472             const bool is_rounded = *feature=='_';
4473             if (is_rounded) ++feature;
4474 
4475             try {
4476               CImg<double> output;
4477               img.eval(output,feature,0,0,0,0,&images,&images);
4478               if (is_string) {
4479                 vs.assign(output.height() + 1,1,1,1).fill(output).back() = 0;
4480                 CImg<char>::string(vs,false,true).
4481                   append_string_to(substituted_items,ptr_sub);
4482               } else {
4483                 if (output.height()>1) { // Vector-valued result
4484                   output.value_string(',',0,is_rounded?"%g":"%.17g").move_to(vs);
4485                   if (vs && *vs) { --vs._width; vs.append_string_to(substituted_items,ptr_sub); }
4486                 } else { // Scalar result
4487                   if (is_rounded) cimg_snprintf(substr,substr.width(),"%g",*output);
4488                   else cimg_snprintf(substr,substr.width(),"%.17g",*output);
4489                   is_substituted = true;
4490                 }
4491               }
4492             } catch (CImgException& e) {
4493               const char *const e_ptr = std::strstr(e.what(),": ");
4494               if (is_string) inbraces[inbraces.width() - 2] = '`';
4495               error(true,images,0,0,
4496                     "Item substitution '{%s}': %s",
4497                     cimg::strellipsize(inbraces,64,false),e_ptr?e_ptr + 2:e.what());
4498             }
4499           }
4500         }
4501         if (is_substituted && *substr)
4502           CImg<char>(substr.data(),(unsigned int)std::strlen(substr),1,1,1,true).
4503             append_string_to(substituted_items,ptr_sub);
4504         continue;
4505 
4506         //  '${...}' and '$${...}' expressions.
4507       } else if (nsource[1]=='{' || (nsource[1]=='$' && nsource[2]=='{')) {
4508         is_2dollars = nsource[1]=='$';
4509         const char *const ptr_beg = nsource + 2 + (is_2dollars?1:0), *ptr_end = ptr_beg; unsigned int p = 0;
4510         for (p = 1; p>0 && *ptr_end; ++ptr_end) { if (*ptr_end=='{') ++p; if (*ptr_end=='}') --p; }
4511         if (p) {
4512           CImg<char>::append_string_to(*(nsource++),substituted_items,ptr_sub);
4513           CImg<char>::append_string_to(*(nsource++),substituted_items,ptr_sub);
4514           if (is_2dollars) CImg<char>::append_string_to(*(nsource++),substituted_items,ptr_sub);
4515           continue;
4516         }
4517         l_inbraces = (int)(ptr_end - ptr_beg - 1);
4518         if (l_inbraces>0) {
4519           inbraces.assign(ptr_beg,l_inbraces + 1).back() = 0;
4520           substitute_item(inbraces,images,images_names,parent_images,parent_images_names,variables_sizes,
4521                           command_selection,false).move_to(inbraces);
4522         }
4523         is_braces = true;
4524       }
4525 
4526       // Substitute '$?' -> String to describes the current command selection.
4527       // (located here to avoid substitution when verbosity<1).
4528       if (nsource[1]=='?') {
4529         if (command_selection) {
4530           const unsigned int substr_width = (unsigned int)substr.width();
4531           selection2string(*command_selection,images_names,1,substr);
4532           CImg<char>(substr.data(),(unsigned int)std::strlen(substr),1,1,1,true).
4533             append_string_to(substituted_items,ptr_sub);
4534           substr.assign(substr_width);
4535           nsource+=2;
4536         } else CImg<char>::append_string_to(*(nsource++),substituted_items,ptr_sub);
4537 
4538         // Substitute '$!' -> Number of images in the list.
4539       } else if (nsource[1]=='!') {
4540         cimg_snprintf(substr,substr.width(),"%u",images.size());
4541         CImg<char>(substr.data(),(unsigned int)std::strlen(substr),1,1,1,true).
4542           append_string_to(substituted_items,ptr_sub);
4543         nsource+=2;
4544 
4545         // Substitute '$^' -> Verbosity level.
4546       } else if (nsource[1]=='^') {
4547         cimg_snprintf(substr,substr.width(),"%d",verbosity);
4548         CImg<char>(substr.data(),(unsigned int)std::strlen(substr),1,1,1,true).
4549           append_string_to(substituted_items,ptr_sub);
4550         nsource+=2;
4551 
4552         // Substitute '$|' -> Timer value.
4553       } else if (nsource[1]=='|') {
4554         cimg_snprintf(substr,substr.width(),"%g",(cimg::time() - reference_time)/1000.);
4555         CImg<char>(substr.data(),(unsigned int)std::strlen(substr),1,1,1,true).
4556           append_string_to(substituted_items,ptr_sub);
4557         nsource+=2;
4558 
4559         // Substitute '$/' -> Current call stack.
4560       } else if (nsource[1]=='/') {
4561         cimglist_for(callstack,i) {
4562           callstack[i].append_string_to(substituted_items,ptr_sub);
4563           *(ptr_sub - 1) = '/';
4564         }
4565         nsource+=2;
4566 
4567         // Substitute '$>' and '$<' -> Forward/backward index of current loop.
4568       } else if (nsource[1]=='>' || nsource[1]=='<') {
4569         if (!nb_repeatdones)
4570           error(true,images,0,0,
4571                 "Item substitution '$%c': There is no loop currently running.",
4572                 nsource[1]);
4573         const unsigned int *const rd = repeatdones.data(0,nb_repeatdones - 1);
4574         cimg_snprintf(substr,substr.width(),"%u",nsource[1]=='>'?rd[2]:rd[1] - 1);
4575         CImg<char>(substr.data(),(unsigned int)std::strlen(substr),1,1,1,true).
4576           append_string_to(substituted_items,ptr_sub);
4577         nsource+=2;
4578 
4579         // Substitute '$$command' and '$${command}' -> Source of custom command.
4580       } else if (nsource[1]=='$' &&
4581                  (((is_braces && cimg_sscanf(inbraces,"%255[a-zA-Z0-9_]",
4582                                              substr.assign(256).data())==1) &&
4583                    !inbraces[std::strlen(substr)]) ||
4584                   (cimg_sscanf(nsource + 2,"%255[a-zA-Z0-9_]",substr.assign(256).data())==1)) &&
4585                  (*substr<'0' || *substr>'9')) {
4586         const CImg<char>& name = is_braces?inbraces:substr;
4587         const unsigned int
4588           hash = hashcode(name,false),
4589           l_name = is_braces?l_inbraces + 4:(unsigned int)std::strlen(name) + 2;
4590         unsigned int uind = 0;
4591         if (search_sorted(name,commands_names[hash],commands_names[hash].size(),uind)) {
4592           CImgList<char> sc = CImg<char>::string(commands[hash][uind],false,true).get_split(CImg<char>::vector(' '));
4593           cimglist_for(sc,l) if (sc(l,0)==1) sc.remove(l--); // Discard debug info
4594           (sc>'y').autocrop(' ').unroll('x').move_to(inbraces);
4595           inbraces.append_string_to(substituted_items,ptr_sub);
4596         }
4597         nsource+=l_name;
4598 
4599         // Substitute '$name' and '${name}' -> Variable, image index or environment variable.
4600       } else if ((((is_braces && cimg_sscanf(inbraces,"%255[a-zA-Z0-9_]",
4601                                              substr.assign(256).data())==1) &&
4602                    !inbraces[std::strlen(substr)]) ||
4603                   (cimg_sscanf(nsource + 1,"%255[a-zA-Z0-9_]",substr.assign(256).data())==1)) &&
4604                  (*substr<'0' || *substr>'9')) {
4605         const CImg<char>& name = is_braces?inbraces:substr;
4606         const unsigned int
4607           hash = hashcode(name,true),
4608           l_name = is_braces?l_inbraces + 3:(unsigned int)std::strlen(name) + 1;
4609         const bool
4610           is_global = *name=='_',
4611           is_thread_global = is_global && name[1]=='_';
4612         const int lind = is_global?0:(int)variables_sizes[hash];
4613         if (is_thread_global) cimg::mutex(30);
4614         const CImgList<char>
4615           &__variables = *variables[hash],
4616           &__variables_names = *variables_names[hash];
4617         bool is_name_found = false;
4618         for (int l = __variables.width() - 1; l>=lind; --l)
4619           if (!std::strcmp(__variables_names[l],name)) {
4620             is_name_found = true; ind = l; break;
4621           }
4622         if (is_name_found) { // Regular variable
4623           if (__variables[ind].size()>1) {
4624             if (*(__variables[ind])==gmic_store && !std::strncmp(__variables[ind].data() + 1,"*store/",7)
4625                 && __variables(ind,8)) {
4626               const char *const zero = (char*)std::memchr(__variables[ind].data() + 8,0,__variables[ind].width());
4627               CImg<char>(__variables[ind].data(),zero - __variables[ind].data(),1,1,1,true).
4628                 append_string_to(substituted_items,ptr_sub);
4629             } else
4630               CImg<char>(__variables[ind].data(),(unsigned int)(__variables[ind].size() - 1),1,1,1,true).
4631                 append_string_to(substituted_items,ptr_sub);
4632           }
4633         } else {
4634           for (int l = images.width() - 1; l>=0; --l)
4635             if (images_names[l] && !std::strcmp(images_names[l],name)) {
4636               is_name_found = true; ind = l; break;
4637             }
4638           if (is_name_found) { // Latest image index
4639             cimg_snprintf(substr,substr.width(),"%d",ind);
4640             CImg<char>(substr.data(),(unsigned int)std::strlen(substr),1,1,1,true).
4641               append_string_to(substituted_items,ptr_sub);
4642           } else { // Environment variable
4643             const char *const s_env = std::getenv(name);
4644             if (s_env) CImg<char>(s_env,(unsigned int)std::strlen(s_env),1,1,1,true).
4645                          append_string_to(substituted_items,ptr_sub);
4646           }
4647         }
4648         if (is_thread_global) cimg::mutex(30,0);
4649         nsource+=l_name;
4650 
4651         // Substitute '${"command"}' -> Status value after command execution.
4652       } else if (is_braces) {
4653         nsource+=l_inbraces + 3;
4654         if (l_inbraces>0) {
4655           const CImgList<char>
4656             ncommands_line = commands_line_to_CImgList(strreplace_fw(inbraces));
4657           unsigned int nposition = 0;
4658           CImg<char>::string("*substitute").move_to(callstack);
4659           CImg<unsigned int> nvariables_sizes(gmic_varslots);
4660           cimg_forX(nvariables_sizes,l) nvariables_sizes[l] = variables[l]->size();
4661           _run(ncommands_line,nposition,images,images_names,parent_images,parent_images_names,
4662                nvariables_sizes,0,inbraces,command_selection);
4663           for (unsigned int l = 0; l<nvariables_sizes._width - 2; ++l) if (variables[l]->size()>nvariables_sizes[l]) {
4664               variables_names[l]->remove(nvariables_sizes[l],variables[l]->size() - 1);
4665               variables[l]->remove(nvariables_sizes[l],variables[l]->size() - 1);
4666             }
4667           callstack.remove();
4668           is_return = false;
4669         }
4670         if (status.width()>1)
4671           CImg<char>(status.data(),(unsigned int)std::strlen(status),1,1,1,true).
4672             append_string_to(substituted_items,ptr_sub);
4673 
4674         // Replace '$' by itself.
4675       } else CImg<char>::append_string_to(*(nsource++),substituted_items,ptr_sub);
4676     }
4677   *ptr_sub = 0;
4678   return CImg<char>(substituted_items.data(),(unsigned int)(ptr_sub - substituted_items.data() + 1));
4679 }
4680 
4681 // Main parsing procedures.
4682 //-------------------------
4683 template<typename T>
run(const char * const commands_line,float * const p_progress,bool * const p_is_abort,const T & pixel_type)4684 gmic& gmic::run(const char *const commands_line,
4685                 float *const p_progress, bool *const p_is_abort,
4686                 const T& pixel_type) {
4687   cimg::unused(pixel_type);
4688   gmic_list<T> images;
4689   gmic_list<char> images_names;
4690   return run(commands_line,images,images_names,
4691              p_progress,p_is_abort);
4692 }
4693 
4694 template<typename T>
run(const char * const commands_line,gmic_list<T> & images,gmic_list<char> & images_names,float * const p_progress,bool * const p_is_abort)4695 gmic& gmic::run(const char *const commands_line,
4696                 gmic_list<T> &images, gmic_list<char> &images_names,
4697                 float *const p_progress, bool *const p_is_abort) {
4698   cimg::mutex(26);
4699   if (is_running)
4700     error(true,images,0,0,
4701           "An instance of G'MIC interpreter %p is already running.",
4702           (void*)this);
4703   is_running = true;
4704   cimg::mutex(26,0);
4705   starting_commands_line = commands_line;
4706   is_debug = false;
4707   _run(commands_line_to_CImgList(commands_line),
4708        images,images_names,p_progress,p_is_abort);
4709   is_running = false;
4710   return *this;
4711 }
4712 
4713 template<typename T>
_run(const gmic_list<char> & commands_line,gmic_list<T> & images,gmic_list<char> & images_names,float * const p_progress,bool * const p_is_abort)4714 gmic& gmic::_run(const gmic_list<char>& commands_line,
4715                  gmic_list<T> &images, gmic_list<char> &images_names,
4716                  float *const p_progress, bool *const p_is_abort) {
4717   CImg<unsigned int> variables_sizes(gmic_varslots,1,1,1,0);
4718   unsigned int position = 0;
4719   setlocale(LC_NUMERIC,"C");
4720   callstack.assign(1U);
4721   callstack._data[0].assign(2,1,1,1);
4722   callstack._data[0]._data[0] = '.';
4723   callstack._data[0]._data[1] = 0;
4724   dowhiles.assign(nb_dowhiles = 0U);
4725   fordones.assign(nb_fordones = 0U);
4726   repeatdones.assign(nb_repeatdones = 0U);
4727   status.assign(0U);
4728   nb_carriages = 0;
4729   debug_filename = ~0U;
4730   debug_line = ~0U;
4731   is_change = false;
4732   is_debug_info = false;
4733   is_debug = false;
4734   is_start = true;
4735   is_quit = false;
4736   is_return = false;
4737   if (p_progress) progress = p_progress; else { _progress = -1; progress = &_progress; }
4738   if (p_is_abort) is_abort = p_is_abort; else { _is_abort = false; is_abort = &_is_abort; }
4739   is_abort_thread = false;
4740   abort_ptr(is_abort);
4741   *progress = -1;
4742   cimglist_for(commands_line,l) {
4743     const char *it = commands_line[l].data();
4744     it+=*it=='-';
4745     if (!std::strcmp("debug",it)) { is_debug = true; break; }
4746   }
4747   return _run(commands_line,position,images,images_names,images,images_names,variables_sizes,0,0,0);
4748 }
4749 
4750 #if defined(_MSC_VER) && !defined(_WIN64)
4751 #pragma optimize("y", off)
4752 #endif // #if defined(_MSC_VER) && !defined(_WIN64)
4753 
4754 template<typename T>
_run(const CImgList<char> & commands_line,unsigned int & position,CImgList<T> & images,CImgList<char> & images_names,CImgList<T> & parent_images,CImgList<char> & parent_images_names,const unsigned int * const variables_sizes,bool * const is_noarg,const char * const parent_arguments,const CImg<unsigned int> * const command_selection)4755 gmic& gmic::_run(const CImgList<char>& commands_line, unsigned int& position,
4756                  CImgList<T>& images, CImgList<char>& images_names,
4757                  CImgList<T>& parent_images, CImgList<char>& parent_images_names,
4758                  const unsigned int *const variables_sizes,
4759                  bool *const is_noarg, const char *const parent_arguments,
4760                  const CImg<unsigned int> *const command_selection) {
4761   if (*callstack.back()!='*' && (!commands_line || position>=commands_line._width)) {
4762     if (is_debug) debug(images,"Return from empty command '%s/'.",
4763                         callstack.back().data());
4764     return *this;
4765   }
4766 
4767   // Add current run to managed list of gmic runs.
4768   cimg::mutex(24);
4769   CImgList<void*> &grl = gmic_runs();
4770   CImg<void*> gr(7);
4771   gr[0] = (void*)this;
4772   gr[1] = (void*)&images;
4773   gr[2] = (void*)&images_names;
4774   gr[3] = (void*)&parent_images;
4775   gr[4] = (void*)&parent_images_names;
4776   gr[5] = (void*)variables_sizes;
4777   gr[6] = (void*)command_selection;
4778   gr.move_to(grl);
4779   cimg::mutex(24,0);
4780 
4781   typedef typename cimg::superset<T,float>::type Tfloat;
4782   typedef typename cimg::superset<T,cimg_long>::type Tlong;
4783   typedef typename cimg::last<T,cimg_long>::type longT;
4784   typedef typename cimg::last<T,cimg_uint64>::type uint64T;
4785   typedef typename cimg::last<T,cimg_int64>::type int64T;
4786   const unsigned int initial_callstack_size = callstack.size(), initial_debug_line = debug_line;
4787 
4788   CImgList<_gmic_parallel<T> > gmic_threads;
4789   CImgList<unsigned int> primitives;
4790   CImgList<unsigned char> g_list_uc;
4791   CImgList<float> g_list_f;
4792   CImgList<char> g_list_c;
4793   CImgList<T> g_list;
4794 
4795   CImg<unsigned int> ind, ind0, ind1;
4796   CImg<unsigned char> g_img_uc;
4797   CImg<char> name, o_status;
4798   CImg<float> vertices;
4799   CImg<T> g_img;
4800 
4801   unsigned int next_debug_line = ~0U, next_debug_filename = ~0U, _debug_line, _debug_filename,
4802     is_high_connectivity, __ind = 0, boundary = 0, pattern = 0, exit_on_anykey = 0, wind = 0,
4803     interpolation = 0, hash = 0;
4804   char end, sep = 0, sep0 = 0, sep1 = 0, sepx = 0, sepy = 0, sepz = 0, sepc = 0, axis = 0;
4805   double vmin = 0, vmax = 0, value, value0, value1, nvalue, nvalue0, nvalue1;
4806   bool is_cond, is_endlocal = false, check_elif = false, run_entrypoint = false;
4807   float opacity = 0;
4808   int err;
4809 
4810   // Allocate string variables, widely used afterwards
4811   // (prevents stack overflow on recursive calls while remaining thread-safe).
4812   CImg<char> _formula(4096), _color(4096), message(1024), _title(256), _indices(256),
4813     _argx(256), _argy(256), _argz(256), _argc(256), argument_text(81),
4814     _command(256), _s_selection(256);
4815 
4816   char
4817     *const formula = _formula.data(),
4818     *const color = _color.data(),
4819     *const title = _title.data(),
4820     *const indices = _indices.data(),
4821     *const argx = _argx.data(),
4822     *const argy = _argy.data(),
4823     *const argz = _argz.data(),
4824     *const argc = _argc.data(),
4825     *const command = _command.data(),
4826     *s_selection = _s_selection.data();
4827   *formula = *color = *title = *indices = *argx = *argy = *argz = *argc =
4828     *command = *s_selection = 0;
4829 
4830   try {
4831 
4832     // Init interpreter environment.
4833     if (images.size()<images_names.size())
4834       images_names.remove(images.size(),images_names.size() - 1);
4835     else if (images.size()>images_names.size())
4836       images_names.insert(images.size() - images_names.size(),CImg<char>::string("[unnamed]"));
4837 
4838     if (is_debug) {
4839       if (is_start) {
4840         print(images,0,"Start G'MIC interpreter (in debug mode).");
4841         debug(images,"Initial command line: '%s'.",starting_commands_line);
4842         commands_line_to_CImgList(starting_commands_line); // Do it twice, when debug enabled
4843       }
4844       nb_carriages = 2;
4845       debug(images,"%sEnter scope '%s/'.%s",
4846             cimg::t_bold,callstack.back().data(),cimg::t_normal);
4847       is_start = false;
4848     }
4849 
4850     // Begin command line parsing.
4851     const int starting_verbosity = verbosity;
4852     if (!commands_line && is_start) { print(images,0,"Start G'MIC interpreter."); is_start = false; }
4853     while (position<commands_line.size() && !is_quit && !is_return) {
4854       const bool is_first_item = !position;
4855       *command = *s_selection = 0;
4856 
4857       // Process debug info.
4858       if (next_debug_line!=~0U) { debug_line = next_debug_line; next_debug_line = ~0U; }
4859       if (next_debug_filename!=~0U) { debug_filename = next_debug_filename; next_debug_filename = ~0U; }
4860       while (position<commands_line.size() && *commands_line[position]==1) {
4861         if (cimg_sscanf(commands_line[position].data() + 1,"%x,%x",&_debug_line,&(_debug_filename=0))>0) {
4862           is_debug_info = true; debug_line = _debug_line; debug_filename = _debug_filename;
4863         }
4864         ++position;
4865       }
4866       if (position>=commands_line.size()) break;
4867 
4868       // Check consistency of the interpreter environment.
4869       if (images_names.size()!=images.size())
4870         error(true,"G'MIC encountered a fatal error (images (%u) and images names (%u) have different size). "
4871                "Please submit a bug report, at: https://github.com/dtschump/gmic/issues",
4872               images_names.size(),images.size());
4873       if (!callstack)
4874         error(true,"G'MIC encountered a fatal error (empty call stack). "
4875               "Please submit a bug report, at: https://github.com/dtschump/gmic/issues");
4876       if (callstack.size()>=64)
4877         error(true,"Call stack overflow (infinite recursion?).");
4878 
4879       // Substitute expressions in current item.
4880       const char
4881         *const initial_item = run_entrypoint?"_main_":commands_line[position].data(),
4882         *const empty_argument = "",
4883         *initial_argument = empty_argument;
4884 
4885       unsigned int position_argument = position + 1;
4886       while (position_argument<commands_line.size() && *(commands_line[position_argument])==1) {
4887         if (cimg_sscanf(commands_line[position_argument].data() + 1,"%x,%x",&_debug_line,&(_debug_filename=0))>0) {
4888           is_debug_info = true; next_debug_line = _debug_line; next_debug_filename = _debug_filename;
4889         }
4890         ++position_argument;
4891       }
4892       if (position_argument<commands_line.size()) initial_argument = commands_line[position_argument];
4893 
4894       CImg<char> _item, _argument;
4895       substitute_item(initial_item,images,images_names,parent_images,parent_images_names,
4896                       variables_sizes,command_selection,false).move_to(_item);
4897       char *item = _item;
4898       const char *argument = initial_argument;
4899 
4900       // Check if current item is a known command.
4901 #define _gmic_eok(i) (!item[i] || item[i]=='[' || (item[i]=='.' && (!item[i + 1] || item[i + 1]=='.')))
4902 
4903       const bool
4904         is_simple_hyphen = *item=='-' && item[1] &&
4905         item[1]!='[' && item[1]!='.' && (item[1]!='3' || item[2]!='d'),
4906         is_plus = *item=='+' && item[1] &&
4907         item[1]!='[' && item[1]!='.' && (item[1]!='3' || item[2]!='d'),
4908         is_double_hyphen = *item=='-' && item[1]=='-' &&
4909         item[2] && item[2]!='[' && item[2]!='.' && (item[2]!='3' || item[3]!='d');
4910       item+=is_double_hyphen?2:is_simple_hyphen || is_plus?1:0;
4911       const bool is_get = is_double_hyphen || is_plus;
4912 
4913       unsigned int hash_custom = ~0U, ind_custom = ~0U;
4914       bool is_command = *item>='a' && *item<='z' && _gmic_eok(1); // Alphabetical shortcut commands
4915       is_command|= *item=='m' && (item[1]=='*' || item[1]=='/') && _gmic_eok(2); // Shortcuts 'm*' and 'm/'
4916       is_command|= *item=='f' && item[1]=='i' && _gmic_eok(2); // Shortcuts 'fi'
4917       if (!is_command) {
4918         *command = sep0 = sep1 = 0;
4919         switch (*item) {
4920         case '!' : is_command = item[1]=='=' && _gmic_eok(2); break;
4921         case '%' : case '&' : case '^' : case '|' :
4922           is_command = _gmic_eok(1); break;
4923         case '*' : case '+' : case '-' : case '/' :
4924           is_command = _gmic_eok(1) || (item[1]=='3' && item[2]=='d' && _gmic_eok(3)); break;
4925         case '<' : case '=' : case '>' :
4926           is_command = _gmic_eok(1) || ((item[1]==*item || item[1]=='=') && _gmic_eok(2)); break;
4927         default :
4928 
4929           // Extract command name.
4930           // (same as but faster than 'err = cimg_sscanf(item,"%255[a-zA-Z_0-9]%c%c",command,&sep0,&sep1);').
4931           const char *ps = item;
4932           char *pd = command;
4933           char *const pde = _command.end() - 1;
4934           for (err = 0; *ps && pd<pde; ++ps) {
4935             const char c = *ps;
4936             if ((c>='a' && c<='z') || (c>='A' && c<='Z') || (c>='0' && c<='9') || c=='_') *(pd++) = c;
4937             else break;
4938           }
4939           if (pd!=command) {
4940             *pd = 0;
4941             ++err;
4942             if (*ps) {
4943               sep0 = *(ps++);
4944               ++err;
4945               if (*ps) { sep1 = *(ps++); ++err; }
4946             }
4947           }
4948 
4949           is_command = err==1 || (err==2 && sep0=='.') || (err==3 && (sep0=='[' || (sep0=='.' && sep1=='.')));
4950           is_command&=*item<'0' || *item>'9';
4951           if (is_command) { // Look for a builtin command
4952             const int
4953               _ind0 = builtin_commands_inds[(unsigned int)*command],
4954               _ind1 = builtin_commands_inds((unsigned int)*command,1);
4955             if (_ind0>=0) is_command = search_sorted(command,builtin_commands_names + _ind0,_ind1 - _ind0 + 1U,__ind);
4956             if (!is_command) { // Look for a custom command
4957               hash_custom = hashcode(command,false);
4958               is_command = search_sorted(command,commands_names[hash_custom],
4959                                          commands_names[hash_custom].size(),ind_custom);
4960             }
4961           }
4962         }
4963       }
4964 
4965       // Split command/selection, if necessary.
4966       bool is_selection = false;
4967       const unsigned int siz = images._width, selsiz = _s_selection._width;
4968       CImg<unsigned int> selection;
4969       if (is_command) {
4970         sep0 = sep1 = 0;
4971         strreplace_fw(item);
4972 
4973         // Extract selection.
4974         // (same as but faster than 'err = cimg_sscanf(item,"%255[^[]%c%255[a-zA-Z_0-9.eE%^,:+-]%c%c",
4975         //                                             command,&sep0,s_selection,&sep1,&end);
4976         if (selsiz<_item._width) { // Expand size for getting a possibly large selection
4977           _s_selection.assign(_item.width());
4978           s_selection = _s_selection.data();
4979           *s_selection = 0;
4980         }
4981 
4982         const char *ps = item;
4983         char *pd = command;
4984         char *const pde = _command.end() - 1;
4985         for (err = 0; *ps && *ps!='[' && pd<pde; ++ps) *(pd++) = *ps;
4986         if (pd!=command) {
4987           *pd = 0;
4988           ++err;
4989           if (*ps) {
4990             sep0 = *(ps++);
4991             ++err;
4992             if (*ps) {
4993               const char *pde2 = _s_selection.end() - 1;
4994               for (pd = s_selection; *ps && pd<pde2; ++ps) {
4995                 const char c = *ps;
4996                 if ((c>='a' && c<='z') || (c>='A' && c<='Z') || (c>='0' && c<='9') ||
4997                     c=='_' || c=='.' || c=='e' || c=='E' || c=='%' || c=='^' || c==','
4998                     || c==':' || c=='+' || c=='-') *(pd++) = c;
4999                 else break;
5000               }
5001               if (pd!=s_selection) {
5002                 *pd = 0;
5003                 ++err;
5004                 if (*ps) {
5005                   sep1 = *(ps++);
5006                   ++err;
5007                   if (*ps) ++err;
5008                 }
5009               }
5010             }
5011           }
5012         }
5013 
5014         const unsigned int l_command = err==1?(unsigned int)std::strlen(command):0;
5015         if (err==1 && l_command>=2 && command[l_command - 1]=='.') { // Selection shortcut
5016           err = 4; sep0 = '['; sep1 = ']';
5017           if (command[l_command - 2]!='.') {
5018             *s_selection = '-'; s_selection[1] = '1'; s_selection[2] = 0; command[l_command - 1] = 0;
5019           }
5020           else if (l_command>=3 && command[l_command - 3]!='.') {
5021             *s_selection = '-'; s_selection[1] = '2'; s_selection[2] = 0; command[l_command - 2] = 0;
5022           }
5023           else if (l_command>=4 && command[l_command - 4]!='.') {
5024             *s_selection = '-'; s_selection[1] = '3'; s_selection[2] = 0; command[l_command - 3] = 0;
5025           }
5026           else { is_command = false; ind_custom = ~0U; *s_selection = 0; }
5027         }
5028 
5029         if (err==1) { // No selection -> all images
5030           selection.assign(1,siz);
5031           cimg_forY(selection,y) selection[y] = (unsigned int)y;
5032         } else if (err==2 && sep0=='[' && item[std::strlen(command) + 1]==']') { // Empty selection
5033           selection.assign(); is_selection = true;
5034         } else if (err==4 && sep1==']') { // Other selections
5035           is_selection = true;
5036           if (!is_get && (!std::strcmp("wait",command) ||
5037                           !std::strcmp("cursor",command)))
5038             selection2cimg(s_selection,10,CImgList<char>::empty(),command).move_to(selection);
5039           else if (!is_get && *command=='i' && (!command[1] || !std::strcmp("input",command)))
5040             selection2cimg(s_selection,siz + 1,images_names,command,true).move_to(selection);
5041           else if (!is_get &&
5042                    ((*command=='e' && (!command[1] ||
5043                                        !std::strcmp("echo",command) ||
5044                                        !std::strcmp("error",command))) ||
5045                     !std::strcmp("warn",command)))
5046             selection2cimg(s_selection,callstack.size(),CImgList<char>::empty(),command).move_to(selection);
5047           else if (!is_get && !std::strcmp("pass",command))
5048             selection2cimg(s_selection,parent_images.size(),parent_images_names,command).move_to(selection);
5049           else
5050             selection2cimg(s_selection,siz,images_names,command).move_to(selection);
5051         } else {
5052           std::strncpy(command,item,_command.width() - 1);
5053           is_command = false; ind_custom = ~0U;
5054           command[_command.width() - 1] = *s_selection = 0;
5055         }
5056       } else {
5057         std::strncpy(command,item,_command.width() - 1);
5058         command[_command.width() - 1] = *s_selection = 0;
5059       }
5060       position = position_argument;
5061       if (_s_selection._width!=selsiz) { // Go back to initial size for selection image.
5062         _s_selection.assign(selsiz);
5063         s_selection = _s_selection.data();
5064         *s_selection = 0;
5065       }
5066 
5067       const bool
5068         is_command_verbose = is_get?false:
5069           is_command && *item=='v' && (!item[1] || !std::strcmp(item,"verbose")),
5070         is_command_echo = is_get || is_command_verbose?false:
5071           is_command && *command=='e' && (!command[1] || !std::strcmp(command,"echo")),
5072         is_command_error = is_get || is_command_verbose || is_command_echo?false:
5073           is_command && *command=='e' && !std::strcmp(command,"error"),
5074         is_command_warn = is_get || is_command_verbose || is_command_echo || is_command_error?false:
5075           is_command && *command=='w' && !std::strcmp(command,"warn"),
5076         is_command_input = is_get || is_command_verbose || is_command_echo || is_command_error ||
5077           is_command_warn?false:
5078           is_command && *command=='i' && (!command[1] || !std::strcmp(command,"input")),
5079         is_command_check = is_get || is_command_verbose || is_command_echo || is_command_error ||
5080           is_command_warn || is_command_input?false:
5081           is_command && *command=='c' && !std::strcmp(item,"check"),
5082         is_command_skip = is_get || is_command_verbose || is_command_echo || is_command_error ||
5083           is_command_warn || is_command_input || is_command_check?false:
5084           is_command && *command=='s' && !std::strcmp(item,"skip");
5085 
5086       // Check for verbosity command, prior to the first output of a log message.
5087       bool is_verbose = verbosity>=1 || is_debug, is_verbose_argument = false;
5088       const int old_verbosity = verbosity;
5089       if (is_command_verbose) {
5090         // Do a first fast check.
5091         if (*argument=='-' && !argument[1]) { --verbosity; is_verbose_argument = true; }
5092         else if (*argument=='+' && !argument[1]) { ++verbosity; is_verbose_argument = true; }
5093         else {
5094           gmic_substitute_args(false);
5095           if (*argument=='-' && !argument[1]) { --verbosity; is_verbose_argument = true; }
5096           else if (*argument=='+' && !argument[1]) { ++verbosity; is_verbose_argument = true; }
5097           else {
5098             float level = 0;
5099             if (cimg_sscanf(argument,"%f%c",&level,&end)==1) {
5100               verbosity = (int)cimg::round(level);
5101               is_verbose_argument = true;
5102             }
5103             else arg_error("verbose");
5104           }
5105         }
5106       }
5107       is_verbose = verbosity>=1 || is_debug;
5108       const bool is_very_verbose = verbosity>1 || is_debug;
5109 
5110       // Generate strings for displaying image selections when verbosity>=1.
5111       CImg<char> gmic_selection;
5112       if (is_debug ||
5113           (verbosity>=1 && !is_command_check && !is_command_skip &&
5114            !is_command_verbose && !is_command_echo && !is_command_error && !is_command_warn))
5115         selection2string(selection,images_names,1,gmic_selection);
5116 
5117       if (is_debug) {
5118         if (std::strcmp(item,initial_item))
5119           debug(images,"Item '%s' -> '%s', selection%s.",
5120                 initial_item,item,gmic_selection.data());
5121         else
5122           debug(images,"Item '%s', selection%s.",
5123                 initial_item,gmic_selection.data());
5124       }
5125 
5126       // Display starting message.
5127       if (is_start) {
5128         print(images,0,"Start G'MIC interpreter.");
5129         is_start = false;
5130       }
5131 
5132       // Cancellation point.
5133       if (*is_abort || is_abort_thread)
5134         throw CImgAbortException();
5135 
5136       // Begin command interpretation.
5137       if (is_command) {
5138 
5139         // Convert command shortcuts to full names.
5140         char command0 = *command;
5141         const char
5142           command1 = command0?command[1]:0, command2 = command1?command[2]:0,
5143           command3 = command2?command[3]:0, command4 = command3?command[4]:0;
5144 
5145         static const char* onechar_shortcuts[] = {
5146           0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 0-31
5147           0,0,0,0,0,"mod","and",0,0,0,"mul","add",0,"sub",0,"div",0,0,0,0,0,0,0,0,0,0,0,0, // 32-59
5148           "lt","set","gt",0, // 60-63
5149           0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,"pow",0, // 64-95
5150           0,"append","blur","cut","display","echo","fill","gradient",0,"input","image","keep", // 96-107
5151           "local","command","normalize","output","print","quit","resize","split","text","status", // 108-117
5152           "verbose","window","exec","unroll","crop",0,"or",0,0,0 // 118-127
5153         };
5154 
5155         if (!command1) { // Single-char shortcut
5156           const bool
5157             is_mquvx = command0=='m' || command0=='q' || command0=='u' || command0=='v' || command0=='x',
5158             is_deiopwx = command0=='d' || command0=='e' || command0=='i' || command0=='o' || command0=='p' ||
5159                          command0=='w' || command0=='x';
5160           if ((unsigned int)command0<128 && onechar_shortcuts[(unsigned int)command0] &&
5161               (!is_mquvx || (!is_get && !is_selection)) &&
5162               (!is_deiopwx || !is_get)) {
5163             std::strcpy(command,onechar_shortcuts[(unsigned int)command0]);
5164             if (is_mquvx) { CImg<char>::string(command).move_to(_item); *command = 0; }
5165             else *item = 0;
5166           }
5167 
5168         } else if (!command2) { // Two-chars shortcuts
5169           if (command0=='s' && command1=='h' && !is_get) std::strcpy(command,"shared");
5170           else if (command0=='m' && command1=='v') std::strcpy(command,"move");
5171           else if (command0=='n' && command1=='m' && !is_get) std::strcpy(command,"name");
5172           else if (command0=='r' && command1=='m') std::strcpy(command,"remove");
5173           else if (command0=='r' && command1=='v') std::strcpy(command,"reverse");
5174           else if (command0=='<' && command1=='<') std::strcpy(command,"bsl");
5175           else if (command0=='>' && command1=='>') std::strcpy(command,"bsr");
5176           else if (command0=='=' && command1=='=') std::strcpy(command,"eq");
5177           else if (command0=='>' && command1=='=') std::strcpy(command,"ge");
5178           else if (command0=='<' && command1=='=') std::strcpy(command,"le");
5179           else if (command0=='m' && command1=='/') std::strcpy(command,"mdiv");
5180           else if (command0=='m' && command1=='*') std::strcpy(command,"mmul");
5181           else if (command0=='!' && command1=='=') std::strcpy(command,"neq");
5182 
5183         } else if (!command3 && command1=='3' && command2=='d') switch (command0) {
5184             // Three-chars shortcuts (ending with '3d').
5185           case 'd' : if (!is_get) std::strcpy(command,"display3d"); break;
5186           case 'j' : std::strcpy(command,"object3d"); break;
5187           case '+' : std::strcpy(command,"add3d"); break;
5188           case '/' : std::strcpy(command,"div3d"); break;
5189           case 'f' : if (!is_get && !is_selection)
5190               CImg<char>::string("focale3d").move_to(_item);
5191             break;
5192           case 'l' : if (!is_get && !is_selection)
5193               CImg<char>::string("light3d").move_to(_item);
5194             break;
5195           case 'm' : if (!is_get && !is_selection)
5196               CImg<char>::string("mode3d").move_to(_item);
5197             break;
5198           case '*' : std::strcpy(command,"mul3d"); break;
5199           case 'o' : std::strcpy(command,"opacity3d"); break;
5200           case 'r' : std::strcpy(command,"rotate3d"); break;
5201           case 's' : std::strcpy(command,"split3d"); break;
5202           case '-' : std::strcpy(command,"sub3d"); break;
5203           } else if (!is_get && !command3 && command0=='n' && command1=='m' && command2=='d') {
5204           std::strcpy(command,"named"); // Shortcut 'nmd' for 'named".
5205         } else if (!command4 && command2=='3' && command3=='d') {
5206           // Four-chars shortcuts (ending with '3d').
5207           if (command0=='d' && command1=='b') {
5208             if (!is_get && !is_selection) CImg<char>::string("double3d").move_to(_item);
5209           } else if (command0=='m' && command1=='d') {
5210             if (!is_get && !is_selection) CImg<char>::string("moded3d").move_to(_item);
5211           }
5212           else if (command0=='r' && command1=='v') std::strcpy(command,"reverse3d");
5213           else if (command0=='s' && command1=='l') {
5214             if (!is_get && !is_selection) CImg<char>::string("specl3d").move_to(_item);
5215           }
5216           else if (command0=='s' && command1=='s') {
5217             if (!is_get && !is_selection) CImg<char>::string("specs3d").move_to(_item);
5218           }
5219         }
5220         if (item!=_item.data() + (is_double_hyphen?2:is_simple_hyphen || is_plus?1:0)) item = _item;
5221         command0 = *command?*command:*item;
5222 
5223         // Dispatch to dedicated parsing code, regarding the first character of the command.
5224         // We rely on the compiler to optimize this using an associative array (verified with g++).
5225         switch (command0) {
5226         case 'a' : goto gmic_commands_a;
5227         case 'b' : goto gmic_commands_b;
5228         case 'c' : goto gmic_commands_c;
5229         case 'd' : goto gmic_commands_d;
5230         case 'e' : goto gmic_commands_e;
5231         case 'f' :
5232           if (command[1]=='i' && !command[2]) goto gmic_commands_e; // (Skip for 'fi')
5233           goto gmic_commands_f;
5234         case 'g' : goto gmic_commands_g;
5235         case 'h' : goto gmic_commands_h;
5236         case 'i' :
5237           if (command[1]=='f' && !command[2]) goto gmic_commands_others; // (Skip for 'if')
5238           goto gmic_commands_i;
5239         case 'k' : goto gmic_commands_k;
5240         case 'l' : goto gmic_commands_l;
5241         case 'm' : goto gmic_commands_m;
5242         case 'n' : goto gmic_commands_n;
5243         case 'o' : goto gmic_commands_o;
5244         case 'p' : goto gmic_commands_p;
5245         case 'q' : goto gmic_commands_q;
5246         case 'r' : goto gmic_commands_r;
5247         case 's' : goto gmic_commands_s;
5248         case 't' : goto gmic_commands_t;
5249         case 'u' : goto gmic_commands_u;
5250         case 'v' : goto gmic_commands_v;
5251         case 'w' : goto gmic_commands_w;
5252         case 'x' : goto gmic_commands_x;
5253         default : goto gmic_commands_others;
5254         }
5255 
5256         //-----------------------------
5257         // Commands starting by 'a...'
5258         //-----------------------------
5259       gmic_commands_a :
5260 
5261         // Append.
5262         if (!std::strcmp("append",command)) {
5263           gmic_substitute_args(true);
5264           float align = 0;
5265           axis = sep = 0;
5266           if ((cimg_sscanf(argument,"%c%c",
5267                            &axis,&end)==1 ||
5268                cimg_sscanf(argument,"%c,%f%c",
5269                            &axis,&align,&end)==2) &&
5270               (axis=='x' || axis=='y' || axis=='z' || axis=='c')) {
5271             print(images,0,"Append image%s along the '%c'-axis, with alignment %g.",
5272                   gmic_selection.data(),
5273                   axis,align);
5274             if (selection) {
5275               cimg_forY(selection,l) if (gmic_check(images[selection[l]]))
5276                 g_list.insert(gmic_check(images[selection[l]]),~0U,true);
5277               CImg<T> img = g_list.get_append(axis,align);
5278               name = images_names[selection[0]];
5279               if (is_get) {
5280                 img.move_to(images);
5281                 images_names.insert(name.copymark());
5282               } else if (selection.height()>=2) {
5283                 remove_images(images,images_names,selection,1,selection.height() - 1);
5284                 img.move_to(images[selection[0]].assign());
5285                 name.move_to(images_names[selection[0]]);
5286               }
5287               g_list.assign();
5288             }
5289           } else if ((cimg_sscanf(argument,"[%255[a-zA-Z0-9_.%+-]%c,%c%c",
5290                                   indices,&sep,&axis,&end)==3 ||
5291                       cimg_sscanf(argument,"[%255[a-zA-Z0-9_.%+-]%c,%c,%f%c",
5292                                   indices,&sep,&axis,&(align=0),&end)==4) &&
5293                      (axis=='x' || axis=='y' || axis=='z' || axis=='c') &&
5294                      sep==']' &&
5295                      (ind=selection2cimg(indices,images.size(),images_names,"append")).height()==1) {
5296             print(images,0,"Append image [%u] to image%s, along the '%c'-axis, with alignment %g.",
5297                   *ind,gmic_selection.data(),axis,align);
5298             const CImg<T> img0 = gmic_image_arg(*ind);
5299             cimg_forY(selection,l) gmic_apply(append(img0,axis,align));
5300           } else arg_error("append");
5301           is_change = true; ++position; continue;
5302         }
5303 
5304         // Autocrop.
5305         if (!std::strcmp("autocrop",command)) {
5306           gmic_substitute_args(false);
5307           if (*argument && cimg_sscanf(argument,"%4095[0-9.,eEinfa+-]%c",formula,&end)==1)
5308             try { CImg<T>(1).fill(argument,true,false).move_to(g_img); }
5309             catch (CImgException&) { g_img.assign(); }
5310           if (g_img) {
5311             print(images,0,"Auto-crop image%s by vector '%s'.",
5312                   gmic_selection.data(),
5313                   gmic_argument_text_printed());
5314             ++position;
5315           } else print(images,0,"Auto-crop image%s.",
5316                        gmic_selection.data());
5317           cimg_forY(selection,l) {
5318             if (g_img) {
5319               CImg<T>& img = images[selection[l]];
5320               g_img.assign(img.spectrum()).fill(argument,true,false);
5321               gmic_apply(gmic_autocrop(g_img));
5322             } else gmic_apply(gmic_autocrop());
5323           }
5324           g_img.assign();
5325           is_change = true; continue;
5326         }
5327 
5328         // Add.
5329         gmic_arithmetic_command("add",
5330                                 operator+=,
5331                                 "Add %g%s to image%s",
5332                                 value,ssep,gmic_selection.data(),Tfloat,
5333                                 operator+=,
5334                                 "Add image [%d] to image%s",
5335                                 ind[0],gmic_selection.data(),
5336                                 operator_pluseq,
5337                                 "Add expression %s to image%s",
5338                                 gmic_argument_text_printed(),gmic_selection.data(),
5339                                 "Add image%s");
5340 
5341         // Add 3D objects together, or shift a 3D object.
5342         if (!std::strcmp("add3d",command)) {
5343           gmic_substitute_args(true);
5344           float tx = 0, ty = 0, tz = 0;
5345           sep = *indices = 0;
5346           if (cimg_sscanf(argument,"%f%c",
5347                           &tx,&end)==1 ||
5348               cimg_sscanf(argument,"%f,%f%c",
5349                           &tx,&ty,&end)==2 ||
5350               cimg_sscanf(argument,"%f,%f,%f%c",
5351                           &tx,&ty,&tz,&end)==3) {
5352             print(images,0,"Shift 3D object%s by displacement (%g,%g,%g).",
5353                   gmic_selection.data(),
5354                   tx,ty,tz);
5355             cimg_forY(selection,l) {
5356               const unsigned int uind = selection[l];
5357               CImg<T>& img = images[uind];
5358               try { gmic_apply(shift_CImg3d(tx,ty,tz)); }
5359               catch (CImgException&) {
5360                 if (!img.is_CImg3d(true,&(*message=0)))
5361                   error(true,images,0,0,
5362                         "Command 'add3d': Invalid 3D object [%d], in selected image%s (%s).",
5363                         uind,gmic_selection_err.data(),message.data());
5364                 else throw;
5365               }
5366             }
5367             ++position;
5368           } else if (cimg_sscanf(argument,"[%255[a-zA-Z0-9_.%+-]%c%c",indices,&sep,&end)==2 &&
5369                      sep==']' &&
5370                      (ind=selection2cimg(indices,images.size(),images_names,"add3d")).height()==1) {
5371             const CImg<T> img0 = gmic_image_arg(*ind);
5372             print(images,0,"Merge 3D object%s with 3D object [%u].",
5373                   gmic_selection.data(),*ind);
5374             cimg_forY(selection,l) {
5375               const unsigned int _ind = selection[l];
5376               CImg<T>& img = gmic_check(images[_ind]);
5377               g_list.assign(2);
5378               g_list[0].assign(img,true);
5379               g_list[1].assign(img0,true);
5380               CImg<T> res;
5381               try { CImg<T>::append_CImg3d(g_list).move_to(res); }
5382               catch (CImgException&) {
5383                 if (!img0.is_CImg3d(true,&(*message=0)))
5384                   error(true,images,0,0,
5385                         "Command 'add3d': Invalid 3D object [%u], in specified "
5386                         "argument '%s' (%s).",
5387                         *ind,gmic_argument_text(),message.data());
5388                 else if (!img.is_CImg3d(true,message))
5389                   error(true,images,0,0,
5390                         "Command 'add3d': Invalid 3D object [%d], in selected image%s (%s).",
5391                         _ind,gmic_selection_err.data(),message.data());
5392                 else throw;
5393               }
5394               if (is_get) {
5395                 res.move_to(images);
5396                 images_names[_ind].get_copymark().move_to(images_names);
5397               } else res.move_to(images[_ind].assign());
5398             }
5399             g_list.assign();
5400             ++position;
5401           } else {
5402             print(images,0,"Merge 3D object%s.",
5403                   gmic_selection.data());
5404             if (selection) {
5405               g_list.assign(selection.height());
5406               cimg_forY(selection,l) g_list[l].assign(gmic_check(images[selection[l]]),true);
5407               CImg<T> img;
5408               try { CImg<T>::append_CImg3d(g_list).move_to(img); }
5409               catch (CImgException&) {
5410                 cimg_forY(selection,l) {
5411                   const unsigned int uind = selection[l];
5412                   if (!images[uind].is_CImg3d(true,&(*message=0)))
5413                     error(true,images,0,0,
5414                           "Command 'add3d': Invalid 3D object [%d], in selected image%s (%s).",
5415                           uind,gmic_selection_err.data(),message.data());
5416                 }
5417                 throw;
5418               }
5419               name = images_names[selection[0]];
5420               if (is_get) {
5421                 img.move_to(images);
5422                 images_names.insert(name.copymark());
5423               } else if (selection.height()>=2) {
5424                 remove_images(images,images_names,selection,1,selection.height() - 1);
5425                 img.move_to(images[selection[0]].assign());
5426                 name.move_to(images_names[selection[0]]);
5427               }
5428               g_list.assign();
5429             }
5430           }
5431           is_change = true; continue;
5432         }
5433 
5434         // Absolute value.
5435         gmic_simple_command("abs",abs,"Compute pointwise absolute value of image%s.");
5436 
5437         // Bitwise and.
5438         gmic_arithmetic_command("and",
5439                                 operator&=,
5440                                 "Compute bitwise AND of image%s by %g%s",
5441                                 gmic_selection.data(),value,ssep,Tlong,
5442                                 operator&=,
5443                                 "Compute bitwise AND of image%s by image [%d]",
5444                                 gmic_selection.data(),ind[0],
5445                                 operator_andeq,
5446                                 "Compute bitwise AND of image%s by expression %s",
5447                                 gmic_selection.data(),gmic_argument_text_printed(),
5448                                 "Compute sequential bitwise AND of image%s");
5449 
5450         // Arctangent (two arguments).
5451         if (!std::strcmp("atan2",command)) {
5452           gmic_substitute_args(true);
5453           sep = 0;
5454           if (cimg_sscanf(argument,"[%255[a-zA-Z0-9_.%+-]%c%c",
5455                           indices,&sep,&end)==2 && sep==']' &&
5456               (ind=selection2cimg(indices,images.size(),images_names,"atan2")).height()==1) {
5457             print(images,0,"Compute pointwise oriented arctangent of image%s, "
5458                   "with x-argument [%u].",
5459                   gmic_selection.data(),
5460                   *ind);
5461             const CImg<T> img0 = gmic_image_arg(*ind);
5462             cimg_forY(selection,l) gmic_apply(atan2(img0));
5463           } else arg_error("atan2");
5464           is_change = true; ++position; continue;
5465         }
5466 
5467         // Arccosine.
5468         gmic_simple_command("acos",acos,"Compute pointwise arccosine of image%s.");
5469 
5470         // Arcsine.
5471         gmic_simple_command("asin",asin,"Compute pointwise arcsine of image%s.");
5472 
5473         // Arctangent.
5474         gmic_simple_command("atan",atan,"Compute pointwise arctangent of image%s.");
5475 
5476         // Hyperbolic arccosine.
5477         gmic_simple_command("acosh",acosh,"Compute pointwise hyperbolic arccosine of image%s.");
5478 
5479         // Hyperbolic arcsine.
5480         gmic_simple_command("asinh",asinh,"Compute pointwise hyperbolic arcsine of image%s.");
5481 
5482         // Hyperbolic arctangent.
5483         gmic_simple_command("atanh",atanh,"Compute pointwise hyperbolic arctangent of image%s.");
5484 
5485         goto gmic_commands_others;
5486 
5487         //-----------------------------
5488         // Commands starting by 'b...'
5489         //-----------------------------
5490       gmic_commands_b :
5491 
5492         // Blur.
5493         if (!std::strcmp("blur",command)) {
5494           gmic_substitute_args(false);
5495           unsigned int is_gaussian = 0;
5496           float sigma = -1;
5497           sep = *argx = 0;
5498           boundary = 1;
5499 
5500           const char *p_argument = argument;
5501           if (cimg_sscanf(argument,"%255[xyzc]%c",argx,&sep)==2 && sep==',') {
5502             p_argument+=1 + std::strlen(argx);
5503           } else sep = *argx = 0;
5504 
5505           if ((cimg_sscanf(p_argument,"%f%c",
5506                            &sigma,&end)==1 ||
5507                (cimg_sscanf(p_argument,"%f%c%c",
5508                             &sigma,&sep,&end)==2 && sep=='%') ||
5509                cimg_sscanf(p_argument,"%f,%u%c",
5510                            &sigma,&boundary,&end)==2 ||
5511                (cimg_sscanf(p_argument,"%f%c,%u%c",
5512                             &sigma,&sep,&boundary,&end)==3 && sep=='%') ||
5513                cimg_sscanf(p_argument,"%f,%u,%u%c",
5514                            &sigma,&boundary,&is_gaussian,&end)==3 ||
5515                (cimg_sscanf(p_argument,"%f%c,%u,%u%c",
5516                             &sigma,&sep,&boundary,&is_gaussian,&end)==4 && sep=='%')) &&
5517               sigma>=0 && boundary<=1 && is_gaussian<=1) {
5518             print(images,0,"Blur image%s%s%s%s with standard deviation %g%s, %s boundary conditions "
5519                   "and %s kernel.",
5520                   gmic_selection.data(),
5521                   *argx?" along the '":"",
5522                   *argx?argx:"",
5523                   *argx?std::strlen(argx)==1?"'-axis,":"'-axes,":"",
5524                   sigma,sep=='%'?"%":"",
5525                   boundary?"neumann":"dirichlet",
5526                   is_gaussian?"gaussian":"quasi-gaussian");
5527             if (sep=='%') sigma = -sigma;
5528             if (*argx) {
5529               g_img.assign(4,1,1,1,(T)0);
5530               for (const char *s = argx; *s; ++s) g_img[*s>='x'?*s - 'x':3]+=sigma;
5531               cimg_forY(selection,l) gmic_apply(gmic_blur(g_img[0],g_img[1],g_img[2],g_img[3],
5532                                                           (bool)boundary,(bool)is_gaussian));
5533               g_img.assign();
5534             } else cimg_forY(selection,l) gmic_apply(blur(sigma,(bool)boundary,(bool)is_gaussian));
5535           } else arg_error("blur");
5536           is_change = true; ++position; continue;
5537         }
5538 
5539         // Box filter.
5540         if (!std::strcmp("boxfilter",command)) {
5541           unsigned int order = 0;
5542           gmic_substitute_args(false);
5543           float sigma = -1;
5544           sep = *argx = 0;
5545           boundary = 1;
5546           value = 1;
5547           const char *p_argument = argument;
5548           if (cimg_sscanf(argument,"%255[xyzc]%c",argx,&sep)==2 && sep==',') {
5549             p_argument+=1 + std::strlen(argx);
5550           } else sep = *argx = 0;
5551           if ((cimg_sscanf(p_argument,"%f%c",
5552                            &sigma,&end)==1 ||
5553                (cimg_sscanf(p_argument,"%f%c%c",
5554                             &sigma,&sep,&end)==2 && sep=='%') ||
5555                cimg_sscanf(p_argument,"%f,%u%c",
5556                            &sigma,&order,&end)==2 ||
5557                (cimg_sscanf(p_argument,"%f%c,%u%c",
5558                             &sigma,&sep,&order,&end)==3 && sep=='%') ||
5559                cimg_sscanf(p_argument,"%f,%u,%u%c",
5560                            &sigma,&order,&boundary,&end)==3 ||
5561                (cimg_sscanf(p_argument,"%f%c,%u,%u%c",
5562                             &sigma,&sep,&order,&boundary,&end)==4 && sep=='%') ||
5563                cimg_sscanf(p_argument,"%f,%u,%u,%lf%c",
5564                            &sigma,&order,&boundary,&value,&end)==4 ||
5565                (cimg_sscanf(p_argument,"%f%c,%u,%u,%lf%c",
5566                             &sigma,&sep,&order,&boundary,&value,&end)==5 && sep=='%')) &&
5567               sigma>=0 && boundary<=1 && order<=2 && value>=0) {
5568             const unsigned int nb_iter = (unsigned int)cimg::round(value);
5569             print(images,0,"Blur image%s%s%s%s with normalized box filter of size %g%s, order %u and "
5570                   "%s boundary conditions (%u iteration%s).",
5571                   gmic_selection.data(),
5572                   *argx?" along the '":"",
5573                   *argx?argx:"",
5574                   *argx?std::strlen(argx)==1?"'-axis,":"'-axes,":"",
5575                   sigma,sep=='%'?"%":"",
5576                   order,
5577                   boundary?"neumann":"dirichlet",
5578                   nb_iter,nb_iter!=1?"s":"");
5579             if (sep=='%') sigma = -sigma;
5580             if (*argx) {
5581               g_img.assign(4,1,1,1,(T)0);
5582               for (const char *s = argx; *s; ++s) g_img[*s>='x'?*s - 'x':3]+=sigma;
5583               cimg_forY(selection,l) gmic_apply(gmic_blur_box(g_img[0],g_img[1],g_img[2],g_img[3],
5584                                                               order,(bool)boundary,nb_iter));
5585               g_img.assign();
5586             } else cimg_forY(selection,l) gmic_apply(gmic_blur_box(sigma,order,(bool)boundary,nb_iter));
5587           } else arg_error("boxfilter");
5588           is_change = true; ++position; continue;
5589         }
5590 
5591         // Bitwise right shift.
5592         gmic_arithmetic_command("bsr",
5593                                 operator>>=,
5594                                 "Compute bitwise right shift of image%s by %g%s",
5595                                 gmic_selection.data(),value,ssep,Tlong,
5596                                 operator>>=,
5597                                 "Compute bitwise right shift of image%s by image [%d]",
5598                                 gmic_selection.data(),ind[0],
5599                                 operator_brseq,
5600                                 "Compute bitwise right shift of image%s by expression %s",
5601                                 gmic_selection.data(),gmic_argument_text_printed(),
5602                                 "Compute sequential bitwise right shift of image%s");
5603 
5604         // Bitwise left shift.
5605         gmic_arithmetic_command("bsl",
5606                                 operator<<=,
5607                                 "Compute bitwise left shift of image%s by %g%s",
5608                                 gmic_selection.data(),value,ssep,Tlong,
5609                                 operator<<=,
5610                                 "Compute bitwise left shift of image%s by image [%d]",
5611                                 gmic_selection.data(),ind[0],
5612                                 operator_blseq,
5613                                 "Compute bitwise left shift of image%s by expression %s",
5614                                 gmic_selection.data(),gmic_argument_text_printed(),
5615                                 "Compute sequential bitwise left shift of image%s");
5616 
5617         // Bilateral filter.
5618         if (!std::strcmp("bilateral",command)) {
5619           gmic_substitute_args(true);
5620           float sigma_s = 0, sigma_r = 0, sampling_s = 0, sampling_r = 0;
5621           sep0 = sep1 = *argz = *argc = 0;
5622           if ((cimg_sscanf(argument,"[%255[a-zA-Z0-9_.%+-]],%255[0-9.eE%+-],%255[0-9.eE%+-]%c",
5623                            indices,argx,argy,&end)==3 ||
5624                cimg_sscanf(argument,"[%255[a-zA-Z0-9_.%+-]],%255[0-9.eE%+-],%255[0-9.eE%+-],%f,%f%c",
5625                            indices,argx,argy,&sampling_s,&sampling_r,&end)==5) &&
5626               (cimg_sscanf(argx,"%f%c",&sigma_s,&end)==1 ||
5627                (cimg_sscanf(argx,"%f%c%c",&sigma_s,&sep0,&end)==2 && sep0=='%')) &&
5628               (cimg_sscanf(argy,"%f%c",&sigma_r,&end)==1 ||
5629                (cimg_sscanf(argy,"%f%c%c",&sigma_r,&sep1,&end)==2 && sep1=='%')) &&
5630               (ind=selection2cimg(indices,images.size(),images_names,"bilateral")).height()==1 &&
5631               sigma_s>=0 && sigma_r>=0 && sampling_s>=0 && sampling_r>=0) {
5632             print(images,0,"Apply joint bilateral filter on image%s, with guide image [%u], "
5633                   " standard deviations (%g%s,%g%s) and sampling (%g,%g).",
5634                   gmic_selection.data(),
5635                   *ind,
5636                   sigma_s,sep0=='%'?"%":"",
5637                   sigma_r,sep1=='%'?"%":"",
5638                   sampling_s,sampling_r);
5639             const CImg<T> guide = gmic_image_arg(*ind);
5640             if (sep0=='%') sigma_s = -sigma_s;
5641             if (sep1=='%') sigma_r = -sigma_r;
5642             cimg_forY(selection,l) gmic_apply(blur_bilateral(guide,sigma_s,sigma_r,sampling_s,sampling_r));
5643           } else if ((cimg_sscanf(argument,"%255[0-9.eE%+-],%255[0-9.eE%+-]%c",
5644                                   argx,argy,&end)==2 ||
5645                       cimg_sscanf(argument,"%255[0-9.eE%+-],%255[0-9.eE%+-],%f,%f%c",
5646                                   argx,argy,&sampling_s,&sampling_r,&end)==4) &&
5647                      (cimg_sscanf(argx,"%f%c",&sigma_s,&end)==1 ||
5648                       (cimg_sscanf(argx,"%f%c%c",&sigma_s,&sep0,&end)==2 && sep0=='%')) &&
5649                      (cimg_sscanf(argy,"%f%c",&sigma_r,&end)==1 ||
5650                       (cimg_sscanf(argy,"%f%c%c",&sigma_r,&sep1,&end)==2 && sep1=='%')) &&
5651                      sigma_s>=0 && sigma_r>=0 && sampling_s>=0 && sampling_r>=0) {
5652             print(images,0,"Apply bilateral filter on image%s, with standard deviations (%g%s,%g%s) and "
5653                   "sampling (%g,%g).",
5654                   gmic_selection.data(),
5655                   sigma_s,sep0=='%'?"%":"",
5656                   sigma_r,sep1=='%'?"%":"",
5657                   sampling_s,sampling_r);
5658             if (sep0=='%') sigma_s = -sigma_s;
5659             if (sep1=='%') sigma_r = -sigma_r;
5660             cimg_forY(selection,l)
5661               gmic_apply(blur_bilateral(images[selection[l]],sigma_s,sigma_r,sampling_s,sampling_r));
5662           } else arg_error("bilateral");
5663           is_change = true; ++position; continue;
5664         }
5665 
5666         goto gmic_commands_others;
5667 
5668         //-----------------------------
5669         // Commands starting by 'c...'
5670         //-----------------------------
5671       gmic_commands_c :
5672 
5673         // Check expression or filename.
5674         if (is_command_check) {
5675           gmic_substitute_args(false);
5676           is_cond = check_cond(argument,images,"check");
5677           if (is_very_verbose)
5678             print(images,0,"Check condition '%s' -> %s.",gmic_argument_text_printed(),is_cond?"true":"false");
5679           if (!is_cond) {
5680             if (is_first_item && callstack.size()>1 && callstack.back()[0]!='*')
5681               error(true,images,0,callstack.back(),"Command '%s': Invalid argument '%s'.",
5682                     callstack.back().data(),_gmic_argument_text(parent_arguments,argument_text,true));
5683             else error(true,images,0,0,
5684                        "Command 'check': Expression '%s' evaluated to false.",
5685                        gmic_argument_text());
5686           }
5687           ++position; continue;
5688         }
5689 
5690         // Crop.
5691         if (!std::strcmp("crop",command)) {
5692           gmic_substitute_args(false);
5693           name.assign(64,8);
5694           char
5695             *const st0 = name.data(0,0), *const st1 = name.data(0,1), *const st2 = name.data(0,2),
5696             *const st3 = name.data(0,3), *const st4 = name.data(0,4), *const st5 = name.data(0,5),
5697             *const st6 = name.data(0,6), *const st7 = name.data(0,7);
5698           char sep2 = sep0 = sep1 = 0, sep3 = 0, sep4 = 0, sep5 = 0, sep6 = 0, sep7 = 0;
5699           float a0 = 0, a1 = 0, a2 = 0, a3 = 0, a4 = 0, a5 = 0, a6 = 0, a7 = 0;
5700           *st0 = *st1 = *st2 = *st3 = *st4 = *st5 = *st6 = *st7 = 0;
5701           boundary = 0;
5702           if ((boundary=0,cimg_sscanf(argument,"%63[0-9.eE%+-],%63[0-9.eE%+-]%c",
5703                                       st0,
5704                                       st1,&end)==2 ||
5705                cimg_sscanf(argument,"%63[0-9.eE%+-],%63[0-9.eE%+-],%u%c",
5706                            st0,
5707                            st1,&boundary,&end)==3) &&
5708               (cimg_sscanf(st0,"%f%c",&a0,&end)==1 ||
5709                (cimg_sscanf(st0,"%f%c%c",&a0,&sep0,&end)==2 && sep0=='%')) &&
5710               (cimg_sscanf(st1,"%f%c",&a1,&end)==1 ||
5711                (cimg_sscanf(st1,"%f%c%c",&a1,&sep1,&end)==2 && sep1=='%')) &&
5712               boundary<=3) {
5713             print(images,0,"Crop image%s with coordinates (%g%s) - (%g%s) and "
5714                   "%s boundary conditions.",
5715                   gmic_selection.data(),
5716                   a0,sep0=='%'?"%":"",
5717                   a1,sep1=='%'?"%":"",
5718                   boundary==0?"dirichlet":boundary==1?"neumann":boundary==2?"periodic":"mirror");
5719             cimg_forY(selection,l) {
5720               CImg<T> &img = images[selection[l]];
5721               const int
5722                 x0 = (int)cimg::round(sep0=='%'?a0*(img.width() - 1)/100:a0),
5723                 x1 = (int)cimg::round(sep1=='%'?a1*(img.width() - 1)/100:a1);
5724               gmic_apply(crop(x0,x1,boundary));
5725             }
5726           } else if ((boundary=0,cimg_sscanf(argument,
5727                                              "%63[0-9.eE%+-],%63[0-9.eE%+-],"
5728                                              "%63[0-9.eE%+-],%63[0-9.eE%+-]%c",
5729                                              st0,st1,st2,st3,&end)==4 ||
5730                       cimg_sscanf(argument,
5731                                   "%63[0-9.eE%+-],%63[0-9.eE%+-],"
5732                                   "%63[0-9.eE%+-],%63[0-9.eE%+-],%u%c",
5733                                   st0,st1,st2,st3,&boundary,&end)==5) &&
5734                      (cimg_sscanf(st0,"%f%c",&a0,&end)==1 ||
5735                       (cimg_sscanf(st0,"%f%c%c",&a0,&sep0,&end)==2 && sep0=='%')) &&
5736                      (cimg_sscanf(st1,"%f%c",&a1,&end)==1 ||
5737                       (cimg_sscanf(st1,"%f%c%c",&a1,&sep1,&end)==2 && sep1=='%')) &&
5738                      (cimg_sscanf(st2,"%f%c",&a2,&end)==1 ||
5739                       (cimg_sscanf(st2,"%f%c%c",&a2,&sep2,&end)==2 && sep2=='%')) &&
5740                      (cimg_sscanf(st3,"%f%c",&a3,&end)==1 ||
5741                       (cimg_sscanf(st3,"%f%c%c",&a3,&sep3,&end)==2 && sep3=='%')) &&
5742                      boundary<=3) {
5743             print(images,0,
5744                   "Crop image%s with coordinates (%g%s,%g%s) - (%g%s,%g%s) and "
5745                   "%s boundary conditions.",
5746                   gmic_selection.data(),
5747                   a0,sep0=='%'?"%":"",
5748                   a1,sep1=='%'?"%":"",
5749                   a2,sep2=='%'?"%":"",
5750                   a3,sep3=='%'?"%":"",
5751                   boundary==0?"dirichlet":boundary==1?"neumann":boundary==2?"periodic":"mirror");
5752             cimg_forY(selection,l) {
5753               CImg<T> &img = images[selection[l]];
5754               const int
5755                 x0 = (int)cimg::round(sep0=='%'?a0*(img.width() - 1)/100:a0),
5756                 y0 = (int)cimg::round(sep1=='%'?a1*(img.height() - 1)/100:a1),
5757                 x1 = (int)cimg::round(sep2=='%'?a2*(img.width() - 1)/100:a2),
5758                 y1 = (int)cimg::round(sep3=='%'?a3*(img.height() - 1)/100:a3);
5759               gmic_apply(crop(x0,y0,x1,y1,boundary));
5760             }
5761           } else if ((boundary=0,cimg_sscanf(argument,
5762                                              "%63[0-9.eE%+-],%63[0-9.eE%+-],%63[0-9.eE%+-],"
5763                                              "%63[0-9.eE%+-],%63[0-9.eE%+-],%63[0-9.eE%+-]%c",
5764                                              st0,st1,st2,st3,st4,st5,&end)==6 ||
5765                       cimg_sscanf(argument,"%63[0-9.eE%+-],%63[0-9.eE%+-],%63[0-9.eE%+-],"
5766                                   "%63[0-9.eE%+-],%63[0-9.eE%+-],%63[0-9.eE%+-],%u%c",
5767                                   st0,st1,st2,st3,st4,st5,&boundary,&end)==7) &&
5768                      (cimg_sscanf(st0,"%f%c",&a0,&end)==1 ||
5769                       (cimg_sscanf(st0,"%f%c%c",&a0,&sep0,&end)==2 && sep0=='%')) &&
5770                      (cimg_sscanf(st1,"%f%c",&a1,&end)==1 ||
5771                       (cimg_sscanf(st1,"%f%c%c",&a1,&sep1,&end)==2 && sep1=='%')) &&
5772                      (cimg_sscanf(st2,"%f%c",&a2,&end)==1 ||
5773                       (cimg_sscanf(st2,"%f%c%c",&a2,&sep2,&end)==2 && sep2=='%')) &&
5774                      (cimg_sscanf(st3,"%f%c",&a3,&end)==1 ||
5775                       (cimg_sscanf(st3,"%f%c%c",&a3,&sep3,&end)==2 && sep3=='%')) &&
5776                      (cimg_sscanf(st4,"%f%c",&a4,&end)==1 ||
5777                       (cimg_sscanf(st4,"%f%c%c",&a4,&sep4,&end)==2 && sep4=='%')) &&
5778                      (cimg_sscanf(st5,"%f%c",&a5,&end)==1 ||
5779                       (cimg_sscanf(st5,"%f%c%c",&a5,&sep5,&end)==2 && sep5=='%')) &&
5780                      boundary<=3) {
5781             print(images,0,"Crop image%s with coordinates (%g%s,%g%s,%g%s) - (%g%s,%g%s,%g%s) "
5782                   "and %s boundary conditions.",
5783                   gmic_selection.data(),
5784                   a0,sep0=='%'?"%":"",
5785                   a1,sep1=='%'?"%":"",
5786                   a2,sep2=='%'?"%":"",
5787                   a3,sep3=='%'?"%":"",
5788                   a4,sep4=='%'?"%":"",
5789                   a5,sep5=='%'?"%":"",
5790                   boundary==0?"dirichlet":boundary==1?"neumann":boundary==2?"periodic":"mirror");
5791             cimg_forY(selection,l) {
5792               CImg<T> &img = images[selection[l]];
5793               const int
5794                 x0 = (int)cimg::round(sep0=='%'?a0*(img.width() - 1)/100:a0),
5795                 y0 = (int)cimg::round(sep1=='%'?a1*(img.height() - 1)/100:a1),
5796                 z0 = (int)cimg::round(sep2=='%'?a2*(img.depth() - 1)/100:a2),
5797                 x1 = (int)cimg::round(sep3=='%'?a3*(img.width() - 1)/100:a3),
5798                 y1 = (int)cimg::round(sep4=='%'?a4*(img.height() - 1)/100:a4),
5799                 z1 = (int)cimg::round(sep5=='%'?a5*(img.depth() - 1)/100:a5);
5800               gmic_apply(crop(x0,y0,z0,x1,y1,z1,boundary));
5801             }
5802           } else if ((boundary=0,cimg_sscanf(argument,
5803                                              "%63[0-9.eE%+-],%63[0-9.eE%+-],%63[0-9.eE%+-],"
5804                                              "%63[0-9.eE%+-],%63[0-9.eE%+-],%63[0-9.eE%+-],"
5805                                              "%63[0-9.eE%+-],%63[0-9.eE%+-]%c",
5806                                              st0,st1,st2,st3,st4,st5,st6,st7,&end)==8 ||
5807                       cimg_sscanf(argument,"%63[0-9.eE%+-],%63[0-9.eE%+-],%63[0-9.eE%+-],"
5808                                   "%63[0-9.eE%+-],%63[0-9.eE%+-],%63[0-9.eE%+-],"
5809                                   "%63[0-9.eE%+-],%63[0-9.eE%+-],%u%c",
5810                                   st0,st1,st2,st3,st4,st5,st6,st7,&boundary,&end)==9) &&
5811                      (cimg_sscanf(st0,"%f%c",&a0,&end)==1 ||
5812                       (cimg_sscanf(st0,"%f%c%c",&a0,&sep0,&end)==2 && sep0=='%')) &&
5813                      (cimg_sscanf(st1,"%f%c",&a1,&end)==1 ||
5814                       (cimg_sscanf(st1,"%f%c%c",&a1,&sep1,&end)==2 && sep1=='%')) &&
5815                      (cimg_sscanf(st2,"%f%c",&a2,&end)==1 ||
5816                       (cimg_sscanf(st2,"%f%c%c",&a2,&sep2,&end)==2 && sep2=='%')) &&
5817                      (cimg_sscanf(st3,"%f%c",&a3,&end)==1 ||
5818                       (cimg_sscanf(st3,"%f%c%c",&a3,&sep3,&end)==2 && sep3=='%')) &&
5819                      (cimg_sscanf(st4,"%f%c",&a4,&end)==1 ||
5820                       (cimg_sscanf(st4,"%f%c%c",&a4,&sep4,&end)==2 && sep4=='%')) &&
5821                      (cimg_sscanf(st5,"%f%c",&a5,&end)==1 ||
5822                       (cimg_sscanf(st5,"%f%c%c",&a5,&sep5,&end)==2 && sep5=='%')) &&
5823                      (cimg_sscanf(st6,"%f%c",&a6,&end)==1 ||
5824                       (cimg_sscanf(st6,"%f%c%c",&a6,&sep6,&end)==2 && sep6=='%')) &&
5825                      (cimg_sscanf(st7,"%f%c",&a7,&end)==1 ||
5826                       (cimg_sscanf(st7,"%f%c%c",&a7,&sep7,&end)==2 && sep7=='%')) &&
5827                      boundary<=3) {
5828             print(images,0,
5829                   "Crop image%s with coordinates (%g%s,%g%s,%g%s,%g%s) - (%g%s,%g%s,%g%s,%g%s) "
5830                   "and %s boundary conditions.",
5831                   gmic_selection.data(),
5832                   a0,sep0=='%'?"%":"",
5833                   a1,sep1=='%'?"%":"",
5834                   a2,sep2=='%'?"%":"",
5835                   a3,sep3=='%'?"%":"",
5836                   a4,sep4=='%'?"%":"",
5837                   a5,sep5=='%'?"%":"",
5838                   a6,sep6=='%'?"%":"",
5839                   a7,sep7=='%'?"%":"",
5840                   boundary==0?"dirichlet":boundary==1?"neumann":boundary==2?"periodic":"mirror");
5841             cimg_forY(selection,l) {
5842               CImg<T> &img = images[selection[l]];
5843               const int
5844                 x0 = (int)cimg::round(sep0=='%'?a0*(img.width() - 1)/100:a0),
5845                 y0 = (int)cimg::round(sep1=='%'?a1*(img.height() - 1)/100:a1),
5846                 z0 = (int)cimg::round(sep2=='%'?a2*(img.depth() - 1)/100:a2),
5847                 v0 = (int)cimg::round(sep3=='%'?a3*(img.spectrum() - 1)/100:a3),
5848                 x1 = (int)cimg::round(sep4=='%'?a4*(img.width() - 1)/100:a4),
5849                 y1 = (int)cimg::round(sep5=='%'?a5*(img.height() - 1)/100:a5),
5850                 z1 = (int)cimg::round(sep6=='%'?a6*(img.depth() - 1)/100:a6),
5851                 v1 = (int)cimg::round(sep7=='%'?a7*(img.spectrum() - 1)/100:a7);
5852               gmic_apply(crop(x0,y0,z0,v0,x1,y1,z1,v1,boundary));
5853             }
5854           } else arg_error("crop");
5855           is_change = true; ++position; continue;
5856         }
5857 
5858         // Cut.
5859         if (!std::strcmp("cut",command)) {
5860           gmic_substitute_args(true);
5861           ind0.assign(); ind1.assign();
5862           sep0 = sep1 = *argx = *argy = *indices = 0;
5863           value0 = value1 = 0;
5864           if (cimg_sscanf(argument,"%255[][a-zA-Z0-9_.eE%+-],%255[][a-zA-Z0-9_.eE%+-]%c",
5865                           argx,argy,&end)==2 &&
5866               ((cimg_sscanf(argx,"[%255[a-zA-Z0-9_.%+-]%c%c",indices,&sep0,&end)==2 &&
5867                 sep0==']' &&
5868                 (ind0=selection2cimg(indices,images.size(),images_names,"cut")).height()==1) ||
5869                (cimg_sscanf(argx,"%lf%c%c",&value0,&sep0,&end)==2 && sep0=='%') ||
5870                cimg_sscanf(argx,"%lf%c",&value0,&end)==1) &&
5871               ((cimg_sscanf(argy,"[%255[a-zA-Z0-9_.%+-]%c%c",formula,&sep1,&end)==2 &&
5872                 sep1==']' &&
5873                 (ind1=selection2cimg(formula,images.size(),images_names,"cut")).height()==1) ||
5874                (cimg_sscanf(argy,"%lf%c%c",&value1,&sep1,&end)==2 && sep1=='%') ||
5875                cimg_sscanf(argy,"%lf%c",&value1,&end)==1)) {
5876             if (ind0) { value0 = images[*ind0].min(); sep0 = 0; }
5877             if (ind1) { value1 = images[*ind1].max(); sep1 = 0; }
5878             print(images,0,"Cut image%s in range [%g%s,%g%s].",
5879                   gmic_selection.data(),
5880                   value0,sep0=='%'?"%":"",
5881                   value1,sep1=='%'?"%":"");
5882             cimg_forY(selection,l) {
5883               CImg<T> &img = images[selection[l]];
5884               nvalue0 = value0; nvalue1 = value1;
5885               vmin = vmax = 0;
5886               if (sep0=='%' || sep1=='%') {
5887                 if (img) vmax = (double)img.max_min(vmin);
5888                 if (sep0=='%') nvalue0 = vmin + (vmax - vmin)*value0/100;
5889                 if (sep1=='%') nvalue1 = vmin + (vmax - vmin)*value1/100;
5890               }
5891               gmic_apply(cut((T)nvalue0,(T)nvalue1));
5892             }
5893           } else if (cimg_sscanf(argument,"[%255[a-zA-Z0-9_.%+-]%c%c",indices,&sep0,&end)==2 &&
5894                      sep0==']' &&
5895                      (ind0=selection2cimg(indices,images.size(),images_names,"cut")).height()==1) {
5896             if (images[*ind0]) value1 = (double)images[*ind0].max_min(value0);
5897             print(images,0,"Cut image%s in range [%g,%g].",
5898                   gmic_selection.data(),
5899                   value0,
5900                   value1);
5901             cimg_forY(selection,l) gmic_apply(cut((T)value0,(T)value1));
5902           } else arg_error("cut");
5903           is_change = true; ++position; continue;
5904         }
5905 
5906         // Keep channels.
5907         if (!std::strcmp("channels",command)) {
5908           gmic_substitute_args(true);
5909           ind0.assign(); ind1.assign();
5910           sep0 = sep1 = *argx = *argy = *indices = 0;
5911           value0 = value1 = 0;
5912           if (cimg_sscanf(argument,"%255[][a-zA-Z0-9_.eE%+-]%c",
5913                           argx,&end)==1 &&
5914               ((cimg_sscanf(argx,"[%255[a-zA-Z0-9_.%+-]%c%c]",indices,&sep0,&end)==2 &&
5915                 sep0==']' &&
5916                 (ind0=selection2cimg(indices,images.size(),images_names,"channels")).height()==1) ||
5917                cimg_sscanf(argx,"%lf%c",&value0,&end)==1 ||
5918                (cimg_sscanf(argx,"%lf%c%c",&value0,&sep0,&end)==2 && sep0=='%'))) {
5919             if (ind0) { value0 = images[*ind0].spectrum() - 1.; sep0 = 0; }
5920             print(images,0,"Keep channel %g%s of image%s.",
5921                   value0,sep0=='%'?"%":"",
5922                   gmic_selection.data());
5923             cimg_forY(selection,l) {
5924               CImg<T> &img = images[selection[l]];
5925               nvalue0 = cimg::round(sep0=='%'?value0*(img.spectrum() - 1)/100:value0);
5926               gmic_apply(channel((int)nvalue0));
5927             }
5928           } else if (cimg_sscanf(argument,"%255[][a-zA-Z0-9_.eE%+-],%255[][a-zA-Z0-9_.eE%+-]%c",
5929                                  argx,argy,&end)==2 &&
5930                      ((cimg_sscanf(argx,"[%255[a-zA-Z0-9_.%+-]%c%c",indices,&sep0,&end)==2 &&
5931                        sep0==']' &&
5932                        (ind0=selection2cimg(indices,images.size(),images_names,"channels")).height()==1) ||
5933                       cimg_sscanf(argx,"%lf%c",&value0,&end)==1 ||
5934                       (cimg_sscanf(argx,"%lf%c%c",&value0,&sep0,&end)==2 && sep0=='%')) &&
5935                      ((cimg_sscanf(argx,"[%255[a-zA-Z0-9_.%+-]%c%c",formula,&sep0,&end)==2 &&
5936                        sep0==']' &&
5937                        (ind1=selection2cimg(formula,images.size(),images_names,"channels")).height()==1) ||
5938                       cimg_sscanf(argy,"%lf%c",&value1,&end)==1 ||
5939                       (cimg_sscanf(argy,"%lf%c%c",&value1,&sep1,&end)==2 && sep1=='%'))) {
5940             if (ind0) { value0 = images[*ind0].spectrum() - 1.; sep0 = 0; }
5941             if (ind1) { value1 = images[*ind1].spectrum() - 1.; sep1 = 0; }
5942             print(images,0,"Keep channels %g%s...%g%s of image%s.",
5943                   value0,sep0=='%'?"%":"",
5944                   value1,sep1=='%'?"%":"",
5945                   gmic_selection.data());
5946             cimg_forY(selection,l) {
5947               CImg<T> &img = images[selection[l]];
5948               nvalue0 = cimg::round(sep0=='%'?value0*(img.spectrum() - 1)/100:value0);
5949               nvalue1 = cimg::round(sep1=='%'?value1*(img.spectrum() - 1)/100:value1);
5950               gmic_apply(channels((int)nvalue0,(int)nvalue1));
5951             }
5952           } else arg_error("channels");
5953           is_change = true; ++position; continue;
5954         }
5955 
5956         // Keep columns.
5957         if (!std::strcmp("columns",command)) {
5958           gmic_substitute_args(true);
5959           ind0.assign(); ind1.assign();
5960           sep0 = sep1 = *argx = *argy = *indices = 0;
5961           value0 = value1 = 0;
5962           if (cimg_sscanf(argument,"%255[][a-zA-Z0-9_.eE%+-]%c",
5963                           argx,&end)==1 &&
5964               ((cimg_sscanf(argx,"[%255[a-zA-Z0-9_.%+-]%c%c]",indices,&sep0,&end)==2 &&
5965                 sep0==']' &&
5966                 (ind0=selection2cimg(indices,images.size(),images_names,"columns")).height()==1) ||
5967                cimg_sscanf(argx,"%lf%c",&value0,&end)==1 ||
5968                (cimg_sscanf(argx,"%lf%c%c",&value0,&sep0,&end)==2 && sep0=='%'))) {
5969             if (ind0) { value0 = images[*ind0].width() - 1.; sep0 = 0; }
5970             print(images,0,"Keep column %g%s of image%s.",
5971                   value0,sep0=='%'?"%":"",
5972                   gmic_selection.data());
5973             cimg_forY(selection,l) {
5974               CImg<T> &img = images[selection[l]];
5975               nvalue0 = cimg::round(sep0=='%'?value0*(img.width() - 1)/100:value0);
5976               gmic_apply(column((int)nvalue0));
5977             }
5978           } else if (cimg_sscanf(argument,"%255[][a-zA-Z0-9_.eE%+-],%255[][a-zA-Z0-9_.eE%+-]%c",
5979                                  argx,argy,&end)==2 &&
5980                      ((cimg_sscanf(argx,"[%255[a-zA-Z0-9_.%+-]%c%c",indices,&sep0,&end)==2 &&
5981                        sep0==']' &&
5982                        (ind0=selection2cimg(indices,images.size(),images_names,"columns")).height()==1) ||
5983                       cimg_sscanf(argx,"%lf%c",&value0,&end)==1 ||
5984                       (cimg_sscanf(argx,"%lf%c%c",&value0,&sep0,&end)==2 && sep0=='%')) &&
5985                      ((cimg_sscanf(argx,"[%255[a-zA-Z0-9_.%+-]%c%c",formula,&sep0,&end)==2 &&
5986                        sep0==']' &&
5987                        (ind1=selection2cimg(formula,images.size(),images_names,"columns")).height()==1) ||
5988                       cimg_sscanf(argy,"%lf%c",&value1,&end)==1 ||
5989                       (cimg_sscanf(argy,"%lf%c%c",&value1,&sep1,&end)==2 && sep1=='%'))) {
5990             if (ind0) { value0 = images[*ind0].width() - 1.; sep0 = 0; }
5991             if (ind1) { value1 = images[*ind1].width() - 1.; sep1 = 0; }
5992             print(images,0,"Keep columns %g%s...%g%s of image%s.",
5993                   value0,sep0=='%'?"%":"",
5994                   value1,sep1=='%'?"%":"",
5995                   gmic_selection.data());
5996             cimg_forY(selection,l) {
5997               CImg<T> &img = images[selection[l]];
5998               nvalue0 = cimg::round(sep0=='%'?value0*(img.width() - 1)/100:value0);
5999               nvalue1 = cimg::round(sep1=='%'?value1*(img.width() - 1)/100:value1);
6000               gmic_apply(columns((int)nvalue0,(int)nvalue1));
6001             }
6002           } else arg_error("columns");
6003           is_change = true; ++position; continue;
6004         }
6005 
6006         // Import custom commands.
6007         if (!is_get && !std::strcmp("command",item)) {
6008           gmic_substitute_args(false);
6009           name.assign(argument,(unsigned int)std::strlen(argument) + 1);
6010           const char *arg_command_text = gmic_argument_text_printed();
6011           unsigned int offset_argument_text = 0, count_new = 0, count_replaced = 0;
6012           char *arg_command = name;
6013           strreplace_fw(arg_command);
6014 
6015           bool add_debug_info = true;
6016           if ((*arg_command=='0' || *arg_command=='1') && arg_command[1]==',') {
6017             add_debug_info = (*arg_command=='1');
6018             arg_command+=2; arg_command_text+=2; offset_argument_text = 2;
6019           }
6020 
6021           std::FILE *file = cimg::std_fopen(arg_command,"rb");
6022           if (file) {
6023             print(images,0,"Import commands from file '%s'%s",
6024                   arg_command_text,
6025                   !add_debug_info?" without debug info":"");
6026             add_commands(file,add_debug_info?arg_command:0,&count_new,&count_replaced);
6027             cimg::fclose(file);
6028           } else if (!cimg::strncasecmp(arg_command,"http://",7) ||
6029                      !cimg::strncasecmp(arg_command,"https://",8)) { // Try to read from network
6030             print(images,0,"Import commands from URL '%s'%s",
6031                   arg_command_text,
6032                   !add_debug_info?" without debug info":"");
6033             try {
6034               file = cimg::std_fopen(cimg::load_network(arg_command,argx),"r");
6035             } catch (...) {
6036               file = 0;
6037             }
6038             if (file) {
6039               status.move_to(o_status); // Save status because 'add_commands()' can change it, with 'error()'
6040               const int o_verbosity = verbosity;
6041               const bool o_is_debug = is_debug;
6042               verbosity = 0;
6043               is_debug = false;
6044               try {
6045                 add_commands(file,add_debug_info?arg_command:0,&count_new,&count_replaced);
6046                 cimg::fclose(file);
6047               } catch (...) {
6048                 cimg::fclose(file);
6049                 file = 0;
6050               }
6051               is_debug = o_is_debug;
6052               verbosity = o_verbosity;
6053               o_status.move_to(status);
6054             }
6055             if (!file)
6056               error(true,images,0,0,
6057                     "Command 'command': Unable to load custom command file '%s' "
6058                     "from network.",
6059                     gmic_argument_text() + offset_argument_text);
6060             std::remove(argx);
6061           } else {
6062             print(images,0,"Import custom commands from expression '%s'",
6063                   arg_command_text);
6064             add_commands(arg_command,0,&count_new,&count_replaced);
6065           }
6066           if (is_verbose) {
6067             unsigned int count_total = 0;
6068             for (unsigned int l = 0; l<gmic_comslots; ++l) count_total+=commands[l].size();
6069             cimg::mutex(29);
6070             if (count_new && count_replaced)
6071               std::fprintf(cimg::output()," (%u new, %u replaced, total: %u).",
6072                            count_new,count_replaced,count_total);
6073             else if (count_new)
6074               std::fprintf(cimg::output()," (%u new, total: %u).",
6075                            count_new,count_total);
6076             else
6077               std::fprintf(cimg::output()," (%u replaced, total: %u).",
6078                            count_replaced,count_total);
6079             std::fflush(cimg::output());
6080             cimg::mutex(29,0);
6081           }
6082           ++position; continue;
6083         }
6084 
6085         // Check validity of 3D object.
6086         if (!is_get && !std::strcmp("check3d",command)) {
6087           gmic_substitute_args(false);
6088           bool is_full_check = true;
6089           if (!argument[1] && (*argument=='0' || *argument=='1')) {
6090             is_full_check = (*argument=='1');
6091             ++position;
6092           } else is_full_check = true;
6093           if (is_very_verbose)
6094             print(images,0,"Check validity of 3D object%s (%s check)",
6095                   gmic_selection.data(),
6096                   is_full_check?"full":"fast");
6097           cimg_forY(selection,l) {
6098             const unsigned int uind = selection[l];
6099             CImg<T>& img = gmic_check(images[uind]);
6100             if (!img.is_CImg3d(is_full_check,&(*message=0))) {
6101               if (is_very_verbose) {
6102                 cimg::mutex(29);
6103                 std::fprintf(cimg::output()," -> invalid.");
6104                 std::fflush(cimg::output());
6105                 cimg::mutex(29,0);
6106               }
6107               error(true,images,0,0,
6108                     "Command 'check3d': Invalid 3D object [%d], in selected image%s (%s).",
6109                     uind,gmic_selection_err.data(),message.data());
6110             }
6111           }
6112           if (is_very_verbose) {
6113             cimg::mutex(29);
6114             std::fprintf(cimg::output()," -> valid.");
6115             std::fflush(cimg::output());
6116             cimg::mutex(29,0);
6117           }
6118           continue;
6119         }
6120 
6121         // Cosine.
6122         gmic_simple_command("cos",cos,"Compute pointwise cosine of image%s.");
6123 
6124         // Convolve & Correlate.
6125         if (!std::strcmp("convolve",command) || !std::strcmp("correlate",command)) {
6126           gmic_substitute_args(true);
6127           unsigned int
6128             is_normalized = 0, channel_mode = 1,
6129             xcenter = ~0U, ycenter = ~0U, zcenter = ~0U,
6130             xstart = 0, ystart = 0, zstart = 0,
6131             xend = ~0U, yend = ~0U, zend = ~0U;
6132           float
6133             xstride = 1, ystride = 1, zstride = 1,
6134             xdilation = 1, ydilation = 1 , zdilation = 1;
6135           is_cond = command[2]=='n'; // is_convolve?
6136           boundary = 1;
6137           sep = 0;
6138 
6139           if (((cimg_sscanf(argument,"[%255[a-zA-Z0-9_.%+-]%c%c",
6140                             indices,&sep,&end)==2 && sep==']') ||
6141                cimg_sscanf(argument,"[%255[a-zA-Z0-9_.%+-]],%u%c",
6142                            indices,&boundary,&end)==2 ||
6143                cimg_sscanf(argument,"[%255[a-zA-Z0-9_.%+-]],%u,%u%c",
6144                            indices,&boundary,&is_normalized,&end)==3 ||
6145                cimg_sscanf(argument,"[%255[a-zA-Z0-9_.%+-]],%u,%u,%u%c",
6146                            indices,&boundary,&is_normalized,&channel_mode,&end)==4 ||
6147                cimg_sscanf(argument,"[%255[a-zA-Z0-9_.%+-]],%u,%u,%u,%u,%u,%u%c",
6148                            indices,&boundary,&is_normalized,&channel_mode,&xcenter,&ycenter,&zcenter,&end)==7 ||
6149                cimg_sscanf(argument,"[%255[a-zA-Z0-9_.%+-]],%u,%u,%u,%u,%u,%u,%u,%u,%u,%u,%u,%u%c",
6150                            indices,&boundary,&is_normalized,&channel_mode,&xcenter,&ycenter,&zcenter,
6151                            &xstart,&ystart,&zstart,&xend,&yend,&zend,&end)==13 ||
6152                cimg_sscanf(argument,"[%255[a-zA-Z0-9_.%+-]],%u,%u,%u,%u,%u,%u,%u,%u,%u,%u,%u,%u,%f,%f,%f%c",
6153                            indices,&boundary,&is_normalized,&channel_mode,&xcenter,&ycenter,&zcenter,
6154                            &xstart,&ystart,&zstart,&xend,&yend,&zend,&xstride,&ystride,&zstride,&end)==16 ||
6155                cimg_sscanf(argument,"[%255[a-zA-Z0-9_.%+-]],%u,%u,%u,%u,%u,%u,%u,%u,%u,%u,%u,%u,%f,%f,%f,%f,%f,%f%c",
6156                            indices,&boundary,&is_normalized,&channel_mode,&xcenter,&ycenter,&zcenter,
6157                            &xstart,&ystart,&zstart,&xend,&yend,&zend,&xstride,&ystride,&zstride,
6158                            &xdilation,&ydilation,&zdilation,&end)==19) &&
6159               (ind=selection2cimg(indices,images.size(),images_names,"correlate")).height()==1 &&
6160               boundary<=3 && channel_mode<=2) {
6161 
6162             *argx = *argy = *argz = *argc = 0;
6163             if (is_verbose) {
6164               if (xcenter!=~0U || ycenter!=~0U || zcenter!=~0U)
6165                 cimg_snprintf(argx,_argx.width(),", kernel center (%d,%d,%d)",
6166                               (int)xcenter,(int)ycenter,(int)zcenter);
6167               if (xstart!=0 || ystart!=0 || zstart!=0 || xend!=~0U || yend!=~0U || zend!=~0U)
6168                 cimg_snprintf(argy,_argy.width(),", crop coordinates (%d,%d,%d) - (%d,%d,%d)",
6169                               (int)xstart,(int)ystart,(int)zstart,(int)xend,(int)yend,(int)zend);
6170               if (xstride!=1 || ystride!=1 || zstride!=1)
6171                 cimg_snprintf(argz,_argz.width(),", strides (%g,%g,%g)",
6172                               xstride,ystride,zstride);
6173               if (xdilation!=1 || ydilation!=1 || zdilation!=1)
6174                 cimg_snprintf(argc,_argc.width(),", dilations (%g,%g,%g)",
6175                               xdilation,ydilation,zdilation);
6176             }
6177 
6178             print(images,0,
6179                   "%s image%s with kernel [%u], %s boundary conditions, "
6180                   "with%s normalization, %s channel mode%s%s%s.",
6181                   is_cond?"Convolve":"Correlate",
6182                   gmic_selection.data(),
6183                   *ind,
6184                   boundary==0?"dirichlet":boundary==1?"neumann":boundary==2?"periodic":"mirror",
6185                   is_normalized?"":"out",
6186                   channel_mode==0?"sum input":
6187                   channel_mode==1?"one-for-one":"expand",
6188                   *argx?argx:"",*argy?argy:"",*argz?argz:"",*argc?argc:"");
6189             const CImg<T> kernel = gmic_image_arg(*ind);
6190             if (is_cond) {
6191               cimg_forY(selection,l) gmic_apply(convolve(kernel,boundary,(bool)is_normalized,channel_mode,
6192                                                          xcenter,ycenter,zcenter,xstart,ystart,zstart,xend,yend,zend,
6193                                                          xstride,ystride,zstride,xdilation,ydilation,zdilation));
6194             } else {
6195               cimg_forY(selection,l) gmic_apply(correlate(kernel,boundary,(bool)is_normalized,channel_mode,
6196                                                           xcenter,ycenter,zcenter,xstart,ystart,zstart,xend,yend,zend,
6197                                                           xstride,ystride,zstride,xdilation,ydilation,zdilation));
6198             }
6199           } else arg_error(is_cond?"convolve":"correlate");
6200           is_change = true; ++position; continue;
6201         }
6202 
6203         // Set 3D object color.
6204         if (!std::strcmp("color3d",command) || !std::strcmp("col3d",command)) {
6205           gmic_substitute_args(false);
6206           float R = 200, G = 200, B = 200;
6207           opacity = -1;
6208           if ((cimg_sscanf(argument,"%f%c",
6209                            &R,&end)==1 && ((B=G=R),1)) ||
6210               (cimg_sscanf(argument,"%f,%f%c",
6211                            &R,&G,&end)==2 && ((B=0),1)) ||
6212               cimg_sscanf(argument,"%f,%f,%f%c",
6213                           &R,&G,&B,&end)==3 ||
6214               cimg_sscanf(argument,"%f,%f,%f,%f%c",
6215                           &R,&G,&B,&opacity,&end)==4) {
6216             const bool set_opacity = (opacity>=0);
6217             if (set_opacity)
6218               print(images,0,"Set colors of 3D object%s to (%g,%g,%g), with opacity %g.",
6219                     gmic_selection.data(),
6220                     R,G,B,
6221                     opacity);
6222             else
6223               print(images,0,"Set color of 3D object%s to (%g,%g,%g).",
6224                     gmic_selection.data(),
6225                     R,G,B);
6226             cimg_forY(selection,l) {
6227               const unsigned int uind = selection[l];
6228               CImg<T>& img = gmic_check(images[uind]);
6229               try { gmic_apply(color_CImg3d(R,G,B,opacity,true,set_opacity)); }
6230               catch (CImgException&) {
6231                 if (!img.is_CImg3d(true,&(*message=0)))
6232                   error(true,images,0,0,
6233                         "Command 'color3d': Invalid 3D object [%d], "
6234                         "in selected image%s (%s).",
6235                         uind,gmic_selection_err.data(),message.data());
6236                 else throw;
6237               }
6238             }
6239           } else arg_error("color3d");
6240           is_change = true; ++position; continue;
6241         }
6242 
6243         // Cumulate.
6244         if (!std::strcmp("cumulate",command)) {
6245           gmic_substitute_args(false);
6246           bool is_axes_argument = true;
6247           for (const char *s = argument; *s; ++s) {
6248             const char _s = *s;
6249             if (_s!='x' && _s!='y' && _s!='z' && _s!='c') { is_axes_argument = false; break; }
6250           }
6251           if (*argument && is_axes_argument) {
6252             print(images,0,"Cumulate values of image%s along the '%s'-ax%cs.",
6253                   gmic_selection.data(),
6254                   gmic_argument_text_printed(),
6255                   std::strlen(argument)>1?'e':'i');
6256             cimg_forY(selection,l) gmic_apply(cumulate(argument));
6257             ++position;
6258           } else {
6259             print(images,0,"Cumulate values of image%s.",
6260                   gmic_selection.data());
6261             cimg_forY(selection,l) gmic_apply(cumulate());
6262           }
6263           is_change = true; continue;
6264         }
6265 
6266         // Hyperbolic cosine.
6267         gmic_simple_command("cosh",cosh,"Compute pointwise hyperbolic cosine of image%s.");
6268 
6269         // Camera input.
6270         if (!is_get && !std::strcmp("camera",item)) {
6271           gmic_substitute_args(false);
6272           float
6273             cam_index = 0, nb_frames = 1, skip_frames = 0,
6274             capture_width = 0, capture_height = 0;
6275           if ((cimg_sscanf(argument,"%f%c",
6276                            &cam_index,&end)==1 ||
6277                cimg_sscanf(argument,"%f,%f%c",
6278                            &cam_index,&nb_frames,&end)==2 ||
6279                cimg_sscanf(argument,"%f,%f,%f%c",
6280                            &cam_index,&nb_frames,&skip_frames,&end)==3 ||
6281                cimg_sscanf(argument,"%f,%f,%f,%f,%f%c",
6282                            &cam_index,&nb_frames,&skip_frames,
6283                            &capture_width,&capture_height,&end)==5) &&
6284               cam_index>=0 && nb_frames>=0 && skip_frames>=0 &&
6285               ((!capture_width && !capture_height) || (capture_width>0 && capture_height>0)))
6286             ++position;
6287           else { cam_index = skip_frames = capture_width = capture_height = 0; nb_frames = 1; }
6288           cam_index = cimg::round(cam_index);
6289           nb_frames = cimg::round(nb_frames);
6290           skip_frames = cimg::round(skip_frames);
6291           capture_width = cimg::round(capture_width);
6292           capture_height = cimg::round(capture_height);
6293           if (!nb_frames) {
6294             print(images,0,"Release camera #%g.",cam_index);
6295             CImg<T>::get_load_camera((unsigned int)cam_index,0,0,0,true);
6296           } else {
6297             if (capture_width)
6298               print(images,0,"Insert %g image%s from camera #%g, with %g frames skipping "
6299                     "and resolution %gx%g.",
6300                     cam_index,nb_frames,nb_frames>1?"s":"",skip_frames,
6301                     capture_width,capture_height);
6302             else print(images,0,"Insert %g image%s from camera #%g, with %g frames skipping.",
6303                        cam_index,nb_frames,nb_frames>1?"s":"",skip_frames);
6304             cimg_snprintf(title,_title.width(),"[Camera #%g]",cam_index);
6305             CImg<char>::string(title).move_to(name);
6306             if (nb_frames>1) {
6307               cimg::mutex(29);
6308               std::fputc('\n',cimg::output());
6309               std::fflush(cimg::output());
6310               cimg::mutex(29,0);
6311             }
6312             for (unsigned int k = 0; k<(unsigned int)nb_frames; ++k) {
6313               if (nb_frames>1 && is_verbose) {
6314                 cimg::mutex(29);
6315                 std::fprintf(cimg::output(),"\r  > Image %u/%u        ",
6316                              k + 1,(unsigned int)nb_frames);
6317                 std::fflush(cimg::output());
6318                 cimg::mutex(29,0);
6319               }
6320               CImg<T>::get_load_camera((unsigned int)cam_index,
6321                                        (unsigned int)capture_width,(unsigned int)capture_height,
6322                                        (unsigned int)skip_frames,false).
6323                 move_to(images);
6324               images_names.insert(name);
6325             }
6326           }
6327           is_change = true; continue;
6328         }
6329 
6330         // Show/hide mouse cursor.
6331         if (!is_get && !std::strcmp("cursor",command)) {
6332           gmic_substitute_args(false);
6333           if (!is_selection)
6334             CImg<unsigned int>::vector(0,1,2,3,4,5,6,7,8,9).move_to(selection);
6335           bool state = true;
6336           if (!argument[1] && (*argument=='0' || *argument=='1')) {
6337             state = (*argument=='1'); ++position;
6338           } else state = true;
6339 
6340           if (!is_display_available) {
6341             print(images,0,"%s mouse cursor for display window%s (skipped, no display %s).",
6342                   state?"Show":"Hide",
6343                   gmic_selection.data(),cimg_display?"available":"support");
6344           } else {
6345             if (state) cimg_forY(selection,l) {
6346                 if (!display_window(l).is_closed()) display_window(selection[l]).show_mouse();
6347               }
6348             else cimg_forY(selection,l) {
6349                 if (!display_window(l).is_closed()) display_window(selection[l]).hide_mouse();
6350               }
6351             print(images,0,"%s mouse cursor for display window%s.",
6352                   state?"Show":"Hide",
6353                   gmic_selection.data());
6354           }
6355           continue;
6356         }
6357 
6358         goto gmic_commands_others;
6359 
6360         //-----------------------------
6361         // Commands starting by 'd...'
6362         //-----------------------------
6363       gmic_commands_d :
6364 
6365         // Done.
6366         if (!is_get && !std::strcmp("done",item)) {
6367           const CImg<char> &s = callstack.back();
6368           if (s[0]!='*' || (s[1]!='r' && s[1]!='f'))
6369             error(true,images,0,0,
6370                   "Command 'done': Not associated to a 'repeat' or 'for' command "
6371                   "within the same scope.");
6372           if (s[1]=='r') { // End a 'repeat...done' block
6373             *title = 0;
6374             unsigned int *const rd = repeatdones.data(0,nb_repeatdones - 1);
6375             const unsigned int pos = rd[4];
6376             hash = rd[3];
6377             ++rd[2];
6378             if (--rd[1]) {
6379               position = rd[0] + 1;
6380               if (hash!=~0U) {
6381                 cimg_snprintf(argx,_argx.width(),"%u",rd[2]);
6382                 CImg<char>::string(argx).move_to((*variables[hash])[pos]);
6383               }
6384               next_debug_line = debug_line; next_debug_filename = debug_filename;
6385             } else {
6386               if (is_very_verbose) print(images,0,"End 'repeat...done' block.");
6387               if (hash!=~0U) {
6388                 variables[hash]->remove(pos);
6389                 variables_names[hash]->remove(pos);
6390               }
6391               --nb_repeatdones;
6392               callstack.remove();
6393             }
6394           } else { // End a 'for...done' block
6395             fordones(1,nb_fordones - 1) = 1; // Mark 'for' as already visited
6396             position = fordones(0,nb_fordones - 1) - 1;
6397             next_debug_line = debug_line; next_debug_filename = debug_filename;
6398           }
6399           continue;
6400         }
6401 
6402         // Do...while.
6403         if (!is_get && !std::strcmp("do",item)) {
6404           if (is_debug_info && debug_line!=~0U) {
6405             cimg_snprintf(argx,_argx.width(),"*do#%u",debug_line);
6406             CImg<char>::string(argx).move_to(callstack);
6407           } else CImg<char>::string("*do").move_to(callstack);
6408           if (is_very_verbose) print(images,0,"Start 'do...while' block.");
6409           if (nb_dowhiles>=dowhiles._height) dowhiles.resize(1,std::max(2*dowhiles._height,8U),1,1,0);
6410           dowhiles[nb_dowhiles++] = position;
6411           continue;
6412         }
6413 
6414         // Discard value.
6415         if (!std::strcmp("discard",command)) {
6416           gmic_substitute_args(false);
6417           CImg<T> values;
6418           *argx = 0;
6419 
6420           if (cimg_sscanf(argument,"%255[xyzc]%c",argx,&end)==1) {
6421 
6422             // Discard neighboring duplicate values along axes.
6423             print(images,0,"Discard neighboring duplicate values along '%s'-ax%cs, in image%s.",
6424                   argx,
6425                   std::strlen(argx)>1?'e':'i',
6426                   gmic_selection.data());
6427             cimg_forY(selection,l) gmic_apply(gmic_discard(argx));
6428             ++position;
6429 
6430           } else if (cimg_sscanf(argument,"%255[xyzc]%c",argx,&end)==2 && end==',') {
6431 
6432             // Discard sequence of values along axes.
6433             unsigned int nb_values = 1, s_argx = (unsigned int)std::strlen(argx) + 1;
6434             for (const char *s = argument + s_argx; *s; ++s) if (*s==',') ++nb_values;
6435             try { values.assign(nb_values,1,1,1).fill(argument + s_argx,true,false); }
6436             catch (CImgException&) { arg_error("discard"); }
6437             print(images,0,"Discard sequence of values '%s' along '%s'-ax%cs, in image%s.",
6438                   gmic_argument_text_printed() + s_argx,
6439                   argx,
6440                   std::strlen(argx)>1?'e':'i',
6441                   gmic_selection.data());
6442             cimg_forY(selection,l) gmic_apply(gmic_discard(values,argx));
6443             ++position;
6444 
6445           } else { // Discard sequence of values or neighboring duplicate values
6446             unsigned int nb_values = *argument?1U:0U;
6447             for (const char *s = argument; *s; ++s) if (*s==',') ++nb_values;
6448             try { values.assign(nb_values,1,1,1).fill(argument,true,false); }
6449             catch (CImgException&) { values.assign(); }
6450             if (values) {
6451               print(images,0,"Discard sequence of values '%s' in image%s.",
6452                     gmic_argument_text_printed(),
6453                     gmic_selection.data());
6454               cimg_forY(selection,l) gmic_apply(discard(values));
6455               ++position;
6456 
6457             } else {
6458               print(images,0,"Discard neighboring duplicate values in image%s.",
6459                     gmic_selection.data());
6460               cimg_forY(selection,l) gmic_apply(discard());
6461             }
6462           }
6463           is_change = true; continue;
6464         }
6465 
6466         // Enable debug mode (useful when 'debug' is invoked from a custom command).
6467         if (!is_get && !std::strcmp("debug",item)) {
6468           is_debug = true;
6469           continue;
6470         }
6471 
6472         // Divide.
6473         gmic_arithmetic_command("div",
6474                                 operator/=,
6475                                 "Divide image%s by %g%s",
6476                                 gmic_selection.data(),value,ssep,Tfloat,
6477                                 div,
6478                                 "Divide image%s by image [%d]",
6479                                 gmic_selection.data(),ind[0],
6480                                 operator_diveq,
6481                                 "Divide image%s by expression %s",
6482                                 gmic_selection.data(),gmic_argument_text_printed(),
6483                                 "Divide image%s");
6484 
6485         // Distance function.
6486         if (!std::strcmp("distance",command)) {
6487           gmic_substitute_args(true);
6488           unsigned int algorithm = 0, off = 0;
6489           int metric = 2;
6490           sep0 = sep1 = *indices = 0;
6491           value = 0;
6492           if ((cimg_sscanf(argument,"%lf%c",
6493                            &value,&end)==1 ||
6494                (cimg_sscanf(argument,"%lf%c%c",
6495                             &value,&sep0,&end)==2 && sep0=='%') ||
6496                cimg_sscanf(argument,"%lf,%d%c",
6497                            &value,&metric,&end)==2 ||
6498                (cimg_sscanf(argument,"%lf%c,%d%c",
6499                             &value,&sep0,&metric,&end)==3 && sep0=='%')) &&
6500               metric>=0 && metric<=3) {
6501             print(images,0,"Compute distance map to isovalue %g%s in image%s, "
6502                   "with %s metric.",
6503                   value,sep0=='%'?"%":"",
6504                   gmic_selection.data(),
6505                   metric==0?"chebyshev":metric==1?"manhattan":metric==2?"euclidean":
6506                   "squared-euclidean");
6507             cimg_forY(selection,l) {
6508               CImg<T> &img = gmic_check(images[selection[l]]);
6509               nvalue = value;
6510               if (sep0=='%' && img) {
6511                 vmax = (double)img.max_min(vmin);
6512                 nvalue = vmin + value*(vmax - vmin)/100;
6513               }
6514               gmic_apply(distance((T)nvalue,metric));
6515             }
6516           } else if ((((cimg_sscanf(argument,"%lf,[%255[a-zA-Z0-9_.%+-]%c%c",
6517                                     &value,indices,&sep1,&end)==3 ||
6518                         (cimg_sscanf(argument,"%lf%c,[%255[a-zA-Z0-9_.%+-]%c%c",
6519                                      &value,&sep0,indices,&sep1,&end)==4 && sep0=='%')) &&
6520                        sep1==']') ||
6521                       ((cimg_sscanf(argument,"%lf,[%255[a-zA-Z0-9_.%+-]],%u%c",
6522                                     &value,indices,&algorithm,&end)==3 ||
6523                         (cimg_sscanf(argument,"%lf%c,[%255[a-zA-Z0-9_.%+-]],%u%c",
6524                                      &value,&sep0,indices,&algorithm,&end)==4 && sep0=='%')) &&
6525                        algorithm<=4)) &&
6526                      (ind=selection2cimg(indices,images.size(),images_names,"distance")).height()==1) {
6527             print(images,0,"Compute distance map%s to isovalue %g%s in image%s, "
6528                   "using %s algorithm, with metric [%u].",
6529                   selection.height()>1?(algorithm>=3?"s and return paths":"s"):
6530                   (algorithm>=3?" and return path":""),
6531                   value,sep0=='%'?"%":"",
6532                   gmic_selection.data(),
6533                   algorithm==0?"fast-marching":algorithm==1||algorithm==3?
6534                   "low-connectivity dijkstra":"high-connectivity dijkstra",
6535                   *ind);
6536             const CImg<T> custom_metric = gmic_image_arg(*ind);
6537             if (algorithm<3) cimg_forY(selection,l) {
6538                 CImg<T> &img = gmic_check(images[selection[l]]);
6539                 nvalue = value;
6540                 if (sep0=='%' && img) {
6541                   vmax = (double)img.max_min(vmin);
6542                   nvalue = vmin + value*(vmax - vmin)/100;
6543                 }
6544                 if (!algorithm) { gmic_apply(distance_eikonal((T)nvalue,custom_metric)); }
6545                 else gmic_apply(distance_dijkstra((T)nvalue,custom_metric,algorithm==2));
6546               }
6547             else cimg_forY(selection,l) {
6548                 const unsigned int uind = selection[l] + off;
6549                 CImg<T>& img = gmic_check(images[uind]);
6550                 nvalue = value;
6551                 if (sep0=='%' && img) {
6552                   vmax = (double)img.max_min(vmin);
6553                   nvalue = vmin + value*(vmax - vmin)/100;
6554                 }
6555                 name = images_names[uind];
6556                 CImg<T> path(1),
6557                   dist = img.get_distance_dijkstra((T)nvalue,custom_metric,algorithm==4,path);
6558                 if (is_get) {
6559                   images_names.insert(2,name.copymark());
6560                   dist.move_to(images,~0U);
6561                   path.move_to(images,~0U);
6562                 } else {
6563                   off+=1;
6564                   dist.move_to(images[uind].assign());
6565                   path.move_to(images,uind + 1);
6566                   images_names[uind] = name;
6567                   images_names.insert(name.copymark(),uind + 1);
6568                 }
6569               }
6570           } else arg_error("distance");
6571           is_change = true; ++position; continue;
6572         }
6573 
6574         // Dilate.
6575         if (!std::strcmp("dilate",command)) {
6576           gmic_substitute_args(true);
6577           float sx = 3, sy = 3, sz = 1;
6578           unsigned int is_real = 0;
6579           boundary = 1;
6580           sep = 0;
6581           if (((cimg_sscanf(argument,"[%255[a-zA-Z0-9_.%+-]%c%c",
6582                             indices,&sep,&end)==2 && sep==']') ||
6583                cimg_sscanf(argument,"[%255[a-zA-Z0-9_.%+-]],%u%c",
6584                            indices,&boundary,&end)==2 ||
6585                cimg_sscanf(argument,"[%255[a-zA-Z0-9_.%+-]],%u,%u%c",
6586                            indices,&boundary,&is_real,&end)==3) &&
6587               (ind=selection2cimg(indices,images.size(),images_names,"dilate")).height()==1 &&
6588               boundary<=1) {
6589             print(images,0,"Dilate image%s with kernel [%u] and %s boundary conditions, "
6590                   "in %s mode.",
6591                   gmic_selection.data(),
6592                   *ind,
6593                   boundary?"neumann":"dirichlet",
6594                   is_real?"real":"binary");
6595             const CImg<T> kernel = gmic_image_arg(*ind);
6596             cimg_forY(selection,l) gmic_apply(dilate(kernel,(bool)boundary,(bool)is_real));
6597           } else if ((cimg_sscanf(argument,"%f%c",
6598                                   &sx,&end)==1) &&
6599                      sx>=0) {
6600             sx = cimg::round(sx);
6601             print(images,0,"Dilate image%s with kernel of size %g and neumann boundary conditions.",
6602                   gmic_selection.data(),
6603                   sx);
6604             cimg_forY(selection,l) gmic_apply(dilate((unsigned int)sx));
6605           } else if ((cimg_sscanf(argument,"%f,%f%c",
6606                                   &sx,&sy,&end)==2 ||
6607                       cimg_sscanf(argument,"%f,%f,%f%c",
6608                                   &sx,&sy,&sz,&end)==3) &&
6609                      sx>=0 && sy>=0 && sz>=0) {
6610             sx = cimg::round(sx);
6611             sy = cimg::round(sy);
6612             sz = cimg::round(sz);
6613             print(images,0,"Dilate image%s with %gx%gx%g kernel and neumann boundary conditions.",
6614                   gmic_selection.data(),
6615                   sx,sy,sz);
6616             cimg_forY(selection,l) gmic_apply(dilate((unsigned int)sx,(unsigned int)sy,(unsigned int)sz));
6617           } else arg_error("dilate");
6618           is_change = true; ++position; continue;
6619         }
6620 
6621         // Set double-sided mode for 3D rendering.
6622         if (!is_get && !std::strcmp("double3d",item)) {
6623           gmic_substitute_args(false);
6624           bool state = true;
6625           if (!argument[1] && (*argument=='0' || *argument=='1')) {
6626             state = (*argument=='1');
6627             ++position;
6628           } else state = true;
6629           is_double3d = state;
6630           print(images,0,"%s double-sided mode for 3D rendering.",
6631                 is_double3d?"Enable":"Disable");
6632           continue;
6633         }
6634 
6635         // Patch-based smoothing.
6636         if (!std::strcmp("denoise",command)) {
6637           gmic_substitute_args(true);
6638           float sigma_s = 10, sigma_r = 10, smoothness = 1;
6639           unsigned int is_fast_approximation = 0;
6640           float psize = 5, rsize = 6;
6641           sep0 = sep1 = *argx = *argy = 0;
6642           if ((cimg_sscanf(argument,"[%255[a-zA-Z0-9_.%+-]],%255[0-9.eE%+-]%c",
6643                            indices,argx,&end)==2 ||
6644                cimg_sscanf(argument,"[%255[a-zA-Z0-9_.%+-]],%255[0-9.eE%+-],%255[0-9.eE%+-]%c",
6645                            indices,argx,argy,&end)==3 ||
6646                cimg_sscanf(argument,"[%255[a-zA-Z0-9_.%+-]],%255[0-9.eE%+-],%255[0-9.eE%+-],%f%c",
6647                            indices,argx,argy,&psize,&end)==4 ||
6648                cimg_sscanf(argument,"[%255[a-zA-Z0-9_.%+-]],%255[0-9.eE%+-],%255[0-9.eE%+-],%f,%f%c",
6649                            indices,argx,argy,&psize,&rsize,&end)==5 ||
6650                cimg_sscanf(argument,"[%255[a-zA-Z0-9_.%+-]],%255[0-9.eE%+-],%255[0-9.eE%+-],%f,%f,%f%c",
6651                            indices,argx,argy,&psize,&rsize,&smoothness,&end)==6 ||
6652                cimg_sscanf(argument,"[%255[a-zA-Z0-9_.%+-]],%255[0-9.eE%+-],%255[0-9.eE%+-],%f,%f,%f,%u%c",
6653                            indices,argx,argy,&psize,&rsize,&smoothness,
6654                            &is_fast_approximation,&end)==7) &&
6655               (cimg_sscanf(argx,"%f%c",&sigma_s,&end)==1 ||
6656                (cimg_sscanf(argx,"%f%c%c",&sigma_s,&sep0,&end)==2 && sep0=='%')) &&
6657               (cimg_sscanf(argy,"%f%c",&sigma_r,&end)==1 ||
6658                (cimg_sscanf(argy,"%f%c%c",&sigma_r,&sep1,&end)==2 && sep1=='%')) &&
6659               (ind=selection2cimg(indices,images.size(),images_names,"denoise")).height()==1 &&
6660               sigma_s>=0 && sigma_r>=0 && psize>=0 && rsize>=0 && is_fast_approximation<=1) {
6661             psize = cimg::round(psize);
6662             rsize = cimg::round(rsize);
6663             print(images,0,"Denoise image%s using guide image [%u], with %gx%g patches, "
6664                   "standard deviations (%g%s,%g%s), lookup size %g and smoothness %g.",
6665                   gmic_selection.data(),*ind,psize,psize,
6666                   sigma_s,sep0=='%'?"%":"",
6667                   sigma_r,sep1=='%'?"%":"",
6668                   rsize,smoothness);
6669             const CImg<T> guide = gmic_image_arg(*ind);
6670             if (sep0=='%') sigma_s = -sigma_s;
6671             if (sep1=='%') sigma_r = -sigma_r;
6672             cimg_forY(selection,l)
6673               gmic_apply(blur_patch(guide,sigma_s,sigma_r,(unsigned int)psize,(unsigned int)rsize,smoothness,
6674                                     (bool)is_fast_approximation));
6675           } else if ((cimg_sscanf(argument,"%255[0-9.eE%+-]%c",
6676                                   argx,&end)==1 ||
6677                       cimg_sscanf(argument,"%255[0-9.eE%+-],%255[0-9.eE%+-]%c",
6678                                   argx,argy,&end)==2 ||
6679                       cimg_sscanf(argument,"%255[0-9.eE%+-],%255[0-9.eE%+-],%f%c",
6680                                   argx,argy,&psize,&end)==3 ||
6681                       cimg_sscanf(argument,"%255[0-9.eE%+-],%255[0-9.eE%+-],%f,%f%c",
6682                                   argx,argy,&psize,&rsize,&end)==4 ||
6683                       cimg_sscanf(argument,"%255[0-9.eE%+-],%255[0-9.eE%+-],%f,%f,%f%c",
6684                                   argx,argy,&psize,&rsize,&smoothness,&end)==5 ||
6685                       cimg_sscanf(argument,"%255[0-9.eE%+-],%255[0-9.eE%+-],%f,%f,%f,%u%c",
6686                                   argx,argy,&psize,&rsize,&smoothness,
6687                                   &is_fast_approximation,&end)==6) &&
6688                      (cimg_sscanf(argx,"%f%c",&sigma_s,&end)==1 ||
6689                       (cimg_sscanf(argx,"%f%c%c",&sigma_s,&sep0,&end)==2 && sep0=='%')) &&
6690                      (cimg_sscanf(argy,"%f%c",&sigma_r,&end)==1 ||
6691                       (cimg_sscanf(argy,"%f%c%c",&sigma_r,&sep1,&end)==2 && sep1=='%')) &&
6692                      sigma_s>=0 && sigma_r>=0 && psize>=0 && rsize>=0 && is_fast_approximation<=1) {
6693             psize = cimg::round(psize);
6694             rsize = cimg::round(rsize);
6695             print(images,0,"Denoise image%s with %gx%g patches, standard deviations (%g%s,%g%s), "
6696                   "lookup size %g and smoothness %g.",
6697                   gmic_selection.data(),psize,psize,
6698                   sigma_s,sep0=='%'?"%":"",
6699                   sigma_r,sep1=='%'?"%":"",
6700                   rsize,smoothness);
6701             if (sep0=='%') sigma_s = -sigma_s;
6702             if (sep1=='%') sigma_r = -sigma_r;
6703             cimg_forY(selection,l)
6704               gmic_apply(blur_patch(sigma_s,sigma_r,(unsigned int)psize,(unsigned int)rsize,smoothness,
6705                                     (bool)is_fast_approximation));
6706           } else arg_error("denoise");
6707           is_change = true; ++position; continue;
6708         }
6709 
6710         // Deriche filter.
6711         if (!std::strcmp("deriche",command)) {
6712           gmic_substitute_args(false);
6713           unsigned int order = 0;
6714           float sigma = 0;
6715           axis = sep = 0;
6716           boundary = 1;
6717           if ((cimg_sscanf(argument,"%f,%u,%c%c",&sigma,&order,&axis,&end)==3 ||
6718                (cimg_sscanf(argument,"%f%c,%u,%c%c",&sigma,&sep,&order,&axis,&end)==4 &&
6719                 sep=='%') ||
6720                cimg_sscanf(argument,"%f,%u,%c,%u%c",&sigma,&order,&axis,&boundary,&end)==4 ||
6721                (cimg_sscanf(argument,"%f%c,%u,%c,%u%c",
6722                             &sigma,&sep,&order,&axis,&boundary,&end)==5 && sep=='%')) &&
6723               sigma>=0 && order<=2 && (axis=='x' || axis=='y' || axis=='z' || axis=='c') &&
6724               boundary<=1) {
6725             print(images,0,"Apply %u-order Deriche filter on image%s, along axis '%c' with standard "
6726                   "deviation %g%s and %s boundary conditions.",
6727                   order,gmic_selection.data(),axis,
6728                   sigma,sep=='%'?"%":"",
6729                   boundary?"neumann":"dirichlet");
6730             if (sep=='%') sigma = -sigma;
6731             cimg_forY(selection,l) gmic_apply(deriche(sigma,order,axis,(bool)boundary));
6732           } else arg_error("deriche");
6733           is_change = true; ++position; continue;
6734         }
6735 
6736         // Dijkstra algorithm.
6737         if (!std::strcmp("dijkstra",command)) {
6738           gmic_substitute_args(false);
6739           float snode = 0, enode = 0;
6740           if (cimg_sscanf(argument,"%f,%f%c",&snode,&enode,&end)==2 &&
6741               snode>=0 && enode>=0) {
6742             snode = cimg::round(snode);
6743             enode = cimg::round(enode);
6744             print(images,0,"Compute minimal path from adjacency matri%s%s with the "
6745                   "Dijkstra algorithm.",
6746                   selection.height()>1?"ce":"x",gmic_selection.data());
6747             unsigned int off = 0;
6748             cimg_forY(selection,l) {
6749               const unsigned int uind = selection[l] + off;
6750               name = images_names[uind];
6751               if (is_get) {
6752                 CImg<T> path, dist = gmic_check(images[uind]).get_dijkstra((unsigned int)snode,
6753                                                                            (unsigned int)enode,
6754                                                                            path);
6755                 images_names.insert(name.copymark());
6756                 name.move_to(images_names);
6757                 dist.move_to(images);
6758                 path.move_to(images);
6759               } else {
6760                 CImg<T> path;
6761                 gmic_check(images[uind]).dijkstra((unsigned int)snode,(unsigned int)enode,path);
6762                 images_names.insert(name.get_copymark(),uind + 1);
6763                 name.move_to(images_names[uind]);
6764                 images.insert(path,uind + 1);
6765                 ++off;
6766               }
6767             }
6768           } else arg_error("dijkstra");
6769           is_change = true; ++position; continue;
6770         }
6771 
6772         // Estimate displacement field.
6773         if (!std::strcmp("displacement",command)) {
6774           gmic_substitute_args(true);
6775           float nb_scales = 0, nb_iterations = 10000, smoothness = 0.1f, precision = 5.f;
6776           unsigned int is_backward = 1;
6777           sep = *argx = 0;
6778           ind0.assign();
6779           if (((cimg_sscanf(argument,"[%255[a-zA-Z0-9_.%+-]%c%c",
6780                             indices,&sep,&end)==2 && sep==']') ||
6781                cimg_sscanf(argument,"[%255[a-zA-Z0-9_.%+-]],%f%c",
6782                            indices,&smoothness,&end)==2 ||
6783                cimg_sscanf(argument,"[%255[a-zA-Z0-9_.%+-]],%f,%f%c",
6784                            indices,&smoothness,&precision,&end)==3 ||
6785                cimg_sscanf(argument,"[%255[a-zA-Z0-9_.%+-]],%f,%f,%f%c",
6786                            indices,&smoothness,&precision,&nb_scales,&end)==4 ||
6787                cimg_sscanf(argument,"[%255[a-zA-Z0-9_.%+-]],%f,%f,%f,%f%c",
6788                            indices,&smoothness,&precision,&nb_scales,&nb_iterations,&end)==5 ||
6789                cimg_sscanf(argument,"[%255[a-zA-Z0-9_.%+-]],%f,%f,%f,%f,%u%c",
6790                            indices,&smoothness,&precision,&nb_scales,&nb_iterations,
6791                            &is_backward,&end)==6 ||
6792                (cimg_sscanf(argument,"[%255[a-zA-Z0-9_.%+-]],%f,%f,%f,%f,%u,[%255[a-zA-Z0-9_.%+-]%c%c",
6793                             indices,&smoothness,&precision,&nb_scales,&nb_iterations,
6794                             &is_backward,argx,&sep,&end)==8 && sep==']')) &&
6795               (ind=selection2cimg(indices,images.size(),images_names,"displacement")).height()==1 &&
6796               precision>=0 && nb_scales>=0 && nb_iterations>=0 && is_backward<=1 &&
6797               (!*argx || (ind0=selection2cimg(argx,images.size(),images_names,"displacement")).height()==1)) {
6798             nb_scales = cimg::round(nb_scales);
6799             nb_iterations = cimg::round(nb_iterations);
6800             if (nb_scales) cimg_snprintf(argx,_argx.width(),"%g ",nb_scales); else std::strcpy(argx,"auto-");
6801             if (ind0) cimg_snprintf(argy,_argy.width()," with guide [%u]",*ind0); else *_argy = 0;
6802 
6803             print(images,0,"Estimate displacement field from source [%u] to image%s, with "
6804                   "%s smoothness %g, precision %g, %sscales, %g iteration%s, in %s direction%s.",
6805                   *ind,
6806                   gmic_selection.data(),
6807                   smoothness>=0?"isotropic":"anisotropic",cimg::abs(smoothness),
6808                   precision,
6809                   argx,
6810                   nb_iterations,nb_iterations!=1?"s":"",
6811                   is_backward?"backward":"forward",
6812                   argy);
6813             const CImg<T> source = gmic_image_arg(*ind);
6814             const CImg<T> constraints = ind0?gmic_image_arg(*ind0):CImg<T>::empty();
6815             cimg_forY(selection,l) gmic_apply(displacement(source,smoothness,precision,(unsigned int)nb_scales,
6816                                                            (unsigned int)nb_iterations,(bool)is_backward,
6817                                                            constraints));
6818           } else arg_error("displacement");
6819           is_change = true; ++position; continue;
6820         }
6821 
6822         // Display.
6823         if (!is_get && !std::strcmp("display",command)) {
6824           gmic_substitute_args(false);
6825           *argx = *argy = *argz = sep = sep0 = sep1 = 0;
6826           value = value0 = value1 = 0;
6827           exit_on_anykey = 0;
6828           if (((cimg_sscanf(argument,"%255[0-9.eE%+-]%c",
6829                             argx,&end)==1) ||
6830                (cimg_sscanf(argument,"%255[0-9.eE%+-],%255[0-9.eE%+-]%c",
6831                             argx,argy,&end)==2) ||
6832                (cimg_sscanf(argument,"%255[0-9.eE%+-],%255[0-9.eE%+-],%255[0-9.eE%+-]%c",
6833                             argx,argy,argz,&end)==3) ||
6834                (cimg_sscanf(argument,"%255[0-9.eE%+-],%255[0-9.eE%+-],%255[0-9.eE%+-],%u%c",
6835                             argx,argy,argz,&exit_on_anykey,&end)==4)) &&
6836               (cimg_sscanf(argx,"%lf%c",&value,&end)==1 ||
6837                (cimg_sscanf(argx,"%lf%c%c",&value,&sep,&end)==2 && sep=='%')) &&
6838               (!*argy ||
6839                cimg_sscanf(argy,"%lf%c",&value0,&end)==1 ||
6840                (cimg_sscanf(argy,"%lf%c%c",&value0,&sep0,&end)==2 && sep0=='%')) &&
6841               (!*argz ||
6842                cimg_sscanf(argz,"%lf%c",&value1,&end)==1 ||
6843                (cimg_sscanf(argz,"%lf%c%c",&value1,&sep1,&end)==2 && sep1=='%')) &&
6844               value>=0 && value0>=0 && value1>=0 && exit_on_anykey<=1) {
6845             if (!*argx) { value = 50; sep = '%'; }
6846             if (!*argy) { value0 = 50; sep0 = '%'; }
6847             if (!*argz) { value1 = 50; sep1 = '%'; }
6848             ++position;
6849           } else { value = value0 = value1 = 50; sep = sep0 = sep1 = '%'; }
6850 
6851           unsigned int XYZ[3];
6852           if (selection.height()>=1) {
6853             CImg<T> &img = images[selection[0]];
6854             XYZ[0] = (unsigned int)cimg::cut(cimg::round(sep=='%'?(img.width() - 1)*value/100:value),
6855                                              0.,img.width() - 1.);
6856             XYZ[1] = (unsigned int)cimg::cut(cimg::round(sep0=='%'?(img.height() - 1)*value0/100:value0),
6857                                              0.,img.height() - 1.);
6858             XYZ[2] = (unsigned int)cimg::cut(cimg::round(sep1=='%'?(img.depth() - 1)*value1/100:value1),
6859                                              0.,img.depth() - 1.);
6860           }
6861           display_images(images,images_names,selection,XYZ,exit_on_anykey);
6862           is_change = false; continue;
6863         }
6864 
6865         // Display 3D object.
6866         if (!is_get && !std::strcmp("display3d",command)) {
6867           gmic_substitute_args(true);
6868           exit_on_anykey = 0;
6869           sep = *indices = 0;
6870           ind.assign();
6871           if ((cimg_sscanf(argument,"%u%c",
6872                            &exit_on_anykey,&end)==1 ||
6873                (cimg_sscanf(argument,"[%255[a-zA-Z0-9_.%+-]%c%c",
6874                             indices,&sep,&end)==2 && sep==']') ||
6875                cimg_sscanf(argument,"[%255[a-zA-Z0-9_.%+-]],%u%c",
6876                            indices,&exit_on_anykey,&end)==2) &&
6877               exit_on_anykey<=1 &&
6878               (*argument!='[' ||
6879                (ind=selection2cimg(indices,images.size(),images_names,"display3d")).height()==1)) ++position;
6880           if (ind.height()==1) g_img_uc = gmic_image_arg(*ind);
6881           display_objects3d(images,images_names,selection,g_img_uc,exit_on_anykey);
6882           g_img_uc.assign();
6883           is_change = false; continue;
6884         }
6885 
6886         goto gmic_commands_others;
6887 
6888         //-----------------------------
6889         // Commands starting by 'e...'
6890         //-----------------------------
6891       gmic_commands_e :
6892 
6893         // Endif.
6894         if (!is_get && (!std::strcmp("endif",item) || !std::strcmp("fi",item))) {
6895           const CImg<char> &s = callstack.back();
6896           if (s[0]!='*' || s[1]!='i')
6897             error(true,images,0,0,
6898                   "Command 'endif': Not associated to a 'if' command within the same scope.");
6899           if (is_very_verbose) print(images,0,"End 'if...endif' block.");
6900           check_elif = false;
6901           callstack.remove();
6902           continue;
6903         }
6904 
6905         // Else and eluded elif.
6906         if (!is_get && (!std::strcmp("else",item) || (!std::strcmp("elif",item) && !check_elif))) {
6907           const CImg<char> &s = callstack.back();
6908           if (s[0]!='*' || s[1]!='i')
6909             error(true,images,0,0,
6910                   "Command '%s': Not associated to a 'if' command within the same scope.",
6911                   item);
6912           check_elif = false;
6913           if (is_very_verbose) print(images,0,"Reach 'else' block.");
6914           for (int nb_ifs = 1; nb_ifs && position<commands_line.size(); ++position) {
6915             const char *it = commands_line[position].data();
6916             if (*it==1 &&
6917                 cimg_sscanf(commands_line[position].data() + 1,"%x,%x",&_debug_line,&(_debug_filename=0))>0) {
6918               is_debug_info = true; next_debug_line = _debug_line; next_debug_filename = _debug_filename;
6919             } else {
6920               it+=*it=='-';
6921               if (!std::strcmp("if",it)) ++nb_ifs;
6922               else if (!std::strcmp("endif",it) || !std::strcmp("fi",it)) { if (!--nb_ifs) --position; }
6923             }
6924           }
6925           continue;
6926         }
6927 
6928         // End local environment.
6929         if (!is_get && (!std::strcmp("endlocal",item) || !std::strcmp("endl",item))) {
6930           const CImg<char> &s = callstack.back();
6931           if (s[0]!='*' || s[1]!='l')
6932             error(true,images,0,0,
6933                   "Command 'endlocal': Not associated to a 'local' command within "
6934                   "the same scope.");
6935           if (is_very_verbose) print(images,0,"End 'local...endlocal' block.");
6936           is_endlocal = true;
6937           break;
6938         }
6939 
6940         // Evaluate expression.
6941         if (!std::strcmp("eval",command)) {
6942           if (is_get && !is_selection)
6943             error(true,images,0,0,
6944                   "Command 'eval': Image selection is missing.");
6945           gmic_substitute_args(false);
6946           gmic_argument_text_printed();
6947           if (*argument_text=='\'') cimg::strpare(argument_text,'\'',true,false);
6948           name.assign(argument,(unsigned int)std::strlen(argument) + 1);
6949           cimg::strpare(name,'\'',true,false);
6950           strreplace_fw(name);
6951 
6952           if (!is_selection) { // No selection -> single evaluation
6953             print(images,0,"Evaluate expression '%s' and assign it to status.",
6954                   argument_text.data());
6955             CImg<T> &img = images.size()?images.back():CImg<T>::empty();
6956             CImg<double> output;
6957             img.eval(output,name,0,0,0,0,&images,&images);
6958             if (output.height()>1) // Vector-valued result
6959               output.value_string().move_to(status);
6960             else { // Scalar result
6961               cimg_snprintf(formula,_formula.width(),"%.17g",*output);
6962               CImg<char>::string(formula).move_to(status);
6963             }
6964           } else { // Selection -> loop over images
6965             print(images,0,"Evaluate expression '%s' looped over image%s.",
6966                   argument_text.data(),
6967                   gmic_selection.data());
6968             cimg_forY(selection,l) gmic_apply(gmic_eval(name.data(),images));
6969             is_change = true;
6970           }
6971           ++position; continue;
6972         }
6973 
6974         // Echo.
6975         if (is_command_echo) {
6976           if (verbosity>=0 || is_debug) {
6977             gmic_substitute_args(false);
6978             name.assign(argument,(unsigned int)std::strlen(argument) + 1);
6979             cimg::strunescape(name);
6980             ++verbosity;
6981             if (is_selection) print(images,&selection,"%s",name.data());
6982             else print(images,0,"%s",name.data());
6983             --verbosity;
6984           }
6985           ++position; continue;
6986         }
6987 
6988         // Exec.
6989         if (!is_get && !std::strcmp("exec",item)) {
6990           gmic_substitute_args(false);
6991           name.assign(argument,(unsigned int)std::strlen(argument) + 1);
6992           const char *arg_exec_text = gmic_argument_text_printed();
6993           char *arg_exec = name;
6994           cimg::strunescape(arg_exec);
6995           strreplace_fw(arg_exec);
6996 
6997           is_verbose = true; // is_verbose
6998           if ((*arg_exec=='0' || *arg_exec=='1') && arg_exec[1]==',') {
6999             is_verbose = (*arg_exec=='1');
7000             arg_exec+=2; arg_exec_text+=2;
7001           }
7002 
7003 #ifdef gmic_noexec
7004           print(images,0,"Execute external command '%s' %s (skipped, no exec allowed).",
7005                 arg_exec_text,
7006                 is_verbose?"in verbose mode":"");
7007 #else // #ifdef gmic_noexec
7008           print(images,0,"Execute external command '%s' %s",
7009                 arg_exec_text,
7010                 is_verbose?"in verbose mode.\n":".");
7011           cimg::mutex(31);
7012           const int errcode = cimg::system(arg_exec,0,is_verbose);
7013           cimg::mutex(31,0);
7014           cimg_snprintf(title,_title.width(),"%d",errcode);
7015           CImg<char>::string(title).move_to(status);
7016           if (errcode) print(images,0,"Command 'exec' returned error code '%d'.",
7017                              errcode);
7018 #endif // #ifdef gmic_noexec
7019           ++position; continue;
7020         }
7021 
7022         // Error.
7023         if (is_command_error) {
7024           gmic_substitute_args(false);
7025           name.assign(argument,(unsigned int)std::strlen(argument) + 1);
7026           cimg::strunescape(name);
7027           if (is_selection) error(true,images,&selection,0,"%s",name.data());
7028           else error(true,images,0,0,"%s",name.data());
7029         }
7030 
7031         // Invert endianness.
7032         if (!std::strcmp("endian",command)) {
7033           gmic_substitute_args(false);
7034           if (!std::strcmp(argument,"uchar") ||
7035               !std::strcmp(argument,"unsigned char") || !std::strcmp(argument,"char") ||
7036               !std::strcmp(argument,"ushort") || !std::strcmp(argument,"unsigned short") ||
7037               !std::strcmp(argument,"short") || !std::strcmp(argument,"uint") ||
7038               !std::strcmp(argument,"unsigned int") || !std::strcmp(argument,"int") ||
7039               !std::strcmp(argument,"uint64") || !std::strcmp(argument,"unsigned int64") ||
7040               !std::strcmp(argument,"int64") || !std::strcmp(argument,"float") ||
7041               !std::strcmp(argument,"double")) {
7042             print(images,0,"Invert data endianness of image%s, with assumed pixel type '%s'.",
7043                   gmic_selection.data(),argument);
7044             ++position;
7045           } else print(images,0,"Invert data endianness of image%s.",
7046                        gmic_selection.data());
7047           cimg_forY(selection,l) gmic_apply(gmic_invert_endianness(argument));
7048           is_change = true; continue;
7049         }
7050 
7051         // Exponential.
7052         gmic_simple_command("exp",exp,"Compute pointwise exponential of image%s.");
7053 
7054         // Test equality.
7055         gmic_arithmetic_command("eq",
7056                                 operator_eq,
7057                                 "Compute boolean equality between image%s and %g%s",
7058                                 gmic_selection.data(),value,ssep,T,
7059                                 operator_eq,
7060                                 "Compute boolean equality between image%s and image [%d]",
7061                                 gmic_selection.data(),ind[0],
7062                                 operator_eq,
7063                                 "Compute boolean equality between image%s and expression %s'",
7064                                 gmic_selection.data(),gmic_argument_text_printed(),
7065                                 "Compute boolean equality between image%s");
7066 
7067         // Draw ellipse.
7068         if (!std::strcmp("ellipse",command)) {
7069           gmic_substitute_args(false);
7070           float x = 0, y = 0, R = 0, r = 0, angle = 0;
7071           sep = sepx = sepy = sepz = sepc = *argx = *argy = *argz = *argc = *color = 0;
7072           pattern = ~0U; opacity = 1;
7073           if ((cimg_sscanf(argument,"%255[0-9.eE%+-],%255[0-9.eE%+-],%255[0-9.eE%+-]%c",
7074                            argx,argy,argz,&end)==3 ||
7075                cimg_sscanf(argument,"%255[0-9.eE%+-],%255[0-9.eE%+-],%255[0-9.eE%+-],"
7076                            "%255[0-9.eE%+-]%c",
7077                            argx,argy,argz,argc,&end)==4 ||
7078                cimg_sscanf(argument,"%255[0-9.eE%+-],%255[0-9.eE%+-],%255[0-9.eE%+-],"
7079                            "%255[0-9.eE%+-],%f%c",
7080                            argx,argy,argz,argc,&angle,&end)==5 ||
7081                cimg_sscanf(argument,"%255[0-9.eE%+-],%255[0-9.eE%+-],%255[0-9.eE%+-],"
7082                            "%255[0-9.eE%+-],%f,%f%c",
7083                            argx,argy,argz,argc,&angle,&opacity,&end)==6 ||
7084                (cimg_sscanf(argument,"%255[0-9.eE%+-],%255[0-9.eE%+-],%255[0-9.eE%+-],"
7085                             "%255[0-9.eE%+-],%f,%f,0%c%x%c",
7086                             argx,argy,argz,argc,&angle,&opacity,&sep,&pattern,
7087                             &end)==8 &&
7088                 sep=='x') ||
7089                (cimg_sscanf(argument,"%255[0-9.eE%+-],%255[0-9.eE%+-],%255[0-9.eE%+-],"
7090                             "%255[0-9.eE%+-],%f,%f,0%c%x,%4095[0-9.eEinfa,+-]%c",
7091                             argx,argy,argz,argc,&angle,&opacity,&sep,
7092                             &pattern,color,&end)==9 &&
7093                 sep=='x') ||
7094                (cimg_sscanf(argument,"%255[0-9.eE%+-],%255[0-9.eE%+-],%255[0-9.eE%+-],"
7095                             "%255[0-9.eE%+-],%f,%f,%4095[0-9.eEinfa,+-]%c",
7096                             argx,argy,argz,argc,&angle,&opacity,color,&end)==7)) &&
7097               (cimg_sscanf(argx,"%f%c",&x,&end)==1 ||
7098                (cimg_sscanf(argx,"%f%c%c",&x,&sepx,&end)==2 && sepx=='%')) &&
7099               (cimg_sscanf(argy,"%f%c",&y,&end)==1 ||
7100                (cimg_sscanf(argy,"%f%c%c",&y,&sepy,&end)==2 && sepy=='%')) &&
7101               (cimg_sscanf(argz,"%f%c",&R,&end)==1 ||
7102                (cimg_sscanf(argz,"%f%c%c",&R,&sepz,&end)==2 && sepz=='%')) &&
7103               (!*argc ||
7104                cimg_sscanf(argc,"%f%c",&r,&end)==1 ||
7105                (cimg_sscanf(argc,"%f%c%c",&r,&sepc,&end)==2 && sepc=='%'))) {
7106             if (!*argc) r = R;
7107             print(images,0,"Draw %s ellipse at (%g%s,%g%s) with radii (%g%s,%g%s) on image%s, "
7108                   "with orientation %g deg., opacity %g and color (%s).",
7109                   sep=='x'?"outlined":"filled",
7110                   x,sepx=='%'?"%":"",
7111                   y,sepy=='%'?"%":"",
7112                   R,sepz=='%'?"%":"",
7113                   r,sepc=='%'?"%":"",
7114                   gmic_selection.data(),
7115                   angle,
7116                   opacity,
7117                   *color?color:"default");
7118             cimg_forY(selection,l) {
7119               CImg<T> &img = images[selection[l]];
7120               g_img.assign(img.spectrum(),1,1,1,(T)0).fill(color,true,false);
7121               const float rmax = std::sqrt((float)cimg::sqr(img.width()) +
7122                                            cimg::sqr(img.height()));
7123               const int
7124                 nx = (int)cimg::round(sepx=='%'?x*(img.width() - 1)/100:x),
7125                 ny = (int)cimg::round(sepy=='%'?y*(img.height() - 1)/100:y);
7126               const float
7127                 nR = sepz=='%'?R*rmax/100:R,
7128                 nr = sepc=='%'?r*rmax/100:r;
7129               if (sep=='x') {
7130                 gmic_apply(draw_ellipse(nx,ny,nR,nr,angle,g_img.data(),opacity,pattern));
7131               } else {
7132                 gmic_apply(draw_ellipse(nx,ny,nR,nr,angle,g_img.data(),opacity));
7133               }
7134             }
7135           } else arg_error("ellipse");
7136           g_img.assign();
7137           is_change = true; ++position; continue;
7138         }
7139 
7140         // Equalize.
7141         if (!std::strcmp("equalize",command)) {
7142           gmic_substitute_args(false);
7143           float nb_levels = 256;
7144           bool no_min_max = false;
7145           sep = sep0 = sep1 = 0;
7146           value0 = value1 = 0;
7147           if (((cimg_sscanf(argument,"%f%c",
7148                             &nb_levels,&end)==1 && (no_min_max=true)) ||
7149                ((cimg_sscanf(argument,"%f%c%c",
7150                              &nb_levels,&sep,&end)==2 && sep=='%') && (no_min_max=true)) ||
7151                cimg_sscanf(argument,"%f,%lf,%lf%c",
7152                            &nb_levels,&value0,&value1,&end)==3 ||
7153                (cimg_sscanf(argument,"%f%c,%lf,%lf%c",
7154                             &nb_levels,&sep,&value0,&value1,&end)==4 && sep=='%') ||
7155                (cimg_sscanf(argument,"%f,%lf%c,%lf%c",
7156                             &nb_levels,&value0,&sep0,&value1,&end)==4 && sep0=='%') ||
7157                (cimg_sscanf(argument,"%f%c,%lf%c,%lf%c",
7158                             &nb_levels,&sep,&value0,&sep0,&value1,&end)==5 && sep=='%' &&
7159                 sep0=='%') ||
7160                (cimg_sscanf(argument,"%f,%lf,%lf%c%c",
7161                             &nb_levels,&value0,&value1,&sep1,&end)==4 && sep1=='%') ||
7162                (cimg_sscanf(argument,"%f%c,%lf,%lf%c%c",
7163                             &nb_levels,&sep,&value0,&value1,&sep1,&end)==5 && sep=='%' &&
7164                 sep1=='%') ||
7165                (cimg_sscanf(argument,"%f,%lf%c,%lf%c%c",
7166                             &nb_levels,&value0,&sep0,&value1,&sep1,&end)==5 && sep0=='%' &&
7167                 sep1=='%') ||
7168                (cimg_sscanf(argument,"%f%c,%lf%c,%lf%c%c",
7169                             &nb_levels,&sep,&value0,&sep0,&value1,&sep1,&end)==6 && sep=='%' &&
7170                 sep0=='%' && sep1=='%')) &&
7171               nb_levels>=0.5) { nb_levels = cimg::round(nb_levels); ++position; }
7172           else { nb_levels = 256; value0 = 0; value1 = 100; sep = 0; sep0 = sep1 = '%'; }
7173           if (no_min_max) { value0 = 0; value1 = 100; sep0 = sep1 = '%'; }
7174           print(images,0,"Equalize histogram of image%s, with %g%s levels in range [%g%s,%g%s].",
7175                 gmic_selection.data(),
7176                 nb_levels,sep=='%'?"%":"",
7177                 value0,sep0=='%'?"%":"",
7178                 value1,sep1=='%'?"%":"");
7179           cimg_forY(selection,l) {
7180             CImg<T>& img = gmic_check(images[selection[l]]);
7181             nvalue0 = value0; nvalue1 = value1;
7182             vmin = vmax = 0;
7183             if (sep0=='%' || sep1=='%') {
7184               if (img) vmax = (double)img.max_min(vmin);
7185               if (sep0=='%') nvalue0 = vmin + (vmax - vmin)*value0/100;
7186               if (sep1=='%') nvalue1 = vmin + (vmax - vmin)*value1/100;
7187             }
7188             const unsigned int
7189               _nb_levels = std::max(1U,
7190                                     (unsigned int)cimg::round(sep=='%'?
7191                                                               nb_levels*(1 + nvalue1 - nvalue0)/100:
7192                                                               nb_levels));
7193             gmic_apply(equalize(_nb_levels,(T)nvalue0,(T)nvalue1));
7194           }
7195           is_change = true; continue;
7196         }
7197 
7198         // Erode.
7199         if (!std::strcmp("erode",command)) {
7200           gmic_substitute_args(true);
7201           unsigned int is_real = 0;
7202           float sx = 3, sy = 3, sz = 1;
7203           boundary = 1;
7204           sep = 0;
7205           if (((cimg_sscanf(argument,"[%255[a-zA-Z0-9_.%+-]%c%c",
7206                             indices,&sep,&end)==2 && sep==']') ||
7207                cimg_sscanf(argument,"[%255[a-zA-Z0-9_.%+-]],%u%c",
7208                            indices,&boundary,&end)==2 ||
7209                cimg_sscanf(argument,"[%255[a-zA-Z0-9_.%+-]],%u,%u%c",
7210                            indices,&boundary,&is_real,&end)==3) &&
7211               (ind=selection2cimg(indices,images.size(),images_names,"erode")).height()==1 &&
7212               boundary<=1) {
7213             print(images,0,"Erode image%s with kernel [%u] and %s boundary conditions, "
7214                   "in %s mode.",
7215                   gmic_selection.data(),
7216                   *ind,
7217                   boundary?"neumann":"dirichlet",
7218                   is_real?"real":"binary");
7219             const CImg<T> kernel = gmic_image_arg(*ind);
7220             cimg_forY(selection,l) gmic_apply(erode(kernel,(bool)boundary,(bool)is_real));
7221           } else if ((cimg_sscanf(argument,"%f%c",
7222                                   &sx,&end)==1) &&
7223                      sx>=0) {
7224             sx = cimg::round(sx);
7225             print(images,0,"Erode image%s with kernel of size %g and neumann boundary conditions.",
7226                   gmic_selection.data(),
7227                   sx);
7228             cimg_forY(selection,l) gmic_apply(erode((unsigned int)sx));
7229           } else if ((cimg_sscanf(argument,"%f,%f%c",
7230                                   &sx,&sy,&end)==2 ||
7231                       cimg_sscanf(argument,"%f,%f,%f%c",
7232                                   &sx,&sy,&sz,&end)==3) &&
7233                      sx>=0 && sy>=0 && sz>=0) {
7234             sx = cimg::round(sx);
7235             sy = cimg::round(sy);
7236             sz = cimg::round(sz);
7237             print(images,0,"Erode image%s with %gx%gx%g kernel and neumann boundary conditions.",
7238                   gmic_selection.data(),
7239                   sx,sy,sz);
7240             cimg_forY(selection,l) gmic_apply(erode((unsigned int)sx,(unsigned int)sy,(unsigned int)sz));
7241           } else arg_error("erode");
7242           is_change = true; ++position; continue;
7243         }
7244 
7245         // Build 3d elevation.
7246         if (!std::strcmp("elevation3d",command)) {
7247           gmic_substitute_args(true);
7248           sep = *formula = *indices = 0;
7249           float fact = 1;
7250           if (cimg_sscanf(argument,"'%4095[^']'%c",formula,&end)==1) {
7251             print(images,0,"Build 3D elevation of image%s, with elevation formula '%s'.",
7252                   gmic_selection.data(),
7253                   formula);
7254             cimg_forY(selection,l) {
7255               CImg<T>& img = gmic_check(images[selection[l]]);
7256               CImg<typename CImg<T>::Tfloat> elev(img.width(),img.height(),1,1,formula,true);
7257               img.get_elevation3d(primitives,g_list_f,elev).move_to(vertices);
7258               vertices.object3dtoCImg3d(primitives,g_list_f,false);
7259               gmic_apply(replace(vertices));
7260               primitives.assign();
7261             }
7262             ++position;
7263           } else if (cimg_sscanf(argument,"[%255[a-zA-Z0-9_.%+-]%c%c",indices,&sep,&end)==2 &&
7264                      sep==']' &&
7265                      (ind=selection2cimg(indices,images.size(),images_names,"elevation3d")).height()==1) {
7266             print(images,0,"Build 3D elevation of image%s, with elevation map [%u].",
7267                   gmic_selection.data(),
7268                   *ind);
7269             CImg<typename CImg<T>::Tfloat> elev;
7270             if (images[*ind].spectrum()>1) images[*ind].get_norm().move_to(elev);
7271             else elev = gmic_image_arg(*ind);
7272             cimg_forY(selection,l) {
7273               CImg<T>& img = gmic_check(images[selection[l]]);
7274               img.get_elevation3d(primitives,g_list_f,elev).move_to(vertices);
7275               vertices.object3dtoCImg3d(primitives,g_list_f,false);
7276               gmic_apply(replace(vertices));
7277               primitives.assign();
7278             }
7279             ++position;
7280           } else {
7281             if (cimg_sscanf(argument,"%f%c",
7282                             &fact,&end)==1) {
7283               print(images,0,"Build 3D elevation of image%s, with elevation factor %g.",
7284                     gmic_selection.data(),
7285                     fact);
7286               ++position;
7287             } else
7288               print(images,0,"Build 3D elevation of image%s.",
7289                     gmic_selection.data());
7290             cimg_forY(selection,l) {
7291               CImg<T>& img = gmic_check(images[selection[l]]);
7292               CImg<typename CImg<T>::Tfloat> elev;
7293               if (fact==1 && img.spectrum()==1) elev = img.get_shared();
7294               else if (img.spectrum()>1) (img.get_norm().move_to(elev))*=fact;
7295               else (elev = img)*=fact;
7296               img.get_elevation3d(primitives,g_list_f,elev).move_to(vertices);
7297               vertices.object3dtoCImg3d(primitives,g_list_f,false);
7298               gmic_apply(replace(vertices));
7299               primitives.assign();
7300             }
7301           }
7302           g_list_f.assign();
7303           is_change = true; continue;
7304         }
7305 
7306         // Eigenvalues/eigenvectors.
7307         if (!std::strcmp("eigen",command)) {
7308           print(images,0,"Compute eigen-values/vectors of symmetric matri%s or matrix field%s.",
7309                 selection.height()>1?"ce":"x",gmic_selection.data());
7310           unsigned int off = 0;
7311           cimg_forY(selection,l) {
7312             const unsigned int uind = selection[l] + off;
7313             name = images_names[uind];
7314             CImg<float> val, vec;
7315             gmic_check(images[uind]).gmic_symmetric_eigen(val,vec);
7316             if (is_get) {
7317               images_names.insert(name.copymark());
7318               name.move_to(images_names);
7319               val.move_to(images);
7320               vec.move_to(images);
7321             } else {
7322               images_names.insert(name.get_copymark(),uind + 1); name.move_to(images_names[uind]);
7323               val.move_to(images[uind].assign()); images.insert(vec,uind + 1);
7324               ++off;
7325             }
7326           }
7327           is_change = true; continue;
7328         }
7329 
7330         goto gmic_commands_others;
7331 
7332         //-----------------------------
7333         // Commands starting by 'f...'
7334         //-----------------------------
7335       gmic_commands_f :
7336 
7337         // For.
7338         if (!is_get && !std::strcmp("for",item)) {
7339           gmic_substitute_args(false);
7340           is_cond = check_cond(argument,images,"for");
7341           const bool is_first = !nb_fordones || fordones(0,nb_fordones - 1)!=position;
7342           if (is_very_verbose)
7343             print(images,0,"%s %s -> condition '%s' %s.",
7344                   !is_first?"Reach":is_cond?"Start":"Skip",
7345                   is_first?"'for...done' block":"'for' command",
7346                   gmic_argument_text_printed(),
7347                   is_cond?"holds":"does not hold");
7348           if (is_cond) {
7349             if (is_first) {
7350               if (is_debug_info && debug_line!=~0U) {
7351                 cimg_snprintf(argx,_argx.width(),"*for#%u",debug_line);
7352                 CImg<char>::string(argx).move_to(callstack);
7353               } else CImg<char>::string("*for").move_to(callstack);
7354               if (nb_fordones>=fordones._height) fordones.resize(2,std::max(2*fordones._height,8U),1,1,0);
7355               fordones(0,nb_fordones) = position;
7356               fordones(1,nb_fordones++) = 0;
7357             }
7358             ++position;
7359           } else {
7360             int nb_repeat_fors = 0;
7361             for (nb_repeat_fors = 1; nb_repeat_fors && position<commands_line.size(); ++position) {
7362               const char *it = commands_line[position].data();
7363               it+=*it=='-';
7364               if (!std::strcmp("repeat",it) || !std::strcmp("for",it)) ++nb_repeat_fors;
7365               else if (!std::strcmp("done",it)) --nb_repeat_fors;
7366             }
7367             if (nb_repeat_fors && position>=commands_line.size())
7368               error(true,images,0,0,
7369                     "Command 'for': Missing associated 'done' command.");
7370             if (!is_first) { --nb_fordones; callstack.remove(); }
7371           }
7372           continue;
7373         }
7374 
7375         // Fill.
7376         if (!std::strcmp("fill",command)) {
7377           gmic_substitute_args(true);
7378           sep = *indices = 0;
7379           value = 0;
7380           if (cimg_sscanf(argument,"%lf%c",
7381                           &value,&end)==1) {
7382             print(images,0,"Fill image%s with %g.",
7383                   gmic_selection.data(),
7384                   value);
7385             cimg_forY(selection,l) gmic_apply(fill((T)value));
7386           } else if (cimg_sscanf(argument,"[%255[a-zA-Z0-9_.%+-]%c%c",indices,&sep,&end)==2 &&
7387                      sep==']' &&
7388                      (ind=selection2cimg(indices,images.size(),images_names,"fill")).height()==1) {
7389             print(images,0,"Fill image%s with values from image [%u].",
7390                   gmic_selection.data(),
7391                   *ind);
7392             const CImg<T> values = gmic_image_arg(*ind);
7393             cimg_forY(selection,l) gmic_apply(fill(values));
7394           } else {
7395             gmic_argument_text_printed();
7396             if (*argument_text=='\'') cimg::strpare(argument_text,'\'',true,false);
7397             print(images,0,"Fill image%s with expression '%s'.",
7398                   gmic_selection.data(),
7399                   argument_text.data());
7400             name.assign(argument,(unsigned int)std::strlen(argument) + 1);
7401             cimg::strpare(name,'\'',true,false);
7402             strreplace_fw(name);
7403             cimg_forY(selection,l) gmic_apply(fill(name.data(),true,true,&images,&images));
7404           }
7405           is_change = true; ++position; continue;
7406         }
7407 
7408         // Flood fill.
7409         if (!std::strcmp("flood",command)) {
7410           gmic_substitute_args(false);
7411           float x = 0, y = 0, z = 0, tolerance = 0;
7412           sepx = sepy = sepz = *argx = *argy = *argz = *color = 0;
7413           is_high_connectivity = 0;
7414           opacity = 1;
7415           if ((cimg_sscanf(argument,"%255[0-9.eE%+-]%c",
7416                            argx,&end)==1 ||
7417                cimg_sscanf(argument,"%255[0-9.eE%+-],%255[0-9.eE%+-]%c",
7418                            argx,argy,&end)==2 ||
7419                cimg_sscanf(argument,"%255[0-9.eE%+-],%255[0-9.eE%+-],%255[0-9.eE%+-]%c",
7420                            argx,argy,argz,&end)==3 ||
7421                cimg_sscanf(argument,"%255[0-9.eE%+-],%255[0-9.eE%+-],%255[0-9.eEinfa%+-],%f%c",
7422                            argx,argy,argz,&tolerance,&end)==4 ||
7423                cimg_sscanf(argument,"%255[0-9.eE%+-],%255[0-9.eE%+-],%255[0-9.eEinfa%+-],%f,%u%c",
7424                            argx,argy,argz,&tolerance,&is_high_connectivity,&end)==5 ||
7425                cimg_sscanf(argument,"%255[0-9.eE%+-],%255[0-9.eE%+-],%255[0-9.eEinfa%+-],%f,%u,%f%c",
7426                            argx,argy,argz,&tolerance,&is_high_connectivity,&opacity,&end)==6 ||
7427                cimg_sscanf(argument,"%255[0-9.eE%+-],%255[0-9.eE%+-],%255[0-9.eEinfa%+-],%f,%u,%f,"
7428                            "%4095[0-9.eEinfa,+-]%c",
7429                            argx,argy,argz,&tolerance,&is_high_connectivity,
7430                            &opacity,color,&end)==7) &&
7431               (cimg_sscanf(argx,"%f%c",&x,&end)==1 ||
7432                (cimg_sscanf(argx,"%f%c%c",&x,&sepx,&end)==2 && sepx=='%')) &&
7433               (!*argy ||
7434                cimg_sscanf(argy,"%f%c",&y,&end)==1 ||
7435                (cimg_sscanf(argy,"%f%c%c",&y,&sepy,&end)==2 && sepy=='%')) &&
7436               (!*argz ||
7437                cimg_sscanf(argz,"%f%c",&z,&end)==1 ||
7438                (cimg_sscanf(argz,"%f%c%c",&z,&sepz,&end)==2 && sepz=='%')) &&
7439               tolerance>=0) {
7440             print(images,0,
7441                   "Flood fill image%s from (%g%s,%g%s,%g%s), with tolerance %g, %s connectivity, "
7442                   "opacity %g and color (%s).",
7443                   gmic_selection.data(),
7444                   x,sepx=='%'?"%":"",
7445                   y,sepy=='%'?"%":"",
7446                   z,sepz=='%'?"%":"",
7447                   tolerance,
7448                   is_high_connectivity?"high":"low",
7449                   opacity,
7450                   *color?color:"default");
7451             cimg_forY(selection,l) {
7452               CImg<T> &img = images[selection[l]];
7453               g_img.assign(img.spectrum(),1,1,1,(T)0).fill(color,true,false);
7454               const int
7455                 nx = (int)cimg::round(sepx=='%'?x*(img.width() - 1)/100:x),
7456                 ny = (int)cimg::round(sepy=='%'?y*(img.height() - 1)/100:y),
7457                 nz = (int)cimg::round(sepz=='%'?z*(img.depth() - 1)/100:z);
7458               gmic_apply(draw_fill(nx,ny,nz,g_img.data(),opacity,tolerance,(bool)is_high_connectivity));
7459             }
7460           } else arg_error("flood");
7461           g_img.assign();
7462           is_change = true; ++position; continue;
7463         }
7464 
7465         // List of directory files.
7466         if (!is_get && !std::strcmp("files",item)) {
7467           gmic_substitute_args(false);
7468           unsigned int mode = 5;
7469           if ((*argument>='0' && *argument<='5') &&
7470               argument[1]==',' && argument[2]) {
7471             mode = (unsigned int)(*argument - '0');
7472             argument+=2;
7473           }
7474           const unsigned int _mode = mode%3;
7475           print(images,0,"Get list of %s from location '%s'.",
7476                 _mode==0?"files":_mode==1?"folders":"files and folders",
7477                 argument);
7478           g_list_c = cimg::files(argument,true,_mode,mode>=3);
7479           cimglist_for(g_list_c,l) {
7480             strreplace_bw(g_list_c[l]);
7481             g_list_c[l].back() = ',';
7482           }
7483           if (g_list_c) {
7484             g_list_c.back().back() = 0;
7485             (g_list_c>'x').move_to(status);
7486           } else status.assign();
7487           g_list_c.assign();
7488           ++position; continue;
7489         }
7490 
7491         // Set 3D focale.
7492         if (!is_get && !std::strcmp("focale3d",item)) {
7493           gmic_substitute_args(false);
7494           value = 700;
7495           if (cimg_sscanf(argument,"%lf%c",&value,&end)==1) ++position;
7496           else value = 700;
7497           focale3d = (float)value;
7498           print(images,0,"Set 3D focale to %g.",
7499                 focale3d);
7500           continue;
7501         }
7502 
7503         goto gmic_commands_others;
7504 
7505         //-----------------------------
7506         // Commands starting by 'g...'
7507         //-----------------------------
7508       gmic_commands_g :
7509 
7510         // Greater or equal.
7511         gmic_arithmetic_command("ge",
7512                                 operator_ge,
7513                                 "Compute boolean 'greater or equal than' between image%s and %g%s",
7514                                 gmic_selection.data(),value,ssep,T,
7515                                 operator_ge,
7516                                 "Compute boolean 'greater or equal than' between image%s "
7517                                 "and image [%d]",
7518                                 gmic_selection.data(),ind[0],
7519                                 operator_ge,
7520                                 "Compute boolean 'greater or equal than' between image%s "
7521                                 "and expression %s'",
7522                                 gmic_selection.data(),gmic_argument_text_printed(),
7523                                 "Compute boolean 'greater or equal than' between image%s");
7524 
7525         // Greater than.
7526         gmic_arithmetic_command("gt",
7527                                 operator_gt,
7528                                 "Compute boolean 'greater than' between image%s and %g%s",
7529                                 gmic_selection.data(),value,ssep,T,
7530                                 operator_gt,
7531                                 "Compute boolean 'greater than' between image%s and image [%d]",
7532                                 gmic_selection.data(),ind[0],
7533                                 operator_gt,
7534                                 "Compute boolean 'greater than' between image%s and expression %s'",
7535                                 gmic_selection.data(),gmic_argument_text_printed(),
7536                                 "Compute boolean 'greater than' between image%s");
7537 
7538         // Compute gradient.
7539         if (!std::strcmp("gradient",command)) {
7540           gmic_substitute_args(false);
7541           int scheme = 0;
7542           *argx = 0;
7543           if ((cimg_sscanf(argument,"%15[xyz]%c",
7544                            argx,&end)==1 ||
7545                cimg_sscanf(argument,"%15[xyz],%d%c",
7546                            argx,&scheme,&end)==2) &&
7547               scheme>=-1 && scheme<=5) {
7548             ++position;
7549             print(images,0,"Compute gradient of image%s along axes '%s', with %s scheme.",
7550                   gmic_selection.data(),
7551                   argx,
7552                   scheme==-1?"backward differences":scheme==4?"deriche":scheme==5?"vanvliet":
7553                   scheme==1?"forward differences":scheme==2?"sobel":
7554                   scheme==3?"rotation invariant":"centered differences");
7555           } else print(images,0,"Compute gradient of image%s, with rotation invariant scheme.",
7556                        gmic_selection.data());
7557           unsigned int off = 0;
7558           cimg_forY(selection,l) {
7559             const unsigned int uind = selection[l] + off;
7560             CImg<T>& img = gmic_check(images[uind]);
7561             g_list = img.get_gradient(*argx?argx:0,scheme);
7562             name = images_names[uind];
7563             if (is_get) {
7564               images_names.insert(g_list.size(),name.copymark());
7565               g_list.move_to(images,~0U);
7566             } else {
7567               off+=g_list.size() - 1;
7568               g_list[0].move_to(images[uind].assign());
7569               for (unsigned int i = 1; i<g_list.size(); ++i) g_list[i].move_to(images,uind + i);
7570               images_names[uind] = name;
7571               if (g_list.size()>1)
7572                 images_names.insert(g_list.size() - 1,name.copymark(),uind + 1);
7573             }
7574           }
7575           g_list.assign();
7576           is_change = true; continue;
7577         }
7578 
7579         // Guided filter.
7580         if (!std::strcmp("guided",command)) {
7581           gmic_substitute_args(true);
7582           float radius = 0, regularization = 0;
7583           *argz = *argc = 0;
7584           sep0 = sep1 = 0;
7585           if (cimg_sscanf(argument,"[%255[a-zA-Z0-9_.%+-]],%255[0-9.eE%+-],%255[0-9.eE%+-]%c",
7586                           indices,argx,argy,&end)==3 &&
7587               (cimg_sscanf(argx,"%f%c",&radius,&end)==1 ||
7588                (cimg_sscanf(argx,"%f%c%c",&radius,&sep0,&end)==2 && sep0=='%')) &&
7589               (cimg_sscanf(argy,"%f%c",&regularization,&end)==1 ||
7590                (cimg_sscanf(argy,"%f%c%c",&regularization,&sep1,&end)==2 && sep1=='%')) &&
7591               (ind=selection2cimg(indices,images.size(),images_names,"guided")).height()==1 &&
7592               radius>=0 && regularization>=0) {
7593             print(images,0,"Apply guided filter on image%s, with guide image [%u], "
7594                   "radius %g%s and regularization %g%s.",
7595                   gmic_selection.data(),
7596                   *ind,
7597                   radius,sep0=='%'?"%":"",
7598                   regularization,sep1=='%'?"%":"");
7599             const CImg<T> guide = gmic_image_arg(*ind);
7600             if (sep0=='%') radius = -radius;
7601             if (sep1=='%') regularization = -regularization;
7602             cimg_forY(selection,l) gmic_apply(blur_guided(guide,radius,regularization));
7603           } else if (cimg_sscanf(argument,"%255[0-9.eE%+-],%255[0-9.eE%+-]%c",
7604                                  argx,argy,&end)==2 &&
7605                      (cimg_sscanf(argx,"%f%c",&radius,&end)==1 ||
7606                       (cimg_sscanf(argx,"%f%c%c",&radius,&sep0,&end)==2 && sep0=='%')) &&
7607                      (cimg_sscanf(argy,"%f%c",&regularization,&end)==1 ||
7608                       (cimg_sscanf(argy,"%f%c%c",&regularization,&sep1,&end)==2 && sep1=='%')) &&
7609                      radius>=0 && regularization>=0) {
7610             print(images,0,"Apply guided filter on image%s, with radius %g%s and regularization %g%s.",
7611                   gmic_selection.data(),
7612                   radius,sep0=='%'?"%":"",
7613                   regularization,sep1=='%'?"%":"");
7614             if (sep0=='%') radius = -radius;
7615             if (sep1=='%') regularization = -regularization;
7616             cimg_forY(selection,l) gmic_apply(blur_guided(images[selection[l]],radius,regularization));
7617           } else arg_error("guided");
7618           is_change = true; ++position; continue;
7619         }
7620 
7621         // Draw graph.
7622         if (!std::strcmp("graph",command)) {
7623           gmic_substitute_args(true);
7624           double ymin = 0, ymax = 0, xmin = 0, xmax = 0;
7625           unsigned int plot_type = 1, vertex_type = 1;
7626           float resolution = 65536;
7627           *formula = *color = sep = sep1 = 0;
7628           pattern = ~0U; opacity = 1;
7629           if (((cimg_sscanf(argument,"'%1023[^']%c%c",
7630                             formula,&sep,&end)==2 && sep=='\'') ||
7631                cimg_sscanf(argument,"'%1023[^']',%f%c",
7632                            formula,&resolution,&end)==2 ||
7633                cimg_sscanf(argument,"'%1023[^']',%f,%u%c",
7634                            formula,&resolution,&plot_type,&end)==3 ||
7635                cimg_sscanf(argument,"'%1023[^']',%f,%u,%u%c",
7636                            formula,&resolution,&plot_type,&vertex_type,&end)==4 ||
7637                cimg_sscanf(argument,"'%1023[^']',%f,%u,%u,%lf,%lf%c",
7638                            formula,&resolution,&plot_type,&vertex_type,&xmin,&xmax,&end)==6 ||
7639                cimg_sscanf(argument,"'%1023[^']',%f,%u,%u,%lf,%lf,%lf,%lf%c",
7640                            formula,&resolution,&plot_type,&vertex_type,&xmin,&xmax,
7641                            &ymin,&ymax,&end)==8 ||
7642                cimg_sscanf(argument,"'%1023[^']',%f,%u,%u,%lf,%lf,%lf,%lf,%f%c",
7643                            formula,&resolution,&plot_type,&vertex_type,
7644                            &xmin,&xmax,&ymin,&ymax,&opacity,&end)==9 ||
7645                (cimg_sscanf(argument,"'%1023[^']',%f,%u,%u,%lf,%lf,%lf,%lf,%f,0%c%x%c",
7646                             formula,&resolution,&plot_type,&vertex_type,&xmin,&xmax,
7647                             &ymin,&ymax,&opacity,&sep1,&pattern,&end)==11 && sep1=='x') ||
7648                (cimg_sscanf(argument,"'%1023[^']',%f,%u,%u,%lf,%lf,%lf,%lf,%f,%4095[0-9.eEinfa,+-]%c",
7649                             formula,&resolution,&plot_type,&vertex_type,&xmin,&xmax,&ymin,&ymax,
7650                             &opacity,color,&end)==10 && (bool)(pattern=~0U)) ||
7651                (*color=0,cimg_sscanf(argument,"'%1023[^']',%f,%u,%u,%lf,%lf,%lf,%lf,%f,0%c%x,"
7652                                      "%4095[0-9.eEinfa,+-]%c",
7653                                      formula,&resolution,&plot_type,&vertex_type,&xmin,&xmax,
7654                                      &ymin,&ymax,&opacity,&sep1,&pattern,color,&end)==12 &&
7655                 sep1=='x')) &&
7656               resolution>0 && plot_type<=3 && vertex_type<=7) {
7657             resolution = cimg::round(resolution);
7658             strreplace_fw(formula);
7659             print(images,0,
7660                   "Draw graph of formula '%s' on image%s, with resolution %g, %s contours, "
7661                   "%s vertices, x-range = (%g,%g), y-range = (%g,%g), opacity %g, "
7662                   "pattern 0x%x and color (%s).",
7663                   formula,
7664                   gmic_selection.data(),
7665                   resolution,
7666                   plot_type==0?"no":plot_type==1?"linear":plot_type==2?"spline":"bar",
7667                   vertex_type==0?"no":vertex_type==1?"dot":vertex_type==2?"straight cross":
7668                   vertex_type==3?"diagonal cross":vertex_type==4?"filled circle":
7669                   vertex_type==5?"outlined circle":vertex_type==6?"square":"diamond",
7670                   xmin,xmax,
7671                   ymin,ymax,
7672                   opacity,pattern,
7673                   *color?color:"default");
7674             if (xmin==0 && xmax==0) { xmin = -4; xmax = 4; }
7675             if (!plot_type && !vertex_type) plot_type = 1;
7676             if (resolution<1) resolution = 65536;
7677 
7678             CImg<double> values(4,(unsigned int)resolution--,1,1,0);
7679             const double dx = xmax - xmin;
7680             cimg_forY(values,X) values(0,X) = xmin + X*dx/resolution;
7681             cimg::eval(formula,values).move_to(values);
7682 
7683             cimg_forY(selection,l) {
7684               CImg<T> &img = images[selection[l]];
7685               g_img.assign(img.spectrum(),1,1,1,(T)0).fill(color,true,false);
7686               gmic_apply(draw_graph(values,g_img.data(),opacity,plot_type,vertex_type,ymin,ymax,pattern));
7687             }
7688           } else if (((cimg_sscanf(argument,"[%255[a-zA-Z0-9_.%+-]%c%c",
7689                                    indices,&sep,&end)==2 && sep==']') ||
7690                       cimg_sscanf(argument,"[%255[a-zA-Z0-9_.%+-]],%u%c",
7691                                   indices,&plot_type,&end)==2 ||
7692                       cimg_sscanf(argument,"[%255[a-zA-Z0-9_.%+-]],%u,%u%c",
7693                                   indices,&plot_type,&vertex_type,&end)==3 ||
7694                       cimg_sscanf(argument,"[%255[a-zA-Z0-9_.%+-]],%u,%u,%lf,%lf%c",
7695                                   indices,&plot_type,&vertex_type,&ymin,&ymax,&end)==5 ||
7696                       cimg_sscanf(argument,"[%255[a-zA-Z0-9_.%+-]],%u,%u,%lf,%lf,%f%c",
7697                                   indices,&plot_type,&vertex_type,&ymin,&ymax,&opacity,&end)==6||
7698                       (cimg_sscanf(argument,"[%255[a-zA-Z0-9_.%+-]],%u,%u,%lf,%lf,%f,0%c%x%c",
7699                                    indices,&plot_type,&vertex_type,&ymin,&ymax,&opacity,&sep1,
7700                                    &pattern,&end)==8 &&
7701                        sep1=='x') ||
7702                       (cimg_sscanf(argument,"[%255[a-zA-Z0-9_.%+-]],%u,%u,%lf,%lf,%f,"
7703                                    "%4095[0-9.eEinfa,+-]%c",
7704                                    indices,&plot_type,&vertex_type,&ymin,&ymax,&opacity,
7705                                    color,&end)==7 &&
7706                        (bool)(pattern=~0U)) ||
7707                       (*color=0,cimg_sscanf(argument,"[%255[a-zA-Z0-9_.%+-]],%u,%u,%lf,%lf,"
7708                                             "%f,0%c%x,%4095[0-9.eEinfa,+-]%c",
7709                                             indices,&plot_type,&vertex_type,&ymin,&ymax,
7710                                             &opacity,&sep1,&pattern,color,&end)==9 &&
7711                        sep1=='x')) &&
7712                      (ind=selection2cimg(indices,images.size(),images_names,"graph")).height()==1 &&
7713                      plot_type<=3 && vertex_type<=7) {
7714             if (!plot_type && !vertex_type) plot_type = 1;
7715             print(images,0,"Draw graph of dataset [%u] on image%s, with %s contours, %s vertices, "
7716                   "y-range = (%g,%g), opacity %g, pattern 0x%x and color (%s).",
7717                   *ind,
7718                   gmic_selection.data(),
7719                   plot_type==0?"no":plot_type==1?"linear":plot_type==2?"spline":"bar",
7720                   vertex_type==0?"no":vertex_type==1?"dot":vertex_type==2?"straight cross":
7721                   vertex_type==3?"diagonal cross":vertex_type==4?"filled circle":
7722                   vertex_type==5?"outlined circle":vertex_type==6?"square":"diamond",
7723                   ymin,ymax,
7724                   opacity,pattern,
7725                   *color?color:"default");
7726             const CImg<T> values = gmic_image_arg(*ind);
7727             cimg_forY(selection,l) {
7728               CImg<T> &img = images[selection[l]];
7729               g_img.assign(img.spectrum(),1,1,1,(T)0).fill(color,true,false);
7730               gmic_apply(draw_graph(values,g_img.data(),opacity,plot_type,vertex_type,ymin,ymax,pattern));
7731             }
7732           } else arg_error("graph");
7733           g_img.assign();
7734           is_change = true; ++position; continue;
7735         }
7736 
7737         goto gmic_commands_others;
7738 
7739         //-----------------------------
7740         // Commands starting by 'h...'
7741         //-----------------------------
7742       gmic_commands_h :
7743 
7744         // Histogram.
7745         if (!std::strcmp("histogram",command)) {
7746           gmic_substitute_args(false);
7747           float nb_levels = 256;
7748           bool no_min_max = false;
7749           sep = sep0 = sep1 = 0;
7750           value0 = value1 = 0;
7751           if (((cimg_sscanf(argument,"%f%c",
7752                             &nb_levels,&end)==1 && (no_min_max=true)) ||
7753                ((cimg_sscanf(argument,"%f%c%c",
7754                              &nb_levels,&sep,&end)==2 && sep=='%') && (no_min_max=true)) ||
7755                cimg_sscanf(argument,"%f,%lf,%lf%c",
7756                            &nb_levels,&value0,&value1,&end)==3 ||
7757                (cimg_sscanf(argument,"%f%c,%lf,%lf%c",
7758                             &nb_levels,&sep,&value0,&value1,&end)==4 && sep=='%') ||
7759                (cimg_sscanf(argument,"%f,%lf%c,%lf%c",
7760                             &nb_levels,&value0,&sep0,&value1,&end)==4 && sep0=='%') ||
7761                (cimg_sscanf(argument,"%f%c,%lf%c,%lf%c",
7762                             &nb_levels,&sep,&value0,&sep0,&value1,&end)==5 && sep=='%' &&
7763                 sep0=='%') ||
7764                (cimg_sscanf(argument,"%f,%lf,%lf%c%c",
7765                             &nb_levels,&value0,&value1,&sep1,&end)==4 && sep1=='%') ||
7766                (cimg_sscanf(argument,"%f%c,%lf,%lf%c%c",
7767                             &nb_levels,&sep,&value0,&value1,&sep1,&end)==5 && sep=='%' &&
7768                 sep1=='%') ||
7769                (cimg_sscanf(argument,"%f,%lf%c,%lf%c%c",
7770                             &nb_levels,&value0,&sep0,&value1,&sep1,&end)==5 && sep0=='%' &&
7771                 sep1=='%') ||
7772                (cimg_sscanf(argument,"%f%c,%lf%c,%lf%c%c",
7773                             &nb_levels,&sep,&value0,&sep0,&value1,&sep1,&end)==6 && sep=='%' &&
7774                 sep0=='%' && sep1=='%')) &&
7775               nb_levels>=0.5) { nb_levels = cimg::round(nb_levels); ++position; }
7776           else { nb_levels = 256; value0 = 0; value1 = 100; sep = 0; sep0 = sep1 = '%'; }
7777           if (no_min_max) { value0 = 0; value1 = 100; sep0 = sep1 = '%'; }
7778           print(images,0,"Compute histogram of image%s, using %g%s level%s in range [%g%s,%g%s].",
7779                 gmic_selection.data(),
7780                 nb_levels,sep=='%'?"%":"",
7781                 nb_levels>1?"s":"",
7782                 value0,sep0=='%'?"%":"",
7783                 value1,sep1=='%'?"%":"");
7784           cimg_forY(selection,l) {
7785             CImg<T> &img = gmic_check(images[selection[l]]);
7786             nvalue0 = value0; nvalue1 = value1;
7787             vmin = vmax = 0;
7788             if (sep0=='%' || sep1=='%') {
7789               if (img) vmax = (double)img.max_min(vmin);
7790               if (sep0=='%') nvalue0 = vmin + (vmax - vmin)*value0/100;
7791               if (sep1=='%') nvalue1 = vmin + (vmax - vmin)*value1/100;
7792             }
7793             const unsigned int
7794               _nb_levels = std::max(1U,
7795                                     (unsigned int)cimg::round(sep=='%'?
7796                                                               nb_levels*(1 + nvalue1 - nvalue0)/100:
7797                                                               nb_levels));
7798             gmic_apply(histogram(_nb_levels,(T)nvalue0,(T)nvalue1));
7799           }
7800           is_change = true; continue;
7801         }
7802 
7803         // Compute Hessian.
7804         if (!std::strcmp("hessian",command)) {
7805           gmic_substitute_args(false);
7806           *argx = 0;
7807           if (cimg_sscanf(argument,"%255[xyz]%c",
7808                           argx,&end)==1) {
7809             ++position;
7810             print(images,0,"Compute Hessian of image%s along axes '%s'.",
7811                   gmic_selection.data(),
7812                   argx);
7813           } else
7814             print(images,0,"Compute Hessian of image%s.",
7815                   gmic_selection.data());
7816           unsigned int off = 0;
7817           cimg_forY(selection,l) {
7818             const unsigned int uind = selection[l] + off;
7819             CImg<T>& img = gmic_check(images[uind]);
7820             g_list = img.get_hessian(*argx?argx:0);
7821             name = images_names[uind];
7822             if (is_get) {
7823               images_names.insert(g_list.size(),name.copymark());
7824               g_list.move_to(images,~0U);
7825             } else {
7826               off+=g_list.size() - 1;
7827               g_list[0].move_to(images[uind].assign());
7828               for (unsigned int i = 1; i<g_list.size(); ++i) g_list[i].move_to(images,uind + i);
7829               images_names[uind] = name;
7830               if (g_list.size()>1) images_names.insert(g_list.size() - 1,name.copymark(),uind + 1);
7831             }
7832           }
7833           g_list.assign();
7834           is_change = true; continue;
7835         }
7836 
7837         goto gmic_commands_others;
7838 
7839         //-----------------------------
7840         // Commands starting by 'i...'
7841         //-----------------------------
7842       gmic_commands_i :
7843 
7844         // Draw image.
7845         if (!std::strcmp("image",command)) {
7846           gmic_substitute_args(true);
7847           name.assign(256);
7848           float x = 0, y = 0, z = 0, c = 0, max_opacity_mask = 1;
7849           *indices = *name = *argx = *argy = *argz = *argc = sep = sepx = sepy = sepz = sepc = 0;
7850           ind0.assign();
7851           opacity = 1;
7852           if (((cimg_sscanf(argument,"[%255[a-zA-Z0-9_.%+-]%c%c",
7853                             indices,&sep,&end)==2 && sep==']') ||
7854                cimg_sscanf(argument,"[%255[a-zA-Z0-9_.%+-]],%255[0-9.eE%~+-]%c",
7855                            indices,argx,&end)==2 ||
7856                cimg_sscanf(argument,"[%255[a-zA-Z0-9_.%+-]],%255[0-9.eE%~+-],%255[0-9.eE%~+-]%c",
7857                            indices,argx,argy,&end)==3 ||
7858                cimg_sscanf(argument,"[%255[a-zA-Z0-9_.%+-]],%255[0-9.eE%~+-],%255[0-9.eE%~+-],"
7859                            "%255[0-9.eE%~+-]%c",
7860                            indices,argx,argy,argz,&end)==4 ||
7861                cimg_sscanf(argument,"[%255[a-zA-Z0-9_.%+-]],%255[0-9.eE%~+-],%255[0-9.eE%~+-],"
7862                            "%255[0-9.eE%~+-],%255[0-9.eE%~+-]%c",
7863                            indices,argx,argy,argz,argc,&end)==5 ||
7864                cimg_sscanf(argument,"[%255[a-zA-Z0-9_.%+-]],%255[0-9.eE%~+-],%255[0-9.eE%~+-],"
7865                            "%255[0-9.eE%~+-],%255[0-9.eE%~+-],%f%c",
7866                            indices,argx,argy,argz,argc,&opacity,&end)==6 ||
7867                (cimg_sscanf(argument,"[%255[a-zA-Z0-9_.%+-]],%255[0-9.eE%~+-],%255[0-9.eE%~+-],"
7868                             "%255[0-9.eE%~+-],%255[0-9.eE%~+-],%f,[%255[a-zA-Z0-9_.%+-]%c%c",
7869                             indices,argx,argy,argz,argc,&opacity,name.data(),&sep,&end)==8 &&
7870                 sep==']') ||
7871                cimg_sscanf(argument,"[%255[a-zA-Z0-9_.%+-]],%255[0-9.eE%~+-],%255[0-9.eE%~+-],"
7872                            "%255[0-9.eE%~+-],%255[0-9.eE%~+-],%f,[%255[a-zA-Z0-9_.%+-]],%f%c",
7873                            indices,argx,argy,argz,argc,&opacity,name.data(),
7874                            &max_opacity_mask,&end)==8) &&
7875               (ind=selection2cimg(indices,images.size(),images_names,"image")).height()==1 &&
7876               (!*name ||
7877                (ind0=selection2cimg(name,images.size(),images_names,"image")).height()==1) &&
7878               (!*argx ||
7879                cimg_sscanf(argx,"%f%c",&x,&end)==1 ||
7880                (cimg_sscanf(argx,"%f%c%c",&x,&sepx,&end)==2 && (sepx=='%' || sepx=='~'))) &&
7881               (!*argy ||
7882                cimg_sscanf(argy,"%f%c",&y,&end)==1 ||
7883                (cimg_sscanf(argy,"%f%c%c",&y,&sepy,&end)==2 && (sepy=='%' || sepy=='~'))) &&
7884               (!*argz ||
7885                cimg_sscanf(argz,"%f%c",&z,&end)==1 ||
7886                (cimg_sscanf(argz,"%f%c%c",&z,&sepz,&end)==2 && (sepz=='%' || sepz=='~'))) &&
7887               (!*argc ||
7888                cimg_sscanf(argc,"%f%c",&c,&end)==1 ||
7889                (cimg_sscanf(argc,"%f%c%c",&c,&sepc,&end)==2 && (sepc=='%' || sepc=='~')))) {
7890             const CImg<T> sprite = gmic_image_arg(*ind);
7891             CImg<T> mask;
7892             if (ind0) {
7893               mask = gmic_image_arg(*ind0);
7894               print(images,0,"Draw image [%u] at (%g%s,%g%s,%g%s,%g%s) on image%s, "
7895                     "with opacity %g and mask [%u].",
7896                     *ind,
7897                     x,sepx=='%'?"%":sepx=='~'?"~":"",
7898                     y,sepy=='%'?"%":sepy=='~'?"~":"",
7899                     z,sepz=='%'?"%":sepz=='~'?"~":"",
7900                     c,sepc=='%'?"%":sepc=='~'?"~":"",
7901                     gmic_selection.data(),
7902                     opacity,
7903                     *ind0);
7904             } else print(images,0,"Draw image [%u] at (%g%s,%g%s,%g%s,%g%s) on image%s, "
7905                          "with opacity %g.",
7906                          *ind,
7907                          x,sepx=='%'?"%":sepx=='~'?"~":"",
7908                          y,sepy=='%'?"%":sepy=='~'?"~":"",
7909                          z,sepz=='%'?"%":sepz=='~'?"~":"",
7910                          c,sepc=='%'?"%":sepc=='~'?"~":"",
7911                          gmic_selection.data(),
7912                          opacity);
7913             cimg_forY(selection,l)
7914               if (ind0) {
7915                 gmic_apply(gmic_draw_image(x,y,z,c,sepx,sepy,sepz,sepc,sprite,mask,opacity,max_opacity_mask));
7916               }
7917               else {
7918                 gmic_apply(gmic_draw_image(x,y,z,c,sepx,sepy,sepz,sepc,sprite,opacity));
7919               }
7920           } else arg_error("image");
7921           is_change = true; ++position; continue;
7922         }
7923 
7924         // Index image with a LUT.
7925         if (!std::strcmp("index",command)) {
7926           gmic_substitute_args(true);
7927           unsigned int lut_type = 0, map_indexes = 0;
7928           float dithering = 0;
7929           sep = 0;
7930           if (((cimg_sscanf(argument,"[%255[a-zA-Z0-9_.%+-]%c%c",
7931                             indices,&sep,&end)==2 && sep==']') ||
7932                cimg_sscanf(argument,"[%255[a-zA-Z0-9_.%+-]],%f%c",
7933                            indices,&dithering,&end)==2 ||
7934                cimg_sscanf(argument,"[%255[a-zA-Z0-9_.%+-]],%f,%u%c",
7935                            indices,&dithering,&map_indexes,&end)==3) &&
7936               (ind=selection2cimg(indices,images.size(),images_names,"index")).height()==1) {
7937             const float ndithering = dithering<0?0:dithering>1?1:dithering;
7938             print(images,0,"Index values in image%s by LUT [%u], with dithering level %g%s.",
7939                   gmic_selection.data(),
7940                   *ind,
7941                   ndithering,
7942                   map_indexes?" and index mapping":"");
7943             const CImg<T> palette = gmic_image_arg(*ind);
7944             cimg_forY(selection,l) gmic_apply(index(palette,ndithering,(bool)map_indexes));
7945           } else if ((cimg_sscanf(argument,"%u%c",&lut_type,&end)==1 ||
7946                       cimg_sscanf(argument,"%u,%f%c",&lut_type,&dithering,&end)==2 ||
7947                       cimg_sscanf(argument,"%u,%f,%u%c",
7948                                   &lut_type,&dithering,&map_indexes,&end)==3) &&
7949                      lut_type<=7) {
7950             const float ndithering = dithering<0?0:dithering>1?1:dithering;
7951             print(images,0,"Index values in image%s by %s color LUT, with dithering level %g%s.",
7952                   gmic_selection.data(),
7953                   lut_type==0?"default":lut_type==1?"HSV":lut_type==2?"lines":lut_type==3?"hot":
7954                   lut_type==4?"cool":lut_type==5?"jet":lut_type==6?"flag":"cube",
7955                   ndithering,map_indexes?" and index mapping":"");
7956             const CImg<T>
7957               palette = lut_type==0?CImg<T>::default_LUT256():lut_type==1?CImg<T>::HSV_LUT256():
7958               lut_type==2?CImg<T>::lines_LUT256():lut_type==3?CImg<T>::hot_LUT256():
7959               lut_type==4?CImg<T>::cool_LUT256():lut_type==5?CImg<T>::jet_LUT256():
7960               lut_type==6?CImg<T>::flag_LUT256():CImg<T>::cube_LUT256();
7961             cimg_forY(selection,l) gmic_apply(index(palette,ndithering,(bool)map_indexes));
7962           } else arg_error("index");
7963           is_change = true; ++position; continue;
7964         }
7965 
7966         // Matrix inverse.
7967         gmic_simple_command("invert",invert,"Invert matrix image%s.");
7968 
7969         // Extract 3D isoline.
7970         if (!std::strcmp("isoline3d",command)) {
7971           gmic_substitute_args(false);
7972           float x0 = -3, y0 = -3, x1 = 3, y1 = 3, dx = 256, dy = 256;
7973           sep = sepx = sepy = *formula = 0;
7974           value = 0;
7975           if (cimg_sscanf(argument,"%lf%c",
7976                           &value,&end)==1 ||
7977               cimg_sscanf(argument,"%lf%c%c",
7978                           &value,&sep,&end)==2) {
7979             print(images,0,"Extract 3D isolines from image%s, using isovalue %g%s.",
7980                   gmic_selection.data(),
7981                   value,sep=='%'?"%":"");
7982             cimg_forY(selection,l) {
7983               const unsigned int uind = selection[l];
7984               CImg<T>& img = gmic_check(images[uind]);
7985               if (img) {
7986                 vertices.assign();
7987                 primitives.assign();
7988                 g_list_uc.assign();
7989                 g_img_uc.assign(3,img.spectrum(),1,1,220).noise(35,1);
7990                 if (img.spectrum()==1) g_img_uc(0) = g_img_uc(1) = g_img_uc(2) = 200;
7991                 else {
7992                   g_img_uc(0,0) = 255; g_img_uc(1,0) = g_img_uc(2,0) = 30;
7993                   g_img_uc(0,1) = g_img_uc(2,1) = 30; g_img_uc(1,1) = 255;
7994                   if (img.spectrum()>=3) { g_img_uc(0,2) = g_img_uc(1,2) = 30; g_img_uc(2,2) = 255; }
7995                 }
7996                 cimg_forC(img,k) {
7997                   const CImg<T> channel = img.get_shared_channel(k);
7998                   nvalue = value;
7999                   if (sep=='%') {
8000                     vmax = (double)channel.max_min(vmin);
8001                     nvalue = vmin + (vmax - vmin)*value/100;
8002                   }
8003                   CImgList<unsigned int> prims;
8004                   const CImg<float> pts = img.get_shared_channel(k).get_isoline3d(prims,(float)nvalue);
8005                   vertices.append_object3d(primitives,pts,prims);
8006                   g_list_uc.insert(prims.size(),CImg<unsigned char>::vector(g_img_uc(0,k),
8007                                                                             g_img_uc(1,k),
8008                                                                             g_img_uc(2,k)));
8009                 }
8010                 if (!vertices)
8011                   warn(images,0,false,
8012                        "Command 'isoline3d': Isovalue %g%s not found in image [%u].",
8013                        value,sep=='%'?"%":"",uind);
8014                 vertices.object3dtoCImg3d(primitives,g_list_uc,false);
8015                 gmic_apply(replace(vertices));
8016                 primitives.assign();
8017                 g_list_uc.assign();
8018                 g_img_uc.assign();
8019               } else gmic_apply(replace(img));
8020             }
8021           } else if ((cimg_sscanf(argument,"'%4095[^']',%lf%c",
8022                                   formula,&value,&end)==2 ||
8023                       cimg_sscanf(argument,"'%4095[^']',%lf,%f,%f,%f,%f%c",
8024                                   formula,&value,&x0,&y0,&x1,&y1,&end)==6 ||
8025                       cimg_sscanf(argument,"'%4095[^']',%lf,%f,%f,%f,%f,%f,%f%c",
8026                                   formula,&value,&x0,&y0,&x1,&y1,&dx,&dy,&end)==8 ||
8027                       (cimg_sscanf(argument,"'%4095[^']',%lf,%f,%f,%f,%f,%f%c,%f%c",
8028                                    formula,&value,&x0,&y0,&x1,&y1,&dx,&sepx,&dy,&end)==9 &&
8029                        sepx=='%') ||
8030                       (cimg_sscanf(argument,"'%4095[^']',%lf,%f,%f,%f,%f,%f,%f%c%c",
8031                                    formula,&value,&x0,&y0,&x1,&y1,&dx,&dy,&sepy,&end)==9 &&
8032                        sepy=='%') ||
8033                       (cimg_sscanf(argument,"'%4095[^']',%lf,%f,%f,%f,%f,%f%c,%f%c%c",
8034                                    formula,&value,&x0,&y0,&x1,&y1,&dx,&sepx,&dy,&sepy,&end)==10&&
8035                        sepx=='%' && sepy=='%')) &&
8036                      dx>0 && dy>0) {
8037             dx = cimg::round(dx);
8038             dy = cimg::round(dy);
8039             strreplace_fw(formula);
8040             print(images,0,"Extract 3D isoline %g from formula '%s', in range (%g,%g)-(%g,%g) "
8041                   "with size %g%sx%g%s.",
8042                   value,
8043                   formula,
8044                   x0,y0,
8045                   x1,y1,
8046                   dx,sepx=='%'?"%":"",
8047                   dy,sepy=='%'?"%":"");
8048             if (sepx=='%') dx = -dx;
8049             if (sepy=='%') dy = -dy;
8050             CImg<T>::isoline3d(primitives,(const char*)formula,(float)value,
8051                                x0,y0,x1,y1,(int)dx,(int)dy).move_to(vertices);
8052             vertices.object3dtoCImg3d(primitives,false).move_to(images);
8053             primitives.assign();
8054             cimg_snprintf(title,_title.width(),"[3D isoline %g of '%s']",value,formula);
8055             CImg<char>::string(title).move_to(images_names);
8056           } else arg_error("isoline3d");
8057           is_change = true; ++position; continue;
8058         }
8059 
8060         // Extract 3D isosurface.
8061         if (!std::strcmp("isosurface3d",command)) {
8062           gmic_substitute_args(false);
8063           float x0 = -3, y0 = -3, z0 = -3, x1 = 3, y1 = 3, z1 = 3,
8064             dx = 32, dy = 32, dz = 32;
8065           sep = sepx = sepy = sepz = *formula = 0;
8066           value = 0;
8067           if (cimg_sscanf(argument,"%lf%c",
8068                           &value,&end)==1 ||
8069               cimg_sscanf(argument,"%lf%c%c",
8070                           &value,&sep,&end)==2) {
8071             print(images,0,"Extract 3D isosurface from image%s, using isovalue %g%s.",
8072                   gmic_selection.data(),
8073                   value,sep=='%'?"%":"");
8074             cimg_forY(selection,l) {
8075               const unsigned int uind = selection[l];
8076               CImg<T>& img = gmic_check(images[uind]);
8077               if (img) {
8078                 vertices.assign();
8079                 primitives.assign();
8080                 g_list_uc.assign();
8081                 g_img_uc.assign(3,img.spectrum(),1,1,220).noise(35,1);
8082                 if (img.spectrum()==1) g_img_uc(0) = g_img_uc(1) = g_img_uc(2) = 200;
8083                 else {
8084                   g_img_uc(0,0) = 255; g_img_uc(1,0) = g_img_uc(2,0) = 30;
8085                   g_img_uc(0,1) = g_img_uc(2,1) = 30; g_img_uc(1,1) = 255;
8086                   if (img.spectrum()>=3) { g_img_uc(0,2) = g_img_uc(1,2) = 30; g_img_uc(2,2) = 255; }
8087                 }
8088                 cimg_forC(img,k) {
8089                   const CImg<T> channel = img.get_shared_channel(k);
8090                   nvalue = value;
8091                   if (sep=='%') {
8092                     vmax = (double)channel.max_min(vmin);
8093                     nvalue = vmin + (vmax - vmin)*value/100;
8094                   }
8095                   CImgList<unsigned int> prims;
8096                   const CImg<float> pts = channel.get_isosurface3d(prims,(float)nvalue);
8097                   vertices.append_object3d(primitives,pts,prims);
8098                   g_list_uc.insert(prims.size(),CImg<unsigned char>::vector(g_img_uc(0,k),
8099                                                                             g_img_uc(1,k),
8100                                                                             g_img_uc(2,k)));
8101                 }
8102                 if (!vertices) {
8103                   if (img.depth()>1)
8104                     warn(images,0,false,
8105                          "Command 'isosurface3d': Isovalue %g%s not found in image [%u].",
8106                          value,sep=='%'?"%":"",uind);
8107                   else
8108                     warn(images,0,false,
8109                          "Command 'isosurface3d': Image [%u] has a single slice, "
8110                          "isovalue %g%s not found.",
8111                          uind,value,sep=='%'?"%":"");
8112                 }
8113                 vertices.object3dtoCImg3d(primitives,g_list_uc,false);
8114                 gmic_apply(replace(vertices));
8115                 primitives.assign(); g_list_uc.assign(); g_img_uc.assign();
8116               } else gmic_apply(replace(img));
8117             }
8118           } else if ((cimg_sscanf(argument,"'%4095[^']',%lf%c",
8119                                   formula,&value,&end)==2 ||
8120                       cimg_sscanf(argument,"'%4095[^']',%lf,%f,%f,%f,%f,%f,%f%c",
8121                                   formula,&value,&x0,&y0,&z0,&x1,&y1,&z1,&end)==8 ||
8122                       cimg_sscanf(argument,"'%4095[^']',%lf,%f,%f,%f,%f,%f,%f,%f,%f,%f%c",
8123                                   formula,&value,&x0,&y0,&z0,&x1,&y1,&z1,&dx,&dy,&dz,&end)==11 ||
8124                       (cimg_sscanf(argument,"'%4095[^']',%lf,%f,%f,%f,%f,%f,%f,%f%c,%f,%f%c",
8125                                    formula,&value,&x0,&y0,&z0,&x1,&y1,&z1,
8126                                    &dx,&sepx,&dy,&dz,&end)==12 &&
8127                        sepx=='%') ||
8128                       (cimg_sscanf(argument,"'%4095[^']',%lf,%f,%f,%f,%f,%f,%f,%f,%f%c,%f%c",
8129                                    formula,&value,&x0,&y0,&z0,&x1,&y1,&z1,
8130                                    &dx,&dy,&sepy,&dz,&end)==12 &&
8131                        sepy=='%') ||
8132                       (cimg_sscanf(argument,"'%4095[^']',%lf,%f,%f,%f,%f,%f,%f,%f,%f,%f%c%c",
8133                                    formula,&value,&x0,&y0,&z0,&x1,&y1,&z1,
8134                                    &dx,&dy,&dz,&sepz,&end)==12 &&
8135                        sepz=='%') ||
8136                       (cimg_sscanf(argument,"'%4095[^']',%lf,%f,%f,%f,%f,%f,%f,%f%c,%f%c,%f%c",
8137                                    formula,&value,&x0,&y0,&z0,&x1,&y1,&z1,
8138                                    &dx,&sepx,&dy,&sepy,&dz,&end)==13 &&
8139                        sepx=='%' && sepy=='%') ||
8140                       (cimg_sscanf(argument,"'%4095[^']',%lf,%f,%f,%f,%f,%f,%f,%f%c,%f,%f%c%c",
8141                                    formula,&value,&x0,&y0,&z0,&x1,&y1,&z1,
8142                                    &dx,&sepx,&dy,&dz,&sepz,&end)==13 &&
8143                        sepx=='%' && sepz=='%') ||
8144                       (cimg_sscanf(argument,"'%4095[^']',%lf,%f,%f,%f,%f,%f,%f,%f,%f%c,%f%c%c",
8145                                    formula,&value,&x0,&y0,&z0,&x1,&y1,&z1,
8146                                    &dx,&dy,&sepy,&dz,&sepz,&end)==13 &&
8147                        sepy=='%' && sepz=='%') ||
8148                       (cimg_sscanf(argument,"'%4095[^']',%lf,%f,%f,%f,%f,%f,%f,%f%c,%f%c,%f%c%c",
8149                                    formula,&value,&x0,&y0,&z0,&x1,&y1,&z1,
8150                                    &dx,&sepx,&dy,&sepy,&dz,&sepz,&end)==14 &&
8151                        sepx=='%' && sepy=='%' && sepz=='%')) &&
8152                      dx>0 && dy>0 && dz>0) {
8153             dx = cimg::round(dx);
8154             dy = cimg::round(dy);
8155             dz = cimg::round(dz);
8156             strreplace_fw(formula);
8157             print(images,0,"Extract 3D isosurface %g from formula '%s', "
8158                   "in range (%g,%g,%g)-(%g,%g,%g) with size %g%sx%g%sx%g%s.",
8159                   value,
8160                   formula,
8161                   x0,y0,z0,
8162                   x1,y1,z1,
8163                   dx,sepx=='%'?"%":"",
8164                   dy,sepy=='%'?"%":"",
8165                   dz,sepz=='%'?"%":"");
8166             if (sepx=='%') dx = -dx;
8167             if (sepy=='%') dy = -dy;
8168             if (sepz=='%') dz = -dz;
8169             CImg<T>::isosurface3d(primitives,(const char*)formula,(float)value,
8170                                   x0,y0,z0,x1,y1,z1,(int)dx,(int)dy,(int)dz).move_to(vertices);
8171             vertices.object3dtoCImg3d(primitives,false).move_to(images);
8172             primitives.assign();
8173             cimg_snprintf(title,_title.width(),"[3D isosurface %g of '%s']",value,formula);
8174             CImg<char>::string(title).move_to(images_names);
8175           } else arg_error("isosurface3d");
8176           is_change = true; ++position; continue;
8177         }
8178 
8179         // Inpaint.
8180         if (!std::strcmp("inpaint",command)) {
8181           gmic_substitute_args(true);
8182           float patch_size = 11, lookup_size = 22, lookup_factor = 0.5, lookup_increment = 1,
8183             blend_size = 0, blend_threshold = 0, blend_decay = 0.05f, blend_scales = 10;
8184           unsigned int is_blend_outer = 1, method = 1;
8185           sep = *indices = 0;
8186           if (((cimg_sscanf(argument,"[%255[a-zA-Z0-9_.%+-]%c%c",indices,&sep,&end)==2 &&
8187                 sep==']') ||
8188                (cimg_sscanf(argument,"[%255[a-zA-Z0-9_.%+-]],%c%c",indices,&sep,&end)==2 &&
8189                 sep=='0') ||
8190                cimg_sscanf(argument,"[%255[a-zA-Z0-9_.%+-]],0,%u%c",indices,&method,&end)==2) &&
8191               (ind=selection2cimg(indices,images.size(),images_names,"inpaint")).height()==1 &&
8192               method<=3) {
8193             print(images,0,"Inpaint image%s masked by image [%u], with %s algorithm.",
8194                   gmic_selection.data(),
8195                   *ind,
8196                   method==0?"low-connectivity average":method==1?"high-connectivity average":
8197                   method==2?"low-connectivity median":"high-connectivity median");
8198             const CImg<T> mask = gmic_image_arg(*ind);
8199             cimg_forY(selection,l) gmic_apply(inpaint(mask,method));
8200           } else if (((cimg_sscanf(argument,"[%255[a-zA-Z0-9_.%+-]%c%c",
8201                                    indices,&sep,&end)==2 && sep==']') ||
8202                       cimg_sscanf(argument,"[%255[a-zA-Z0-9_.%+-]],%f%c",
8203                                   indices,&patch_size,&end)==2 ||
8204                       cimg_sscanf(argument,"[%255[a-zA-Z0-9_.%+-]],%f,%f%c",
8205                                   indices,&patch_size,&lookup_size,&end)==3 ||
8206                       cimg_sscanf(argument,"[%255[a-zA-Z0-9_.%+-]],%f,%f,%f%c",
8207                                   indices,&patch_size,&lookup_size,&lookup_factor,&end)==4 ||
8208                       cimg_sscanf(argument,"[%255[a-zA-Z0-9_.%+-]],%f,%f,%f,%f%c",
8209                                   indices,&patch_size,&lookup_size,&lookup_factor,
8210                                   &lookup_increment,&end)==5 ||
8211                       cimg_sscanf(argument,"[%255[a-zA-Z0-9_.%+-]],%f,%f,%f,%f,%f%c",
8212                                   indices,&patch_size,&lookup_size,&lookup_factor,
8213                                   &lookup_increment,&blend_size,&end)==6 ||
8214                       cimg_sscanf(argument,"[%255[a-zA-Z0-9_.%+-]],%f,%f,%f,%f,%f,%f%c",
8215                                   indices,&patch_size,&lookup_size,&lookup_factor,
8216                                   &lookup_increment,&blend_size,&blend_threshold,&end)==7 ||
8217                       cimg_sscanf(argument,"[%255[a-zA-Z0-9_.%+-]],%f,%f,%f,%f,%f,%f,%f%c",
8218                                   indices,&patch_size,&lookup_size,&lookup_factor,
8219                                   &lookup_increment,&blend_size,&blend_threshold,&blend_decay,
8220                                   &end)==8 ||
8221                       cimg_sscanf(argument,"[%255[a-zA-Z0-9_.%+-]],%f,%f,%f,%f,%f,%f,%f,%f%c",
8222                                   indices,&patch_size,&lookup_size,&lookup_factor,
8223                                   &lookup_increment,&blend_size,&blend_threshold,&blend_decay,
8224                                   &blend_scales,&end)==9 ||
8225                       cimg_sscanf(argument,"[%255[a-zA-Z0-9_.%+-]],%f,%f,%f,%f,%f,%f,%f,%f,%u%c",
8226                                   indices,&patch_size,&lookup_size,&lookup_factor,
8227                                   &lookup_increment,&blend_size,&blend_threshold,&blend_decay,
8228                                   &blend_scales,&is_blend_outer,&end)==10) &&
8229                      (ind=selection2cimg(indices,images.size(),images_names,"inpaint")).height()==1 &&
8230                      patch_size>=0.5 && lookup_size>=0.5 && lookup_factor>=0 &&
8231                      blend_size>=0 && blend_threshold>=0 && blend_threshold<=1 &&
8232                      blend_decay>=0 && blend_scales>=0.5 && is_blend_outer<=1) {
8233             const CImg<T> mask = gmic_image_arg(*ind);
8234             patch_size = cimg::round(patch_size);
8235             lookup_size = cimg::round(lookup_size);
8236             lookup_increment = cimg::round(lookup_increment);
8237             blend_size = cimg::round(blend_size);
8238             blend_scales = cimg::round(blend_scales);
8239             print(images,0,"Inpaint image%s masked by image [%d], with patch size %g, "
8240                   "lookup size %g, lookup factor %g, lookup_increment %g, blend size %g, "
8241                   "blend threshold %g, blend decay %g, %g blend scale%s and outer blending %s.",
8242                   gmic_selection.data(),*ind,
8243                   patch_size,lookup_size,lookup_factor,lookup_increment,
8244                   blend_size,blend_threshold,blend_decay,blend_scales,blend_scales!=1?"s":"",
8245                   is_blend_outer?"enabled":"disabled");
8246             cimg_forY(selection,l)
8247               gmic_apply(inpaint_patch(mask,
8248                                        (unsigned int)patch_size,(unsigned int)lookup_size,
8249                                        lookup_factor,
8250                                        (int)lookup_increment,
8251                                        (unsigned int)blend_size,blend_threshold,blend_decay,
8252                                        (unsigned int)blend_scales,(bool)is_blend_outer));
8253           } else arg_error("inpaint");
8254           is_change = true; ++position; continue;
8255         }
8256 
8257         goto gmic_commands_others;
8258 
8259         //-----------------------------
8260         // Commands starting by 'k...'
8261         //-----------------------------
8262       gmic_commands_k :
8263 
8264         // Keep images.
8265         if (!std::strcmp("keep",command)) {
8266           print(images,0,"Keep image%s",
8267                 gmic_selection.data());
8268           g_list.assign(selection.height());
8269           g_list_c.assign(selection.height());
8270           if (is_get) {
8271             cimg_forY(selection,l) {
8272               const unsigned int uind = selection[l];
8273               g_list[l].assign(images[uind]);
8274               g_list_c[l].assign(images_names[uind]).copymark();
8275             }
8276             g_list.move_to(images,~0U);
8277             g_list_c.move_to(images_names,~0U);
8278           } else {
8279             cimg_forY(selection,l) {
8280               const unsigned int uind = selection[l];
8281               g_list[l].swap(images[uind]);
8282               g_list_c[l].swap(images_names[uind]);
8283             }
8284             g_list.swap(images);
8285             g_list_c.swap(images_names);
8286           }
8287           if (is_verbose) {
8288             cimg::mutex(29);
8289             std::fprintf(cimg::output()," (%u image%s left).",
8290                          images.size(),images.size()==1?"":"s");
8291             std::fflush(cimg::output());
8292             cimg::mutex(29,0);
8293           }
8294           g_list.assign(); g_list_c.assign();
8295           is_change = true; continue;
8296         }
8297 
8298         goto gmic_commands_others;
8299 
8300         //-----------------------------
8301         // Commands starting by 'l...'
8302         //-----------------------------
8303       gmic_commands_l :
8304 
8305         // Start local environment.
8306         if (!std::strcmp("local",command)) {
8307           if (is_debug_info && debug_line!=~0U) {
8308             cimg_snprintf(argx,_argx.width(),"*local#%u",debug_line);
8309             CImg<char>::string(argx).move_to(callstack);
8310           } else CImg<char>::string("*local").move_to(callstack);
8311           if (is_very_verbose)
8312             print(images,0,"Start 'local...endlocal' block, with selected image%s.",
8313                   gmic_selection.data());
8314           g_list.assign(selection.height());
8315           g_list_c.assign(selection.height());
8316           gmic_exception exception;
8317 
8318           if (is_get) cimg_forY(selection,l) {
8319               const unsigned int uind = selection[l];
8320               g_list[l].assign(images[uind]);
8321               g_list_c[l].assign(images_names[uind]).copymark();
8322             } else {
8323             cimg::mutex(27);
8324             cimg_forY(selection,l) {
8325               const unsigned int uind = selection[l];
8326               if (images[uind].is_shared())
8327                 g_list[l].assign(images[uind],false);
8328               else {
8329                 if ((images[uind].width() || images[uind].height()) && !images[uind]._spectrum) {
8330                   selection2string(selection,images_names,1,name);
8331                   error(true,images,0,0,
8332                         "Command 'local': Invalid selection%s "
8333                         "(image [%u] is already used in another thread).",
8334                         name.data() + (*name=='s'?1:0),uind);
8335                 }
8336                 g_list[l].swap(images[uind]);
8337                 // Small hack to be able to track images of the selection passed to the new environment.
8338                 std::memcpy(&images[uind]._width,&g_list[l]._data,sizeof(void*));
8339                 images[uind]._spectrum = 0;
8340               }
8341               g_list_c[l] = images_names[uind]; // Make a copy to be still able to recognize 'pass[label]'
8342             }
8343             cimg::mutex(27,0);
8344           }
8345 
8346           const unsigned int local_callstack_size = callstack.size();
8347           const int o_verbosity = verbosity;
8348           try {
8349             if (next_debug_line!=~0U) { debug_line = next_debug_line; next_debug_line = ~0U; }
8350             if (next_debug_filename!=~0U) { debug_filename = next_debug_filename; next_debug_filename = ~0U; }
8351             _run(commands_line,position,g_list,g_list_c,images,images_names,variables_sizes,is_noarg,0,
8352                  command_selection);
8353           } catch (gmic_exception &e) {
8354             check_elif = false;
8355             int nb_locals = 0;
8356             for (nb_locals = 1; nb_locals && position<commands_line.size(); ++position) {
8357               const char *it = commands_line[position].data();
8358               if (*it==1 &&
8359                   cimg_sscanf(commands_line[position].data() + 1,"%x,%x",&_debug_line,&(_debug_filename=0))>0) {
8360                 is_debug_info = true; next_debug_line = _debug_line; next_debug_filename = _debug_filename;
8361               } else {
8362                 const bool _is_get = *it=='+' || (*it=='-' && it[1]=='-');
8363                 it+=(*it=='+' || *it=='-') + (*it=='-' && it[1]=='-');
8364                 if (!std::strcmp("local",it) || !std::strcmp("l",it) ||
8365                     !std::strncmp("local[",it,6) || !std::strncmp("l[",it,2)) ++nb_locals;
8366                 else if (!_is_get && (!std::strcmp("endlocal",it) || !std::strcmp("endl",it))) --nb_locals;
8367                 else if (!_is_get && nb_locals==1 && !std::strcmp("onfail",it)) break;
8368               }
8369             }
8370             if (callstack.size()>local_callstack_size)
8371               for (unsigned int k = callstack.size() - 1; k>=local_callstack_size; --k) {
8372                 const char *const s = callstack[k].data();
8373                 if (*s=='*') switch (s[1]) {
8374                   case 'r' : --nb_repeatdones; break;
8375                   case 'd' : --nb_dowhiles; break;
8376                   case 'f' : --nb_fordones; break;
8377                   }
8378                 callstack.remove(k);
8379               }
8380             if (nb_locals==1 && position<commands_line.size()) { // Onfail block found
8381               verbosity = o_verbosity; // Restore verbosity
8382               if (is_very_verbose) print(images,0,"Reach 'onfail' block.");
8383               try {
8384                 _run(commands_line,++position,g_list,g_list_c,
8385                      parent_images,parent_images_names,variables_sizes,is_noarg,0,0);
8386               } catch (gmic_exception &e2) {
8387                 cimg::swap(exception._command,e2._command);
8388                 cimg::swap(exception._message,e2._message);
8389               }
8390             } else {
8391               cimg::swap(exception._command,e._command);
8392               cimg::swap(exception._message,e._message);
8393             }
8394           }
8395           callstack.remove();
8396           if (is_get) {
8397             g_list.move_to(images,~0U);
8398             g_list_c.move_to(images_names,~0U);
8399           } else {
8400             const unsigned int nb = std::min((unsigned int)selection.height(),g_list.size());
8401             if (nb>0) {
8402               for (unsigned int i = 0; i<nb; ++i) {
8403                 const unsigned int uind = selection[i];
8404                 if (images[uind].is_shared()) {
8405                   images[uind] = g_list[i];
8406                   g_list[i].assign();
8407                 } else images[uind].swap(g_list[i]);
8408                 images_names[uind].swap(g_list_c[i]);
8409               }
8410               g_list.remove(0,nb - 1);
8411               g_list_c.remove(0,nb - 1);
8412             }
8413             if (nb<(unsigned int)selection.height())
8414               remove_images(images,images_names,selection,nb,selection.height() - 1);
8415             else if (g_list) {
8416               const unsigned int uind0 = selection?selection.back() + 1:images.size();
8417               images.insert(g_list,uind0);
8418               g_list_c.move_to(images_names,uind0);
8419             }
8420           }
8421           g_list.assign(); g_list_c.assign();
8422           if (exception._message) throw exception;
8423           continue;
8424         }
8425 
8426         // Less or equal.
8427         gmic_arithmetic_command("le",
8428                                 operator_le,
8429                                 "Compute boolean 'less or equal than' between image%s and %g%s",
8430                                 gmic_selection.data(),value,ssep,T,
8431                                 operator_le,
8432                                 "Compute boolean 'less or equal than' between image%s and image [%d]",
8433                                 gmic_selection.data(),ind[0],
8434                                 operator_le,
8435                                 "Compute boolean 'less or equal than' between image%s and "
8436                                 "expression %s'",
8437                                 gmic_selection.data(),gmic_argument_text_printed(),
8438                                 "Compute boolean 'less or equal than' between image%s");
8439 
8440         // Less than.
8441         gmic_arithmetic_command("lt",
8442                                 operator_lt,
8443                                 "Compute boolean 'less than' between image%s and %g%s",
8444                                 gmic_selection.data(),value,ssep,T,
8445                                 operator_lt,
8446                                 "Compute boolean 'less than' between image%s and image [%d]",
8447                                 gmic_selection.data(),ind[0],
8448                                 operator_lt,
8449                                 "Compute boolean 'less than' between image%s and expression %s'",
8450                                 gmic_selection.data(),gmic_argument_text_printed(),
8451                                 "Compute boolean 'less than' between image%s");
8452 
8453         // Logarithm, base-e.
8454         gmic_simple_command("log",log,"Compute pointwise base-e logarithm of image%s.");
8455 
8456         // Logarithm, base-2.
8457         gmic_simple_command("log2",log2,"Compute pointwise base-2 logarithm of image%s.");
8458 
8459         // Logarithm, base-10.
8460         gmic_simple_command("log10",log10,"Compute pointwise base-10 logarithm of image%s.");
8461 
8462         // Draw line.
8463         if (!std::strcmp("line",command)) {
8464           gmic_substitute_args(false);
8465           *argx = *argy = *argz = *argc = *color = 0;
8466           float x0 = 0, y0 = 0, x1 = 0, y1 = 0;
8467           char sepx0 = 0, sepy0 = 0, sepx1 = 0, sepy1 = 0;
8468           sep1 = 0;
8469           pattern = ~0U; opacity = 1;
8470           if ((cimg_sscanf(argument,"%255[0-9.eE%+-],%255[0-9.eE%+-],%255[0-9.eE%+-],"
8471                            "%255[0-9.eE%+-]%c",
8472                            argx,argy,argz,argc,&end)==4 ||
8473                cimg_sscanf(argument,"%255[0-9.eE%+-],%255[0-9.eE%+-],%255[0-9.eE%+-],"
8474                            "%255[0-9.eE%+-],%f%c",
8475                            argx,argy,argz,argc,&opacity,&end)==5 ||
8476                (cimg_sscanf(argument,"%255[0-9.eE%+-],%255[0-9.eE%+-],%255[0-9.eE%+-],"
8477                             "%255[0-9.eE%+-],%f,0%c%x%c",
8478                             argx,argy,argz,argc,&opacity,&sep1,&pattern,&end)==7 &&
8479                 sep1=='x') ||
8480                (cimg_sscanf(argument,"%255[0-9.eE%+-],%255[0-9.eE%+-],%255[0-9.eE%+-],"
8481                             "%255[0-9.eE%+-],%f,%4095[0-9.eEinfa,+-]%c",
8482                             argx,argy,argz,argc,&opacity,color,&end)==6 && (bool)(pattern=~0U)) ||
8483                (*color=0,cimg_sscanf(argument,"%255[0-9.eE%+-],%255[0-9.eE%+-],%255[0-9.eE%+-],"
8484                                      "%255[0-9.eE%+-],%f,0%c%x,%4095[0-9.eEinfa,+-]%c",
8485                                      argx,argy,argz,argc,&opacity,&sep1,
8486                                      &pattern,color,&end)==8 && sep1=='x')) &&
8487               (cimg_sscanf(argx,"%f%c",&x0,&end)==1 ||
8488                (cimg_sscanf(argx,"%f%c%c",&x0,&sepx0,&end)==2 && sepx0=='%')) &&
8489               (cimg_sscanf(argy,"%f%c",&y0,&end)==1 ||
8490                (cimg_sscanf(argy,"%f%c%c",&y0,&sepy0,&end)==2 && sepy0=='%')) &&
8491               (cimg_sscanf(argz,"%f%c",&x1,&end)==1 ||
8492                (cimg_sscanf(argz,"%f%c%c",&x1,&sepx1,&end)==2 && sepx1=='%')) &&
8493               (cimg_sscanf(argc,"%f%c",&y1,&end)==1 ||
8494                (cimg_sscanf(argc,"%f%c%c",&y1,&sepy1,&end)==2 && sepy1=='%'))) {
8495             print(images,0,"Draw line (%g%s,%g%s) - (%g%s,%g%s) on image%s, with opacity %g, "
8496                   "pattern 0x%x and color (%s).",
8497                   x0,sepx0=='%'?"%":"",
8498                   y0,sepy0=='%'?"%":"",
8499                   x1,sepx1=='%'?"%":"",
8500                   y1,sepy1=='%'?"%":"",
8501                   gmic_selection.data(),
8502                   opacity,pattern,
8503                   *color?color:"default");
8504             cimg_forY(selection,l) {
8505               CImg<T> &img = images[selection[l]];
8506               g_img.assign(img.spectrum(),1,1,1,(T)0).fill(color,true,false);
8507               const int
8508                 nx0 = (int)cimg::round(sepx0=='%'?x0*(img.width() - 1)/100:x0),
8509                 ny0 = (int)cimg::round(sepy0=='%'?y0*(img.height() - 1)/100:y0),
8510                 nx1 = (int)cimg::round(sepx1=='%'?x1*(img.width() - 1)/100:x1),
8511                 ny1 = (int)cimg::round(sepy1=='%'?y1*(img.height() - 1)/100:y1);
8512               gmic_apply(draw_line(nx0,ny0,nx1,ny1,g_img.data(),opacity,pattern));
8513             }
8514           } else arg_error("line");
8515           g_img.assign();
8516           is_change = true; ++position; continue;
8517         }
8518 
8519         // Label connected components.
8520         if (!std::strcmp("label",command)) {
8521           gmic_substitute_args(false);
8522           float tolerance = 0;
8523           is_high_connectivity = 0;
8524           if ((cimg_sscanf(argument,"%f%c",&tolerance,&end)==1 ||
8525                cimg_sscanf(argument,"%f,%u%c",&tolerance,&is_high_connectivity,&end)==2) &&
8526               tolerance>=0) ++position;
8527           else { tolerance = 0; is_high_connectivity = 0; }
8528           print(images,0,
8529                 "Label connected components on image%s, with tolerance %g and "
8530                 "%s connectivity.",
8531                 gmic_selection.data(),tolerance,is_high_connectivity?"high":"low");
8532           cimg_forY(selection,l) gmic_apply(label((bool)is_high_connectivity,tolerance));
8533           is_change = true; continue;
8534         }
8535 
8536         // Set 3D light position.
8537         if (!is_get && !std::strcmp("light3d",item)) {
8538           gmic_substitute_args(true);
8539           float lx = 0, ly = 0, lz = -5e8f;
8540           sep = *indices = 0;
8541           if (cimg_sscanf(argument,"%f,%f,%f%c",
8542                           &lx,&ly,&lz,&end)==3) {
8543             print(images,0,"Set 3D light position to (%g,%g,%g).",
8544                   lx,ly,lz);
8545             light3d_x = lx;
8546             light3d_y = ly;
8547             light3d_z = lz;
8548             ++position;
8549           } else if (cimg_sscanf(argument,"[%255[a-zA-Z0-9_.%+-]%c%c",indices,&sep,&end)==2 &&
8550                      sep==']' &&
8551                      (ind=selection2cimg(indices,images.size(),images_names,"light3d")).height()==1) {
8552             print(images,0,"Set 3D light texture from image [%u].",*ind);
8553             light3d.assign(images[*ind],false);
8554             ++position;
8555           } else {
8556             print(images,0,"Reset 3D light to default.");
8557             light3d.assign();
8558             light3d_x = light3d_y = 0; light3d_z = -5e8f;
8559           }
8560           continue;
8561         }
8562 
8563         goto gmic_commands_others;
8564 
8565         //-----------------------------
8566         // Commands starting by 'm...'
8567         //-----------------------------
8568       gmic_commands_m :
8569 
8570         // Move images.
8571         if (!std::strcmp("move",command)) {
8572           gmic_substitute_args(false);
8573           float pos = 0;
8574           sep = 0;
8575           if (cimg_sscanf(argument,"%f%c",&pos,&end)==1 ||
8576               (cimg_sscanf(argument,"%f%c%c",&pos,&sep,&end)==2 && sep=='%')) {
8577             const int
8578               _iind0 = (int)cimg::round(sep=='%'?pos*images.size()/100:pos),
8579               iind0 = _iind0<0?_iind0 + (int)images.size():_iind0;
8580             if (iind0<0 || iind0>(int)images.size())
8581               error(true,images,0,0,
8582                     "Command 'move': Invalid position '%d' (not in range -%u...%u).",
8583                     _iind0,images.size(),images.size() - 1);
8584             print(images,0,"Move image%s to position %d.",
8585                   gmic_selection.data(),
8586                   iind0);
8587             CImgList<T> _images, nimages;
8588             CImgList<char> _images_names, nimages_names;
8589             if (is_get) {
8590               _images.insert(images.size());
8591               // Copy original list while preserving shared state of each item.
8592               cimglist_for(_images,l) _images[l].assign(images[l],images[l].is_shared());
8593               _images_names.assign(images_names);
8594             }
8595             nimages.insert(selection.height());
8596             cimg_forY(selection,l) {
8597               const unsigned int uind = selection[l];
8598               if (is_get) images[uind].move_to(nimages[l]);
8599               else images[uind].swap(nimages[l]);
8600               // Empty shared image as a special item to be removed later.
8601               images[uind]._is_shared = true;
8602               images_names[uind].move_to(nimages_names);
8603             }
8604             images.insert(nimages.size(),iind0);
8605             cimglist_for(nimages,l) nimages[l].swap(images[iind0 + l]);
8606             nimages_names.move_to(images_names,iind0);
8607             cimglist_for(images,l) if (!images[l] && images[l].is_shared()) {
8608               images.remove(l); images_names.remove(l--); // Remove special items
8609             }
8610             if (is_get) {
8611               cimglist_for(images,l) // Replace shared items by non-shared one for a get version
8612                 if (images[l].is_shared()) {
8613                   CImg<T> tmp; (images[l].move_to(tmp)).swap(images[l]);
8614                 }
8615               images.insert(_images.size(),0);
8616               cimglist_for(_images,l) images[l].swap(_images[l]);
8617               _images_names.move_to(images_names,0);
8618             }
8619           } else arg_error("move");
8620           is_change = true; ++position; continue;
8621         }
8622 
8623         // Mirror.
8624         if (!std::strcmp("mirror",command)) {
8625           gmic_substitute_args(false);
8626           bool is_valid_argument = *argument!=0;
8627           if (is_valid_argument) for (const char *s = argument; *s; ++s) {
8628               const char _s = *s;
8629               if (_s!='x' && _s!='y' && _s!='z' && _s!='c') { is_valid_argument = false; break; }
8630             }
8631           if (is_valid_argument) {
8632             print(images,0,"Mirror image%s along the '%s'-ax%cs.",
8633                   gmic_selection.data(),
8634                   gmic_argument_text_printed(),
8635                   std::strlen(argument)>1?'e':'i');
8636             cimg_forY(selection,l) gmic_apply(mirror(argument));
8637           } else arg_error("mirror");
8638           is_change = true; ++position; continue;
8639         }
8640 
8641         // Multiplication.
8642         gmic_arithmetic_command("mul",
8643                                 operator*=,
8644                                 "Multiply image%s by %g%s",
8645                                 gmic_selection.data(),value,ssep,Tfloat,
8646                                 mul,
8647                                 "Multiply image%s by image [%d]",
8648                                 gmic_selection.data(),ind[0],
8649                                 operator_muleq,
8650                                 "Multiply image%s by expression %s",
8651                                 gmic_selection.data(),gmic_argument_text_printed(),
8652                                 "Multiply image%s");
8653         // Modulo.
8654         gmic_arithmetic_command("mod",
8655                                 operator%=,
8656                                 "Compute pointwise modulo of image%s by %g%s",
8657                                 gmic_selection.data(),value,ssep,T,
8658                                 operator%=,
8659                                 "Compute pointwise modulo of image%s by image [%d]",
8660                                 gmic_selection.data(),ind[0],
8661                                 operator_modeq,
8662                                 "Compute pointwise modulo of image%s by expression %s",
8663                                 gmic_selection.data(),gmic_argument_text_printed(),
8664                                 "Compute sequential pointwise modulo of image%s");
8665 
8666         // Max.
8667         gmic_arithmetic_command("max",
8668                                 max,
8669                                 "Compute pointwise maximum between image%s and %g%s",
8670                                 gmic_selection.data(),value,ssep,T,
8671                                 max,
8672                                 "Compute pointwise maximum between image%s and image [%d]",
8673                                 gmic_selection.data(),ind[0],
8674                                 max,
8675                                 "Compute pointwise maximum between image%s and expression %s",
8676                                 gmic_selection.data(),gmic_argument_text_printed(),
8677                                 "Compute pointwise maximum of all image%s together");
8678         // Min.
8679         gmic_arithmetic_command("min",
8680                                 min,
8681                                 "Compute pointwise minimum between image%s and %g%s",
8682                                 gmic_selection.data(),value,ssep,T,
8683                                 min,
8684                                 "Compute pointwise minimum between image%s and image [%d]",
8685                                 gmic_selection.data(),ind[0],
8686                                 min,
8687                                 "Compute pointwise minimum between image%s and expression %s",
8688                                 gmic_selection.data(),gmic_argument_text_printed(),
8689                                 "Compute pointwise minimum of image%s");
8690 
8691         // Matrix multiplication.
8692         gmic_arithmetic_command("mmul",
8693                                 operator*=,
8694                                 "Multiply matrix/vector%s by %g%s",
8695                                 gmic_selection.data(),value,ssep,Tfloat,
8696                                 operator*=,
8697                                 "Multiply matrix/vector%s by matrix/vector image [%d]",
8698                                 gmic_selection.data(),ind[0],
8699                                 operator_muleq,
8700                                 "Multiply matrix/vector%s by expression %s",
8701                                 gmic_selection.data(),gmic_argument_text_printed(),
8702                                 "Multiply matrix/vector%s");
8703 
8704         // Matrix division.
8705         gmic_arithmetic_command("mdiv",
8706                                 operator/=,
8707                                 "Divide matrix/vector%s by %g%s",
8708                                 gmic_selection.data(),value,ssep,Tfloat,
8709                                 operator/=,
8710                                 "Divide matrix/vector%s by matrix/vector image [%d]",
8711                                 gmic_selection.data(),ind[0],
8712                                 operator_diveq,
8713                                 "Divide matrix/vector%s by expression %s",
8714                                 gmic_selection.data(),gmic_argument_text_printed(),
8715                                 "Divide matrix/vector%s");
8716 
8717         // Set 3D rendering modes.
8718         if (!is_get && !std::strcmp("mode3d",item)) {
8719           gmic_substitute_args(false);
8720           float mode = 4;
8721           if (cimg_sscanf(argument,"%f%c",
8722                           &mode,&end)==1 &&
8723               (mode=cimg::round(mode))>=-1 && mode<=5) ++position;
8724           else mode = 4;
8725           render3d = (int)mode;
8726           print(images,0,"Set static 3D rendering mode to %s.",
8727                 render3d==-1?"bounding-box":
8728                 render3d==0?"pointwise":render3d==1?"linear":render3d==2?"flat":
8729                 render3d==3?"flat-shaded":render3d==4?"Gouraud-shaded":
8730                 render3d==5?"Phong-shaded":"none");
8731           continue;
8732         }
8733 
8734         if (!is_get && !std::strcmp("moded3d",item)) {
8735           gmic_substitute_args(false);
8736           float mode = -1;
8737           if (cimg_sscanf(argument,"%f%c",
8738                           &mode,&end)==1 &&
8739               (mode=cimg::round(mode))>=-1 && mode<=5) ++position;
8740           else mode = -1;
8741           renderd3d = (int)mode;
8742           print(images,0,"Set dynamic 3D rendering mode to %s.",
8743                 renderd3d==-1?"bounding-box":
8744                 renderd3d==0?"pointwise":renderd3d==1?"linear":renderd3d==2?"flat":
8745                 renderd3d==3?"flat-shaded":renderd3d==4?"Gouraud-shaded":
8746                 renderd3d==5?"Phong-shaded":"none");
8747           continue;
8748         }
8749 
8750         // Map LUT.
8751         if (!std::strcmp("map",command)) {
8752           gmic_substitute_args(true);
8753           unsigned int lut_type = 0;
8754           sep = *indices = 0;
8755           boundary = 0;
8756           if (((cimg_sscanf(argument,"[%255[a-zA-Z0-9_.%+-]%c%c",indices,&sep,&end)==2 && sep==']') ||
8757                cimg_sscanf(argument,"[%255[a-zA-Z0-9_.%+-]],%u%c",indices,&boundary,&end)==2) &&
8758               (ind=selection2cimg(indices,images.size(),images_names,"map")).height()==1 &&
8759               boundary<=3) {
8760             print(images,0,"Map LUT [%u] on image%s, with %s boundary conditions.",
8761                   *ind,
8762                   gmic_selection.data(),
8763                   boundary==0?"dirichlet":boundary==1?"neumann":boundary==2?"periodic":"mirror");
8764             const CImg<T> palette = gmic_image_arg(*ind);
8765             cimg_forY(selection,l) gmic_apply(map(palette,boundary));
8766           } else if ((cimg_sscanf(argument,"%u%c",&lut_type,&end)==1 ||
8767                       cimg_sscanf(argument,"%u,%u%c",&lut_type,&boundary,&end)==2) &&
8768                      lut_type<=7 && boundary<=3) {
8769             print(images,0,"Map %s color LUT on image%s, with %s boundary conditions.",
8770                   lut_type==0?"default":lut_type==1?"HSV":lut_type==2?"lines":lut_type==3?"hot":
8771                   lut_type==4?"cool":lut_type==5?"jet":lut_type==6?"flag":"cube",
8772                   gmic_selection.data(),
8773                   boundary==0?"dirichlet":boundary==1?"neumann":boundary==2?"periodic":"mirror");
8774             const CImg<T>
8775               palette = lut_type==0?CImg<T>::default_LUT256():lut_type==1?CImg<T>::HSV_LUT256():
8776               lut_type==2?CImg<T>::lines_LUT256():lut_type==3?CImg<T>::hot_LUT256():
8777               lut_type==4?CImg<T>::cool_LUT256():lut_type==5?CImg<T>::jet_LUT256():
8778               lut_type==6?CImg<T>::flag_LUT256():CImg<T>::cube_LUT256();
8779             cimg_forY(selection,l) gmic_apply(map(palette,boundary));
8780           } else arg_error("map");
8781           is_change = true; ++position; continue;
8782         }
8783 
8784         // Median filter.
8785         if (!std::strcmp("median",command)) {
8786           gmic_substitute_args(false);
8787           float fsiz = 3, threshold = 0;
8788           if ((cimg_sscanf(argument,"%f%c",
8789                            &fsiz,&end)==1 ||
8790                cimg_sscanf(argument,"%f,%f%c",
8791                            &fsiz,&threshold,&end)==2) &&
8792               fsiz>=0 && threshold>=0) {
8793             fsiz = cimg::round(fsiz);
8794             if (threshold)
8795               print(images,0,"Apply median filter of size %g with threshold %g, on image%s.",
8796                     fsiz,threshold,
8797                     gmic_selection.data());
8798             else
8799               print(images,0,"Apply median filter of size %g, on image%s.",
8800                     fsiz,
8801                     gmic_selection.data());
8802             cimg_forY(selection,l) gmic_apply(blur_median((unsigned int)fsiz,threshold));
8803           } else arg_error("median");
8804           is_change = true; ++position; continue;
8805         }
8806 
8807         // MSE.
8808         if (!std::strcmp("mse",command)) {
8809           print(images,0,"Compute the %dx%d matrix of MSE values, from image%s.",
8810                 selection.height(),selection.height(),
8811                 gmic_selection.data());
8812           if (selection) {
8813             g_list.assign(selection.height());
8814             cimg_forY(selection,l) g_list[l].assign(gmic_check(images[selection[l]]),true);
8815             CImg<T> img(g_list.size(),g_list.size(),1,1,(T)0);
8816             cimg_forXY(img,x,y) if (x>y) img(x,y) = img(y,x) = (T)g_list[x].MSE(g_list[y]);
8817             CImg<char>::string("[MSE]").move_to(name);
8818             if (is_get) {
8819               img.move_to(images);
8820               name.move_to(images_names);
8821             } else {
8822               remove_images(images,images_names,selection,1,selection.height() - 1);
8823               img.move_to(images[selection[0]].assign());
8824               name.move_to(images_names[selection[0]]);
8825             }
8826             g_list.assign();
8827           }
8828           is_change = true; continue;
8829         }
8830 
8831         // Get patch-matching correspondence map.
8832         if (!std::strcmp("matchpatch",command)) {
8833           gmic_substitute_args(true);
8834           float patch_width, patch_height, patch_depth = 1, nb_iterations = 5, nb_randoms = 5, occ_penalization = 0;
8835           unsigned int is_score = 0;
8836           *argx = 0; ind0.assign();
8837           if (((cimg_sscanf(argument,"[%255[a-zA-Z0-9_.%+-]],%f,%c",
8838                             indices,&patch_width,&end)==2 && (patch_height=patch_width)) ||
8839                cimg_sscanf(argument,"[%255[a-zA-Z0-9_.%+-]],%f,%f,%c",
8840                            indices,&patch_width,&patch_height,&end)==3 ||
8841                cimg_sscanf(argument,"[%255[a-zA-Z0-9_.%+-]],%f,%f,%f%c",
8842                            indices,&patch_width,&patch_height,&patch_depth,&end)==4 ||
8843                cimg_sscanf(argument,"[%255[a-zA-Z0-9_.%+-]],%f,%f,%f,%f%c",
8844                            indices,&patch_width,&patch_height,&patch_depth,&nb_iterations,&end)==5 ||
8845                cimg_sscanf(argument,"[%255[a-zA-Z0-9_.%+-]],%f,%f,%f,%f,%f%c",
8846                            indices,&patch_width,&patch_height,&patch_depth,&nb_iterations,&nb_randoms,&end)==6 ||
8847                cimg_sscanf(argument,"[%255[a-zA-Z0-9_.%+-]],%f,%f,%f,%f,%f,%f%c",
8848                            indices,&patch_width,&patch_height,&patch_depth,&nb_iterations,&nb_randoms,
8849                            &occ_penalization,&end)==7 ||
8850                cimg_sscanf(argument,"[%255[a-zA-Z0-9_.%+-]],%f,%f,%f,%f,%f,%f,%u%c",
8851                            indices,&patch_width,&patch_height,&patch_depth,&nb_iterations,&nb_randoms,
8852                            &occ_penalization,&is_score,&end)==8 ||
8853                (cimg_sscanf(argument,"[%255[a-zA-Z0-9_.%+-]],%f,%f,%f,%f,%f,%f,%u,[%255[a-zA-Z0-9_.%+-]%c%c",
8854                             indices,&patch_width,&patch_height,&patch_depth,&nb_iterations,&nb_randoms,
8855                             &occ_penalization,&is_score,argx,&sep,&end)==10 && sep==']')) &&
8856               (ind=selection2cimg(indices,images.size(),images_names,"matchpatch")).height()==1 &&
8857               (!*argx ||
8858                (ind0=selection2cimg(argx,images.size(),images_names,"matchpatch")).height()==1) &&
8859               patch_width>=1 && patch_height>=1 && patch_depth>=1 &&
8860               nb_iterations>=0 && nb_randoms>=0 && is_score<=1) {
8861             const CImg<T> *initialization = 0;
8862             patch_width = cimg::round(patch_width);
8863             patch_height = cimg::round(patch_height);
8864             patch_depth = cimg::round(patch_depth);
8865             nb_iterations = cimg::round(nb_iterations);
8866             nb_randoms = cimg::round(nb_randoms);
8867             if (ind0) initialization = &images[*ind0];
8868             print(images,0,"Estimate correspondence map between image%s and patch image [%u], "
8869                   "using %gx%gx%g patches, %g iteration%s, %g randomization%s and occurrence penalization %g "
8870                   "(%sscore returned).",
8871                   gmic_selection.data(),
8872                   *ind,
8873                   patch_width,patch_height,patch_depth,
8874                   nb_iterations,nb_iterations!=1?"s":"",
8875                   nb_randoms,nb_randoms!=1?"s":"",
8876                   occ_penalization,
8877                   is_score?"":"no ");
8878             const CImg<T> patch_image = gmic_image_arg(*ind);
8879             cimg_forY(selection,l) gmic_apply(gmic_matchpatch(patch_image,
8880                                                               (unsigned int)patch_width,
8881                                                               (unsigned int)patch_height,
8882                                                               (unsigned int)patch_depth,
8883                                                               (unsigned int)nb_iterations,
8884                                                               (unsigned int)nb_randoms,
8885                                                               occ_penalization,
8886                                                               (bool)is_score,
8887                                                               initialization));
8888           } else arg_error("matchpatch");
8889           is_change = true; ++position; continue;
8890         }
8891 
8892         // Draw mandelbrot/julia fractal.
8893         if (!std::strcmp("mandelbrot",command)) {
8894           gmic_substitute_args(false);
8895           double z0r = -2, z0i = -2, z1r = 2, z1i = 2, paramr = 0, parami = 0;
8896           unsigned int is_julia = 0;
8897           float itermax = 100;
8898           opacity = 1;
8899           if ((cimg_sscanf(argument,"%lf,%lf,%lf,%lf%c",
8900                            &z0r,&z0i,&z1r,&z1i,&end)==4 ||
8901                cimg_sscanf(argument,"%lf,%lf,%lf,%lf,%f%c",
8902                            &z0r,&z0i,&z1r,&z1i,&itermax,&end)==5 ||
8903                cimg_sscanf(argument,"%lf,%lf,%lf,%lf,%f,%u%c",
8904                            &z0r,&z0i,&z1r,&z1i,&itermax,&is_julia,&end)==6 ||
8905                cimg_sscanf(argument,"%lf,%lf,%lf,%lf,%f,%u,%lf,%lf%c",
8906                            &z0r,&z0i,&z1r,&z1i,&itermax,&is_julia,&paramr,
8907                            &parami,&end)==8 ||
8908                cimg_sscanf(argument,"%lf,%lf,%lf,%lf,%f,%u,%lf,%lf,%f%c",
8909                            &z0r,&z0i,&z1r,&z1i,&itermax,&is_julia,
8910                            &paramr,&parami,&opacity,&end)==9) &&
8911               itermax>=0 && is_julia<=1) {
8912             itermax = cimg::round(itermax);
8913             print(images,0,"Draw %s fractal on image%s, from complex area (%g,%g)-(%g,%g) "
8914                   "with c0 = (%g,%g) and %g iterations.",
8915                   is_julia?"julia":"mandelbrot",
8916                   gmic_selection.data(),
8917                   z0r,z0i,
8918                   z1r,z1i,
8919                   paramr,parami,
8920                   itermax);
8921             cimg_forY(selection,l) gmic_apply(draw_mandelbrot(CImg<T>(),opacity,z0r,z0i,z1r,z1i,(unsigned int)itermax,
8922                                                               true,(bool)is_julia,paramr,parami));
8923           } else arg_error("mandelbrot");
8924           is_change = true; ++position; continue;
8925         }
8926 
8927         // Manage mutexes.
8928         if (!is_get && !std::strcmp("mutex",item)) {
8929           gmic_substitute_args(false);
8930           unsigned int number, is_lock = 1;
8931           if ((cimg_sscanf(argument,"%u%c",
8932                            &number,&end)==1 ||
8933                cimg_sscanf(argument,"%u,%u%c",
8934                            &number,&is_lock,&end)==2) &&
8935               number<256 && is_lock<=1) {
8936             print(images,0,"%s mutex #%u.",
8937                   is_lock?"Lock":"Unlock",number);
8938             if (is_lock) gmic_mutex().lock(number);
8939             else gmic_mutex().unlock(number);
8940           } else arg_error("mutex");
8941           ++position; continue;
8942         }
8943 
8944         goto gmic_commands_others;
8945 
8946         //-----------------------------
8947         // Commands starting by 'n...'
8948         //-----------------------------
8949       gmic_commands_n :
8950 
8951         // Set image name.
8952         if (!is_get && !std::strcmp("name",command)) {
8953           gmic_substitute_args(false);
8954           if (selection.height()>1)
8955             CImg<char>::string(argument).get_split(CImg<char>::vector(','),0,false).move_to(g_list_c);
8956           else CImg<char>::string(argument).move_to(g_list_c);
8957           print(images,0,"Set name%s of image%s to '%s'.",
8958                 selection.height()>1?"s":"",gmic_selection.data(),gmic_argument_text_printed());
8959           cimglist_for(g_list_c,l) {
8960             g_list_c[l].unroll('x');
8961             if (g_list_c[l].back()) g_list_c[l].resize(g_list_c[l].width() + 1,1,1,1,0);
8962             strreplace_fw(g_list_c[l]);
8963           }
8964           cimg_forY(selection,l)
8965             images_names[selection[l]].assign(g_list_c[l%g_list_c.width()]);
8966           g_list_c.assign();
8967           ++position; continue;
8968         }
8969 
8970         // Get image indices from names.
8971         if (!is_get && !std::strcmp("named",command)) {
8972           gmic_substitute_args(false);
8973           if (cimg_sscanf(argument,"%u%c",&pattern,&sep)==2 && pattern<=5 && sep==',') is_cond = true;
8974           else { pattern = 0; is_cond = false; }
8975           boundary = pattern%3;
8976           CImg<char>::string(argument + (is_cond?2:0)).get_split(CImg<char>::vector(','),0,false).move_to(g_list_c);
8977 
8978           // Detect possible empty names in argument list.
8979           bool contains_empty_name = false;
8980           unsigned int sl = 0;
8981           for (; argument[sl]; ++sl) if (argument[sl]==',' && argument[sl + 1]==',') {
8982               contains_empty_name = true;
8983               break;
8984             }
8985           if (!contains_empty_name && sl && (*argument==',' || argument[sl - 1]==',')) contains_empty_name = true;
8986           if (contains_empty_name) CImg<char>(1,1,1,1,0).move_to(g_list_c);
8987 
8988           print(images,0,"Get %s%s with name%s '%s' for image%s (case-%s).",
8989                 boundary==0?"all image ind":boundary==1?"lowest image ind":"highest image ind",
8990                 boundary==0 || g_list_c.size()>1?"ices":"ex",
8991                 g_list_c.size()>1?"s":"",
8992                 gmic_argument_text_printed() + (is_cond?2:0),gmic_selection.data(),
8993                 pattern<3?"sensitive":"insensitive");
8994           int nb_found = 0, last_found = 0;
8995           const bool is_single_res = boundary>0 && g_list_c.size()==1;
8996           if (!is_single_res) ind.assign(selection.height(),1,1,1,0);
8997 
8998           cimglist_for(g_list_c,k) {
8999             g_list_c[k].unroll('x');
9000             if (g_list_c[k].back()) g_list_c[k].resize(g_list_c[k].width() + 1,1,1,1,0);
9001             strreplace_fw(g_list_c[k]);
9002             switch (pattern) {
9003             case 0 : // All indices, case sensitive
9004               cimg_forY(selection,l)
9005                 if (!std::strcmp(g_list_c[k],images_names[selection[l]])) {
9006                   nb_found+=(ind[l]==0); ind[l] = 1; last_found = l;
9007                 }
9008               break;
9009             case 1 : // Lowest index, case sensitive
9010               if (is_single_res) {
9011                 cimg_forY(selection,l)
9012                   if (!std::strcmp(g_list_c[k],images_names[selection[l]])) {
9013                     nb_found = 1; last_found = l; break;
9014                   }
9015               } else
9016                 cimg_forY(selection,l)
9017                   if (!std::strcmp(g_list_c[k],images_names[selection[l]])) {
9018                     nb_found+=(ind[l]==0); ind[l] = 1; last_found = l; break;
9019                   }
9020               break;
9021             case 2 : // Highest index, case sensitive
9022               if (is_single_res) {
9023                 cimg_rofY(selection,l)
9024                   if (!std::strcmp(g_list_c[k],images_names[selection[l]])) {
9025                     nb_found = 1; last_found = l; break;
9026                   }
9027               } else
9028                 cimg_rofY(selection,l)
9029                   if (!std::strcmp(g_list_c[k],images_names[selection[l]])) {
9030                     nb_found+=(ind[l]==0); ind[l] = 1; last_found = l; break;
9031                   }
9032               break;
9033             case 3 : // All indices, case insensitive
9034               cimg_forY(selection,l)
9035                 if (!cimg::strcasecmp(g_list_c[k],images_names[selection[l]])) {
9036                   nb_found+=(ind[l]==0); ind[l] = 1; last_found = l;
9037                 }
9038               break;
9039             case 4 : // Lowest index, case insensitive
9040               if (is_single_res) {
9041                 cimg_forY(selection,l)
9042                   if (!cimg::strcasecmp(g_list_c[k],images_names[selection[l]])) {
9043                     nb_found = 1; last_found = l; break;
9044                   }
9045               } else
9046                 cimg_forY(selection,l)
9047                   if (!cimg::strcasecmp(g_list_c[k],images_names[selection[l]])) {
9048                     nb_found+=(ind[l]==0); ind[l] = 1; last_found = l; break;
9049                   }
9050               break;
9051             default : // Highest index, case insensitive
9052               if (is_single_res) {
9053                 cimg_rofY(selection,l)
9054                   if (!cimg::strcasecmp(g_list_c[k],images_names[selection[l]])) {
9055                     nb_found = 1; last_found = l; break;
9056                   }
9057               } else
9058                 cimg_rofY(selection,l)
9059                   if (!cimg::strcasecmp(g_list_c[k],images_names[selection[l]])) {
9060                     nb_found+=(ind[l]==0); ind[l] = 1; last_found = l; break;
9061                   }
9062               break;
9063             }
9064           }
9065           if (!nb_found) CImg<char>(1,1,1,1,0).move_to(status);
9066           else if (nb_found==1) {
9067             cimg_snprintf(title,_title.width(),"%u",selection[last_found]);
9068             CImg<char>::string(title).move_to(status);
9069           } else {
9070             ind0.assign(nb_found);
9071             nb_found = 0; cimg_forX(ind,l) if (ind[l]) ind0[nb_found++] = selection[l];
9072             ind0.value_string().move_to(status);
9073           }
9074           if (!is_single_res) ind.assign();
9075           g_list_c.assign();
9076           ++position; continue;
9077         }
9078 
9079         // Normalize.
9080         if (!std::strcmp("normalize",command)) {
9081           gmic_substitute_args(true);
9082           ind0.assign(); ind1.assign();
9083           sep0 = sep1 = *argx = *argy = *indices = 0;
9084           value0 = value1 = 0;
9085           if (cimg_sscanf(argument,"%255[][a-zA-Z0-9_.eE%+-],%255[][a-zA-Z0-9_.eE%+-]%c",
9086                           argx,argy,&end)==2 &&
9087               ((cimg_sscanf(argx,"[%255[a-zA-Z0-9_.%+-]%c%c",indices,&sep0,&end)==2 &&
9088                 sep0==']' &&
9089                 (ind0=selection2cimg(indices,images.size(),images_names,"normalize")).height()==1) ||
9090                (cimg_sscanf(argx,"%lf%c%c",&value0,&sep0,&end)==2 && sep0=='%') ||
9091                cimg_sscanf(argx,"%lf%c",&value0,&end)==1) &&
9092               ((cimg_sscanf(argy,"[%255[a-zA-Z0-9_.%+-]%c%c",formula,&sep1,&end)==2 &&
9093                 sep1==']' &&
9094                 (ind1=selection2cimg(formula,images.size(),images_names,"normalize")).height()==1) ||
9095                (cimg_sscanf(argy,"%lf%c%c",&value1,&sep1,&end)==2 && sep1=='%') ||
9096                cimg_sscanf(argy,"%lf%c",&value1,&end)==1)) {
9097             if (ind0) { value0 = images[*ind0].min(); sep0 = 0; }
9098             if (ind1) { value1 = images[*ind1].max(); sep1 = 0; }
9099             print(images,0,"Normalize image%s in range [%g%s,%g%s].",
9100                   gmic_selection.data(),
9101                   value0,sep0=='%'?"%":"",
9102                   value1,sep1=='%'?"%":"");
9103             cimg_forY(selection,l) {
9104               CImg<T>& img = gmic_check(images[selection[l]]);
9105               nvalue0 = value0; nvalue1 = value1;
9106               vmin = vmax = 0;
9107               if (sep0=='%' || sep1=='%') {
9108                 if (img) vmax = (double)img.max_min(vmin);
9109                 if (sep0=='%') nvalue0 = vmin + (vmax - vmin)*value0/100;
9110                 if (sep1=='%') nvalue1 = vmin + (vmax - vmin)*value1/100;
9111               }
9112               gmic_apply(normalize((T)nvalue0,(T)nvalue1));
9113             }
9114           } else if (cimg_sscanf(argument,"[%255[a-zA-Z0-9_.%+-]%c%c",indices,&sep0,&end)==2 &&
9115                      sep0==']' &&
9116                      (ind0=selection2cimg(indices,images.size(),images_names,"normalize")).height()==1) {
9117             if (images[*ind0]) value1 = (double)images[*ind0].max_min(value0);
9118             print(images,0,"Normalize image%s in range [%g,%g].",
9119                   gmic_selection.data(),
9120                   value0,
9121                   value1);
9122             cimg_forY(selection,l) gmic_apply(normalize((T)value0,(T)value1));
9123           } else arg_error("normalize");
9124           is_change = true; ++position; continue;
9125         }
9126 
9127         // Test difference.
9128         gmic_arithmetic_command("neq",
9129                                 operator_neq,
9130                                 "Compute boolean inequality between image%s and %g%s",
9131                                 gmic_selection.data(),value,ssep,T,
9132                                 operator_neq,
9133                                 "Compute boolean inequality between image%s and image [%d]",
9134                                 gmic_selection.data(),ind[0],
9135                                 operator_neq,
9136                                 "Compute boolean inequality between image%s and expression %s'",
9137                                 gmic_selection.data(),gmic_argument_text_printed(),
9138                                 "Compute boolean inequality between image%s");
9139 
9140         // Manage network permissions.
9141         if (!is_get && !std::strcmp("network",item)) {
9142           gmic_substitute_args(false);
9143           if (cimg_sscanf(argument,"%u%c",
9144                           &pattern,&end)==1 &&
9145               pattern<=1) {
9146             print(images,0,"%s load-from-network mode.",
9147                   pattern?"Enable":"Disable");
9148             cimg::network_mode((bool)pattern,true);
9149           } else arg_error("network");
9150           ++position; continue;
9151         }
9152 
9153         // Discard custom command arguments.
9154         if (!is_get && !std::strcmp("noarg",item)) {
9155           print(images,0,"Discard command arguments.");
9156           if (is_noarg) *is_noarg = true;
9157           continue;
9158         }
9159 
9160         // Add noise.
9161         if (!std::strcmp("noise",command)) {
9162           gmic_substitute_args(false);
9163           int noise_type = 0;
9164           float sigma = 0;
9165           sep = 0;
9166           if ((cimg_sscanf(argument,"%f%c",
9167                            &sigma,&end)==1 ||
9168                (cimg_sscanf(argument,"%f%c%c",
9169                             &sigma,&sep,&end)==2 && sep=='%') ||
9170                cimg_sscanf(argument,"%f,%d%c",
9171                            &sigma,&noise_type,&end)==2 ||
9172                (cimg_sscanf(argument,"%f%c,%d%c",
9173                             &sigma,&sep,&noise_type,&end)==3 && sep=='%')) &&
9174               sigma>=0 && noise_type>=0 && noise_type<=4) {
9175             const char *s_type = noise_type==0?"gaussian":
9176               noise_type==1?"uniform":
9177               noise_type==2?"salt&pepper":
9178               noise_type==3?"poisson":"rice";
9179             if (sep=='%' && noise_type!=2) sigma = -sigma;
9180             print(images,0,"Add %s noise to image%s, with standard deviation %g%s.",
9181                   s_type,
9182                   gmic_selection.data(),
9183                   cimg::abs(sigma),sep=='%'?"%":"");
9184             cimg_forY(selection,l) gmic_apply(noise(sigma,noise_type));
9185           } else arg_error("noise");
9186           is_change = true; ++position; continue;
9187         }
9188 
9189         goto gmic_commands_others;
9190 
9191         //-----------------------------
9192         // Commands starting by 'o...'
9193         //-----------------------------
9194       gmic_commands_o :
9195 
9196         // Exception handling in local environments.
9197         if (!is_get && !std::strcmp("onfail",item)) {
9198           const CImg<char> &s = callstack.back();
9199           if (s[0]!='*' || s[1]!='l')
9200             error(true,images,0,0,
9201                   "Command 'onfail': Not associated to a 'local' command within "
9202                   "the same scope.");
9203           for (int nb_locals = 1; nb_locals && position<commands_line.size(); ++position) {
9204             const char *it = commands_line[position].data();
9205             if (*it==1 &&
9206                 cimg_sscanf(commands_line[position].data() + 1,"%x,%x",&_debug_line,&(_debug_filename=0))>0) {
9207               is_debug_info = true; next_debug_line = _debug_line; next_debug_filename = _debug_filename;
9208             } else {
9209               const bool _is_get = *it=='+' || (*it=='-' && it[1]=='-');
9210               it+=(*it=='+' || *it=='-') + (*it=='-' && it[1]=='-');
9211               if (!std::strcmp("local",it) || !std::strcmp("l",it) ||
9212                   !std::strncmp("local[",it,6) || !std::strncmp("l[",it,2)) ++nb_locals;
9213               else if (!_is_get && (!std::strcmp("endlocal",it) || !std::strcmp("endl",it))) {
9214                 --nb_locals; if (!nb_locals) --position;
9215               }
9216             }
9217           }
9218           continue;
9219         }
9220 
9221         // Draw 3D object.
9222         if (!std::strcmp("object3d",command)) {
9223           gmic_substitute_args(true);
9224           float x = 0, y = 0, z = 0;
9225           unsigned int
9226             is_zbuffer = 1,
9227             _render3d = (unsigned int)std::max(0,render3d),
9228             _is_double3d = is_double3d?1U:0U;
9229           float
9230             _focale3d = focale3d,
9231             _light3d_x = light3d_x,
9232             _light3d_y = light3d_y,
9233             _light3d_z = light3d_z,
9234             _specular_lightness3d = specular_lightness3d,
9235             _specular_shininess3d = specular_shininess3d;
9236           sep = sepx = sepy = *argx = *argy = 0;
9237           opacity = 1;
9238           if (((cimg_sscanf(argument,"[%255[a-zA-Z0-9_.%+-]%c%c",
9239                             indices,&sep,&end)==2 && sep==']') ||
9240                cimg_sscanf(argument,"[%255[a-zA-Z0-9_.%+-]],%255[0-9.eE%+-]%c",
9241                            indices,argx,&end)==2 ||
9242                cimg_sscanf(argument,"[%255[a-zA-Z0-9_.%+-]],%255[0-9.eE%+-],%255[0-9.eE%+-]%c",
9243                            indices,argx,argy,&end)==3 ||
9244                cimg_sscanf(argument,"[%255[a-zA-Z0-9_.%+-]],%255[0-9.eE%+-],%255[0-9.eE%+-],"
9245                            "%f%c",
9246                            indices,argx,argy,&z,&end)==4 ||
9247                cimg_sscanf(argument,"[%255[a-zA-Z0-9_.%+-]],%255[0-9.eE%+-],%255[0-9.eE%+-],"
9248                            "%f,%f%c",
9249                            indices,argx,argy,&z,&opacity,&end)==5 ||
9250                cimg_sscanf(argument,"[%255[a-zA-Z0-9_.%+-]],%255[0-9.eE%+-],%255[0-9.eE%+-],"
9251                            "%f,%f,%u%c",
9252                            indices,argx,argy,&z,&opacity,&_render3d,&end)==6 ||
9253                cimg_sscanf(argument,"[%255[a-zA-Z0-9_.%+-]],%255[0-9.eE%+-],%255[0-9.eE%+-],"
9254                            "%f,%f,%u,%u%c",
9255                            indices,argx,argy,&z,&opacity,&_render3d,&_is_double3d,&end)==7 ||
9256                cimg_sscanf(argument,"[%255[a-zA-Z0-9_.%+-]],%255[0-9.eE%+-],%255[0-9.eE%+-],"
9257                            "%f,%f,%u,%u,%u%c",
9258                            indices,argx,argy,&z,&opacity,&_render3d,&_is_double3d,&is_zbuffer,
9259                            &end)==8 ||
9260                cimg_sscanf(argument,"[%255[a-zA-Z0-9_.%+-]],%255[0-9.eE%+-],%255[0-9.eE%+-],"
9261                            "%f,%f,%u,%u,%u,%f%c",
9262                            indices,argx,argy,&z,&opacity,&_render3d,&_is_double3d,&is_zbuffer,
9263                            &_focale3d,&end)==9 ||
9264                cimg_sscanf(argument,"[%255[a-zA-Z0-9_.%+-]],%255[0-9.eE%+-],%255[0-9.eE%+-],"
9265                            "%f,%f,%u,%u,%u,%f,%f,%f,%f%c",
9266                            indices,argx,argy,&z,&opacity,&_render3d,&_is_double3d,&is_zbuffer,
9267                            &_focale3d,&_light3d_x,&_light3d_y,&_light3d_z,&end)==12 ||
9268                cimg_sscanf(argument,"[%255[a-zA-Z0-9_.%+-]],%255[0-9.eE%+-],%255[0-9.eE%+-],"
9269                            "%f,%f,%u,%u,%u,%f,%f,%f,%f,%f%c",
9270                            indices,argx,argy,&z,&opacity,&_render3d,&_is_double3d,&is_zbuffer,
9271                            &_focale3d,&_light3d_x,&_light3d_y,&_light3d_z,
9272                            &_specular_lightness3d,&end)==13 ||
9273                cimg_sscanf(argument,"[%255[a-zA-Z0-9_.%+-]],%255[0-9.eE%+-],%255[0-9.eE%+-],"
9274                            "%f,%f,%u,%u,%u,%f,%f,%f,%f,%f,%f%c",
9275                            indices,argx,argy,&z,&opacity,&_render3d,&_is_double3d,&is_zbuffer,
9276                            &_focale3d,&_light3d_x,&_light3d_y,&_light3d_z,
9277                            &_specular_lightness3d,&_specular_shininess3d,&end)==14) &&
9278               (ind=selection2cimg(indices,images.size(),images_names,"object3d")).height()==1 &&
9279               (!*argx ||
9280                cimg_sscanf(argx,"%f%c",&x,&end)==1 ||
9281                (cimg_sscanf(argx,"%f%c%c",&x,&sepx,&end)==2 && sepx=='%')) &&
9282               (!*argy ||
9283                cimg_sscanf(argy,"%f%c",&y,&end)==1 ||
9284                (cimg_sscanf(argy,"%f%c%c",&y,&sepy,&end)==2 && sepy=='%')) &&
9285               _render3d<=5 && is_zbuffer<=1 && _is_double3d<=1) {
9286             const CImg<T> img0 = gmic_image_arg(*ind);
9287 
9288             print(images,0,"Draw 3D object [%u] at (%g%s,%g%s,%g) on image%s, with opacity %g, "
9289                   "%s rendering, %s-sided mode, %sz-buffer, focale %g, 3D light at (%g,%g,%g) "
9290                   "and specular properties (%g,%g)",
9291                   *ind,
9292                   x,sepx=='%'?"%":"",
9293                   y,sepy=='%'?"%":"",
9294                   z,
9295                   gmic_selection.data(),
9296                   opacity,
9297                   _render3d==0?"dot":_render3d==1?"wireframe":_render3d==2?"flat":
9298                   _render3d==3?"flat-shaded":_render3d==4?"gouraud-shaded":"phong-shaded",
9299                   _is_double3d?"double":"simple",
9300                   is_zbuffer?"":"no ",
9301                   _focale3d,_light3d_x,_light3d_y,_light3d_z,
9302                   _specular_lightness3d,_specular_shininess3d);
9303             CImgList<float> opacities;
9304             vertices.assign(img0,false);
9305 
9306             try {
9307               if (_render3d>=3) {
9308                 vertices.CImg3dtoobject3d(primitives,g_list_uc,opacities,false);
9309                 if (light3d) g_list_uc.insert(light3d,~0U,true);
9310               } else vertices.CImg3dtoobject3d(primitives,g_list_f,opacities,false);
9311             } catch (CImgException&) {
9312               if (!vertices.is_CImg3d(true,&(*message=0)))
9313                 error(true,images,0,0,
9314                       "Command 'object3d': Invalid 3D object [%u], specified "
9315                       "in argument '%s' (%s).",
9316                       *ind,gmic_argument_text(),message.data());
9317               else throw;
9318             }
9319 
9320             cimg_forY(selection,l) {
9321               CImg<T> &img = images[selection[l]];
9322               const float
9323                 nx = sepx=='%'?x*(img.width() - 1)/100:x,
9324                 ny = sepy=='%'?y*(img.height() - 1)/100:y;
9325               CImg<float> zbuffer(is_zbuffer?img.width():0,is_zbuffer?img.height():0,1,1,0);
9326               if (g_list_f) {
9327                 gmic_apply(draw_object3d(nx,ny,z,vertices,primitives,g_list_f,opacities,
9328                                          _render3d,_is_double3d,_focale3d,
9329                                          _light3d_x,_light3d_y,_light3d_z,
9330                                          _specular_lightness3d,_specular_shininess3d,
9331                                          opacity,zbuffer));
9332 
9333               } else {
9334                 gmic_apply(draw_object3d(nx,ny,z,vertices,primitives,g_list_uc,opacities,
9335                                          _render3d,_is_double3d,_focale3d,
9336                                          _light3d_x,_light3d_y,_light3d_z,
9337                                          _specular_lightness3d,_specular_shininess3d,
9338                                          opacity,zbuffer));
9339               }
9340             }
9341           } else arg_error("object3d");
9342           g_list_f.assign();
9343           g_list_uc.assign();
9344           vertices.assign();
9345           primitives.assign();
9346           is_change = true; ++position; continue;
9347         }
9348 
9349         // Bitwise or.
9350         gmic_arithmetic_command("or",
9351                                 operator|=,
9352                                 "Compute bitwise OR of image%s by %g%s",
9353                                 gmic_selection.data(),value,ssep,Tlong,
9354                                 operator|=,
9355                                 "Compute bitwise OR of image%s by image [%d]",
9356                                 gmic_selection.data(),ind[0],
9357                                 operator_oreq,
9358                                 "Compute bitwise OR of image%s by expression %s",
9359                                 gmic_selection.data(),gmic_argument_text_printed(),
9360                                 "Compute sequential bitwise OR of image%s");
9361 
9362         // Set 3d object opacity.
9363         if (!std::strcmp("opacity3d",command)) {
9364           gmic_substitute_args(false);
9365           value = 1;
9366           if (cimg_sscanf(argument,"%lf%c",
9367                           &value,&end)==1) ++position;
9368           else value = 1;
9369           print(images,0,"Set opacity of 3D object%s to %g.",
9370                 gmic_selection.data(),
9371                 value);
9372           cimg_forY(selection,l) {
9373             const unsigned int uind = selection[l];
9374             CImg<T>& img = images[uind];
9375             try { gmic_apply(color_CImg3d(0,0,0,(float)value,false,true)); }
9376             catch (CImgException&) {
9377               if (!img.is_CImg3d(true,&(*message=0)))
9378                 error(true,images,0,0,
9379                       "Command 'opacity3d': Invalid 3D object [%d], "
9380                       "in selected image%s (%s).",
9381                       uind,gmic_selection.data(),message.data());
9382               else throw;
9383             }
9384           }
9385           is_change = true; continue;
9386         }
9387 
9388         // Output.
9389         if (!is_get && !std::strcmp("output",command)) {
9390           gmic_substitute_args(false);
9391 
9392           // Set good alias for shared variables.
9393           CImg<char> &_filename = _color, &filename_tmp = _title, &options = _argc;
9394           char cext[12];
9395           *cext = *_filename = *filename_tmp = *options = 0;
9396           CImgList<unsigned int> empty_indices;
9397           CImg<char> eselec;
9398 
9399           if (cimg_sscanf(argument,"%11[a-zA-Z]:%4095[^,],%255s", // Detect forced file format
9400                           cext,_filename.data(),options.data())<2 ||
9401               !cext[1]) { // Length of preprend 'ext' must be >=2 (avoid case 'C:\\...' on Windows)
9402             *cext = *_filename = *options = 0;
9403             if (cimg_sscanf(argument,"%4095[^,],%255s",_filename.data(),options.data())!=2) {
9404               std::strncpy(_filename,argument,_filename.width() - 1);
9405               _filename[_filename.width() - 1] = 0;
9406             }
9407           }
9408           strreplace_fw(_filename);
9409           strreplace_fw(options);
9410           const bool is_stdout = *_filename=='-' && (!_filename[1] || _filename[1]=='.');
9411 
9412           if (*cext) { // Force output to be written as a '.ext' file : generate random filename
9413             if (is_stdout) {
9414               // Simplify filename 'ext:-.foo' as '-.ext'.
9415               cimg_snprintf(_filename,_filename.width(),"-.%s",cext);
9416               *cext = 0;
9417             } else {
9418               std::FILE *file = 0;
9419               do {
9420                 cimg_snprintf(filename_tmp,filename_tmp.width(),"%s%c%s.%s",
9421                               cimg::temporary_path(),cimg_file_separator,
9422                               cimg::filenamerand(),cext);
9423                 if ((file=cimg::std_fopen(filename_tmp,"rb"))!=0) cimg::fclose(file);
9424               } while (file);
9425             }
9426           }
9427           const char
9428             *const filename = *cext?filename_tmp:_filename,
9429             *const ext = cimg::split_filename(filename);
9430           CImg<char> uext = CImg<char>::string(ext);
9431           cimg::lowercase(uext);
9432 
9433           if (!std::strcmp(uext,"off")) {
9434 
9435             // OFF file (geomview).
9436             *formula = 0;
9437             std::strncpy(formula,filename,_formula.width() - 1);
9438             formula[_formula.width() - 1] = 0;
9439 
9440             if (*options)
9441               error(true,images,0,0,
9442                     "Command 'output': File '%s', format does not take any output options (options '%s' specified).",
9443                     formula,options.data());
9444 
9445             print(images,0,"Output 3D object%s as %s file '%s'.",
9446                   gmic_selection.data(),uext.data(),formula);
9447 
9448             cimg_forY(selection,l) {
9449               const unsigned int uind = selection[l];
9450               const CImg<T>& img = gmic_check(images[uind]);
9451               if (selection.height()!=1) cimg::number_filename(filename,l,6,formula);
9452               CImgList<float> opacities;
9453               vertices.assign(img,false);
9454               try {
9455                 vertices.CImg3dtoobject3d(primitives,g_list_f,opacities,false).
9456                   save_off(primitives,g_list_f,formula);
9457               } catch (CImgException&) {
9458                 if (!vertices.is_CImg3d(true,&(*message=0)))
9459                   error(true,images,0,0,
9460                         "Command 'output': 3D object file '%s', invalid 3D object [%u] "
9461                         "in selected image%s (%s).",
9462                         formula,uind,gmic_selection.data(),message.data());
9463                 else throw;
9464               }
9465             }
9466             vertices.assign();
9467             primitives.assign();
9468             g_list_f.assign();
9469           } else if (!std::strcmp(uext,"cpp") || !std::strcmp(uext,"c") ||
9470                      !std::strcmp(uext,"hpp") || !std::strcmp(uext,"h") ||
9471                      !std::strcmp(uext,"pan") || !std::strcmp(uext,"pnk") ||
9472                      !std::strcmp(uext,"pgm") || !std::strcmp(uext,"ppm") ||
9473                      !std::strcmp(uext,"ppm")) {
9474 
9475             // .cpp, .c, .hpp, .h, .pan, .pnk, .pgm, .ppm or .pnm file.
9476             const char *
9477               stype = (cimg_sscanf(options,"%255[a-z64]%c",&(*argx=0),&(end=0))==1 ||
9478                        (cimg_sscanf(options,"%255[a-z64]%c",&(*argx=0),&end)==2 && end==','))?
9479               argx:"auto";
9480             g_list.assign(selection.height());
9481             cimg_forY(selection,l) if (!gmic_check(images[selection(l)]))
9482               CImg<unsigned int>::vector(selection(l)).move_to(empty_indices);
9483             if (empty_indices && is_verbose) {
9484               selection2string(empty_indices>'y',images_names,1,eselec);
9485               warn(images,0,false,
9486                    "Command 'output': Image%s %s empty.",
9487                    eselec.data(),empty_indices.size()>1?"are":"is");
9488             }
9489             cimg_forY(selection,l)
9490               g_list[l].assign(images[selection[l]],images[selection[l]]?true:false);
9491             if (g_list.size()==1)
9492               print(images,0,
9493                     "Output image%s as %s file '%s', with pixel type '%s' (1 image %dx%dx%dx%d).",
9494                     gmic_selection.data(),
9495                     uext.data(),_filename.data(),
9496                     stype,
9497                     g_list[0].width(),g_list[0].height(),
9498                     g_list[0].depth(),g_list[0].spectrum());
9499             else print(images,0,"Output image%s as %s file '%s', with pixel type '%s'.",
9500                        gmic_selection.data(),
9501                        uext.data(),_filename.data(),
9502                        stype);
9503             if (!g_list)
9504               error(true,images,0,0,
9505                     "Command 'output': File '%s', instance list (%u,%p) is empty.",
9506                     _filename.data(),g_list.size(),g_list.data());
9507 
9508 #define gmic_save_multitype(value_type,svalue_type) \
9509               if (!std::strcmp(stype,svalue_type)) { \
9510                 if (g_list.size()==1) \
9511                   CImg<value_type>::copy_rounded(g_list[0]).save(filename); \
9512                 else { \
9513                   cimglist_for(g_list,l) { \
9514                     cimg::number_filename(filename,l,6,formula); \
9515                     CImg<value_type>::copy_rounded(g_list[l]).save(formula); \
9516                   } \
9517                 } \
9518               }
9519             if (!std::strcmp(stype,"auto")) stype = CImg<T>::storage_type(g_list);
9520             gmic_save_multitype(unsigned char,"uchar")
9521             else gmic_save_multitype(unsigned char,"unsigned char")
9522               else gmic_save_multitype(char,"char")
9523                 else gmic_save_multitype(unsigned short,"ushort")
9524                   else gmic_save_multitype(unsigned short,"unsigned short")
9525                     else gmic_save_multitype(short,"short")
9526                       else gmic_save_multitype(unsigned int,"uint")
9527                         else gmic_save_multitype(unsigned int,"unsigned int")
9528                           else gmic_save_multitype(int,"int")
9529                             else gmic_save_multitype(uint64T,"uint64")
9530                               else gmic_save_multitype(uint64T,"unsigned int64")
9531                                 else gmic_save_multitype(int64T,"int64")
9532                                   else gmic_save_multitype(float,"float")
9533                                     else gmic_save_multitype(double,"double")
9534                                       else error(true,images,0,0,
9535                                                  "Command 'output': File '%s', invalid "
9536                                                  "specified pixel type '%s'.",
9537                                                  _filename.data(),stype);
9538           } else if (!std::strcmp(uext,"tiff") || !std::strcmp(uext,"tif")) {
9539 
9540             // TIFF file.
9541             const char *
9542               stype = (cimg_sscanf(options,"%255[a-z64]%c",&(*argx=0),&(end=0))==1 ||
9543                        (cimg_sscanf(options,"%255[a-z64]%c",&(*argx=0),&end)==2 && end==','))?
9544               argx:"auto";
9545             const unsigned int l_stype = (unsigned int)std::strlen(stype);
9546             const char *const _options = options.data() + (stype!=argx?0:l_stype + (end==','?1:0));
9547             float _is_multipage = 0;
9548             *argy = 0; opacity = 1;
9549             if (cimg_sscanf(_options,"%255[a-z],%f,%f",argy,&_is_multipage,&opacity)<1)
9550               cimg_sscanf(_options,"%f,%f",&_is_multipage,&opacity);
9551             const unsigned int compression_type =
9552               !cimg::strcasecmp(argy,"jpeg") ||
9553               !cimg::strcasecmp(argy,"jpg")?2:
9554               !cimg::strcasecmp(argy,"lzw")?1U:0U;
9555             const bool is_multipage = (bool)cimg::round(_is_multipage);
9556             const bool use_bigtiff = (bool)cimg::round(opacity);
9557 
9558             g_list.assign(selection.height());
9559             cimg_forY(selection,l) if (!gmic_check(images[selection(l)]))
9560               CImg<unsigned int>::vector(selection(l)).move_to(empty_indices);
9561             if (empty_indices && is_verbose) {
9562               selection2string(empty_indices>'y',images_names,1,eselec);
9563               warn(images,0,false,
9564                    "Command 'output': Image%s %s empty.",
9565                    eselec.data(),empty_indices.size()>1?"are":"is");
9566             }
9567             cimg_forY(selection,l)
9568               g_list[l].assign(images[selection[l]],g_list[l]?true:false);
9569             if (g_list.size()==1)
9570               print(images,0,"Output image%s as %s file '%s', with pixel type '%s', %s compression "
9571                     "and %sbigtiff support (1 image %dx%dx%dx%d).",
9572                     gmic_selection.data(),
9573                     uext.data(),_filename.data(),stype,
9574                     compression_type==2?"JPEG":compression_type==1?"LZW":"no",
9575                     use_bigtiff?"":"no ",
9576                     g_list[0].width(),g_list[0].height(),
9577                     g_list[0].depth(),g_list[0].spectrum());
9578             else print(images,0,"Output image%s as %s file '%s', with pixel type '%s', "
9579                        "%s compression, %s-page mode and %s bigtiff support.",
9580                        gmic_selection.data(),
9581                        uext.data(),_filename.data(),stype,
9582                        compression_type==2?"JPEG":compression_type==1?"LZW":"no",
9583                        is_multipage?"multi":"single",
9584                        use_bigtiff?"":"no ");
9585             if (!g_list)
9586               error(true,images,0,0,
9587                     "Command 'output': File '%s', instance list (%u,%p) is empty.",
9588                     _filename.data(),g_list.size(),g_list.data());
9589 
9590 #define gmic_save_tiff(value_type,svalue_type) \
9591               if (!std::strcmp(stype,svalue_type)) { \
9592                 if (g_list.size()==1 || is_multipage) \
9593                   CImgList<value_type>::copy_rounded(g_list). \
9594                     save_tiff(filename,compression_type,0,0,use_bigtiff); \
9595                 else { \
9596                   cimglist_for(g_list,l) { \
9597                     cimg::number_filename(filename,l,6,formula); \
9598                     CImg<value_type>::copy_rounded(g_list[l]). \
9599                       save_tiff(formula,compression_type,0,0,use_bigtiff); \
9600                   } \
9601                 } \
9602               }
9603             if (!std::strcmp(stype,"auto")) stype = CImg<T>::storage_type(g_list);
9604             gmic_save_tiff(unsigned char,"uchar")
9605             else gmic_save_tiff(unsigned char,"unsigned char")
9606               else gmic_save_tiff(char,"char")
9607                 else gmic_save_tiff(unsigned short,"ushort")
9608                   else gmic_save_tiff(unsigned short,"unsigned short")
9609                     else gmic_save_tiff(short,"short")
9610                       else gmic_save_tiff(unsigned int,"uint")
9611                         else gmic_save_tiff(unsigned int,"unsigned int")
9612                           else gmic_save_tiff(int,"int")
9613                             else gmic_save_tiff(uint64T,"uint64")
9614                               else gmic_save_tiff(uint64T,"unsigned int64")
9615                                 else gmic_save_tiff(int64T,"int64")
9616                                   else gmic_save_tiff(float,"float")
9617                                     else gmic_save_tiff(double,"double")
9618                                       else error(true,images,0,0,
9619                                                  "Command 'output': File '%s', invalid "
9620                                                  "specified pixel type '%s'.",
9621                                                  _filename.data(),stype);
9622 
9623           } else if (!std::strcmp(uext,"gif")) {
9624 
9625             // GIF file.
9626             float fps = 0, _nb_loops = 0;
9627             g_list.assign(selection.height());
9628             cimg_forY(selection,l) if (!gmic_check(images[selection(l)]))
9629               CImg<unsigned int>::vector(selection(l)).move_to(empty_indices);
9630             if (empty_indices && is_verbose) {
9631               selection2string(empty_indices>'y',images_names,1,eselec);
9632               warn(images,0,false,
9633                    "Command 'output': Image%s %s empty.",
9634                    eselec.data(),empty_indices.size()>1?"are":"is");
9635             }
9636             cimg_forY(selection,l)
9637               g_list[l].assign(images[selection[l]],g_list[l]?true:false);
9638             if (g_list.size()>1 && cimg_sscanf(options,"%f,%f",&fps,&_nb_loops)>=1 && fps>0) {
9639               // Save animated .gif file.
9640               const unsigned int nb_loops = (unsigned int)cimg::round(_nb_loops);
9641               if (nb_loops)
9642                 print(images,0,
9643                       "Output image%s as animated %s file '%s', with %g fps and %u loops.",
9644                       gmic_selection.data(),uext.data(),_filename.data(),fps,nb_loops);
9645               else
9646                 print(images,0,
9647                       "Output image%s as animated %s file '%s', with %g fps.",
9648                       gmic_selection.data(),uext.data(),_filename.data(),fps);
9649               g_list.save_gif_external(filename,fps,nb_loops);
9650             } else {
9651               if (g_list.size()==1)
9652                 print(images,0,"Output image%s as %s file '%s' (1 image %dx%dx%dx%d).",
9653                       gmic_selection.data(),
9654                       uext.data(),_filename.data(),
9655                       g_list[0].width(),g_list[0].height(),
9656                       g_list[0].depth(),g_list[0].spectrum());
9657               else print(images,0,"Output image%s as %s file '%s'.",
9658                          gmic_selection.data(),uext.data(),_filename.data());
9659               g_list.save(filename); // Save distinct .gif files
9660             }
9661           } else if (!std::strcmp(uext,"jpeg") || !std::strcmp(uext,"jpg")) {
9662 
9663             // JPEG file.
9664             float quality = 100;
9665             if (cimg_sscanf(options,"%f%c",&quality,&end)!=1) quality = 100;
9666             if (quality<0) quality = 0; else if (quality>100) quality = 100;
9667             g_list.assign(selection.height());
9668             cimg_forY(selection,l) if (!gmic_check(images[selection(l)]))
9669               CImg<unsigned int>::vector(selection(l)).move_to(empty_indices);
9670             if (empty_indices && is_verbose) {
9671               selection2string(empty_indices>'y',images_names,1,eselec);
9672               warn(images,0,false,
9673                    "Command 'output': Image%s %s empty.",
9674                    eselec.data(),empty_indices.size()>1?"are":"is");
9675             }
9676             cimg_forY(selection,l)
9677               g_list[l].assign(images[selection[l]],g_list[l]?true:false);
9678             if (g_list.size()==1)
9679               print(images,0,
9680                     "Output image%s as %s file '%s', with quality %g%% (1 image %dx%dx%dx%d).",
9681                     gmic_selection.data(),
9682                     uext.data(),_filename.data(),
9683                     quality,
9684                     g_list[0].width(),g_list[0].height(),
9685                     g_list[0].depth(),g_list[0].spectrum());
9686             else print(images,0,"Output image%s as %s file '%s', with quality %g%%.",
9687                        gmic_selection.data(),
9688                        uext.data(),_filename.data(),
9689                        quality);
9690             if (!g_list)
9691               error(true,images,0,0,
9692                     "Command 'output': File '%s', instance list (%u,%p) is empty.",
9693                     _filename.data(),g_list.size(),g_list.data());
9694             if (g_list.size()==1)
9695               g_list[0].save_jpeg(filename,(unsigned int)cimg::round(quality));
9696             else {
9697               cimglist_for(g_list,l) {
9698                 cimg::number_filename(filename,l,6,formula);
9699                 g_list[l].save_jpeg(formula,(unsigned int)cimg::round(quality));
9700               }
9701             }
9702           } else if (!std::strcmp(uext,"mnc") && *options) {
9703 
9704             // MNC file.
9705             g_list.assign(selection.height());
9706             cimg_forY(selection,l) if (!gmic_check(images[selection(l)]))
9707               CImg<unsigned int>::vector(selection(l)).move_to(empty_indices);
9708             if (empty_indices && is_verbose) {
9709               selection2string(empty_indices>'y',images_names,1,eselec);
9710               warn(images,0,false,
9711                    "Command 'output': Image%s %s empty.",
9712                    eselec.data(),empty_indices.size()>1?"are":"is");
9713             }
9714             cimg_forY(selection,l)
9715               g_list[l].assign(images[selection[l]],g_list[l]?true:false);
9716             if (g_list.size()==1)
9717               print(images,0,
9718                     "Output image%s as %s file '%s', with header get from file '%s' "
9719                     "(1 image %dx%dx%dx%d).",
9720                     gmic_selection.data(),
9721                     uext.data(),_filename.data(),
9722                     options.data(),
9723                     g_list[0].width(),g_list[0].height(),
9724                     g_list[0].depth(),g_list[0].spectrum());
9725             else
9726               print(images,0,
9727                     "Output image%s as %s file '%s', with header get from file '%s'.",
9728                     gmic_selection.data(),
9729                     uext.data(),_filename.data(),
9730                     options.data());
9731             if (g_list.size()==1)
9732               g_list[0].save_minc2(filename,options);
9733             else {
9734               cimglist_for(g_list,l) {
9735                 cimg::number_filename(filename,l,6,formula);
9736                 g_list[l].save_minc2(formula,options);
9737               }
9738             }
9739           } else if (!std::strcmp(uext,"raw")) {
9740 
9741             // RAW data file.
9742             const char *stype = cimg_sscanf(options,"%255[a-z64]%c",argx,&end)==1?argx:"auto";
9743             g_list.assign(selection.height());
9744             cimg_forY(selection,l) if (!gmic_check(images[selection(l)]))
9745               CImg<unsigned int>::vector(selection(l)).move_to(empty_indices);
9746             if (empty_indices && is_verbose) {
9747               selection2string(empty_indices>'y',images_names,1,eselec);
9748               warn(images,0,false,
9749                    "Command 'output': Image%s %s empty.",
9750                    eselec.data(),empty_indices.size()>1?"are":"is");
9751             }
9752             cimg_forY(selection,l)
9753               g_list[l].assign(images[selection[l]],images[selection[l]]?true:false);
9754             if (g_list.size()==1)
9755               print(images,0,
9756                     "Output image%s as %s file '%s', with pixel type '%s' (1 image %dx%dx%dx%d).",
9757                     gmic_selection.data(),
9758                     uext.data(),_filename.data(),
9759                     stype,
9760                     g_list[0].width(),g_list[0].height(),
9761                     g_list[0].depth(),g_list[0].spectrum());
9762             else print(images,0,"Output image%s as %s file '%s', with pixel type '%s'.",
9763                        gmic_selection.data(),
9764                        uext.data(),_filename.data(),
9765                        stype);
9766             if (!g_list)
9767               error(true,images,0,0,
9768                     "Command 'output': File '%s', instance list (%u,%p) is empty.",
9769                     _filename.data(),g_list.size(),g_list.data());
9770 
9771 #define gmic_save_raw(value_type,svalue_type) \
9772               if (!std::strcmp(stype,svalue_type)) { \
9773                 if (g_list.size()==1) \
9774                   CImg<value_type>::copy_rounded(g_list[0]).save_raw(filename); \
9775                 else { \
9776                   cimglist_for(g_list,l) { \
9777                     cimg::number_filename(filename,l,6,formula); \
9778                     CImg<value_type>::copy_rounded(g_list[l]).save_raw(formula); \
9779                   } \
9780                 } \
9781               }
9782             if (!std::strcmp(stype,"auto")) stype = CImg<T>::storage_type(g_list);
9783             gmic_save_raw(unsigned char,"uchar")
9784             else gmic_save_raw(unsigned char,"unsigned char")
9785               else gmic_save_raw(char,"char")
9786                 else gmic_save_raw(unsigned short,"ushort")
9787                   else gmic_save_raw(unsigned short,"unsigned short")
9788                     else gmic_save_raw(short,"short")
9789                       else gmic_save_raw(unsigned int,"uint")
9790                         else gmic_save_raw(unsigned int,"unsigned int")
9791                           else gmic_save_raw(int,"int")
9792                             else gmic_save_raw(uint64T,"uint64")
9793                               else gmic_save_raw(uint64T,"unsigned int64")
9794                                 else gmic_save_raw(int64T,"int64")
9795                                   else gmic_save_raw(float,"float")
9796                                     else gmic_save_raw(double,"double")
9797                                       else error(true,images,0,0,
9798                                                  "Command 'output': File '%s', invalid "
9799                                                  "specified pixel type '%s'.",
9800                                                  _filename.data(),stype);
9801           } else if (!std::strcmp(uext,"yuv")) {
9802 
9803             // YUV sequence.
9804             if (cimg_sscanf(options,"%f",&opacity)!=1) opacity = 444;
9805             else opacity = cimg::round(opacity);
9806             const unsigned int ich = (unsigned int)opacity;
9807             if (ich!=420 && ich!=422 && ich!=444)
9808               error(true,images,0,0,
9809                     "Command 'output': YUV file '%s', specified chroma subsampling '%g' is invalid.",
9810                     _filename.data(),opacity);
9811             g_list.assign(selection.height());
9812             cimg_forY(selection,l)
9813               g_list[l].assign(images[selection[l]],images[selection[l]]?true:false);
9814             print(images,0,"Output image%s as YUV-%u:%u:%u file '%s'.",
9815                   gmic_selection.data(),
9816                   ich/100,(ich/10)%10,ich%10,
9817                   _filename.data());
9818             g_list.save_yuv(filename,ich,true);
9819 
9820           } else if (!std::strcmp(uext,"cimg") || !std::strcmp(uext,"cimgz")) {
9821 
9822             // CImg[z] file.
9823             const char *stype = cimg_sscanf(options,"%255[a-z64]%c",argx,&end)==1?argx:"auto";
9824             g_list.assign(selection.height());
9825             cimg_forY(selection,l)
9826               g_list[l].assign(images[selection[l]],images[selection[l]]?true:false);
9827             print(images,0,"Output image%s as %s file '%s', with pixel type '%s'.",
9828                   gmic_selection.data(),
9829                   uext.data(),_filename.data(),
9830                   stype);
9831 
9832 #define gmic_save_cimg(value_type,svalue_type) \
9833               if (!std::strcmp(stype,svalue_type)) \
9834                 CImgList<value_type>::copy_rounded(g_list).save(filename);
9835 
9836             if (!std::strcmp(stype,"auto")) stype = CImg<T>::storage_type(g_list);
9837             gmic_save_cimg(unsigned char,"uchar")
9838             else gmic_save_cimg(unsigned char,"unsigned char")
9839               else gmic_save_cimg(char,"char")
9840                 else gmic_save_cimg(unsigned short,"ushort")
9841                   else gmic_save_cimg(unsigned short,"unsigned short")
9842                     else gmic_save_cimg(short,"short")
9843                       else gmic_save_cimg(unsigned int,"uint")
9844                         else gmic_save_cimg(unsigned int,"unsigned int")
9845                           else gmic_save_cimg(int,"int")
9846                             else gmic_save_cimg(uint64T,"uint64")
9847                               else gmic_save_cimg(uint64T,"unsigned int64")
9848                                 else gmic_save_cimg(int64T,"int64")
9849                                   else gmic_save_cimg(float,"float")
9850                                     else gmic_save_cimg(double,"double")
9851                                       else error(true,images,0,0,
9852                                                  "Command 'output': File '%s', invalid "
9853                                                  "specified pixel type '%s'.",
9854                                                  _filename.data(),stype);
9855           } else if (!std::strcmp(uext,"gmz") || !*ext) {
9856 
9857             // GMZ file.
9858             const char *stype = cimg_sscanf(options,"%255[a-z64]%c",argx,&end)==1?argx:"auto";
9859             g_list.assign(selection.height());
9860             g_list_c.assign(selection.height());
9861             cimg_forY(selection,l) {
9862               g_list[l].assign(images[selection[l]],images[selection[l]]?true:false);
9863               g_list_c[l].assign(images_names[selection[l]],true);
9864             }
9865             print(images,0,"Output image%s as %s file '%s', with pixel type '%s'.",
9866                   gmic_selection.data(),
9867                   uext.data(),_filename.data(),
9868                   stype);
9869 
9870 #define gmic_save_gmz(value_type,svalue_type) \
9871               if (!std::strcmp(stype,svalue_type)) \
9872                 CImg<value_type>::save_gmz(filename,CImgList<value_type>::copy_rounded(g_list),g_list_c);
9873 
9874             if (!std::strcmp(stype,"auto")) stype = CImg<T>::storage_type(g_list);
9875             gmic_save_gmz(unsigned char,"uchar")
9876             else gmic_save_gmz(unsigned char,"unsigned char")
9877               else gmic_save_gmz(char,"char")
9878                 else gmic_save_gmz(unsigned short,"ushort")
9879                   else gmic_save_gmz(unsigned short,"unsigned short")
9880                     else gmic_save_gmz(short,"short")
9881                       else gmic_save_gmz(unsigned int,"uint")
9882                         else gmic_save_gmz(unsigned int,"unsigned int")
9883                           else gmic_save_gmz(int,"int")
9884                             else gmic_save_gmz(uint64T,"uint64")
9885                               else gmic_save_gmz(uint64T,"unsigned int64")
9886                                 else gmic_save_gmz(int64T,"int64")
9887                                   else gmic_save_gmz(float,"float")
9888                                     else gmic_save_gmz(double,"double")
9889                                       else error(true,images,0,0,
9890                                                  "Command 'output': File '%s', invalid "
9891                                                  "specified pixel type '%s'.",
9892                                                  _filename.data(),stype);
9893           } else if (!std::strcmp(uext,"avi") ||
9894                      !std::strcmp(uext,"mov") ||
9895                      !std::strcmp(uext,"asf") ||
9896                      !std::strcmp(uext,"divx") ||
9897                      !std::strcmp(uext,"flv") ||
9898                      !std::strcmp(uext,"mpg") ||
9899                      !std::strcmp(uext,"m1v") ||
9900                      !std::strcmp(uext,"m2v") ||
9901                      !std::strcmp(uext,"m4v") ||
9902                      !std::strcmp(uext,"mjp") ||
9903                      !std::strcmp(uext,"mp4") ||
9904                      !std::strcmp(uext,"mkv") ||
9905                      !std::strcmp(uext,"mpe") ||
9906                      !std::strcmp(uext,"movie") ||
9907                      !std::strcmp(uext,"ogm") ||
9908                      !std::strcmp(uext,"ogg") ||
9909                      !std::strcmp(uext,"ogv") ||
9910                      !std::strcmp(uext,"qt") ||
9911                      !std::strcmp(uext,"rm") ||
9912                      !std::strcmp(uext,"vob") ||
9913                      !std::strcmp(uext,"wmv") ||
9914                      !std::strcmp(uext,"xvid") ||
9915                      !std::strcmp(uext,"mpeg")) {
9916 
9917             // Generic video file.
9918             float fps = 0, keep_open = 0;
9919             name.assign(8); *name = 0; // Codec
9920             cimg_sscanf(options,"%f,%7[a-zA-Z0-9],%f",&fps,name.data(),&keep_open);
9921             fps = cimg::round(fps);
9922             if (!fps) fps = 25;
9923             if (*name=='0' && !name[1]) *name = 0;
9924             g_list.assign(selection.height());
9925             cimg_forY(selection,l) if (!gmic_check(images[selection(l)]))
9926               CImg<unsigned int>::vector(selection(l)).move_to(empty_indices);
9927             if (empty_indices && is_verbose) {
9928               selection2string(empty_indices>'y',images_names,1,eselec);
9929               warn(images,0,false,
9930                    "Command 'output': Image%s %s empty.",
9931                    eselec.data(),empty_indices.size()>1?"are":"is");
9932             }
9933             cimg_forY(selection,l)
9934               g_list[l].assign(images[selection[l]],g_list[l]?true:false);
9935             print(images,0,"Output image%s as %s file '%s', with %g fps and %s codec.",
9936                   gmic_selection.data(),
9937                   uext.data(),_filename.data(),
9938                   fps,*name?name.data():"(default)");
9939             try {
9940               g_list.save_video(filename,(unsigned int)fps,name,(bool)keep_open);
9941             } catch (CImgException &e) {
9942               warn(images,0,false,
9943                    "Command 'output': Cannot encode file '%s' natively (%s). Trying fallback function.",
9944                    filename,e.what());
9945               g_list.save_ffmpeg_external(filename,(unsigned int)fps);
9946             }
9947           } else { // Any other extension
9948 
9949             // Check if a custom command handling requested file format exists.
9950             cimg_snprintf(formula,_formula.width(),"output_%s",uext.data());
9951             hash = hashcode(formula,false);
9952             if (search_sorted(formula,commands_names[hash],commands_names[hash].size(),pattern)) { // Command found
9953               cimg_snprintf(formula,_formula.width(),"output_%s[%s] \"%s\"",
9954                             uext.data(),*s_selection?s_selection:"^",filename);
9955               const CImgList<char> ncommands_line = commands_line_to_CImgList(formula);
9956               unsigned int nposition = 0;
9957               CImg<char>::string("").move_to(callstack); // Anonymous scope
9958               _run(ncommands_line,nposition,images,images_names,images,images_names,variables_sizes,0,0,0);
9959               callstack.remove();
9960 
9961             } else { // Not found -> Try generic image saver
9962 
9963               g_list.assign(selection.height());
9964               cimg_forY(selection,l) if (!gmic_check(images[selection(l)]))
9965                 CImg<unsigned int>::vector(selection(l)).move_to(empty_indices);
9966               if (empty_indices && is_verbose) {
9967                 selection2string(empty_indices>'y',images_names,1,eselec);
9968                 warn(images,0,false,
9969                      "Command 'output': Image%s %s empty.",
9970                      eselec.data(),empty_indices.size()>1?"are":"is");
9971               }
9972               cimg_forY(selection,l)
9973                 g_list[l].assign(images[selection[l]],g_list[l]?true:false);
9974               if (g_list.size()==1)
9975                 print(images,0,"Output image%s as %s file '%s' (1 image %dx%dx%dx%d).",
9976                       gmic_selection.data(),
9977                       uext.data(),_filename.data(),
9978                       g_list[0].width(),g_list[0].height(),
9979                       g_list[0].depth(),g_list[0].spectrum());
9980               else print(images,0,"Output image%s as %s file '%s'.",
9981                          gmic_selection.data(),uext.data(),_filename.data());
9982 
9983               if (*options)
9984                 error(true,images,0,0,
9985                       "Command 'output': File '%s', format '%s' does not take any output options "
9986                       "(options '%s' specified).",
9987                       _filename.data(),ext,options.data());
9988               if (g_list.size()==1) g_list[0].save(filename); else g_list.save(filename);
9989             }
9990           }
9991 
9992           if (*cext) { // When output forced to 'ext' : copy final file to specified location
9993             try {
9994               CImg<unsigned char>::get_load_raw(filename_tmp).save_raw(_filename);
9995               std::remove(filename_tmp);
9996             } catch (...) { // Failed, maybe 'filename_tmp' consists of several numbered images
9997               bool save_failure = false;
9998               *message = 0;
9999               for (unsigned int i = 0; i!=~0U; ++i) {
10000                 cimg::number_filename(filename_tmp,i,6,formula);
10001                 cimg::number_filename(_filename,i,6,message);
10002                 try { CImg<unsigned char>::get_load_raw(formula).save_raw(message); }
10003                 catch (...) { i = ~0U - 1; if (!i) save_failure = true; }
10004               }
10005               if (save_failure)
10006                 error(true,images,0,0,
10007                       "Command 'output': Invalid write of file '%s' from temporary file '%s'.",
10008                       _filename.data(),filename_tmp.data());
10009             }
10010           }
10011           g_list.assign(); g_list_c.assign();
10012           if (is_stdout) std::fflush(stdout);
10013           is_change = false; ++position; continue;
10014         }
10015 
10016         goto gmic_commands_others;
10017 
10018         //-----------------------------
10019         // Commands starting by 'p...'
10020         //-----------------------------
10021       gmic_commands_p :
10022 
10023         // Pass image from parent context.
10024         if (!is_get && !std::strcmp("pass",command)) {
10025           gmic_substitute_args(false);
10026           if (cimg_sscanf(argument,"%d%c",&(err=2),&end)==1 && err>=-1 && err<=2) ++position;
10027           else err = 2;
10028 
10029           if (err<0)
10030             print(images,0,"Return ind%s of image%s from parent context.",
10031                   selection.height()==1?"ex":"ices",
10032                   gmic_selection.data());
10033           else {
10034             print(images,0,"Insert image%s from parent context %s%s.",
10035                   gmic_selection.data(),
10036                   err==0?"in non-shared state":
10037                   err==1?"in shared state":"using adaptive state",
10038                   selection.height()>1?"s":"");
10039 
10040             cimg_forY(selection,l) {
10041               CImg<T> &img = parent_images[selection[l]];
10042               const T *p = 0;
10043               std::memcpy(&p,&img._width,sizeof(void*));
10044 
10045               if (p && !img.data()) {
10046                 // Parent image is in the current selection -> must search the current list
10047                 bool found_image = false;
10048                 cimglist_for(images,i) {
10049                   if (images[i].data()==p) { // Found it !
10050                     images.insert(images[i],~0U,err==1);
10051                     images_names.insert(images_names[i].get_copymark());
10052                     found_image = true;
10053                     break;
10054                   }
10055                 }
10056                 if (!found_image) error(true,images,0,0,
10057                                         "Command 'pass': Unreferenced image [%d] from parent context "
10058                                         "(has been re-allocated in current context or reserved by another thread).",
10059                                         selection[l]);
10060               } else { // Easy case, parent image not in the current selection
10061                 images.insert(img,~0U,(bool)err);
10062                 images_names.insert(parent_images_names[selection[l]].get_copymark());
10063               }
10064             }
10065           }
10066           selection.value_string().move_to(status);
10067           is_change = true; continue;
10068         }
10069 
10070         // Run multiple commands in parallel.
10071         if (!is_get && !std::strcmp("parallel",item)) {
10072           gmic_substitute_args(false);
10073           const char *_arg = argument, *_arg_text = gmic_argument_text_printed();
10074           bool wait_mode = true;
10075           if ((*_arg=='0' || *_arg=='1') && (_arg[1]==',' || !_arg[1])) {
10076             wait_mode = (bool)(*_arg - '0'); _arg+=2; _arg_text+=2;
10077           }
10078           CImgList<char> arguments = CImg<char>::string(_arg).get_split(CImg<char>::vector(','),0,false);
10079 
10080           CImg<_gmic_parallel<T> >(1,arguments.width()).move_to(gmic_threads);
10081           CImg<_gmic_parallel<T> > &_gmic_threads = gmic_threads.back();
10082 
10083 #ifdef gmic_is_parallel
10084           print(images,0,"Execute %d command%s '%s' in parallel%s.",
10085                 arguments.width(),arguments.width()>1?"s":"",_arg_text,
10086                 wait_mode?" and wait thread termination immediately":
10087                 " and wait thread termination when current environment ends");
10088 #else // #ifdef gmic_is_parallel
10089           print(images,0,"Execute %d commands '%s' (run sequentially, "
10090                 "parallel computing disabled).",
10091                 arguments.width(),_arg_text);
10092 #endif // #ifdef gmic_is_parallel
10093 
10094           // Prepare thread structures.
10095           cimg_forY(_gmic_threads,l) {
10096             gmic &gmic_instance = _gmic_threads[l].gmic_instance;
10097             for (unsigned int i = 0; i<gmic_comslots; ++i) {
10098               gmic_instance.commands[i].assign(commands[i],true);
10099               gmic_instance.commands_names[i].assign(commands_names[i],true);
10100               gmic_instance.commands_has_arguments[i].assign(commands_has_arguments[i],true);
10101             }
10102             for (unsigned int i = 0; i<gmic_varslots; ++i) {
10103               if (i==gmic_varslots - 1) { // Share inter-thread global variables
10104                 gmic_instance.variables[i] = variables[i];
10105                 gmic_instance.variables_names[i] = variables_names[i];
10106               } else {
10107                 if (i==gmic_varslots - 2) { // Make a copy of single-thread global variables
10108                   gmic_instance._variables[i].assign(_variables[i]);
10109                   gmic_instance._variables_names[i].assign(_variables_names[i]);
10110                   _gmic_threads[l].variables_sizes[i] = variables_sizes[i];
10111                 } else _gmic_threads[l].variables_sizes[i] = 0;
10112                 gmic_instance.variables[i] = &gmic_instance._variables[i];
10113                 gmic_instance.variables_names[i] = &gmic_instance._variables_names[i];
10114               }
10115             }
10116             gmic_instance.callstack.assign(callstack);
10117             gmic_instance.commands_files.assign(commands_files,true);
10118             cimg_snprintf(title,_title.width(),"*thread%d",l);
10119             CImg<char>::string(title).move_to(gmic_instance.callstack);
10120             gmic_instance.light3d.assign(light3d);
10121             gmic_instance.status.assign(status);
10122             gmic_instance.debug_filename = debug_filename;
10123             gmic_instance.debug_line = debug_line;
10124             gmic_instance.focale3d = focale3d;
10125             gmic_instance.light3d_x = light3d_x;
10126             gmic_instance.light3d_y = light3d_y;
10127             gmic_instance.light3d_z = light3d_z;
10128             gmic_instance.specular_lightness3d = specular_lightness3d;
10129             gmic_instance.specular_shininess3d = specular_shininess3d;
10130             gmic_instance._progress = 0;
10131             gmic_instance.progress = &gmic_instance._progress;
10132             gmic_instance.is_change = is_change;
10133             gmic_instance.is_debug = is_debug;
10134             gmic_instance.is_start = false;
10135             gmic_instance.is_quit = false;
10136             gmic_instance.is_return = false;
10137             gmic_instance.is_double3d = is_double3d;
10138             gmic_instance.verbosity = verbosity;
10139             gmic_instance.render3d = render3d;
10140             gmic_instance.renderd3d = renderd3d;
10141             gmic_instance._is_abort = _is_abort;
10142             gmic_instance.is_abort = is_abort;
10143             gmic_instance.is_abort_thread = false;
10144             gmic_instance.nb_carriages = nb_carriages;
10145             gmic_instance.reference_time = reference_time;
10146             _gmic_threads[l].images = &images;
10147             _gmic_threads[l].images_names = &images_names;
10148             _gmic_threads[l].parent_images = &parent_images;
10149             _gmic_threads[l].parent_images_names = &parent_images_names;
10150             _gmic_threads[l].gmic_threads = &gmic_threads;
10151             _gmic_threads[l].command_selection = command_selection;
10152             _gmic_threads[l].is_thread_running = true;
10153 
10154             // Substitute special characters codes appearing outside strings.
10155             arguments[l].resize(1,arguments[l].height() + 1,1,1,0);
10156             bool is_dquoted = false;
10157             for (char *s = arguments[l].data(); *s; ++s) {
10158               const char c = *s;
10159               if (c=='\"') is_dquoted = !is_dquoted;
10160               if (!is_dquoted) *s = c<' '?(c==gmic_dollar?'$':c==gmic_lbrace?'{':c==gmic_rbrace?'}':
10161                                            c==gmic_comma?',':c==gmic_dquote?'\"':c):c;
10162             }
10163             gmic_instance.commands_line_to_CImgList(arguments[l].data()).
10164               move_to(_gmic_threads[l].commands_line);
10165           }
10166 
10167           // Run threads.
10168           cimg_forY(_gmic_threads,l) {
10169 #ifdef gmic_is_parallel
10170 #ifdef _PTHREAD_H
10171 
10172 #if defined(__MACOSX__) || defined(__APPLE__)
10173             const uint64T stacksize = (uint64T)8*1024*1024;
10174             pthread_attr_t thread_attr;
10175             if (!pthread_attr_init(&thread_attr) && !pthread_attr_setstacksize(&thread_attr,stacksize))
10176               // Reserve enough stack size for the new thread.
10177               pthread_create(&_gmic_threads[l].thread_id,&thread_attr,gmic_parallel<T>,(void*)&_gmic_threads[l]);
10178             else
10179 #endif // #if defined(__MACOSX__) || defined(__APPLE__)
10180               pthread_create(&_gmic_threads[l].thread_id,0,gmic_parallel<T>,(void*)&_gmic_threads[l]);
10181 
10182 #elif cimg_OS==2 // #ifdef _PTHREAD_H
10183             _gmic_threads[l].thread_id = CreateThread(0,0,gmic_parallel<T>,
10184                                                       (void*)&_gmic_threads[l],0,0);
10185 #endif // #ifdef _PTHREAD_H
10186 #else // #ifdef gmic_is_parallel
10187             gmic_parallel<T>((void*)&_gmic_threads[l]);
10188 #endif // #ifdef gmic_is_parallel
10189           }
10190 
10191           // Wait threads if immediate waiting mode selected.
10192           if (wait_mode) {
10193             wait_threads((void*)&_gmic_threads,false,(T)0);
10194 
10195             // Get 'released' state of the image list.
10196             cimg_forY(_gmic_threads,l) is_change|=_gmic_threads[l].gmic_instance.is_change;
10197 
10198             // Get status modified by first thread.
10199             _gmic_threads[0].gmic_instance.status.move_to(status);
10200 
10201             // Check for possible exceptions thrown by threads.
10202             cimg_forY(_gmic_threads,l) if (_gmic_threads[l].exception._message)
10203               error(false,images,0,_gmic_threads[l].exception.command(),"%s",_gmic_threads[l].exception.what());
10204             gmic_threads.remove();
10205           }
10206 
10207           ++position; continue;
10208         }
10209 
10210         // Permute axes.
10211         if (!std::strcmp("permute",command)) {
10212           gmic_substitute_args(false);
10213           print(images,0,"Permute axes of image%s, with permutation '%s'.",
10214                 gmic_selection.data(),gmic_argument_text_printed());
10215           cimg_forY(selection,l) gmic_apply(permute_axes(argument));
10216           is_change = true; ++position; continue;
10217         }
10218 
10219         // Set progress index.
10220         if (!is_get && !std::strcmp("progress",item)) {
10221           gmic_substitute_args(false);
10222           value = -1;
10223           if (cimg_sscanf(argument,"%lf%c",&value,&end)!=1) {
10224             name.assign(argument,(unsigned int)std::strlen(argument) + 1);
10225             CImg<T> &img = images.size()?images.back():CImg<T>::empty();
10226             strreplace_fw(name);
10227             try { value = img.eval(name,0,0,0,0,&images,&images); }
10228             catch (CImgException &e) {
10229               const char *const e_ptr = std::strstr(e.what(),": ");
10230               error(true,images,0,"progress",
10231                     "Command 'progress': Invalid argument '%s': %s",
10232                     cimg::strellipsize(name,64,false),e_ptr?e_ptr + 2:e.what());
10233             }
10234           }
10235           if (value<0) value = -1; else if (value>100) value = 100;
10236           if (value>=0)
10237             print(images,0,"Set progress index to %g%%.",
10238                   value);
10239           else
10240             print(images,0,"Disable progress index.");
10241           *progress = (float)value;
10242           ++position; continue;
10243         }
10244 
10245         // Print.
10246         if (!is_get && !std::strcmp("print",command)) {
10247           ++verbosity;
10248           print_images(images,images_names,selection);
10249           --verbosity;
10250           is_change = false; continue;
10251         }
10252 
10253         // Power.
10254         gmic_arithmetic_command("pow",
10255                                 pow,
10256                                 "Compute image%s to the power of %g%s",
10257                                 gmic_selection.data(),value,ssep,Tfloat,
10258                                 pow,
10259                                 "Compute image%s to the power of image [%d]",
10260                                 gmic_selection.data(),ind[0],
10261                                 pow,
10262                                 "Compute image%s to the power of expression %s",
10263                                 gmic_selection.data(),gmic_argument_text_printed(),
10264                                 "Compute sequential power of image%s");
10265 
10266         // Draw point.
10267         if (!std::strcmp("point",command)) {
10268           gmic_substitute_args(false);
10269           float x = 0, y = 0, z = 0;
10270           sepx = sepy = sepz = *argx = *argy = *argz = *color = 0;
10271           opacity = 1;
10272           if ((cimg_sscanf(argument,"%255[0-9.eE%+-]%c",
10273                            argx,&end)==1 ||
10274                cimg_sscanf(argument,"%255[0-9.eE%+-],%255[0-9.eE%+-]%c",
10275                            argx,argy,&end)==2 ||
10276                cimg_sscanf(argument,"%255[0-9.eE%+-],%255[0-9.eE%+-],%255[0-9.eE%+-]%c",
10277                            argx,argy,argz,&end)==3 ||
10278                cimg_sscanf(argument,"%255[0-9.eE%+-],%255[0-9.eE%+-],%255[0-9.eE%+-],%f%c",
10279                            argx,argy,argz,&opacity,&end)==4 ||
10280                cimg_sscanf(argument,"%255[0-9.eE%+-],%255[0-9.eE%+-],%255[0-9.eE%+-],%f,"
10281                            "%4095[0-9.eEinfa,+-]%c",
10282                            argx,argy,argz,&opacity,color,&end)==5) &&
10283               (cimg_sscanf(argx,"%f%c",&x,&end)==1 ||
10284                (cimg_sscanf(argx,"%f%c%c",&x,&sepx,&end)==2 && sepx=='%')) &&
10285               (!*argy ||
10286                cimg_sscanf(argy,"%f%c",&y,&end)==1 ||
10287                (cimg_sscanf(argy,"%f%c%c",&y,&sepy,&end)==2 && sepy=='%')) &&
10288               (!*argz ||
10289                cimg_sscanf(argz,"%f%c",&z,&end)==1 ||
10290                (cimg_sscanf(argz,"%f%c%c",&z,&sepz,&end)==2 && sepz=='%'))) {
10291             print(images,0,
10292                   "Draw point (%g%s,%g%s,%g%s) on image%s, with opacity %g and color (%s).",
10293                   x,sepx=='%'?"%":"",
10294                   y,sepy=='%'?"%":"",
10295                   z,sepz=='%'?"%":"",
10296                   gmic_selection.data(),
10297                   opacity,
10298                   *color?color:"default");
10299             cimg_forY(selection,l) {
10300               CImg<T> &img = images[selection[l]];
10301               g_img.assign(img.spectrum(),1,1,1,(T)0).fill(color,true,false);
10302               const int
10303                 nx = (int)cimg::round(sepx=='%'?x*(img.width() - 1)/100:x),
10304                 ny = (int)cimg::round(sepy=='%'?y*(img.height() - 1)/100:y),
10305                 nz = (int)cimg::round(sepz=='%'?z*(img.depth() - 1)/100:z);
10306               gmic_apply(draw_point(nx,ny,nz,g_img.data(),opacity));
10307             }
10308           } else arg_error("point");
10309           g_img.assign();
10310           is_change = true; ++position; continue;
10311         }
10312 
10313         // Draw polygon.
10314         if (!std::strcmp("polygon",command)) {
10315           gmic_substitute_args(false);
10316           name.assign(256);
10317           float N = 0, x0 = 0, y0 = 0;
10318           sep1 = sepx = sepy = *name = *color = 0;
10319           pattern = ~0U; opacity = 1;
10320           if (cimg_sscanf(argument,"%f%c",
10321                           &N,&end)==2 && N>=1) {
10322             N = cimg::round(N);
10323             const char
10324               *nargument = argument + cimg_snprintf(name,name.width(),"%u",
10325                                                     (unsigned int)N) + 1,
10326               *const eargument = argument + std::strlen(argument);
10327             vertices.assign((unsigned int)N,2,1,1,0);
10328             CImg<bool> percents((unsigned int)N,2,1,1,0);
10329             for (unsigned int n = 0; n<(unsigned int)N; ++n) if (nargument<eargument) {
10330                 sepx = sepy = 0;
10331                 if (cimg_sscanf(nargument,"%255[0-9.eE%+-],%255[0-9.eE%+-]",
10332                                 argx,argy)==2 &&
10333                     (cimg_sscanf(argx,"%f%c",&x0,&end)==1 ||
10334                      (cimg_sscanf(argx,"%f%c%c",&x0,&sepx,&end)==2 && sepx=='%')) &&
10335                     (cimg_sscanf(argy,"%f%c",&y0,&end)==1 ||
10336                      (cimg_sscanf(argy,"%f%c%c",&y0,&sepy,&end)==2 && sepy=='%'))) {
10337                   vertices(n,0) = x0; percents(n,0) = (sepx=='%');
10338                   vertices(n,1) = y0; percents(n,1) = (sepy=='%');
10339                   nargument+=std::strlen(argx) + std::strlen(argy) + 2;
10340                 } else arg_error("polygon");
10341               } else arg_error("polygon");
10342             if (nargument<eargument &&
10343                 cimg_sscanf(nargument,"%4095[0-9.eEinfa+-]",color)==1 &&
10344                 cimg_sscanf(color,"%f",&opacity)==1) {
10345               nargument+=std::strlen(color) + 1;
10346               *color = 0;
10347             }
10348             if (nargument<eargument &&
10349                 cimg_sscanf(nargument,"0%c%4095[0-9a-fA-F]",&sep1,color)==2 && sep1=='x' &&
10350                 cimg_sscanf(color,"%x%c",&pattern,&end)==1) {
10351               nargument+=std::strlen(color) + 3;
10352               *color = 0;
10353             }
10354             const char *const p_color = nargument<eargument?nargument:&(end=0);
10355             if (sep1=='x')
10356               print(images,0,"Draw %g-vertices outlined polygon on image%s, with opacity %g, "
10357                     "pattern 0x%x and color (%s).",
10358                     N,
10359                     gmic_selection.data(),
10360                     opacity,pattern,
10361                     *p_color?p_color:"default");
10362             else
10363               print(images,0,"Draw %g-vertices filled polygon on image%s, with opacity %g "
10364                     "and color (%s).",
10365                     N,
10366                     gmic_selection.data(),
10367                     opacity,
10368                     *p_color?p_color:"default");
10369             cimg_forY(selection,l) {
10370               CImg<T> &img = images[selection[l]];
10371               CImg<int> coords(vertices.width(),2,1,1,0);
10372               cimg_forX(coords,p) {
10373                 if (percents(p,0))
10374                   coords(p,0) = (int)cimg::round(vertices(p,0)*(img.width() - 1)/100);
10375                 else coords(p,0) = (int)cimg::round(vertices(p,0));
10376                 if (percents(p,1))
10377                   coords(p,1) = (int)cimg::round(vertices(p,1)*(img.height() - 1)/100);
10378                 else coords(p,1) = (int)cimg::round(vertices(p,1));
10379               }
10380               g_img.assign(img.spectrum(),1,1,1,(T)0).fill(p_color,true,false);
10381               if (sep1=='x') { gmic_apply(draw_polygon(coords,g_img.data(),opacity,pattern)); }
10382               else gmic_apply(draw_polygon(coords,g_img.data(),opacity));
10383             }
10384           } else arg_error("polygon");
10385           vertices.assign();
10386           g_img.assign();
10387           is_change = true; ++position; continue;
10388         }
10389 
10390         // Draw plasma fractal.
10391         if (!std::strcmp("plasma",command)) {
10392           gmic_substitute_args(false);
10393           float alpha = 1, beta = 1, scale = 8;
10394           if ((cimg_sscanf(argument,"%f%c",
10395                            &alpha,&end)==1 ||
10396                cimg_sscanf(argument,"%f,%f%c",
10397                            &alpha,&beta,&end)==2 ||
10398                cimg_sscanf(argument,"%f,%f,%f%c",
10399                            &alpha,&beta,&scale,&end)==3) &&
10400               scale>=0) ++position;
10401           else { alpha = beta = 1; scale = 8; }
10402           const unsigned int _scale = (unsigned int)cimg::round(scale);
10403           print(images,0,"Draw plasma fractal on image%s, with alpha %g, beta %g and scale %u.",
10404                 gmic_selection.data(),
10405                 alpha,
10406                 beta,
10407                 _scale);
10408           cimg_forY(selection,l) gmic_apply(draw_plasma(alpha,beta,_scale));
10409           is_change = true; continue;
10410         }
10411 
10412         // Display as a graph plot.
10413         if (!is_get && !std::strcmp("plot",command)) {
10414           gmic_substitute_args(false);
10415           double ymin = 0, ymax = 0, xmin = 0, xmax = 0;
10416           unsigned int plot_type = 1, vertex_type = 1;
10417           float resolution = 65536;
10418           *formula = sep = 0;
10419           exit_on_anykey = 0;
10420           if (((cimg_sscanf(argument,"'%1023[^']%c%c",
10421                             formula,&sep,&end)==2 && sep=='\'') ||
10422                cimg_sscanf(argument,"'%1023[^']',%f%c",
10423                            formula,&resolution,&end)==2 ||
10424                cimg_sscanf(argument,"'%1023[^']',%f,%u%c",
10425                            formula,&resolution,&plot_type,&end)==3 ||
10426                cimg_sscanf(argument,"'%1023[^']',%f,%u,%u%c",
10427                            formula,&resolution,&plot_type,&vertex_type,&end)==4 ||
10428                cimg_sscanf(argument,"'%1023[^']',%f,%u,%u,%lf,%lf%c",
10429                            formula,&resolution,&plot_type,&vertex_type,&xmin,&xmax,&end)==6 ||
10430                cimg_sscanf(argument,"'%1023[^']',%f,%u,%u,%lf,%lf,%lf,%lf%c",
10431                            formula,&resolution,&plot_type,&vertex_type,
10432                            &xmin,&xmax,&ymin,&ymax,&end)==8 ||
10433                cimg_sscanf(argument,"'%1023[^']',%f,%u,%u,%lf,%lf,%lf,%lf,%u%c",
10434                            formula,&resolution,&plot_type,&vertex_type,
10435                            &xmin,&xmax,&ymin,&ymax,&exit_on_anykey,&end)==9) &&
10436               resolution>0 && plot_type<=3 && vertex_type<=7 && exit_on_anykey<=1) {
10437             resolution = cimg::round(resolution);
10438             strreplace_fw(formula);
10439             if (xmin==0 && xmax==0) { xmin = -4; xmax = 4; }
10440             if (!plot_type && !vertex_type) plot_type = 1;
10441             if (resolution<1) resolution = 65536;
10442             CImgList<double> tmp_img(1);
10443             CImg<double> &values = tmp_img[0];
10444 
10445             values.assign(4,(unsigned int)resolution--,1,1,0);
10446             const double dx = xmax - xmin;
10447             cimg_forY(values,X) values(0,X) = xmin + X*dx/resolution;
10448             cimg::eval(formula,values).move_to(values);
10449 
10450             cimg_snprintf(title,_title.width(),"[Plot of '%s']",formula);
10451             CImg<char>::string(title).move_to(g_list_c);
10452             display_plots(tmp_img,g_list_c,CImg<unsigned int>::vector(0),
10453                           plot_type,vertex_type,xmin,xmax,ymin,ymax,exit_on_anykey);
10454             g_list_c.assign();
10455             ++position;
10456           } else {
10457             plot_type = 1; vertex_type = 0; ymin = ymax = xmin = xmax = 0;
10458             if ((cimg_sscanf(argument,"%u%c",
10459                              &plot_type,&end)==1 ||
10460                  cimg_sscanf(argument,"%u,%u%c",
10461                              &plot_type,&vertex_type,&end)==2 ||
10462                  cimg_sscanf(argument,"%u,%u,%lf,%lf%c",
10463                              &plot_type,&vertex_type,&xmin,&xmax,&end)==4 ||
10464                  cimg_sscanf(argument,"%u,%u,%lf,%lf,%lf,%lf%c",
10465                              &plot_type,&vertex_type,&xmin,&xmax,&ymin,&ymax,&end)==6 ||
10466                  cimg_sscanf(argument,"%u,%u,%lf,%lf,%lf,%lf,%u%c",
10467                              &plot_type,&vertex_type,&xmin,&xmax,&ymin,&ymax,&exit_on_anykey,&end)==7) &&
10468                 plot_type<=3 && vertex_type<=7 && exit_on_anykey<=1) ++position;
10469             if (!plot_type && !vertex_type) plot_type = 1;
10470             display_plots(images,images_names,selection,plot_type,vertex_type,
10471                           xmin,xmax,ymin,ymax,exit_on_anykey);
10472           }
10473           is_change = false; continue;
10474         }
10475 
10476         goto gmic_commands_others;
10477 
10478         //-----------------------------
10479         // Commands starting by 'q...'
10480         //-----------------------------
10481       gmic_commands_q :
10482 
10483         // Quit.
10484         if (!is_get && !std::strcmp("quit",item)) {
10485           print(images,0,"Quit G'MIC interpreter.");
10486           dowhiles.assign(nb_dowhiles = 0);
10487           fordones.assign(nb_fordones = 0);
10488           repeatdones.assign(nb_repeatdones = 0);
10489           position = commands_line.size();
10490           is_change = false;
10491           is_quit = true;
10492           *is_abort = true;
10493           break;
10494         }
10495 
10496         goto gmic_commands_others;
10497 
10498         //-----------------------------
10499         // Commands starting by 'r...'
10500         //-----------------------------
10501       gmic_commands_r :
10502 
10503         // Remove images.
10504         if (!std::strcmp("remove",command)) {
10505           print(images,0,"Remove image%s",
10506                 gmic_selection.data());
10507           if (is_get) { g_list.assign(images); g_list_c.assign(images_names); }
10508           remove_images(images,images_names,selection,0,selection.height() - 1);
10509           if (is_get) {
10510             g_list.move_to(images,0);
10511             g_list_c.move_to(images_names,0);
10512           }
10513           if (is_verbose) {
10514             cimg::mutex(29);
10515             std::fprintf(cimg::output()," (%u image%s left).",
10516                          images.size(),images.size()==1?"":"s");
10517             std::fflush(cimg::output());
10518             cimg::mutex(29,0);
10519           }
10520           g_list.assign(); g_list_c.assign();
10521           is_change = true; continue;
10522         }
10523 
10524         // Repeat.
10525         if (!is_get && !std::strcmp("repeat",item)) {
10526           gmic_substitute_args(false);
10527           const char *varname = title;
10528           *title = 0;
10529           if (cimg_sscanf(argument,"%lf%c",&value,&end)!=1) {
10530             name.assign(argument,(unsigned int)std::strlen(argument) + 1);
10531             for (char *ps = name.data() + name.width() - 2; ps>=name.data(); --ps) {
10532               if (*ps==',' && ps[1] && (ps[1]<'0' || ps[1]>'9')) { varname = ps + 1; *ps = 0; break; }
10533               else if ((*ps<'a' || *ps>'z') && (*ps<'A' || *ps>'Z') && (*ps<'0' || *ps>'9') && *ps!='_') break;
10534             }
10535             if (*name) {
10536               if (cimg_sscanf(name,"%lf%c",&value,&end)!=1) {
10537                 CImg<T> &img = images.size()?images.back():CImg<T>::empty();
10538                 strreplace_fw(name);
10539                 try { value = img.eval(name,0,0,0,0,&images,&images); }
10540                 catch (CImgException &e) {
10541                   const char *const e_ptr = std::strstr(e.what(),": ");
10542                   error(true,images,0,"repeat",
10543                         "Command 'repeat': Invalid argument '%s': %s",
10544                         cimg::strellipsize(name,64,false),e_ptr?e_ptr + 2:e.what());
10545                 }
10546               }
10547             }
10548           }
10549           const unsigned int nb = value<=0?0U:
10550             cimg::type<double>::is_inf(value)?~0U:(unsigned int)cimg::round(value);
10551           if (nb) {
10552             if (is_debug_info && debug_line!=~0U) {
10553               cimg_snprintf(argx,_argx.width(),"*repeat#%u",debug_line);
10554               CImg<char>::string(argx).move_to(callstack);
10555             } else CImg<char>::string("*repeat").move_to(callstack);
10556             if (is_very_verbose) {
10557               if (*varname) print(images,0,"Start 'repeat...done' block with variable '%s' (%u iteration%s).",
10558                                   varname,nb,nb>1?"s":"");
10559               else print(images,0,"Start 'repeat...done' block (%u iteration%s).",
10560                          nb,nb>1?"s":"");
10561             }
10562             const unsigned int l = (unsigned int)std::strlen(varname);
10563             if (nb_repeatdones>=repeatdones._height) repeatdones.resize(5,std::max(2*repeatdones._height,8U),1,1,0);
10564             unsigned int *const rd = repeatdones.data(0,nb_repeatdones++);
10565             rd[0] = position; rd[1] = nb; rd[2] = 0;
10566             if (l) {
10567               hash = hashcode(varname,true);
10568               rd[3] = hash;
10569               rd[4] = variables[hash]->_width;
10570               CImg<char>::string(varname).move_to(*variables_names[hash]);
10571               CImg<char>::string("0").move_to(*variables[hash]);
10572             } else rd[3] = rd[4] = ~0U;
10573           } else {
10574             if (is_very_verbose) {
10575               if (*varname) print(images,0,"Skip 'repeat...done' block with variable '%s' (0 iteration).",
10576                                   varname);
10577               else print(images,0,"Skip 'repeat...done' block (0 iteration).");
10578             }
10579             int nb_repeat_fors = 0;
10580             for (nb_repeat_fors = 1; nb_repeat_fors && position<commands_line.size(); ++position) {
10581               const char *it = commands_line[position].data();
10582               it+=*it=='-';
10583               if (!std::strcmp("repeat",it) || !std::strcmp("for",it)) ++nb_repeat_fors;
10584               else if (!std::strcmp("done",it)) --nb_repeat_fors;
10585             }
10586             if (nb_repeat_fors && position>=commands_line.size())
10587               error(true,images,0,0,
10588                     "Command 'repeat': Missing associated 'done' command.");
10589             continue;
10590           }
10591           ++position; continue;
10592         }
10593 
10594         // Resize.
10595         if (!std::strcmp("resize",command)) {
10596           gmic_substitute_args(true);
10597           float valx = 100, valy = 100, valz = 100, valc = 100, cx = 0, cy = 0, cz = 0, cc = 0;
10598           CImg<char> indicesy(256), indicesz(256), indicesc(256);
10599           CImg<unsigned int> indx, indy, indz, indc;
10600           *indices = *indicesy = *indicesz = *indicesc = *argx = *argy = *argz = *argc = sep = 0;
10601           sepx = sepy = sepz = sepc = '%';
10602           int iinterpolation = 1;
10603           boundary = 0;
10604           ind.assign();
10605 
10606           if (((cimg_sscanf(argument,"[%255[a-zA-Z0-9_.%+-]%c%c",
10607                             indices,&sep,&end)==2 && sep==']') ||
10608                cimg_sscanf(argument,"[%255[a-zA-Z0-9_.%+-]],%d%c",
10609                            indices,&iinterpolation,&end)==2 ||
10610                cimg_sscanf(argument,"[%255[a-zA-Z0-9_.%+-]],%d,%u%c",
10611                            indices,&iinterpolation,&boundary,&end)==3 ||
10612                cimg_sscanf(argument,"[%255[a-zA-Z0-9_.%+-]],%d,%u,%f%c",
10613                            indices,&iinterpolation,&boundary,&cx,&end)==4 ||
10614                cimg_sscanf(argument,"[%255[a-zA-Z0-9_.%+-]],%d,%u,%f,%f%c",
10615                            indices,&iinterpolation,&boundary,&cx,&cy,&end)==5 ||
10616                cimg_sscanf(argument,"[%255[a-zA-Z0-9_.%+-]],%d,%u,%f,%f,%f%c",
10617                            indices,&iinterpolation,&boundary,&cx,&cy,&cz,&end)==6 ||
10618                cimg_sscanf(argument,"[%255[a-zA-Z0-9_.%+-]],%d,%u,%f,%f,%f,%f%c",
10619                            indices,&iinterpolation,&boundary,&cx,&cy,&cz,&cc,&end)==7) &&
10620               (ind=selection2cimg(indices,images.size(),images_names,"resize")).height()==1 &&
10621               iinterpolation>=-1 && iinterpolation<=6 && boundary<=3 &&
10622               cx>=0 && cx<=1 && cy>=0 && cy<=1 && cz>=0 && cz<=1 && cc>=0 && cc<=1) {
10623             const int
10624               nvalx = images[*ind].width(),
10625               nvaly = images[*ind].height(),
10626               nvalz = images[*ind].depth(),
10627               nvalc = images[*ind].spectrum();
10628             print(images,0,"Resize image%s to %dx%dx%dx%d, with %s interpolation, "
10629                   "%s boundary conditions and alignment (%g,%g,%g,%g).",
10630                   gmic_selection.data(),
10631                   nvalx,nvaly,nvalz,nvalc,
10632                   iinterpolation<=0?"no":iinterpolation==1?"nearest-neighbor":
10633                   iinterpolation==2?"moving average":iinterpolation==3?"linear":
10634                   iinterpolation==4?"grid":iinterpolation==5?"cubic":"lanczos",
10635                   boundary<=0?"dirichlet":boundary==1?"neumann":boundary==2?"periodic":"mirror",
10636                   cx,cy,cz,cc);
10637             cimg_forY(selection,l) gmic_apply(resize(nvalx,nvaly,nvalz,nvalc,iinterpolation,boundary,cx,cy,cz,cc));
10638           } else if ((cx=cy=cz=cc=0, iinterpolation=1, boundary=0, true) &&
10639                      (cimg_sscanf(argument,"%255[][a-zA-Z0-9_.eE%+-]%c",
10640                                   argx,&end)==1 ||
10641                       cimg_sscanf(argument,"%255[][a-zA-Z0-9_.eE%+-],%255[][a-zA-Z0-9_.eE%+-]%c",
10642                                   argx,argy,&end)==2 ||
10643                       cimg_sscanf(argument,"%255[][a-zA-Z0-9_.eE%+-],%255[][a-zA-Z0-9_.eE%+-],"
10644                                   "%255[][a-zA-Z0-9_.eE%+-]%c",
10645                                   argx,argy,argz,&end)==3 ||
10646                       cimg_sscanf(argument,"%255[][a-zA-Z0-9_.eE%+-],%255[][a-zA-Z0-9_.eE%+-],"
10647                                   "%255[][a-zA-Z0-9_.eE%+-],%255[][a-zA-Z0-9_.eE%+-]%c",
10648                                   argx,argy,argz,argc,&end)==4 ||
10649                       cimg_sscanf(argument,"%255[][a-zA-Z0-9_.eE%+-],%255[][a-zA-Z0-9_.eE%+-],"
10650                                   "%255[][a-zA-Z0-9_.eE%+-],%255[][a-zA-Z0-9_.eE%+-],%d%c",
10651                                   argx,argy,argz,argc,&iinterpolation,&end)==5 ||
10652                       cimg_sscanf(argument,"%255[][a-zA-Z0-9_.eE%+-],%255[][a-zA-Z0-9_.eE%+-],"
10653                                   "%255[][a-zA-Z0-9_.eE%+-],%255[][a-zA-Z0-9_.eE%+-],%d,%u%c",
10654                                   argx,argy,argz,argc,&iinterpolation,&boundary,&end)==6 ||
10655                       cimg_sscanf(argument,"%255[][a-zA-Z0-9_.eE%+-],%255[][a-zA-Z0-9_.eE%+-],"
10656                                   "%255[][a-zA-Z0-9_.eE%+-],%255[][a-zA-Z0-9_.eE%+-],%d,%u,%f%c",
10657                                   argx,argy,argz,argc,&iinterpolation,&boundary,&cx,&end)==7 ||
10658                       cimg_sscanf(argument,"%255[][a-zA-Z0-9_.eE%+-],%255[][a-zA-Z0-9_.eE%+-],"
10659                                   "%255[][a-zA-Z0-9_.eE%+-],%255[][a-zA-Z0-9_.eE%+-],%d,%u,%f,"
10660                                   "%f%c",
10661                                   argx,argy,argz,argc,&iinterpolation,&boundary,&cx,&cy,&end)==8||
10662                       cimg_sscanf(argument,"%255[][a-zA-Z0-9_.eE%+-],%255[][a-zA-Z0-9_.eE%+-],"
10663                                   "%255[][a-zA-Z0-9_.eE%+-],%255[][a-zA-Z0-9_.eE%+-],%d,%u,%f,"
10664                                   "%f,%f%c",
10665                                   argx,argy,argz,argc,&iinterpolation,&boundary,
10666                                   &cx,&cy,&cz,&end)==9 ||
10667                       cimg_sscanf(argument,"%255[][a-zA-Z0-9_.eE%+-],%255[][a-zA-Z0-9_.eE%+-],"
10668                                   "%255[][a-zA-Z0-9_.eE%+-],%255[][a-zA-Z0-9_.eE%+-],%d,%u,%f,"
10669                                   "%f,%f,%f%c",
10670                                   argx,argy,argz,argc,&iinterpolation,&boundary,
10671                                   &cx,&cy,&cz,&cc,&end)==10) &&
10672                      ((cimg_sscanf(argx,"[%255[a-zA-Z0-9_.%+-]%c%c",indices,&sepx,&end)==2 &&
10673                        sepx==']' &&
10674                        (indx=selection2cimg(indices,images.size(),images_names,"resize")).height()==1) ||
10675                       (sepx=0,cimg_sscanf(argx,"%f%c",&valx,&sepx)==1 && valx>=1) ||
10676                       (cimg_sscanf(argx,"%f%c%c",&valx,&sepx,&end)==2 && sepx=='%')) &&
10677                      (!*argy ||
10678                       (cimg_sscanf(argy,"[%255[a-zA-Z0-9_.%+-]%c%c",indicesy.data(),&sepy,
10679                                    &end)==2 &&
10680                        sepy==']' &&
10681                        (indy=selection2cimg(indicesy,images.size(),images_names,"resize")).height()==1) ||
10682                       (sepy=0,cimg_sscanf(argy,"%f%c",&valy,&sepy)==1 && valy>=1) ||
10683                       (cimg_sscanf(argy,"%f%c%c",&valy,&sepy,&end)==2 && sepy=='%')) &&
10684                      (!*argz ||
10685                       (cimg_sscanf(argz,"[%255[a-zA-Z0-9_.%+-]%c%c",indicesz.data(),&sepz,
10686                                    &end)==2 &&
10687                        sepz==']' &&
10688                        (indz=selection2cimg(indicesz,images.size(),images_names,"resize")).height()==1) ||
10689                       (sepz=0,cimg_sscanf(argz,"%f%c",&valz,&sepz)==1 && valz>=1) ||
10690                       (cimg_sscanf(argz,"%f%c%c",&valz,&sepz,&end)==2 && sepz=='%')) &&
10691                      (!*argc ||
10692                       (cimg_sscanf(argc,"[%255[a-zA-Z0-9_.%+-]%c%c",indicesc.data(),&sepc,
10693                                    &end)==2 &&
10694                        sepc==']' &&
10695                        (indc=selection2cimg(indicesc,images.size(),images_names,"resize")).height()==1) ||
10696                       (sepc=0,cimg_sscanf(argc,"%f%c",&valc,&sepc)==1 && valc>=1) ||
10697                       (cimg_sscanf(argc,"%f%c%c",&valc,&sepc,&end)==2 && sepc=='%')) &&
10698                      valx>0 && valy>0 && valz>0 && valc>0 &&
10699                      iinterpolation>=-1 && iinterpolation<=6 && boundary<=3 &&
10700                      cx>=0 && cx<=1 && cy>=0 && cy<=1 && cz>=0 && cz<=1 && cc>=0 && cc<=1) {
10701             if (indx) { valx = (float)images[*indx].width(); sepx = 0; }
10702             if (indy) { valy = (float)images[*indy].height(); sepy = 0; }
10703             if (indz) { valz = (float)images[*indz].depth(); sepz = 0; }
10704             if (indc) { valc = (float)images[*indc].spectrum(); sepc = 0; }
10705             print(images,0,
10706                   "Resize image%s to %g%s%g%s%g%s%g%s, with %s interpolation, "
10707                   "%s boundary conditions and alignment (%g,%g,%g,%g).",
10708                   gmic_selection.data(),
10709                   valx,sepx=='%'?"%x":"x",
10710                   valy,sepy=='%'?"%x":"x",
10711                   valz,sepz=='%'?"%x":"x",
10712                   valc,sepc=='%'?"% ":"",
10713                   iinterpolation<=0?"no":iinterpolation==1?"nearest-neighbor":
10714                   iinterpolation==2?"moving average":iinterpolation==3?"linear":
10715                   iinterpolation==4?"grid":iinterpolation==5?"cubic":"lanczos",
10716                   boundary<=0?"dirichlet":boundary==1?"neumann":boundary==2?"periodic":"mirror",
10717                   cx,cy,cz,cc);
10718             cimg_forY(selection,l) {
10719               CImg<T>& img = images[selection[l]];
10720               const int
10721                 _nvalx = (int)cimg::round(sepx=='%'?valx*img.width()/100:valx),
10722                 _nvaly = (int)cimg::round(sepy=='%'?valy*img.height()/100:valy),
10723                 _nvalz = (int)cimg::round(sepz=='%'?valz*img.depth()/100:valz),
10724                 _nvalc = (int)cimg::round(sepc=='%'?valc*img.spectrum()/100:valc),
10725                 nvalx = _nvalx?_nvalx:1,
10726                 nvaly = _nvaly?_nvaly:1,
10727                 nvalz = _nvalz?_nvalz:1,
10728                 nvalc = _nvalc?_nvalc:1;
10729               gmic_apply(resize(nvalx,nvaly,nvalz,nvalc,iinterpolation,boundary,cx,cy,cz,cc));
10730             }
10731           } else arg_error("resize");
10732           is_change = true; ++position; continue;
10733         }
10734 
10735         // Reverse positions.
10736         if (!std::strcmp("reverse",command)) {
10737           print(images,0,"Reverse positions of image%s.",
10738                 gmic_selection.data());
10739           if (is_get) cimg_forY(selection,l) {
10740               const unsigned int i = selection[selection.height() - 1 - l];
10741               images.insert(images[i]);
10742               images_names.insert(images_names[i]);
10743             } else for (unsigned int l = 0; l<selection._height/2; ++l) {
10744               const unsigned int i0 = selection[l], i1 = selection[selection.height() - 1 - l];
10745               images[i0].swap(images[i1]);
10746               images_names[i0].swap(images_names[i1]);
10747             }
10748           is_change = true; continue;
10749         }
10750 
10751         // Return.
10752         if (!is_get && !std::strcmp("return",item)) {
10753           if (is_very_verbose) print(images,0,"Return.");
10754           position = commands_line.size();
10755           while (callstack && callstack.back()[0]=='*') {
10756             const char c = callstack.back()[1];
10757             if (c=='d') --nb_dowhiles;
10758             else if (c=='f') --nb_fordones;
10759             else if (c=='r') --nb_repeatdones;
10760             else if (c=='l' || c=='>' || c=='s') break;
10761             callstack.remove();
10762           }
10763           is_return = true;
10764           break;
10765         }
10766 
10767         // Keep rows.
10768         if (!std::strcmp("rows",command)) {
10769           gmic_substitute_args(true);
10770           ind0.assign(); ind1.assign();
10771           sep0 = sep1 = *argx = *argy = *indices = 0;
10772           value0 = value1 = 0;
10773           if (cimg_sscanf(argument,"%255[][a-zA-Z0-9_.eE%+-]%c",
10774                           argx,&end)==1 &&
10775               ((cimg_sscanf(argx,"[%255[a-zA-Z0-9_.%+-]%c%c]",indices,&sep0,&end)==2 &&
10776                 sep0==']' &&
10777                 (ind0=selection2cimg(indices,images.size(),images_names,"rows")).height()==1) ||
10778                cimg_sscanf(argx,"%lf%c",&value0,&end)==1 ||
10779                (cimg_sscanf(argx,"%lf%c%c",&value0,&sep0,&end)==2 && sep0=='%'))) {
10780             if (ind0) { value0 = images[*ind0].height() - 1.; sep0 = 0; }
10781             print(images,0,"Keep rows %g%s of image%s.",
10782                   value0,sep0=='%'?"%":"",
10783                   gmic_selection.data());
10784             cimg_forY(selection,l) {
10785               CImg<T> &img = images[selection[l]];
10786               nvalue0 = cimg::round(sep0=='%'?value0*(img.height() - 1)/100:value0);
10787               gmic_apply(row((int)nvalue0));
10788             }
10789           } else if (cimg_sscanf(argument,"%255[][a-zA-Z0-9_.eE%+-],%255[][a-zA-Z0-9_.eE%+-]%c",
10790                                  argx,argy,&end)==2 &&
10791                      ((cimg_sscanf(argx,"[%255[a-zA-Z0-9_.%+-]%c%c",indices,&sep0,&end)==2 &&
10792                        sep0==']' &&
10793                        (ind0=selection2cimg(indices,images.size(),images_names,"rows")).height()==1) ||
10794                       cimg_sscanf(argx,"%lf%c",&value0,&end)==1 ||
10795                       (cimg_sscanf(argx,"%lf%c%c",&value0,&sep0,&end)==2 && sep0=='%')) &&
10796                      ((cimg_sscanf(argx,"[%255[a-zA-Z0-9_.%+-]%c%c",formula,&sep0,&end)==2 &&
10797                        sep0==']' &&
10798                        (ind1=selection2cimg(formula,images.size(),images_names,"rows")).height()==1) ||
10799                       cimg_sscanf(argy,"%lf%c",&value1,&end)==1 ||
10800                       (cimg_sscanf(argy,"%lf%c%c",&value1,&sep1,&end)==2 && sep1=='%'))) {
10801             if (ind0) { value0 = images[*ind0].height() - 1.; sep0 = 0; }
10802             if (ind1) { value1 = images[*ind1].height() - 1.; sep1 = 0; }
10803             print(images,0,"Keep rows %g%s...%g%s of image%s.",
10804                   value0,sep0=='%'?"%":"",
10805                   value1,sep1=='%'?"%":"",
10806                   gmic_selection.data());
10807             cimg_forY(selection,l) {
10808               CImg<T> &img = images[selection[l]];
10809               nvalue0 = cimg::round(sep0=='%'?value0*(img.height() - 1)/100:value0);
10810               nvalue1 = cimg::round(sep1=='%'?value1*(img.height() - 1)/100:value1);
10811               gmic_apply(rows((int)nvalue0,(int)nvalue1));
10812             }
10813           } else arg_error("rows");
10814           is_change = true; ++position; continue;
10815         }
10816 
10817         // Rotate.
10818         if (!std::strcmp("rotate",command)) {
10819           gmic_substitute_args(false);
10820           float angle = 0, u = 0, v = 0, w = 0, cx = 0, cy = 0, cz = 0;
10821           char sep2 = sep1 = sep0 = *argx = *argy = *argz = 0;
10822           interpolation = 1;
10823           boundary = 0;
10824           if ((cimg_sscanf(argument,"%f%c",
10825                            &angle,&end)==1 ||
10826                cimg_sscanf(argument,"%f,%u%c",
10827                            &angle,&interpolation,&end)==2 ||
10828                cimg_sscanf(argument,"%f,%u,%u%c",
10829                            &angle,&interpolation,&boundary,&end)==3 ||
10830                cimg_sscanf(argument,"%f,%u,%u,%255[0-9.eE%+-],%255[0-9.eE%+-]%c",
10831                            &angle,&interpolation,&boundary,argx,argy,&end)==5) &&
10832               (!*argx ||
10833                cimg_sscanf(argx,"%f%c",&cx,&end)==1 ||
10834                (cimg_sscanf(argx,"%f%c%c",&cx,&sep0,&end)==2 && sep0=='%')) &&
10835               (!*argy ||
10836                cimg_sscanf(argy,"%f%c",&cy,&end)==1 ||
10837                (cimg_sscanf(argy,"%f%c%c",&cy,&sep1,&end)==2 && sep1=='%')) &&
10838               interpolation<=2 && boundary<=3) { // 2D rotation
10839             if (*argx) {
10840               print(images,0,"Rotate image%s of %g deg., with %s interpolation, %s boundary conditions "
10841                     "and center at (%g%s,%g%s).",
10842                     gmic_selection.data(),angle,
10843                     interpolation==0?"nearest-neighbor":interpolation==1?"linear":"cubic",
10844                     boundary==0?"dirichlet":boundary==1?"neumann":boundary==2?"periodic":"mirror",
10845                     cx,sep0=='%'?"%":"",cy,sep1=='%'?"%":"");
10846               cimg_forY(selection,l) {
10847                 CImg<T> &img = images[selection[l]];
10848                 const float
10849                   ncx = sep0=='%'?cx*(img.width() - 1)/100:cx,
10850                   ncy = sep1=='%'?cy*(img.height() - 1)/100:cy;
10851                 gmic_apply(rotate(angle,ncx,ncy,interpolation,boundary));
10852               }
10853             } else {
10854               print(images,0,"Rotate image%s of %g deg., with %s interpolation and %s boundary conditions.",
10855                     gmic_selection.data(),angle,
10856                     interpolation==0?"nearest-neighbor":interpolation==1?"linear":"cubic",
10857                     boundary==0?"dirichlet":boundary==1?"neumann":boundary==2?"periodic":"mirror");
10858               cimg_forY(selection,l) gmic_apply(rotate(angle,interpolation,boundary));
10859             }
10860           } else if ((cimg_sscanf(argument,"%f,%f,%f,%f,%u,%u,%255[0-9.eE%+-],%255[0-9.eE%+-],%255[0-9.eE%+-]%c",
10861                                   &u,&v,&w,&angle,&interpolation,&boundary,&(*argx=0),&(*argy=0),argz,&end)==9 ||
10862                       cimg_sscanf(argument,"%f,%f,%f,%f,%u,%u%c",
10863                                   &u,&v,&w,&angle,&interpolation,&boundary,&end)==6) &&
10864                      (!*argx ||
10865                       cimg_sscanf(argx,"%f%c",&cx,&end)==1 ||
10866                       (cimg_sscanf(argx,"%f%c%c",&cx,&sep0,&end)==2 && sep0=='%')) &&
10867                      (!*argy ||
10868                       cimg_sscanf(argy,"%f%c",&cy,&end)==1 ||
10869                       (cimg_sscanf(argy,"%f%c%c",&cy,&sep1,&end)==2 && sep1=='%')) &&
10870                      (!*argz ||
10871                       cimg_sscanf(argz,"%f%c",&cz,&end)==1 ||
10872                       (cimg_sscanf(argz,"%f%c%c",&cz,&sep1,&end)==2 && sep2=='%')) &&
10873                      interpolation<=2 && boundary<=3) { // 3D rotation
10874             if (*argx) {
10875               print(images,0,"Rotate image%s around axis (%g,%g,%g) with angle %g deg., %s interpolation, "
10876                     "%s boundary conditions and center at (%g%s,%g%s,%g%s).",
10877                     gmic_selection.data(),u,v,w,angle,
10878                     interpolation==0?"nearest-neighbor":interpolation==1?"linear":"cubic",
10879                     boundary==0?"dirichlet":boundary==1?"neumann":boundary==2?"periodic":"mirror",
10880                     cx,sep0=='%'?"%":"",cy,sep1=='%'?"%":"",cz,sep2=='%'?"%":"");
10881               cimg_forY(selection,l) {
10882                 CImg<T> &img = images[selection[l]];
10883                 const float
10884                   ncx = sep0=='%'?cx*(img.width() - 1)/100:cx,
10885                   ncy = sep1=='%'?cy*(img.height() - 1)/100:cy,
10886                   ncz = sep2=='%'?cy*(img.depth() - 1)/100:cz;
10887                 gmic_apply(rotate(u,v,w,angle,ncx,ncy,ncz,interpolation,boundary));
10888               }
10889             } else {
10890               print(images,0,"Rotate image%s around axis (%g,%g,%g) with angle %g deg., %s interpolation "
10891                     "and %s boundary conditions.",
10892                     gmic_selection.data(),u,v,w,angle,
10893                     interpolation==0?"nearest-neighbor":interpolation==1?"linear":"cubic",
10894                     boundary==0?"dirichlet":boundary==1?"neumann":boundary==2?"periodic":"mirror");
10895               cimg_forY(selection,l) gmic_apply(rotate(u,v,w,angle,interpolation,boundary));
10896             }
10897           } else arg_error("rotate");
10898           is_change = true; ++position; continue;
10899         }
10900 
10901         // Round.
10902         if (!std::strcmp("round",command)) {
10903           gmic_substitute_args(false);
10904           int rounding_type = 0;
10905           value = 1;
10906           if ((cimg_sscanf(argument,"%lf%c",
10907                            &value,&end)==1 ||
10908                cimg_sscanf(argument,"%lf,%d%c",
10909                            &value,&rounding_type,&end)==2) &&
10910               value>=0 && rounding_type>=-1 && rounding_type<=1) ++position;
10911           else { value = 1; rounding_type = 0; }
10912           print(images,0,"Round values of image%s by %g and %s rounding.",
10913                 gmic_selection.data(),
10914                 value,
10915                 rounding_type<0?"backward":rounding_type>0?"forward":"nearest");
10916           cimg_forY(selection,l) gmic_apply(round(value,rounding_type));
10917           is_change = true; continue;
10918         }
10919 
10920         // Fill with random values.
10921         if (!std::strcmp("rand",command)) {
10922           gmic_substitute_args(true);
10923           ind0.assign(); ind1.assign();
10924           sep0 = sep1 = *argx = *argy = *indices = 0;
10925           value0 = value1 = 0;
10926           if (cimg_sscanf(argument,"%255[][a-zA-Z0-9_.eE%+-],%255[][a-zA-Z0-9_.eE%+-]%c",
10927                           argx,argy,&end)==2 &&
10928               ((cimg_sscanf(argx,"[%255[a-zA-Z0-9_.%+-]%c%c",indices,&sep0,&end)==2 &&
10929                 sep0==']' &&
10930                 (ind0=selection2cimg(indices,images.size(),images_names,"rand")).height()==1) ||
10931                (cimg_sscanf(argx,"%lf%c%c",&value0,&sep0,&end)==2 && sep0=='%') ||
10932                cimg_sscanf(argx,"%lf%c",&value0,&end)==1) &&
10933               ((cimg_sscanf(argy,"[%255[a-zA-Z0-9_.%+-]%c%c",formula,&sep1,&end)==2 &&
10934                 sep1==']' &&
10935                 (ind1=selection2cimg(formula,images.size(),images_names,"rand")).height()==1) ||
10936                (cimg_sscanf(argy,"%lf%c%c",&value1,&sep1,&end)==2 && sep1=='%') ||
10937                cimg_sscanf(argy,"%lf%c",&value1,&end)==1)) {
10938             if (ind0) { value0 = images[*ind0].min(); sep0 = 0; }
10939             if (ind1) { value1 = images[*ind1].max(); sep1 = 0; }
10940             print(images,0,"Fill image%s with random values, in range [%g%s,%g%s].",
10941                   gmic_selection.data(),
10942                   value0,sep0=='%'?"%":"",
10943                   value1,sep1=='%'?"%":"");
10944             cimg_forY(selection,l) {
10945               CImg<T>& img = gmic_check(images[selection[l]]);
10946               nvalue0 = value0; nvalue1 = value1;
10947               vmin = vmax = 0;
10948               if (sep0=='%' || sep1=='%') {
10949                 if (img) vmax = (double)img.max_min(vmin);
10950                 if (sep0=='%') nvalue0 = vmin + (vmax - vmin)*value0/100;
10951                 if (sep1=='%') nvalue1 = vmin + (vmax - vmin)*value1/100;
10952               }
10953               gmic_apply(rand((T)nvalue0,(T)nvalue1));
10954             }
10955           } else if (cimg_sscanf(argument,"[%255[a-zA-Z0-9_.%+-]%c%c",indices,&sep0,&end)==2 &&
10956                      sep0==']' &&
10957                      (ind0=selection2cimg(indices,images.size(),images_names,"rand")).height()==1) {
10958             if (images[*ind0]) value1 = (double)images[*ind0].max_min(value0);
10959             print(images,0,"Fill image%s with random values, in range [%g,%g] from image [%d].",
10960                   gmic_selection.data(),
10961                   value0,
10962                   value1,
10963                   *ind0);
10964             cimg_forY(selection,l) gmic_apply(rand((T)value0,(T)value1));
10965           } else arg_error("rand");
10966           is_change = true; ++position; continue;
10967         }
10968 
10969         // Rotate 3D object.
10970         if (!std::strcmp("rotate3d",command)) {
10971           gmic_substitute_args(false);
10972           float u = 0, v = 0, w = 1, angle = 0;
10973           if (cimg_sscanf(argument,"%f,%f,%f,%f%c",
10974                           &u,&v,&w,&angle,&end)==4) {
10975             print(images,0,"Rotate 3D object%s around axis (%g,%g,%g), with angle %g deg.",
10976                   gmic_selection.data(),
10977                   u,v,w,
10978                   angle);
10979             CImg<float>::rotation_matrix(u,v,w,angle).move_to(vertices);
10980             cimg_forY(selection,l) {
10981               const unsigned int uind = selection[l];
10982               CImg<T>& img = images[uind];
10983               try { gmic_apply(rotate_CImg3d(vertices)); }
10984               catch (CImgException&) {
10985                 if (!img.is_CImg3d(true,&(*message=0)))
10986                   error(true,images,0,0,
10987                         "Command 'rotate3d': Invalid 3D object [%d], "
10988                         "in selected image%s (%s).",
10989                         uind,gmic_selection_err.data(),message.data());
10990                 else throw;
10991               }
10992             }
10993           } else arg_error("rotate3d");
10994           vertices.assign();
10995           is_change = true; ++position; continue;
10996         }
10997 
10998         // Bitwise left rotation.
10999         gmic_arithmetic_command("rol",
11000                                 rol,
11001                                 "Compute bitwise left rotation of image%s by %g%s",
11002                                 gmic_selection.data(),value,ssep,unsigned int,
11003                                 rol,
11004                                 "Compute bitwise left rotation of image%s by image [%d]",
11005                                 gmic_selection.data(),ind[0],
11006                                 rol,
11007                                 "Compute bitwise left rotation of image%s by expression %s",
11008                                 gmic_selection.data(),gmic_argument_text_printed(),
11009                                 "Compute sequential bitwise left rotation of image%s");
11010 
11011         // Bitwise right rotation.
11012         gmic_arithmetic_command("ror",
11013                                 ror,
11014                                 "Compute bitwise right rotation of image%s by %g%s",
11015                                 gmic_selection.data(),value,ssep,unsigned int,
11016                                 ror,
11017                                 "Compute bitwise right rotation of image%s by image [%d]",
11018                                 gmic_selection.data(),ind[0],
11019                                 ror,
11020                                 "Compute bitwise left rotation of image%s by expression %s",
11021                                 gmic_selection.data(),gmic_argument_text_printed(),
11022                                 "Compute sequential bitwise left rotation of image%s");
11023 
11024         // Reverse 3D object orientation.
11025         if (!std::strcmp("reverse3d",command)) {
11026           print(images,0,"Reverse orientation of 3D object%s.",
11027                 gmic_selection.data());
11028           cimg_forY(selection,l) {
11029             const unsigned int uind = selection[l];
11030             CImg<T> &img = images[uind];
11031             try { gmic_apply(reverse_CImg3d()); }
11032             catch (CImgException&) {
11033               if (!img.is_CImg3d(true,&(*message=0)))
11034                 error(true,images,0,0,
11035                       "Command 'reverse3d': Invalid 3D object [%d], "
11036                       "in selected image%s (%s).",
11037                       uind,gmic_selection_err.data(),message.data());
11038               else throw;
11039             }
11040           }
11041           is_change = true; continue;
11042         }
11043 
11044         goto gmic_commands_others;
11045 
11046         //-----------------------------
11047         // Commands starting by 's...'
11048         //-----------------------------
11049       gmic_commands_s :
11050 
11051         // Set status.
11052         if (!is_get && !std::strcmp("status",item)) {
11053           gmic_substitute_args(false);
11054           print(images,0,"Set status to '%s'.",gmic_argument_text_printed());
11055           CImg<char>::string(argument).move_to(status);
11056           ++position; continue;
11057         }
11058 
11059         // Skip argument.
11060         if (is_command_skip) {
11061           gmic_substitute_args(false);
11062           if (is_very_verbose)
11063             print(images,0,"Skip argument '%s'.",
11064                   gmic_argument_text_printed());
11065           ++position;
11066           continue;
11067         }
11068 
11069         // Set pixel value.
11070         if (!std::strcmp("set",command)) {
11071           gmic_substitute_args(false);
11072           float x = 0, y = 0, z = 0, c = 0;
11073           value = 0;
11074           sepx = sepy = sepz = sepc = *argx = *argy = *argz = *argc = 0;
11075           if ((cimg_sscanf(argument,"%lf%c",
11076                            &value,&end)==1 ||
11077                cimg_sscanf(argument,"%lf,%255[0-9.eE%+-]%c",
11078                            &value,argx,&end)==2 ||
11079                cimg_sscanf(argument,"%lf,%255[0-9.eE%+-],%255[0-9.eE%+-]%c",
11080                            &value,argx,argy,&end)==3 ||
11081                cimg_sscanf(argument,"%lf,%255[0-9.eE%+-],%255[0-9.eE%+-],%255[0-9.eE%+-]%c",
11082                            &value,argx,argy,argz,&end)==4 ||
11083                cimg_sscanf(argument,"%lf,%255[0-9.eE%+-],%255[0-9.eE%+-],%255[0-9.eE%+-],"
11084                            "%255[0-9.eE%+-]%c",
11085                            &value,argx,argy,argz,argc,&end)==5) &&
11086               (!*argx ||
11087                (cimg_sscanf(argx,"%f%c%c",&x,&sepx,&end)==2 && sepx=='%') ||
11088                cimg_sscanf(argx,"%f%c",&x,&end)==1) &&
11089               (!*argy ||
11090                (cimg_sscanf(argy,"%f%c%c",&y,&sepy,&end)==2 && sepy=='%') ||
11091                cimg_sscanf(argy,"%f%c",&y,&end)==1) &&
11092               (!*argz ||
11093                (cimg_sscanf(argz,"%f%c%c",&z,&sepz,&end)==2 && sepz=='%') ||
11094                cimg_sscanf(argz,"%f%c",&z,&end)==1) &&
11095               (!*argc ||
11096                (cimg_sscanf(argc,"%f%c%c",&c,&sepc,&end)==2 && sepc=='%') ||
11097                cimg_sscanf(argc,"%f%c",&c,&end)==1)) {
11098             print(images,0,"Set value %g in image%s, at coordinates (%g%s,%g%s,%g%s,%g%s).",
11099                   value,
11100                   gmic_selection.data(),
11101                   x,sepx=='%'?"%":"",
11102                   y,sepy=='%'?"%":"",
11103                   z,sepz=='%'?"%":"",
11104                   c,sepc=='%'?"%":"");
11105             cimg_forY(selection,l) {
11106               CImg<T> &img = images[selection[l]];
11107               const int
11108                 nx = (int)cimg::round(sepx=='%'?x*(img.width() - 1)/100:x),
11109                 ny = (int)cimg::round(sepy=='%'?y*(img.height() - 1)/100:y),
11110                 nz = (int)cimg::round(sepz=='%'?z*(img.depth() - 1)/100:z),
11111                 nc = (int)cimg::round(sepc=='%'?c*(img.spectrum() - 1)/100:c);
11112               gmic_apply(gmic_set(value,nx,ny,nz,nc));
11113             }
11114           } else arg_error("set");
11115           is_change = true; ++position; continue;
11116         }
11117 
11118         // Split.
11119         if (!std::strcmp("split",command)) {
11120           bool is_valid_argument = false;
11121           gmic_substitute_args(false);
11122           float nb = -1;
11123           char pm = 0;
11124           *argx = 0;
11125           if (cimg_sscanf(argument,"%255[xyzc],%f%c",argx,&nb,&end)==2 ||
11126               (nb = -1,cimg_sscanf(argument,"%255[xyzc]%c",argx,&end))==1) {
11127 
11128             // Split along axes.
11129             nb = cimg::round(nb);
11130             if (nb>0)
11131               print(images,0,"Split image%s along the '%s'-ax%cs, into %g parts.",
11132                     gmic_selection.data(),
11133                     argx,
11134                     std::strlen(argx)>1?'e':'i',
11135                     nb);
11136             else if (nb<0) {
11137               if (nb==-1)
11138                 print(images,0,"Split image%s along the '%s'-ax%cs.",
11139                       gmic_selection.data(),
11140                       argx,
11141                       std::strlen(argx)>1?'e':'i');
11142               else
11143                 print(images,0,"Split image%s along the '%s'-ax%cs, into blocs of %g pixels.",
11144                       gmic_selection.data(),
11145                       argx,
11146                       std::strlen(argx)>1?'e':'i',
11147                       -nb);
11148             } else
11149               print(images,0,"Split image%s along the '%s'-ax%cs, according to constant values.",
11150                     gmic_selection.data(),
11151                     argx,
11152                     std::strlen(argx)>1?'e':'i');
11153 
11154             int off = 0;
11155             cimg_forY(selection,l) {
11156               const unsigned int uind = selection[l] + off;
11157               const CImg<T>& img = gmic_check(images[uind]);
11158               if (!img) {
11159                 if (!is_get) { images.remove(uind); images_names.remove(uind); off-=1; }
11160               } else {
11161                 g_list.assign(img,true);
11162                 name = images_names[uind];
11163                 for (const char *p_axis = argx; *p_axis; ++p_axis) {
11164                   const unsigned int N = g_list.size();
11165                   for (unsigned int q = 0; q<N; ++q) {
11166                     g_list[0].get_split(*p_axis,(int)nb).move_to(g_list,~0U);
11167                     g_list.remove(0);
11168                   }
11169                 }
11170                 if (is_get) {
11171                   images_names.insert(g_list.size(),name.copymark());
11172                   g_list.move_to(images,~0U);
11173                 } else {
11174                   images.remove(uind); images_names.remove(uind);
11175                   off+=(int)g_list.size() - 1;
11176                   images_names.insert(g_list.size(),name.get_copymark(),uind);
11177                   name.move_to(images_names[uind]);
11178                   g_list.move_to(images,uind);
11179                 }
11180               }
11181             }
11182             g_list.assign();
11183             is_valid_argument = true;
11184           } else if (cimg_sscanf(argument,"%c%c",&pm,&end)==2 && (pm=='+' || pm=='-') && end==',') {
11185 
11186             // Split according to values sequence (opt. with axes too).
11187             const char *s_values = argument + 2;
11188             *argx = 0;
11189             if (cimg_sscanf(s_values,"%255[xyzc]%c",argx,&end)==2 && end==',') s_values+=std::strlen(argx) + 1;
11190             unsigned int nb_values = 1;
11191             for (const char *s = s_values; *s; ++s) if (*s==',') ++nb_values;
11192             CImg<T> values;
11193             try { values.assign(nb_values,1,1,1).fill(s_values,true,false); }
11194             catch (CImgException&) { values.assign(); }
11195             if (values) {
11196               if (*argx)
11197                 print(images,0,"Split image%s in '%s' mode along '%s'-ax%cs, according to value sequence '%s'.",
11198                       gmic_selection.data(),
11199                       pm=='-'?"discard":"keep",
11200                       argx,
11201                       std::strlen(argx)>1?'e':'i',
11202                       gmic_argument_text_printed() + (s_values - argument));
11203               else
11204                 print(images,0,"Split image%s in '%s' mode, according to value sequence '%s'.",
11205                       gmic_selection.data(),
11206                       pm=='-'?"discard":"keep",
11207                       gmic_argument_text_printed() + 2);
11208               int off = 0;
11209               cimg_forY(selection,l) {
11210                 const unsigned int uind = selection[l] + off;
11211                 const CImg<T>& img = gmic_check(images[uind]);
11212                 if (!img) {
11213                   if (!is_get) { images.remove(uind); images_names.remove(uind); off-=1; }
11214                 } else {
11215                   name = images_names[uind];
11216 
11217                   if (*argx) { // Along axes
11218                     g_list.assign(img,true);
11219                     for (const char *p_axis = argx; *p_axis; ++p_axis) {
11220                       const unsigned int N = g_list.size();
11221                       for (unsigned int q = 0; q<N; ++q) {
11222                         g_list[0].get_split(values,*p_axis,pm=='+').move_to(g_list,~0U);
11223                         g_list.remove(0);
11224                       }
11225                     }
11226                   } else // Without axes
11227                     img.get_split(values,0,pm=='+').move_to(g_list);
11228 
11229                   if (is_get) {
11230                     if (g_list) {
11231                       images_names.insert(g_list.size(),name.copymark());
11232                       g_list.move_to(images,~0U);
11233                     }
11234                   } else {
11235                     images.remove(uind);
11236                     images_names.remove(uind);
11237                     off+=(int)g_list.size() - 1;
11238                     if (g_list) {
11239                       images_names.insert(g_list.size(),name.get_copymark(),uind);
11240                       name.move_to(images_names[uind]);
11241                       g_list.move_to(images,uind);
11242                     }
11243                   }
11244                 }
11245               }
11246               g_list.assign();
11247               is_valid_argument = true;
11248             }
11249           }
11250 
11251           if (is_valid_argument) ++position;
11252           else {
11253 
11254             // Split as constant one-column vectors.
11255             print(images,0,"Split image%s as constant one-column vectors.",
11256                   gmic_selection.data());
11257 
11258             int off = 0;
11259             cimg_forY(selection,l) {
11260               const unsigned int uind = selection[l] + off;
11261               CImg<T>& img = gmic_check(images[uind]);
11262               if (!img) {
11263                 if (!is_get) { images.remove(uind); images_names.remove(uind); off-=1; }
11264               } else {
11265                 g_list = CImg<T>(img.data(),1,(unsigned int)img.size(),1,1,true).get_split('y',0);
11266                 name = images_names[uind];
11267                 if (is_get) {
11268                   images_names.insert(g_list.size(),name.copymark());
11269                   g_list.move_to(images,~0U);
11270                 } else {
11271                   images.remove(uind); images_names.remove(uind);
11272                   off+=(int)g_list.size() - 1;
11273                   images_names.insert(g_list.size(),name.get_copymark(),uind);
11274                   name.move_to(images_names[uind]);
11275                   g_list.move_to(images,uind);
11276                 }
11277               }
11278             }
11279           }
11280           g_list.assign();
11281           is_change = true; continue;
11282         }
11283 
11284         // Shared input.
11285         if (!std::strcmp("shared",command)) {
11286           gmic_substitute_args(false);
11287           CImg<char> st0(256), st1(256), st2(256), st3(256), st4(256);
11288           char sep2 = 0, sep3 = 0, sep4 = 0;
11289           float a0 = 0, a1 = 0, a2 = 0, a3 = 0, a4 = 0;
11290           sep0 = sep1 = *st0 = *st1 = *st2 = *st3 = *st4 = 0;
11291           if (cimg_sscanf(argument,
11292                           "%255[0-9.eE%+],%255[0-9.eE%+],%255[0-9.eE%+],%255[0-9.eE%+],"
11293                           "%255[0-9.eE%+]%c",
11294                           st0.data(),st1.data(),st2.data(),st3.data(),st4.data(),&end)==5 &&
11295               (cimg_sscanf(st0,"%f%c",&a0,&end)==1 ||
11296                (cimg_sscanf(st0,"%f%c%c",&a0,&sep0,&end)==2 && sep0=='%')) &&
11297               (cimg_sscanf(st1,"%f%c",&a1,&end)==1 ||
11298                (cimg_sscanf(st1,"%f%c%c",&a1,&sep1,&end)==2 && sep1=='%')) &&
11299               (cimg_sscanf(st2,"%f%c",&a2,&end)==1 ||
11300                (cimg_sscanf(st2,"%f%c%c",&a2,&sep2,&end)==2 && sep2=='%')) &&
11301               (cimg_sscanf(st3,"%f%c",&a3,&end)==1 ||
11302                (cimg_sscanf(st3,"%f%c%c",&a3,&sep3,&end)==2 && sep3=='%')) &&
11303               (cimg_sscanf(st4,"%f%c",&a4,&end)==1 ||
11304                (cimg_sscanf(st4,"%f%c%c",&a4,&sep4,&end)==2 && sep4=='%'))) {
11305             print(images,0,
11306                   "Insert shared buffer%s from points (%g%s->%g%s,%g%s,%g%s,%g%s) of image%s.",
11307                   selection.height()>1?"s":"",
11308                   a0,sep0=='%'?"%":"",
11309                   a1,sep1=='%'?"%":"",
11310                   a2,sep2=='%'?"%":"",
11311                   a3,sep3=='%'?"%":"",
11312                   a4,sep4=='%'?"%":"",
11313                   gmic_selection.data());
11314             cimg_forY(selection,l) {
11315               CImg<T>& img = images[selection[l]];
11316               nvalue0 = cimg::round(sep0=='%'?a0*(img.width() - 1)/100:a0);
11317               nvalue1 = cimg::round(sep1=='%'?a1*(img.width() - 1)/100:a1);
11318               const unsigned int
11319                 y =  (unsigned int)cimg::round(sep2=='%'?a2*(img.height() - 1)/100:a2),
11320                 z =  (unsigned int)cimg::round(sep3=='%'?a3*(img.depth() - 1)/100:a3),
11321                 c =  (unsigned int)cimg::round(sep4=='%'?a4*(img.spectrum() - 1)/100:a4);
11322               images.insert(img.get_shared_points((unsigned int)nvalue0,(unsigned int)nvalue1,y,z,c),~0U,true);
11323               images_names.insert(images_names[selection[l]].get_copymark());
11324             }
11325             ++position;
11326           } else if (cimg_sscanf(argument,
11327                                  "%255[0-9.eE%+],%255[0-9.eE%+],%255[0-9.eE%+],"
11328                                  "%255[0-9.eE%+],%c",
11329                                  st0.data(),st1.data(),st2.data(),st3.data(),&end)==4 &&
11330                      (cimg_sscanf(st0,"%f%c",&a0,&end)==1 ||
11331                       (cimg_sscanf(st0,"%f%c%c",&a0,&sep0,&end)==2 && sep0=='%')) &&
11332                      (cimg_sscanf(st1,"%f%c",&a1,&end)==1 ||
11333                       (cimg_sscanf(st1,"%f%c%c",&a1,&sep1,&end)==2 && sep1=='%')) &&
11334                      (cimg_sscanf(st2,"%f%c",&a2,&end)==1 ||
11335                       (cimg_sscanf(st2,"%f%c%c",&a2,&sep2,&end)==2 && sep2=='%')) &&
11336                      (cimg_sscanf(st3,"%f%c",&a3,&end)==1 ||
11337                       (cimg_sscanf(st3,"%f%c%c",&a3,&sep3,&end)==2 && sep3=='%'))) {
11338             print(images,0,"Insert shared buffer%s from lines (%g%s->%g%s,%g%s,%g%s) of image%s.",
11339                   selection.height()>1?"s":"",
11340                   a0,sep0=='%'?"%":"",
11341                   a1,sep1=='%'?"%":"",
11342                   a2,sep2=='%'?"%":"",
11343                   a3,sep3=='%'?"%":"",
11344                   gmic_selection.data());
11345             cimg_forY(selection,l) {
11346               CImg<T>& img = images[selection[l]];
11347               nvalue0 = cimg::round(sep0=='%'?a0*(img.height() - 1)/100:a0);
11348               nvalue1 = cimg::round(sep1=='%'?a1*(img.height() - 1)/100:a1);
11349               const unsigned int
11350                 z =  (unsigned int)cimg::round(sep2=='%'?a2*(img.depth() - 1)/100:a2),
11351                 c =  (unsigned int)cimg::round(sep3=='%'?a3*(img.spectrum() - 1)/100:a3);
11352               images.insert(img.get_shared_rows((unsigned int)nvalue0,(unsigned int)nvalue1,z,c),~0U,true);
11353               images_names.insert(images_names[selection[l]].get_copymark());
11354             }
11355             ++position;
11356           } else if (cimg_sscanf(argument,"%255[0-9.eE%+],%255[0-9.eE%+],%255[0-9.eE%+]%c",
11357                                  st0.data(),st1.data(),st2.data(),&end)==3 &&
11358                      (cimg_sscanf(st0,"%f%c",&a0,&end)==1 ||
11359                       (cimg_sscanf(st0,"%f%c%c",&a0,&sep0,&end)==2 && sep0=='%')) &&
11360                      (cimg_sscanf(st1,"%f%c",&a1,&end)==1 ||
11361                       (cimg_sscanf(st1,"%f%c%c",&a1,&sep1,&end)==2 && sep1=='%')) &&
11362                      (cimg_sscanf(st2,"%f%c",&a2,&end)==1 ||
11363                       (cimg_sscanf(st2,"%f%c%c",&a2,&sep2,&end)==2 && sep2=='%'))) {
11364             print(images,0,"Insert shared buffer%s from planes (%g%s->%g%s,%g%s) of image%s.",
11365                   selection.height()>1?"s":"",
11366                   a0,sep0=='%'?"%":"",
11367                   a1,sep1=='%'?"%":"",
11368                   a2,sep2=='%'?"%":"",
11369                   gmic_selection.data());
11370             cimg_forY(selection,l) {
11371               CImg<T>& img = images[selection[l]];
11372               nvalue0 = cimg::round(sep0=='%'?a0*(img.depth() - 1)/100:a0);
11373               nvalue1 = cimg::round(sep1=='%'?a1*(img.depth() - 1)/100:a1);
11374               const unsigned int
11375                 c =  (unsigned int)cimg::round(sep2=='%'?a2*(img.spectrum() - 1)/100:a2);
11376               images.insert(img.get_shared_slices((unsigned int)nvalue0,(unsigned int)nvalue1,c),~0U,true);
11377               images_names.insert(images_names[selection[l]].get_copymark());
11378             }
11379             ++position;
11380           } else if (cimg_sscanf(argument,"%255[0-9.eE%+],%255[0-9.eE%+]%c",
11381                                  st0.data(),st1.data(),&end)==2 &&
11382                      (cimg_sscanf(st0,"%f%c",&a0,&end)==1 ||
11383                       (cimg_sscanf(st0,"%f%c%c",&a0,&sep0,&end)==2 && sep0=='%')) &&
11384                      (cimg_sscanf(st1,"%f%c",&a1,&end)==1 ||
11385                       (cimg_sscanf(st1,"%f%c%c",&a1,&sep1,&end)==2 && sep1=='%'))) {
11386             print(images,0,"Insert shared buffer%s from channels (%g%s->%g%s) of image%s.",
11387                   selection.height()>1?"s":"",
11388                   a0,sep0=='%'?"%":"",
11389                   a1,sep1=='%'?"%":"",
11390                   gmic_selection.data());
11391             cimg_forY(selection,l) {
11392               CImg<T>& img = images[selection[l]];
11393               nvalue0 = cimg::round(sep0=='%'?a0*(img.spectrum() - 1)/100:a0);
11394               nvalue1 = cimg::round(sep1=='%'?a1*(img.spectrum() - 1)/100:a1);
11395               images.insert(img.get_shared_channels((unsigned int)nvalue0,(unsigned int)nvalue1),~0U,true);
11396               images_names.insert(images_names[selection[l]].get_copymark());
11397             }
11398             ++position;
11399           } else if (cimg_sscanf(argument,"%255[0-9.eE%+]%c",
11400                                  st0.data(),&end)==1 &&
11401                      (cimg_sscanf(st0,"%f%c",&a0,&end)==1 ||
11402                       (cimg_sscanf(st0,"%f%c%c",&a0,&sep0,&end)==2 && sep0=='%'))) {
11403             print(images,0,"Insert shared buffer%s from channel %g%s of image%s.",
11404                   selection.height()>1?"s":"",
11405                   a0,sep0=='%'?"%":"",
11406                   gmic_selection.data());
11407             cimg_forY(selection,l) {
11408               CImg<T>& img = images[selection[l]];
11409               nvalue0 = cimg::round(sep0=='%'?a0*(img.spectrum() - 1)/100:a0);
11410               images.insert(img.get_shared_channel((unsigned int)nvalue0),~0U,true);
11411               images_names.insert(images_names[selection[l]].get_copymark());
11412             }
11413             ++position;
11414           } else {
11415             print(images,0,"Insert shared buffer%s from image%s.",
11416                   selection.height()>1?"s":"",
11417                   gmic_selection.data());
11418             cimg_forY(selection,l) {
11419               CImg<T> &img = images[selection[l]];
11420               images.insert(img,~0U,true);
11421               images_names.insert(images_names[selection[l]].get_copymark());
11422             }
11423           }
11424           is_change = true; continue;
11425         }
11426 
11427         // Store.
11428         if (!std::strcmp("store",command)) {
11429           gmic_substitute_args(false);
11430           if (cimg_sscanf(argument,"%4095[,a-zA-Z0-9_]%c",&(*formula=0),&end)==1 &&
11431               (*formula<'0' || *formula>'9') && *formula!=',') {
11432             char *current = formula, *next = std::strchr(formula,','), saved = 0;
11433             pattern = 1U;
11434             if (next) // Count number of specified variable names
11435               for (const char *ptr = next; ptr; ptr = std::strchr(ptr,',')) { ++ptr; ++pattern; }
11436             print(images,0,
11437                   "Store image%s as variable%s '%s'",
11438                   gmic_selection.data(),
11439                   next?"s":"",
11440                   gmic_argument_text_printed());
11441 
11442             if (pattern!=1 && (int)pattern!=selection.height())
11443               error(true,images,0,0,
11444                     "Command 'store': Specified arguments '%s' do not match numbers of selected images.",
11445                     gmic_argument_text());
11446 
11447             g_list.assign(selection.height());
11448             g_list_c.assign(g_list.size());
11449             cimg_forY(selection,l) {
11450               const unsigned int uind = selection[l];
11451               if (is_get) {
11452                 g_list[l] = images[uind].get_shared();
11453                 g_list_c[l] = images_names[uind].get_shared();
11454               } else {
11455                 images[uind].move_to(g_list[l]);
11456                 images_names[uind].move_to(g_list_c[l]);
11457               }
11458             }
11459 
11460             if (pattern==1) { // Assignment to a single variable
11461               (g_list_c>'x').move_to(name).resize(name.width() + 4,1,1,1,0,0,1);
11462               name[0] = 'G'; name[1] = 'M'; name[2] = 'Z'; name[3] = 0;
11463               name.unroll('y').move_to(g_list);
11464               g_list.get_serialize(false).unroll('x').move_to(name);
11465               name.resize(name.width() + 9 + std::strlen(formula),1,1,1,0,0,1);
11466               std::sprintf(name,"%c*store/%s",gmic_store,_formula.data());
11467               set_variable(formula,name,variables_sizes);
11468             } else for (unsigned int n = 0; n<pattern; ++n) { // Assignment to multiple variables
11469               if (!*current)
11470                 error(true,images,0,0,
11471                       "Command 'store': Empty variable name specified in arguments '%s'.",
11472                       gmic_argument_text());
11473               saved = next?*next:0;
11474               if (next) *next = 0;
11475 
11476               CImgList<T> tmp(2);
11477               g_list_c[n].move_to(name).resize(name.width() + 4,1,1,1,0,0,1);
11478               name[0] = 'G'; name[1] = 'M'; name[2] = 'Z'; name[3] = 0;
11479               name.unroll('y').move_to(tmp[1]);
11480               g_list[n].move_to(tmp[0]);
11481               tmp.get_serialize(false).unroll('x').move_to(name);
11482               name.resize(name.width() + 9 + std::strlen(current),1,1,1,0,0,1);
11483               std::sprintf(name,"%c*store/%s",gmic_store,current);
11484               set_variable(current,name,variables_sizes);
11485 
11486               if (saved) { // other variables names follow
11487                 *next = saved;
11488                 current = next + 1;
11489                 next = std::strchr(next + 1,',');
11490               }
11491             }
11492             if (!is_get) remove_images(images,images_names,selection,0,selection.height() - 1);
11493             name.assign();
11494             g_list.assign();
11495             g_list_c.assign();
11496           } else arg_error("store");
11497           ++position; continue;
11498         }
11499 
11500         // Shift.
11501         if (!std::strcmp("shift",command)) {
11502           gmic_substitute_args(false);
11503           float dx = 0, dy = 0, dz = 0, dc = 0;
11504           sepx = sepy = sepz = sepc = *argx = *argy = *argz = *argc = 0;
11505           interpolation = boundary = 0;
11506           if ((cimg_sscanf(argument,"%255[0-9.eE%+-]%c",
11507                            argx,&end)==1 ||
11508                cimg_sscanf(argument,"%255[0-9.eE%+-],%255[0-9.eE%+-]%c",
11509                            argx,argy,&end)==2 ||
11510                cimg_sscanf(argument,"%255[0-9.eE%+-],%255[0-9.eE%+-],%255[0-9.eE%+-]%c",
11511                            argx,argy,argz,&end)==3 ||
11512                cimg_sscanf(argument,"%255[0-9.eE%+-],%255[0-9.eE%+-],%255[0-9.eE%+-],"
11513                            "%255[0-9.eE%+-]%c",
11514                            argx,argy,argz,argc,&end)==4 ||
11515                cimg_sscanf(argument,"%255[0-9.eE%+-],%255[0-9.eE%+-],%255[0-9.eE%+-],"
11516                            "%255[0-9.eE%+-],%u%c",
11517                            argx,argy,argz,argc,&boundary,&end)==5 ||
11518                cimg_sscanf(argument,"%255[0-9.eE%+-],%255[0-9.eE%+-],%255[0-9.eE%+-],"
11519                            "%255[0-9.eE%+-],%u,%u%c",
11520                            argx,argy,argz,argc,&boundary,&interpolation,&end)==6) &&
11521               (cimg_sscanf(argx,"%f%c",&dx,&end)==1 ||
11522                (cimg_sscanf(argx,"%f%c%c",&dx,&sepx,&end)==2 && sepx=='%')) &&
11523               (!*argy ||
11524                cimg_sscanf(argy,"%f%c",&dy,&end)==1 ||
11525                (cimg_sscanf(argy,"%f%c%c",&dy,&sepy,&end)==2 && sepy=='%')) &&
11526               (!*argz ||
11527                cimg_sscanf(argz,"%f%c",&dz,&end)==1 ||
11528                (cimg_sscanf(argz,"%f%c%c",&dz,&sepz,&end)==2 && sepz=='%')) &&
11529               (!*argc ||
11530                cimg_sscanf(argc,"%f%c",&dc,&end)==1 ||
11531                (cimg_sscanf(argc,"%f%c%c",&dc,&sepc,&end)==2 && sepc=='%')) &&
11532               boundary<=3 && interpolation<=1) {
11533             print(images,0,
11534                   "Shift image%s by displacement vector (%g%s,%g%s,%g%s,%g%s), "
11535                   "%s boundary conditions and %s interpolation.",
11536                   gmic_selection.data(),
11537                   dx,sepx=='%'?"%":"",
11538                   dy,sepy=='%'?"%":"",
11539                   dz,sepz=='%'?"%":"",
11540                   dc,sepc=='%'?"%":"",
11541                   boundary==0?"dirichlet":boundary==1?"neumann":boundary==2?"periodic":"mirror",
11542                   interpolation?"linear":"nearest-neighbor");
11543             cimg_forY(selection,l) {
11544               CImg<T> &img = images[selection[l]];
11545               const float
11546                 ndx = sepx=='%'?dx*img.width()/100:dx,
11547                 ndy = sepy=='%'?dy*img.height()/100:dy,
11548                 ndz = sepz=='%'?dz*img.depth()/100:dz,
11549                 ndc = sepc=='%'?dc*img.spectrum()/100:dc;
11550               gmic_apply(gmic_shift(ndx,ndy,ndz,ndc,boundary,(bool)interpolation));
11551             }
11552           } else arg_error("shift");
11553           is_change = true; ++position; continue;
11554         }
11555 
11556         // Keep slices.
11557         if (!std::strcmp("slices",command)) {
11558           gmic_substitute_args(true);
11559           ind0.assign(); ind1.assign();
11560           sep0 = sep1 = *argx = *argy = *indices = 0;
11561           value0 = value1 = 0;
11562           if (cimg_sscanf(argument,"%255[][a-zA-Z0-9_.eE%+-]%c",
11563                           argx,&end)==1 &&
11564               ((cimg_sscanf(argx,"[%255[a-zA-Z0-9_.%+-]%c%c]",indices,&sep0,&end)==2 &&
11565                 sep0==']' &&
11566                 (ind0=selection2cimg(indices,images.size(),images_names,"slices")).height()==1) ||
11567                cimg_sscanf(argx,"%lf%c",&value0,&end)==1 ||
11568                (cimg_sscanf(argx,"%lf%c%c",&value0,&sep0,&end)==2 && sep0=='%'))) {
11569             if (ind0) { value0 = images[*ind0].depth() - 1.; sep0 = 0; }
11570             print(images,0,"Keep slice %g%s of image%s.",
11571                   value0,sep0=='%'?"%":"",
11572                   gmic_selection.data());
11573             cimg_forY(selection,l) {
11574               CImg<T> &img = images[selection[l]];
11575               nvalue0 = cimg::round(sep0=='%'?value0*(img.depth() - 1)/100:value0);
11576               gmic_apply(slice((int)nvalue0));
11577             }
11578           } else if (cimg_sscanf(argument,"%255[][a-zA-Z0-9_.eE%+-],%255[][a-zA-Z0-9_.eE%+-]%c",
11579                                  argx,argy,&end)==2 &&
11580                      ((cimg_sscanf(argx,"[%255[a-zA-Z0-9_.%+-]%c%c",indices,&sep0,&end)==2 &&
11581                        sep0==']' &&
11582                        (ind0=selection2cimg(indices,images.size(),images_names,"slices")).height()==1) ||
11583                       cimg_sscanf(argx,"%lf%c",&value0,&end)==1 ||
11584                       (cimg_sscanf(argx,"%lf%c%c",&value0,&sep0,&end)==2 && sep0=='%')) &&
11585                      ((cimg_sscanf(argx,"[%255[a-zA-Z0-9_.%+-]%c%c",formula,&sep0,&end)==2 &&
11586                        sep0==']' &&
11587                        (ind1=selection2cimg(formula,images.size(),images_names,"slices")).height()==1) ||
11588                       cimg_sscanf(argy,"%lf%c",&value1,&end)==1 ||
11589                       (cimg_sscanf(argy,"%lf%c%c",&value1,&sep1,&end)==2 && sep1=='%'))) {
11590             if (ind0) { value0 = images[*ind0].depth() - 1.; sep0 = 0; }
11591             if (ind1) { value1 = images[*ind1].depth() - 1.; sep1 = 0; }
11592             print(images,0,"Keep slices %g%s...%g%s of image%s.",
11593                   value0,sep0=='%'?"%":"",
11594                   value1,sep1=='%'?"%":"",
11595                   gmic_selection.data());
11596             cimg_forY(selection,l) {
11597               CImg<T> &img = images[selection[l]];
11598               nvalue0 = cimg::round(sep0=='%'?value0*(img.depth() - 1)/100:value0);
11599               nvalue1 = cimg::round(sep1=='%'?value1*(img.depth() - 1)/100:value1);
11600               gmic_apply(slices((int)nvalue0,(int)nvalue1));
11601             }
11602           } else arg_error("slices");
11603           is_change = true; ++position; continue;
11604         }
11605 
11606         // Sub.
11607         gmic_arithmetic_command("sub",
11608                                 operator-=,
11609                                 "Subtract %g%s to image%s",
11610                                 value,ssep,gmic_selection.data(),Tfloat,
11611                                 operator-=,
11612                                 "Subtract image [%d] to image%s",
11613                                 ind[0],gmic_selection.data(),
11614                                 operator_minuseq,
11615                                 "Subtract expression %s to image%s",
11616                                 gmic_argument_text_printed(),gmic_selection.data(),
11617                                 "Subtract image%s");
11618         // Square root.
11619         gmic_simple_command("sqrt",sqrt,"Compute pointwise square root of image%s.");
11620 
11621         // Square.
11622         gmic_simple_command("sqr",sqr,"Compute pointwise square function of image%s.");
11623 
11624         // Sign.
11625         gmic_simple_command("sign",sign,"Compute pointwise sign of image%s.");
11626 
11627         // Sine.
11628         gmic_simple_command("sin",sin,"Compute pointwise sine of image%s.");
11629 
11630         // Sort.
11631         if (!std::strcmp("sort",command)) {
11632           gmic_substitute_args(false);
11633           char order = '+';
11634           axis = 0;
11635           if ((cimg_sscanf(argument,"%c%c",&order,&end)==1 ||
11636                (cimg_sscanf(argument,"%c,%c%c",&order,&axis,&end)==2 &&
11637                 (axis=='x' || axis=='y' || axis=='z' || axis=='c'))) &&
11638               (order=='+' || order=='-')) ++position;
11639           else { order = '+'; axis = 0; }
11640           if (axis) print(images,0,"Sort values of image%s in %s order, according to axis '%c'.",
11641                           gmic_selection.data(),order=='+'?"ascending":"descending",axis);
11642           else print(images,0,"Sort values of image%s in %s order.",
11643                      gmic_selection.data(),order=='+'?"ascending":"descending");
11644           cimg_forY(selection,l) gmic_apply(sort(order=='+',axis));
11645           is_change = true; continue;
11646         }
11647 
11648         // Solve.
11649         if (!std::strcmp("solve",command)) {
11650           gmic_substitute_args(true);
11651           sep = *indices = 0;
11652           if (cimg_sscanf(argument,"[%255[a-zA-Z0-9_.%+-]%c%c",indices,&sep,&end)==2 &&
11653               sep==']' &&
11654               (ind=selection2cimg(indices,images.size(),images_names,"solve")).height()==1) {
11655             print(images,0,"Solve linear system AX = B, with B-vector%s and A-matrix [%d].",
11656                   gmic_selection.data(),*ind);
11657             const CImg<T> A = gmic_image_arg(*ind);
11658             cimg_forY(selection,l) gmic_apply(solve(A));
11659           } else arg_error("solve");
11660           is_change = true; ++position; continue;
11661         }
11662 
11663         // Shift 3D object, with opposite displacement.
11664         if (!std::strcmp("sub3d",command)) {
11665           gmic_substitute_args(false);
11666           float tx = 0, ty = 0, tz = 0;
11667           if (cimg_sscanf(argument,"%f%c",
11668                           &tx,&end)==1 ||
11669               cimg_sscanf(argument,"%f,%f%c",
11670                           &tx,&ty,&end)==2 ||
11671               cimg_sscanf(argument,"%f,%f,%f%c",
11672                           &tx,&ty,&tz,&end)==3) {
11673             print(images,0,"Shift 3D object%s with displacement -(%g,%g,%g).",
11674                   gmic_selection.data(),
11675                   tx,ty,tz);
11676             cimg_forY(selection,l) {
11677               const unsigned int uind = selection[l];
11678               CImg<T>& img = images[uind];
11679               try { gmic_apply(shift_CImg3d(-tx,-ty,-tz)); }
11680               catch (CImgException&) {
11681                 if (!img.is_CImg3d(true,&(*message=0)))
11682                   error(true,images,0,0,
11683                         "Command 'sub3d': Invalid 3D object [%d], in selected image%s (%s).",
11684                         uind,gmic_selection_err.data(),message.data());
11685                 else throw;
11686               }
11687             }
11688           } else arg_error("sub3d");
11689           is_change = true; ++position; continue;
11690         }
11691 
11692         // Sharpen.
11693         if (!std::strcmp("sharpen",command)) {
11694           gmic_substitute_args(false);
11695           float amplitude = 0, edge = -1, alpha = 0, sigma = 0;
11696           if ((cimg_sscanf(argument,"%f%c",
11697                            &amplitude,&end)==1 ||
11698                cimg_sscanf(argument,"%f,%f%c",
11699                            &amplitude,&edge,&end)==2 ||
11700                cimg_sscanf(argument,"%f,%f,%f%c",
11701                            &amplitude,&edge,&alpha,&end)==3 ||
11702                cimg_sscanf(argument,"%f,%f,%f,%f%c",
11703                            &amplitude,&edge,&alpha,&sigma,&end)==4) &&
11704               amplitude>=0 && (edge==-1 || edge>=0)) {
11705             if (edge>=0)
11706               print(images,0,"Sharpen image%s with shock filters, amplitude %g, edge %g, "
11707                     "alpha %g and sigma %g.",
11708                     gmic_selection.data(),
11709                     amplitude,
11710                     edge,
11711                     alpha,
11712                     sigma);
11713             else
11714               print(images,0,"Sharpen image%s with inverse diffusion and amplitude %g.",
11715                     gmic_selection.data(),
11716                     amplitude);
11717             cimg_forY(selection,l) gmic_apply(sharpen(amplitude,(bool)(edge>=0),edge,alpha,sigma));
11718           } else arg_error("sharpen");
11719           is_change = true; ++position; continue;
11720         }
11721 
11722         // Set random generator seed.
11723         if (!is_get && !std::strcmp("srand",item)) {
11724           gmic_substitute_args(false);
11725           value = 0;
11726           if (cimg_sscanf(argument,"%lf%c",
11727                           &value,&end)==1) {
11728             value = cimg::round(value);
11729             print(images,0,"Set random generator seed to %u.",
11730                   (unsigned int)value);
11731             cimg::srand((unsigned int)value);
11732             ++position;
11733           } else {
11734             print(images,0,"Set random generator seed to random.");
11735             cimg::srand();
11736           }
11737           continue;
11738         }
11739 
11740         // Anisotropic PDE-based smoothing.
11741         if (!std::strcmp("smooth",command)) {
11742           gmic_substitute_args(true);
11743           float sharpness = 0.7f, anisotropy = 0.3f, dl =0.8f, da = 30.f, gauss_prec = 2.f;
11744           unsigned int is_fast_approximation = 1;
11745           *argx = *argy = *argz = sep = sep0 = sep1 = 0;
11746           interpolation = 0;
11747           value0 = 0.6; value1 = 1.1;
11748           if ((cimg_sscanf(argument,"%255[0-9.eE%+-]%c",
11749                            argz,&end)==1 ||
11750                cimg_sscanf(argument,"%255[0-9.eE%+-],%f%c",
11751                            argz,&sharpness,&end)==2 ||
11752                cimg_sscanf(argument,"%255[0-9.eE%+-],%f,%f%c",
11753                            argz,&sharpness,&anisotropy,&end)==3 ||
11754                cimg_sscanf(argument,"%255[0-9.eE%+-],%f,%f,%255[0-9.eE%+-]%c",
11755                            argz,&sharpness,&anisotropy,argx,&end)==4 ||
11756                cimg_sscanf(argument,"%255[0-9.eE%+-],%f,%f,%255[0-9.eE%+-],%255[0-9.eE%+-]%c",
11757                            argz,&sharpness,&anisotropy,argx,argy,&end)==5 ||
11758                cimg_sscanf(argument,"%255[0-9.eE%+-],%f,%f,%255[0-9.eE%+-],%255[0-9.eE%+-],%f%c",
11759                            argz,&sharpness,&anisotropy,argx,argy,&dl,&end)==6 ||
11760                cimg_sscanf(argument,"%255[0-9.eE%+-],%f,%f,%255[0-9.eE%+-],%255[0-9.eE%+-],%f,%f%c",
11761                            argz,&sharpness,&anisotropy,argx,argy,&dl,&da,&end)==7 ||
11762                cimg_sscanf(argument,"%255[0-9.eE%+-],%f,%f,%255[0-9.eE%+-],%255[0-9.eE%+-],%f,%f,%f%c",
11763                            argz,&sharpness,&anisotropy,argx,argy,&dl,&da,&gauss_prec,&end)==8 ||
11764                cimg_sscanf(argument,"%255[0-9.eE%+-],%f,%f,%255[0-9.eE%+-],%255[0-9.eE%+-],%f,%f,%f,%u%c",
11765                            argz,&sharpness,&anisotropy,argx,argy,&dl,&da,&gauss_prec,&interpolation,&end)==9 ||
11766                cimg_sscanf(argument,"%255[0-9.eE%+-],%f,%f,%255[0-9.eE%+-],%255[0-9.eE%+-],%f,%f,%f,%u,%u,%c",
11767                            argz,&sharpness,&anisotropy,argx,argy,&dl,&da,&gauss_prec,&interpolation,
11768                            &is_fast_approximation,&end)==10) &&
11769               (cimg_sscanf(argz,"%lf%c",&value,&end)==1 ||
11770                (cimg_sscanf(argz,"%lf%c%c",&value,&sep,&end)==2 && sep=='%')) &&
11771               (!*argx ||
11772                cimg_sscanf(argx,"%lf%c",&value0,&end)==1 ||
11773                (cimg_sscanf(argx,"%lf%c%c",&value0,&sep0,&end)==2 && sep0=='%')) &&
11774               (!*argy ||
11775                cimg_sscanf(argy,"%lf%c",&value1,&end)==1 ||
11776                (cimg_sscanf(argy,"%lf%c%c",&value1,&sep1,&end)==2 && sep1=='%')) &&
11777               value>=0 && value0>=0 && value1>=0 && sharpness>=0 && anisotropy>=0 && anisotropy<=1 && dl>0 &&
11778               (da>0 || (da==0 && sep!='%')) && gauss_prec>0 && interpolation<=2 && is_fast_approximation<=1) {
11779             if (da>0)
11780               print(images,0,"Smooth image%s anisotropically, with amplitude %g%s, sharpness %g, "
11781                     "anisotropy %g, alpha %g%s, sigma %g%s, dl %g, da %g, precision %g, "
11782                     "%s interpolation and fast approximation %s.",
11783                     gmic_selection.data(),
11784                     value,sep=='%'?"%":"",
11785                     sharpness,anisotropy,value0,sep0=='%'?"%":"",value1,sep1=='%'?"%":"",dl,da,gauss_prec,
11786                     interpolation==0?"nearest-neighbor":interpolation==1?"linear":"runge-kutta",
11787                     is_fast_approximation?"enabled":"disabled");
11788             else {
11789               value = cimg::round(value);
11790               print(images,0,"Smooth image%s anisotropically, with %d iterations, sharpness %g, "
11791                     "anisotropy %g, alpha %g%s, sigma %g%s and dt %g.",
11792                     gmic_selection.data(),
11793                     (int)value,
11794                     sharpness,anisotropy,value0,sep0=='%'?"%":"",value1,sep1=='%'?"%":"",dl);
11795             }
11796             if (sep=='%') value = -value;
11797             if (sep0=='%') value0 = -value0;
11798             if (sep1=='%') value1 = -value1;
11799             cimg_forY(selection,l)
11800               gmic_apply(blur_anisotropic((float)value,sharpness,anisotropy,(float)value0,(float)value1,dl,da,
11801                                           gauss_prec,interpolation,(bool)is_fast_approximation));
11802           } else if (((cimg_sscanf(argument,"[%255[a-zA-Z0-9_.%+-]%c%c",
11803                                    indices,&sep,&end)==2 && sep==']') ||
11804                       cimg_sscanf(argument,"[%255[a-zA-Z0-9_.%+-]],%255[0-9.eE%+-]%c",
11805                                   indices,argx,&end)==2 ||
11806                       cimg_sscanf(argument,"[%255[a-zA-Z0-9_.%+-]],%255[0-9.eE%+-],%f%c",
11807                                   indices,argx,&dl,&end)==3 ||
11808                       cimg_sscanf(argument,"[%255[a-zA-Z0-9_.%+-]],%255[0-9.eE%+-],%f,%f%c",
11809                                   indices,argx,&dl,&da,&end)==4 ||
11810                       cimg_sscanf(argument,"[%255[a-zA-Z0-9_.%+-]],%255[0-9.eE%+-],%f,%f,%f%c",
11811                                   indices,argx,&dl,&da,&gauss_prec,&end)==5 ||
11812                       cimg_sscanf(argument,"[%255[a-zA-Z0-9_.%+-]],%255[0-9.eE%+-],%f,%f,%f,%u%c",
11813                                   indices,argx,&dl,&da,&gauss_prec,&interpolation,&end)==6 ||
11814                       cimg_sscanf(argument,"[%255[a-zA-Z0-9_.%+-]],%255[0-9.eE%+-],%f,%f,%f,%u,%u%c",
11815                                   indices,argx,&dl,&da,&gauss_prec,&interpolation,
11816                                   &is_fast_approximation,&end)==7) &&
11817                      (ind=selection2cimg(indices,images.size(),images_names,"smooth")).height()==1 &&
11818                      (!*argx ||
11819                       cimg_sscanf(argx,"%lf%c",&value0,&end)==1 ||
11820                       (cimg_sscanf(argx,"%lf%c%c",&value0,&sep0,&end)==2 && sep0=='%')) &&
11821                      value0>=0 && dl>0 && (da>0 || (da==0 && sep0!='%')) && gauss_prec>0 && interpolation<=2 &&
11822                      is_fast_approximation<=1) {
11823             const CImg<T> tensors = gmic_image_arg(*ind);
11824             if (da>0)
11825               print(images,0,
11826                     "Smooth image%s anisotropically, with tensor field [%u], amplitude %g%s, "
11827                     "dl %g, da %g, precision %g, %s interpolation and fast approximation %s.",
11828                     gmic_selection.data(),
11829                     *ind,
11830                     value0,sep0=='%'?"%":"",
11831                     dl,da,gauss_prec,interpolation==0?"nearest-neighbor":interpolation==1?"linear":"runge-kutta",
11832                     is_fast_approximation?"enabled":"disabled");
11833             else {
11834               value0 = cimg::round(value0);
11835               print(images,0,
11836                     "Smooth image%s anisotropically, with tensor field [%u], %d iterations "
11837                     "and dt %g.",
11838                     gmic_selection.data(),
11839                     *ind,(int)value0,dl);
11840             }
11841             cimg_forY(selection,l)
11842               gmic_apply(blur_anisotropic(tensors,(float)value0,dl,da,gauss_prec,interpolation,
11843                                           is_fast_approximation));
11844           } else arg_error("smooth");
11845           is_change = true; ++position; continue;
11846         }
11847 
11848         // Split 3D objects, into 6 vector images
11849         // { header,N,vertices,primitives,colors,opacities }
11850         if (!std::strcmp("split3d",command)) {
11851           bool keep_shared = true;
11852           gmic_substitute_args(false);
11853           if ((*argument=='0' || *argument=='1') && !argument[1]) {
11854             keep_shared = *argument=='1';
11855             ++position;
11856           }
11857           print(images,0,"Split 3D object%s into 6 property vectors%s.",
11858                 gmic_selection.data(),
11859                 keep_shared?"":" and clone shared data");
11860           unsigned int off = 0;
11861           cimg_forY(selection,l) {
11862             const unsigned int uind = selection[l] + off;
11863             const CImg<T> &img = gmic_check(images[uind]);
11864             name = images_names[uind];
11865             try {
11866               if (!keep_shared) {
11867                 CImg<T> _vertices;
11868                 CImgList<T> Tcolors, opacities;
11869                 img.get_CImg3dtoobject3d(primitives,Tcolors,opacities,false).move_to(_vertices);
11870                 CImgList<T> _colors(Tcolors,false), _opacities(opacities,false);
11871                 _colors.move_to(Tcolors.assign());
11872                 _opacities.move_to(opacities.assign());
11873                 _vertices.object3dtoCImg3d(primitives,Tcolors,opacities,false).get_split_CImg3d().
11874                   move_to(g_list);
11875                 primitives.assign();
11876               } else img.get_split_CImg3d().move_to(g_list);
11877             } catch (CImgException&) {
11878               if (!img.is_CImg3d(true,&(*message=0)))
11879                 error(true,images,0,0,
11880                       "Command 'split3d': Invalid 3D object [%d], in selected image%s (%s).",
11881                       uind - off,gmic_selection_err.data(),message.data());
11882               else throw;
11883             }
11884             if (is_get) {
11885               images_names.insert(g_list.size(),name.copymark());
11886               g_list.move_to(images,~0U);
11887             } else {
11888               images.remove(uind);
11889               images_names.remove(uind);
11890               off+=g_list.size() - 1;
11891               images_names.insert(g_list.size(),name.get_copymark(),uind);
11892               name.move_to(images_names[uind]);
11893               g_list.move_to(images,uind);
11894             }
11895           }
11896           g_list.assign();
11897           is_change = true; continue;
11898         }
11899 
11900         // Screenshot.
11901         if (!is_get && !std::strcmp("screen",item)) {
11902           gmic_substitute_args(false);
11903           sepx = sepy = sepz = sepc = *argx = *argy = *argz = *argc = 0;
11904           if (cimg_sscanf(argument,"%255[0-9.eE%+-],%255[0-9.eE%+-],%255[0-9.eE%+-],%255[0-9.eE%+-]%c",
11905                           argx,argy,argz,argc,&end)==4 &&
11906               (cimg_sscanf(argx,"%lf%c",&value0,&end)==1 ||
11907                (cimg_sscanf(argx,"%lf%c%c",&value0,&sepx,&end)==2 && sepx=='%')) &&
11908               (cimg_sscanf(argy,"%lf%c",&value1,&end)==1 ||
11909                (cimg_sscanf(argy,"%lf%c%c",&value1,&sepy,&end)==2 && sepy=='%')) &&
11910               (cimg_sscanf(argz,"%lf%c",&nvalue0,&end)==1 ||
11911                (cimg_sscanf(argz,"%lf%c%c",&nvalue0,&sepz,&end)==2 && sepz=='%')) &&
11912               (cimg_sscanf(argc,"%lf%c",&nvalue1,&end)==1 ||
11913                (cimg_sscanf(argc,"%lf%c%c",&nvalue1,&sepc,&end)==2 && sepc=='%'))) {
11914             print(images,0,"Take screenshot, with coordinates (%s,%s) - (%s,%s).",
11915                   argx,argy,argz,argc);
11916             if (sepx=='%') value0 = value0*CImgDisplay::screen_width()/100;
11917             if (sepy=='%') value1 = value1*CImgDisplay::screen_height()/100;
11918             if (sepz=='%') nvalue0 = nvalue0*CImgDisplay::screen_width()/100;
11919             if (sepc=='%') nvalue1 = nvalue1*CImgDisplay::screen_height()/100;
11920             value0 = cimg::round(value0);
11921             value1 = cimg::round(value1);
11922             nvalue0 = cimg::round(nvalue0);
11923             nvalue1 = cimg::round(nvalue1);
11924             cimg_snprintf(title,_title.width(),"[Screenshot (%g,%g)-(%g,%g)]",
11925                           value0,value1,nvalue0,nvalue1);
11926             images.insert(1);
11927             CImgDisplay::screenshot((int)value0,(int)value1,(int)nvalue0,(int)nvalue1,images.back());
11928             ++position;
11929           } else {
11930             print(images,0,"Take screenshot.");
11931             cimg_snprintf(title,_title.width(),"[Screenshot]");
11932             images.insert(1);
11933             CImgDisplay::screenshot(images.back());
11934           }
11935           CImg<char>::string(title).move_to(images_names);
11936           is_change = true; continue;
11937         }
11938 
11939         // SVD.
11940         if (!std::strcmp("svd",command)) {
11941           print(images,0,"Compute SVD decomposition%s of matri%s%s.",
11942                 selection.height()>1?"s":"",selection.height()>1?"ce":"x",gmic_selection.data());
11943           CImg<float> U, S, V;
11944           unsigned int off = 0;
11945           cimg_forY(selection,l) {
11946             const unsigned int uind = selection[l] + off;
11947             const CImg<T>& img = gmic_check(images[uind]);
11948             name = images_names[uind];
11949             img.SVD(U,S,V,true,100);
11950             if (is_get) {
11951               images_names.insert(2,name.copymark());
11952               name.move_to(images_names);
11953               U.move_to(images);
11954               S.move_to(images);
11955               V.move_to(images);
11956             } else {
11957               images_names.insert(2,name.get_copymark(),uind + 1);
11958               name.move_to(images_names[uind]);
11959               U.move_to(images[uind].assign());
11960               images.insert(S,uind + 1);
11961               images.insert(V,uind + 2);
11962               off+=2;
11963             }
11964           }
11965           is_change = true; continue;
11966         }
11967 
11968         // Input 3D sphere.
11969         if (!is_get && !std::strcmp("sphere3d",item)) {
11970           gmic_substitute_args(false);
11971           float radius = 100, recursions = 3;
11972           if ((cimg_sscanf(argument,"%f%c",
11973                            &radius,&end)==1 ||
11974                cimg_sscanf(argument,"%f,%f%c",
11975                            &radius,&recursions,&end)==2) &&
11976               recursions>=0) {
11977             recursions = cimg::round(recursions);
11978             print(images,0,"Input 3D sphere, with radius %g and %g recursions.",
11979                   radius,
11980                   recursions);
11981             CImg<T>::sphere3d(primitives,radius,(unsigned int)recursions).move_to(vertices);
11982             vertices.object3dtoCImg3d(primitives,false).move_to(images);
11983             primitives.assign();
11984             CImg<char>::string("[3D sphere]").move_to(images_names);
11985           } else arg_error("sphere3d");
11986           is_change = true; ++position; continue;
11987         }
11988 
11989         // Set 3D specular light parameters.
11990         if (!is_get && !std::strcmp("specl3d",item)) {
11991           gmic_substitute_args(false);
11992           value = 0.15;
11993           if (cimg_sscanf(argument,"%lf%c",
11994                           &value,&end)==1 && value>=0) ++position;
11995           else value = 0.15;
11996           specular_lightness3d = (float)value;
11997           print(images,0,"Set lightness of 3D specular light to %g.",
11998                 specular_lightness3d);
11999           continue;
12000         }
12001 
12002         if (!is_get && !std::strcmp("specs3d",item)) {
12003           gmic_substitute_args(false);
12004           value = 0.8;
12005           if (cimg_sscanf(argument,"%lf%c",
12006                           &value,&end)==1 && value>=0) ++position;
12007           else value = 0.8;
12008           specular_shininess3d = (float)value;
12009           print(images,0,"Set shininess of 3D specular light to %g.",
12010                 specular_shininess3d);
12011           continue;
12012         }
12013 
12014         // Sine-cardinal.
12015         gmic_simple_command("sinc",sinc,"Compute pointwise sinc function of image%s.");
12016 
12017         // Hyperbolic sine.
12018         gmic_simple_command("sinh",sinh,"Compute pointwise hyperpolic sine of image%s.");
12019 
12020         // Extract 3D streamline.
12021         if (!std::strcmp("streamline3d",command)) {
12022           gmic_substitute_args(false);
12023           unsigned int is_backward = 0, is_oriented_only = 0;
12024           float x = 0, y = 0, z = 0, L = 100, dl = 0.1f;
12025           sepx = sepy = sepz = *formula = 0;
12026           interpolation = 2;
12027           if ((cimg_sscanf(argument,"%255[0-9.eE%+-],%255[0-9.eE%+-],%255[0-9.eE%+-]%c",
12028                            argx,argy,argz,&end)==3 ||
12029                cimg_sscanf(argument,"%255[0-9.eE%+-],%255[0-9.eE%+-],%255[0-9.eE%+-],%f%c",
12030                            argx,argy,argz,&L,&end)==4 ||
12031                cimg_sscanf(argument,"%255[0-9.eE%+-],%255[0-9.eE%+-],%255[0-9.eE%+-],%f,%f%c",
12032                            argx,argy,argz,&L,&dl,&end)==5 ||
12033                cimg_sscanf(argument,"%255[0-9.eE%+-],%255[0-9.eE%+-],%255[0-9.eE%+-],%f,%f,%u%c",
12034                            argx,argy,argz,&L,&dl,&interpolation,&end)==6 ||
12035                cimg_sscanf(argument,"%255[0-9.eE%+-],%255[0-9.eE%+-],%255[0-9.eE%+-],%f,%f,%u,"
12036                            "%u%c",
12037                            argx,argy,argz,&L,&dl,&interpolation,&is_backward,&end)==7 ||
12038                cimg_sscanf(argument,"%255[0-9.eE%+-],%255[0-9.eE%+-],%255[0-9.eE%+-],%f,%f,%u,"
12039                            "%u,%u%c",
12040                            argx,argy,argz,&L,&dl,&interpolation,&is_backward,
12041                            &is_oriented_only,&end)==8) &&
12042               (cimg_sscanf(argx,"%f%c",&x,&end)==1 ||
12043                (cimg_sscanf(argx,"%f%c%c",&x,&sepx,&end)==2 && sepx=='%')) &&
12044               (!*argy ||
12045                cimg_sscanf(argy,"%f%c",&y,&end)==1 ||
12046                (cimg_sscanf(argy,"%f%c%c",&y,&sepy,&end)==2 && sepy=='%')) &&
12047               (!*argz ||
12048                cimg_sscanf(argz,"%f%c",&z,&end)==1 ||
12049                (cimg_sscanf(argz,"%f%c%c",&z,&sepz,&end)==2 && sepz=='%')) &&
12050               L>=0 && dl>0 && interpolation<4 && is_backward<=1 && is_oriented_only<=1) {
12051             print(images,0,"Extract 3D streamline from image%s, starting from (%g%s,%g%s,%g%s).",
12052                   gmic_selection.data(),
12053                   x,sepx=='%'?"%":"",
12054                   y,sepy=='%'?"%":"",
12055                   z,sepz=='%'?"%":"");
12056             cimg_forY(selection,l) {
12057               const unsigned int uind = selection[l];
12058               CImg<T>& img = gmic_check(images[uind]);
12059               const float
12060                 nx = cimg::round(sepx=='%'?x*(img.width() - 1)/100:x),
12061                 ny = cimg::round(sepy=='%'?y*(img.height() - 1)/100:y),
12062                 nz = cimg::round(sepz=='%'?z*(img.depth() - 1)/100:z);
12063               img.get_streamline(nx,ny,nz,L,dl,interpolation,
12064                                  (bool)is_backward,(bool)is_oriented_only).move_to(vertices);
12065               if (vertices.width()>1) {
12066                 primitives.assign(vertices.width() - 1,1,2);
12067                 cimglist_for(primitives,q) { primitives(q,0) = (unsigned int)q; primitives(q,1) = q + 1U; }
12068                 g_list_uc.assign(primitives.size(),1,3,1,1,200);
12069               } else {
12070                 vertices.assign();
12071                 warn(images,0,false,
12072                      "Command 'streamline3d': Empty streamline starting from "
12073                      "(%g%s,%g%s,%g%s) in image [%u].",
12074                      x,sepx=='%'?"%":"",
12075                      y,sepy=='%'?"%":"",
12076                      z,sepz=='%'?"%":"",
12077                      uind);
12078               }
12079               vertices.object3dtoCImg3d(primitives,g_list_uc,false);
12080               gmic_apply(replace(vertices));
12081               primitives.assign();
12082               g_list_uc.assign();
12083             }
12084           } else if ((cimg_sscanf(argument,"'%4095[^']',%f,%f,%f%c",
12085                                   formula,&x,&y,&z,&end)==4 ||
12086                       cimg_sscanf(argument,"'%4095[^']',%f,%f,%f,%f%c",
12087                                   formula,&x,&y,&z,&L,&end)==5 ||
12088                       cimg_sscanf(argument,"'%4095[^']',%f,%f,%f,%f,%f%c",
12089                                   formula,&x,&y,&z,&L,&dl,&end)==6 ||
12090                       cimg_sscanf(argument,"'%4095[^']',%f,%f,%f,%f,%f,%u%c",
12091                                   formula,&x,&y,&z,&L,&dl,&interpolation,&end)==7 ||
12092                       cimg_sscanf(argument,"'%4095[^']',%f,%f,%f,%f,%f,%u,%u%c",
12093                                   formula,&x,&y,&z,&L,&dl,&interpolation,&is_backward,&end)==8 ||
12094                       cimg_sscanf(argument,"'%4095[^']',%f,%f,%f,%f,%f,%u,%u,%u%c",
12095                                   formula,&x,&y,&z,&L,&dl,&interpolation,&is_backward,
12096                                   &is_oriented_only,&end)==9) &&
12097                      dl>0 && interpolation<4) {
12098             strreplace_fw(formula);
12099             print(images,0,"Extract 3D streamline from formula '%s', starting from (%g,%g,%g).",
12100                   formula,
12101                   x,y,z);
12102             CImg<T>::streamline((const char *)formula,x,y,z,L,dl,interpolation,
12103                                 (bool)is_backward,(bool)is_oriented_only).move_to(vertices);
12104             if (vertices.width()>1) {
12105               primitives.assign(vertices.width() - 1,1,2);
12106               cimglist_for(primitives,l) { primitives(l,0) = (unsigned int)l; primitives(l,1) = l + 1U; }
12107               g_list_uc.assign(primitives.size(),1,3,1,1,200);
12108             } else {
12109               vertices.assign();
12110               warn(images,0,false,
12111                    "Command 'streamline3d': Empty streamline starting from (%g,%g,%g) "
12112                    "in expression '%s'.",
12113                    x,y,z,formula);
12114             }
12115             vertices.object3dtoCImg3d(primitives,g_list_uc,false).move_to(images);
12116             primitives.assign();
12117             g_list_uc.assign();
12118             cimg_snprintf(title,_title.width(),"[3D streamline of '%s' at (%g,%g,%g)]",
12119                           formula,x,y,z);
12120             CImg<char>::string(title).move_to(images_names);
12121           } else arg_error("streamline3d");
12122           is_change = true; ++position; continue;
12123         }
12124 
12125         // Compute structure tensor field.
12126         if (!std::strcmp("structuretensors",command)) {
12127           gmic_substitute_args(false);
12128           unsigned int is_fwbw_scheme = 0;
12129           if (cimg_sscanf(argument,"%u%c",&is_fwbw_scheme,&end)==1 &&
12130               is_fwbw_scheme<=1) ++position;
12131           else is_fwbw_scheme = 1;
12132           print(images,0,"Compute structure tensor field of image%s, with %s scheme.",
12133                 gmic_selection.data(),
12134                 is_fwbw_scheme==1?"forward-backward":"centered");
12135           cimg_forY(selection,l) gmic_apply(structure_tensors((bool)is_fwbw_scheme));
12136           is_change = true; continue;
12137         }
12138 
12139         // Select image feature.
12140         if (!std::strcmp("select",command)) {
12141           gmic_substitute_args(false);
12142           unsigned int feature_type = 0, is_deep_selection = 0;
12143           *argx = *argy = *argz = sep = sep0 = sep1 = 0;
12144           value = value0 = value1 = 0;
12145           exit_on_anykey = 0;
12146           if ((cimg_sscanf(argument,"%u%c",&feature_type,&end)==1 ||
12147                (cimg_sscanf(argument,"%u,%255[0-9.eE%+-]%c",
12148                             &feature_type,argx,&end)==2) ||
12149                (cimg_sscanf(argument,"%u,%255[0-9.eE%+-],%255[0-9.eE%+-]%c",
12150                             &feature_type,argx,argy,&end)==3) ||
12151                (cimg_sscanf(argument,"%u,%255[0-9.eE%+-],%255[0-9.eE%+-],%255[0-9.eE%+-]%c",
12152                             &feature_type,argx,argy,argz,&end)==4) ||
12153                (cimg_sscanf(argument,"%u,%255[0-9.eE%+-],%255[0-9.eE%+-],%255[0-9.eE%+-],%u%c",
12154                             &feature_type,argx,argy,argz,&exit_on_anykey,&end)==5) ||
12155                (cimg_sscanf(argument,"%u,%255[0-9.eE%+-],%255[0-9.eE%+-],%255[0-9.eE%+-],%u,%u%c",
12156                             &feature_type,argx,argy,argz,&exit_on_anykey,&is_deep_selection,&end)==6)) &&
12157               (!*argx ||
12158                cimg_sscanf(argx,"%lf%c",&value,&end)==1 ||
12159                (cimg_sscanf(argx,"%lf%c%c",&value,&sep,&end)==2 && sep=='%')) &&
12160               (!*argy ||
12161                cimg_sscanf(argy,"%lf%c",&value0,&end)==1 ||
12162                (cimg_sscanf(argy,"%lf%c%c",&value0,&sep0,&end)==2 && sep0=='%')) &&
12163               (!*argz ||
12164                cimg_sscanf(argz,"%lf%c",&value1,&end)==1 ||
12165                (cimg_sscanf(argz,"%lf%c%c",&value1,&sep1,&end)==2 && sep1=='%')) &&
12166               value>=0 && value0>=0 && value1>=0 && feature_type<=3 && exit_on_anykey<=1 && is_deep_selection<=1) {
12167             if (!*argx) { value = 50; sep = '%'; }
12168             if (!*argy) { value0 = 50; sep0 = '%'; }
12169             if (!*argz) { value1 = 50; sep1 = '%'; }
12170 
12171             if (!is_display_available) {
12172               print(images,0,
12173                     "Select %s in image%s in interactive mode, from point (%g%s,%g%s,%g%s) (skipped no display %s).",
12174                     feature_type==0?"point":feature_type==1?"segment":
12175                     feature_type==2?"rectangle":"ellipse",gmic_selection.data(),
12176                     value,sep=='%'?"%":"",value0,sep0=='%'?"%":"",value1,sep1=='%'?"%":"",
12177                     cimg_display?"available":"support");
12178             } else {
12179               print(images,0,"Select %s in image%s in interactive mode, from point (%g%s,%g%s,%g%s).",
12180                     feature_type==0?"point":feature_type==1?"segment":
12181                     feature_type==2?"rectangle":"ellipse",gmic_selection.data(),
12182                     value,sep=='%'?"%":"",value0,sep0=='%'?"%":"",value1,sep1=='%'?"%":"");
12183 
12184               unsigned int XYZ[3];
12185               cimg_forY(selection,l) {
12186                 CImg<T> &img = images[selection[l]];
12187                 XYZ[0] = (unsigned int)cimg::cut(cimg::round(sep=='%'?(img.width() - 1)*value/100:value),
12188                                                  0.,img.width() - 1.);
12189                 XYZ[1] = (unsigned int)cimg::cut(cimg::round(sep0=='%'?(img.height() - 1)*value0/100:value0),
12190                                                  0.,img.height() - 1.);
12191                 XYZ[2] = (unsigned int)cimg::cut(cimg::round(sep1=='%'?(img.depth() - 1)*value1/100:value1),
12192                                                  0.,img.depth() - 1.);
12193                 if (display_window(0)) {
12194                   gmic_apply(select(display_window(0),feature_type,XYZ,
12195                                     (bool)exit_on_anykey,is_deep_selection));
12196                 } else {
12197                   gmic_apply(select(images_names[selection[l]].data(),feature_type,XYZ,
12198                                     (bool)exit_on_anykey,is_deep_selection));
12199                 }
12200               }
12201             }
12202           } else arg_error("select");
12203           is_change = true; ++position; continue;
12204         }
12205 
12206         // Serialize.
12207         if (!std::strcmp("serialize",command)) {
12208 #define gmic_serialize(value_type,svalue_type) \
12209           if (!std::strcmp(argx,svalue_type)) \
12210             CImgList<value_type>(g_list,cimg::type<T>::string()==cimg::type<value_type>::string()). \
12211               get_serialize((bool)is_compressed).move_to(serialized);
12212 
12213           gmic_substitute_args(false);
12214 #ifdef cimg_use_zlib
12215           bool is_compressed0 = true;
12216 #else
12217           bool is_compressed0 = false;
12218 #endif
12219           unsigned int is_compressed = is_compressed0?1U:0U, is_gmz = 1;
12220           if ((cimg_sscanf(argument,"%255[a-z ]%c",
12221                            argx,&end)==1 ||
12222                cimg_sscanf(argument,"%255[a-z ],%u%c",
12223                            argx,&is_compressed,&end)==2 ||
12224                cimg_sscanf(argument,"%255[a-z ],%u,%u%c",
12225                            argx,&is_compressed,&is_gmz,&end)==3) &&
12226               (!std::strcmp(argx,"auto") ||
12227                !std::strcmp(argx,"uchar") || !std::strcmp(argx,"unsigned char") ||
12228                !std::strcmp(argx,"char") || !std::strcmp(argx,"ushort") ||
12229                !std::strcmp(argx,"unsigned short") || !std::strcmp(argx,"short") ||
12230                !std::strcmp(argx,"uint") || !std::strcmp(argx,"unsigned int") ||
12231                !std::strcmp(argx,"int") || !std::strcmp(argx,"uint64") ||
12232                !std::strcmp(argx,"unsigned int64") || !std::strcmp(argx,"int64") ||
12233                !std::strcmp(argx,"float") || !std::strcmp(argx,"double")) &&
12234               is_compressed<=1 && is_gmz<=1) ++position;
12235           else { std::strcpy(argx,"auto"); is_compressed = is_compressed0?1U:0U; is_gmz = 1; }
12236 
12237           print(images,0,
12238                 "Serialize %simage%s as %s%scompressed buffer%s, with datatype '%s'.",
12239                 is_gmz?"named ":"",
12240                 gmic_selection.data(),
12241                 selection.height()>1?"":"a ",
12242                 is_compressed?"":"un",
12243                 selection.height()>1?"s":"",
12244                 argx);
12245 
12246           if (selection) {
12247             g_list.assign(selection.height());
12248             CImgList<unsigned char> gmz_info;
12249             if (is_gmz) {
12250               gmz_info.assign(1 + selection.height());
12251               CImg<char>::string("GMZ").move_to(gmz_info[0]);
12252             }
12253             cimg_forY(selection,l) {
12254               const unsigned int uind = selection[l];
12255               g_list[l].assign(images[uind],images[uind]?true:false);
12256               if (is_gmz) CImg<char>::string(images_names[uind]).move_to(gmz_info[1 + l]);
12257             }
12258             if (is_gmz) (gmz_info>'x').unroll('y').move_to(g_list);
12259             if (!std::strcmp(argx,"auto")) std::strcpy(argx,CImg<T>::storage_type(g_list));
12260 
12261             CImg<T> serialized;
12262             gmic_serialize(unsigned char,"uchar")
12263             else gmic_serialize(unsigned char,"unsigned char")
12264               else gmic_serialize(char,"char")
12265                 else gmic_serialize(unsigned short,"ushort")
12266                   else gmic_serialize(unsigned short,"unsigned short")
12267                     else gmic_serialize(short,"short")
12268                       else gmic_serialize(unsigned int,"uint")
12269                         else gmic_serialize(unsigned int,"unsigned int")
12270                           else gmic_serialize(int,"int")
12271                             else gmic_serialize(uint64T,"uint64")
12272                               else gmic_serialize(uint64T,"unsigned int64")
12273                                 else gmic_serialize(int64T,"int64")
12274                                   else gmic_serialize(float,"float")
12275                                     else gmic_serialize(double,"double")
12276                                       else error(true,images,0,0,
12277                                                  "Command 'serialize': Invalid "
12278                                                  "specified pixel type '%s'.",
12279                                                  argx);
12280             if (is_get) {
12281               serialized.move_to(images);
12282               images_names[selection[0]].get_copymark().move_to(images_names);
12283             } else {
12284               remove_images(images,images_names,selection,1,selection.height() - 1);
12285               serialized.move_to(images[selection[0]].assign());
12286             }
12287             g_list.assign();
12288           }
12289           is_change = true; continue;
12290         }
12291 
12292         goto gmic_commands_others;
12293 
12294         //-----------------------------
12295         // Commands starting by 't...'
12296         //-----------------------------
12297       gmic_commands_t :
12298 
12299         // Tangent.
12300         gmic_simple_command("tan",tan,"Compute pointwise tangent of image%s.");
12301 
12302         // Draw text.
12303         if (!std::strcmp("text",command)) {
12304           gmic_substitute_args(false);
12305           name.assign(4096);
12306           *argx = *argy = *argz = *name = *color = 0;
12307           float x = 0, y = 0, height = 16;
12308           sep = sepx = sepy = sep0 = 0;
12309           opacity = 1;
12310           if ((cimg_sscanf(argument,"%4095[^,]%c",
12311                            name.data(),&end)==1 ||
12312                cimg_sscanf(argument,"%4095[^,],%255[0-9.eE%~+-]%c",
12313                            name.data(),argx,&end)==2 ||
12314                cimg_sscanf(argument,"%4095[^,],%255[0-9.eE%~+-],%255[0-9.eE%~+-]%c",
12315                            name.data(),argx,argy,&end)==3 ||
12316                cimg_sscanf(argument,"%4095[^,],%255[0-9.eE%~+-],%255[0-9.eE%~+-],%255[0-9.eE%+-]%c",
12317                            name.data(),argx,argy,argz,&end)==4 ||
12318                cimg_sscanf(argument,"%4095[^,],%255[0-9.eE%~+-],%255[0-9.eE%~+-],%255[0-9.eE%+-],%f%c",
12319                            name.data(),argx,argy,argz,&opacity,&end)==5 ||
12320                cimg_sscanf(argument,"%4095[^,],%255[0-9.eE%~+-],%255[0-9.eE%~+-],%255[0-9.eE%+-],%f,"
12321                            "%4095[0-9.eEinfa,+-]%c",
12322                            name.data(),argx,argy,argz,&opacity,color,&end)==6) &&
12323               (!*argx ||
12324                cimg_sscanf(argx,"%f%c",&x,&end)==1 ||
12325                (cimg_sscanf(argx,"%f%c%c",&x,&sepx,&end)==2 && (sepx=='%' || sepx=='~'))) &&
12326               (!*argy ||
12327                cimg_sscanf(argy,"%f%c",&y,&end)==1 ||
12328                (cimg_sscanf(argy,"%f%c%c",&y,&sepy,&end)==2 && (sepy=='%' || sepy=='~'))) &&
12329               (!*argz ||
12330                cimg_sscanf(argz,"%f%c",&height,&end)==1 ||
12331                (cimg_sscanf(argz,"%f%c%c",&height,&sep,&end)==2 && sep=='%')) &&
12332               height>=0) {
12333             strreplace_fw(name);
12334             print(images,0,"Draw text '%s' at position (%g%s,%g%s) on image%s, with font "
12335                   "height %s, opacity %g and color (%s).",
12336                   name.data(),
12337                   x,sepx=='%'?"%":sepx=='~'?"~":"",
12338                   y,sepy=='%'?"%":sepy=='~'?"~":"",
12339                   gmic_selection.data(),
12340                   argz,opacity,
12341                   *color?color:"default");
12342             cimg::strunescape(name);
12343             unsigned int nb_cols = 1;
12344             for (const char *s = color; *s; ++s) if (*s==',') ++nb_cols;
12345             cimg_forY(selection,l) {
12346               CImg<T> &img = images[selection[l]];
12347               const unsigned int font_height = (unsigned int)cimg::round(sep=='%'?
12348                                                                          height*img.height()/100:height);
12349               g_img.assign(std::max(img.spectrum(),(int)nb_cols),1,1,1,(T)0).fill(color,true,false);
12350               gmic_apply(gmic_draw_text(x,y,sepx,sepy,name,g_img,0,opacity,font_height,nb_cols));
12351             }
12352           } else arg_error("text");
12353           g_img.assign();
12354           is_change = true; ++position; continue;
12355         }
12356 
12357         // Tridiagonal solve.
12358         if (!std::strcmp("trisolve",command)) {
12359           gmic_substitute_args(true);
12360           sep = *indices = 0;
12361           if (cimg_sscanf(argument,"[%255[a-zA-Z0-9_.%+-]%c%c",indices,&sep,&end)==2 &&
12362               sep==']' &&
12363               (ind=selection2cimg(indices,images.size(),images_names,"trisolve")).height()==1) {
12364             print(images,0,"Solve tridiagonal system AX = B, with B-vector%s and tridiagonal "
12365                   "A-matrix [%d].",
12366                   gmic_selection.data(),*ind);
12367             const CImg<T> A = gmic_image_arg(*ind);
12368             cimg_forY(selection,l) gmic_apply(solve_tridiagonal(A));
12369           } else arg_error("trisolve");
12370           is_change = true; ++position; continue;
12371         }
12372 
12373         // Hyperbolic tangent.
12374         gmic_simple_command("tanh",tanh,"Compute pointwise hyperbolic tangent of image%s.");
12375 
12376         goto gmic_commands_others;
12377 
12378         //-----------------------------
12379         // Commands starting by 'u...'
12380         //-----------------------------
12381       gmic_commands_u :
12382 
12383         // Unroll.
12384         if (!std::strcmp("unroll",command)) {
12385           gmic_substitute_args(false);
12386           axis = 'y';
12387           if ((*argument=='x' || *argument=='y' ||
12388                *argument=='z' || *argument=='c') && !argument[1]) {
12389             axis = *argument;
12390             ++position;
12391           }
12392           print(images,0,"Unroll image%s along the '%c'-axis.",
12393                 gmic_selection.data(),
12394                 axis);
12395           cimg_forY(selection,l) gmic_apply(unroll(axis));
12396           is_change = true; continue;
12397         }
12398 
12399         // Remove custom command.
12400         if (!is_get && !std::strcmp("uncommand",item)) {
12401           gmic_substitute_args(false);
12402           if (argument[0]=='*' && !argument[1]) { // Discard all custom commands
12403             cimg::mutex(23);
12404             unsigned int nb_commands = 0;
12405             for (unsigned int i = 0; i<gmic_comslots; ++i) {
12406               nb_commands+=commands[i].size();
12407               commands[i].assign();
12408               commands_names[i].assign();
12409               commands_has_arguments[i].assign();
12410             }
12411             print(images,0,"Discard definitions of all custom commands (%u command%s).",
12412                   nb_commands,nb_commands>1?"s":"");
12413             cimg::mutex(23,0);
12414           } else { // Discard one or several custom command
12415             cimg::mutex(23);
12416             g_list_c = CImg<char>::string(argument).get_split(CImg<char>::vector(','),0,false);
12417             print(images,0,"Discard definition%s of custom command%s '%s'",
12418                   g_list_c.width()>1?"s":"",
12419                   g_list_c.width()>1?"s":"",
12420                   gmic_argument_text_printed());
12421             unsigned int nb_removed = 0;
12422             cimglist_for(g_list_c,l) {
12423               CImg<char>& arg_command = g_list_c[l];
12424               arg_command.resize(1,arg_command.height() + 1,1,1,0);
12425               strreplace_fw(arg_command);
12426               if (*arg_command) {
12427                 hash = hashcode(arg_command,false);
12428                 if (search_sorted(arg_command,commands_names[hash],commands_names[hash].size(),pattern)) {
12429                   commands_names[hash].remove(pattern);
12430                   commands[hash].remove(pattern);
12431                   commands_has_arguments[hash].remove(pattern);
12432                   ++nb_removed;
12433                 }
12434               }
12435             }
12436             if (is_verbose) {
12437               cimg::mutex(29);
12438               unsigned int isiz = 0;
12439               for (unsigned int l = 0; l<gmic_comslots; ++l) isiz+=commands[l].size();
12440               std::fprintf(cimg::output()," (%u found, %u command%s left).",
12441                            nb_removed,isiz,isiz>1?"s":"");
12442               std::fflush(cimg::output());
12443               cimg::mutex(29,0);
12444             }
12445             g_list_c.assign();
12446             cimg::mutex(23,0);
12447           }
12448           ++position; continue;
12449         }
12450 
12451         // Unserialize.
12452         if (!std::strcmp("unserialize",command)) {
12453           print(images,0,"Unserialize image%s.",
12454                 gmic_selection.data());
12455           int off = 0;
12456           cimg_forY(selection,l) {
12457             const unsigned int uind = selection[l] + off;
12458             const CImg<T>& img = gmic_check(images[uind]);
12459             g_list = CImgList<T>::get_unserialize(img);
12460             if (g_list) {
12461               const CImg<T>& back = g_list.back();
12462               if (back.width()==1 && back.depth()==1 && back.spectrum()==1 &&
12463                   back[0]=='G' && back[1]=='M' && back[2]=='Z' && !back[3]) { // .gmz serialization
12464                 g_list_c = back.get_split(CImg<char>::vector(0),0,false);
12465                 g_list_c.remove(0);
12466                 cimglist_for(g_list_c,q)
12467                   g_list_c[q].resize(1,g_list_c[q].height() + 1,1,1,0).unroll('x');
12468                 if (g_list_c) g_list.remove();
12469               } else { // .cimg[z] serialization
12470                 g_list_c.insert(images_names[uind]);
12471                 g_list_c.insert(g_list.width() - 1,images_names[uind].get_copymark());
12472               }
12473               if (g_list_c.width()>g_list.width())
12474                 g_list_c.remove(g_list.width(),g_list_c.width() - 1);
12475               else if (g_list_c.width()<g_list.width())
12476                 g_list_c.insert(g_list.width() - g_list_c.width(),CImg<char>::string("[unnamed]"));
12477               if (is_get) {
12478                 g_list.move_to(images,~0U);
12479                 g_list_c.move_to(images_names,~0U);
12480               } else {
12481                 images.remove(uind); images_names.remove(uind);
12482                 off+=(int)g_list.size() - 1;
12483                 g_list.move_to(images,uind);
12484                 g_list_c.move_to(images_names,uind);
12485               }
12486             }
12487           }
12488           g_list.assign(); g_list_c.assign();
12489           is_change = true; continue;
12490         }
12491 
12492         goto gmic_commands_others;
12493 
12494         //-----------------------------
12495         // Commands starting by 'v...'
12496         //-----------------------------
12497       gmic_commands_v :
12498 
12499         // Set verbosity
12500         // (actually only display a log message, since it has been already processed before).
12501         if (is_command_verbose) {
12502           if (*argument=='-' && !argument[1])
12503             print(images,0,"Decrement verbosity level (set to %d).",
12504                   verbosity);
12505           else if (*argument=='+' && !argument[1]) {
12506             if (is_very_verbose) print(images,0,"Increment verbosity level (set to %d).",
12507                                        verbosity);
12508           } else if ((verbosity>=1 && old_verbosity>=1) || is_debug)
12509             print(images,0,"Set verbosity level to %d.",
12510                   verbosity);
12511           if (is_verbose_argument) ++position;
12512           continue;
12513         }
12514 
12515         // Vanvliet filter.
12516         if (!std::strcmp("vanvliet",command)) {
12517           gmic_substitute_args(false);
12518           unsigned int order = 0;
12519           float sigma = 0;
12520           axis = sep = 0;
12521           boundary = 1;
12522           if ((cimg_sscanf(argument,"%f,%u,%c%c",&sigma,&order,&axis,&end)==3 ||
12523                (cimg_sscanf(argument,"%f%c,%u,%c%c",&sigma,&sep,&order,&axis,&end)==4 &&
12524                 sep=='%') ||
12525                cimg_sscanf(argument,"%f,%u,%c,%u%c",&sigma,&order,&axis,&boundary,&end)==4 ||
12526                (cimg_sscanf(argument,"%f%c,%u,%c,%u%c",
12527                             &sigma,&sep,&order,&axis,&boundary,&end)==5 && sep=='%')) &&
12528               sigma>=0 && order<=3 && (axis=='x' || axis=='y' || axis=='z' || axis=='c') &&
12529               boundary<=1) {
12530             print(images,0,"Apply %u-order Vanvliet filter on image%s, along axis '%c' with standard "
12531                   "deviation %g%s and %s boundary conditions.",
12532                   order,gmic_selection.data(),axis,
12533                   sigma,sep=='%'?"%":"",
12534                   boundary?"neumann":"dirichlet");
12535             if (sep=='%') sigma = -sigma;
12536             cimg_forY(selection,l) gmic_apply(vanvliet(sigma,order,axis,(bool)boundary));
12537           } else arg_error("vanvliet");
12538           is_change = true; ++position; continue;
12539         }
12540 
12541         goto gmic_commands_others;
12542 
12543         //-----------------------------
12544         // Commands starting by 'w...'
12545         //-----------------------------
12546       gmic_commands_w :
12547 
12548         // While.
12549         if (!is_get && !std::strcmp("while",item)) {
12550           gmic_substitute_args(false);
12551           const CImg<char>& s = callstack.back();
12552           if (s[0]!='*' || s[1]!='d')
12553             error(true,images,0,0,
12554                   "Command 'while': Not associated to a 'do' command within the same scope.");
12555           is_cond = check_cond(argument,images,"while");
12556           if (is_very_verbose)
12557             print(images,0,"Reach 'while' command -> condition '%s' %s.",
12558                   gmic_argument_text_printed(),
12559                   is_cond?"holds":"does not hold");
12560           if (is_cond) {
12561             position = dowhiles[nb_dowhiles - 1];
12562             next_debug_line = debug_line; next_debug_filename = debug_filename;
12563             continue;
12564           } else {
12565             if (is_very_verbose) print(images,0,"End 'do...while' block.");
12566             --nb_dowhiles;
12567             callstack.remove();
12568           }
12569           ++position; continue;
12570         }
12571 
12572         // Warning.
12573         if (is_command_warn) {
12574           gmic_substitute_args(false);
12575           bool force_visible = false;
12576           if ((*argument=='0' || *argument=='1') && argument[1]==',') {
12577             force_visible = *argument=='1'; argument+=2;
12578           }
12579           name.assign(argument,(unsigned int)std::strlen(argument) + 1);
12580           cimg::strunescape(name);
12581           ++verbosity;
12582           if (is_selection) warn(images,&selection,force_visible,"%s",name.data());
12583           else warn(images,0,force_visible,"%s",name.data());
12584           --verbosity;
12585           ++position; continue;
12586         }
12587 
12588         // Display images in display window.
12589         wind = 0;
12590         if (!is_get &&
12591             (!std::strcmp("window",command) ||
12592              cimg_sscanf(command,"window%u%c",&wind,&end)==1 ||
12593              cimg_sscanf(command,"w%u%c",&wind,&end)==1) &&
12594             wind<10) {
12595           gmic_substitute_args(false);
12596           int norm = -1, fullscreen = -1;
12597           float dimw = -1, dimh = -1, posx = cimg::type<float>::inf(), posy = cimg::type<float>::inf();
12598           sep0 = sep1 = sepx = sepy = *argx = *argy = *argz = *argc = *title = 0;
12599           if ((cimg_sscanf(argument,"%255[0-9.eE%+-]%c",
12600                            argx,&end)==1 ||
12601                cimg_sscanf(argument,"%255[0-9.eE%+-],%255[0-9.eE%+-]%c",
12602                            argx,argy,&end)==2 ||
12603                cimg_sscanf(argument,"%255[0-9.eE%+-],%255[0-9.eE%+-],%d%c",
12604                            argx,argy,&norm,&end)==3 ||
12605                cimg_sscanf(argument,"%255[0-9.eE%+-],%255[0-9.eE%+-],%d,%d%c",
12606                            argx,argy,&norm,&fullscreen,&end)==4 ||
12607                cimg_sscanf(argument,
12608                            "%255[0-9.eE%+-],%255[0-9.eE%+-],%d,%d,%255[0-9.eE%+-],"
12609                            "%255[0-9.eE%+-]%c",
12610                            argx,argy,&norm,&fullscreen,argz,argc,&end)==6 ||
12611                cimg_sscanf(argument,
12612                            "%255[0-9.eE%+-],%255[0-9.eE%+-],%d,%d,%255[0-9.eE%+-],"
12613                            "%255[0-9.eE%+-],%255[^\n]",
12614                            argx,argy,&norm,&fullscreen,argz,argc,title)==7 ||
12615                cimg_sscanf(argument,"%255[0-9.eE%+-],%255[0-9.eE%+-],%d,%d,%255[^\n]",
12616                            &(*argx=*argz=*argc=0),argy,&norm,&fullscreen,title)==5 ||
12617                cimg_sscanf(argument,"%255[0-9.eE%+-],%255[0-9.eE%+-],%d,%255[^\n]",
12618                            argx,argy,&(norm=fullscreen=-1),title)==4 ||
12619                (norm=fullscreen=-1,cimg_sscanf(argument,
12620                                                "%255[0-9.eE%+-],%255[0-9.eE%+-],%255[^\n]",
12621                                                argx,argy,title))==3) &&
12622               (cimg_sscanf(argx,"%f%c",&dimw,&end)==1 ||
12623                (cimg_sscanf(argx,"%f%c%c",&dimw,&sep0,&end)==2 && sep0=='%')) &&
12624               (!*argy ||
12625                cimg_sscanf(argy,"%f%c",&dimh,&end)==1 ||
12626                (cimg_sscanf(argy,"%f%c%c",&dimh,&sep1,&end)==2 && sep1=='%')) &&
12627               (!*argz ||
12628                cimg_sscanf(argz,"%f%c",&posx,&end)==1 ||
12629                (cimg_sscanf(argz,"%f%c%c",&posx,&sepx,&end)==2 && sepx=='%')) &&
12630               (!*argc ||
12631                cimg_sscanf(argc,"%f%c",&posy,&end)==1 ||
12632                (cimg_sscanf(argc,"%f%c%c",&posy,&sepy,&end)==2 && sepy=='%')) &&
12633               (dimw>=0 || dimw==-1) &&
12634               (dimh>=0 || dimh==-1) &&
12635               norm>=-1 && norm<=3) ++position;
12636           else {
12637             dimw = dimh = -1;
12638             norm = fullscreen = -1;
12639             posx = posy = cimg::type<float>::inf();
12640             sep0 = sep1 = 0;
12641           }
12642           if (dimw==0 || dimh==0) dimw = dimh = 0;
12643           if (*title) { strreplace_fw(title); cimg::strunescape(title); }
12644 
12645           if (!is_display_available) {
12646             print(images,0,
12647                   "Display image%s in display window [%d] (skipped, no display %s).",
12648                   gmic_selection.data(),
12649                   wind,cimg_display?"available":"support");
12650           } else {
12651 
12652             // Get images to display and compute associated optimal size.
12653             unsigned int optw = 0, opth = 0;
12654             if (dimw && dimh) cimg_forY(selection,l) {
12655                 const CImg<T>& img = gmic_check(images[selection[l]]);
12656                 if (img) {
12657                   g_list.insert(img,~0U,true);
12658                   optw+=img._width + (img.depth()>1?img._depth:0U);
12659                   if (img.height()>(int)opth) opth = img._height + (img._depth>1?img._depth:0U);
12660                 }
12661               }
12662             optw = optw?optw:sep0=='%'?CImgDisplay::screen_width():256;
12663             opth = opth?opth:sep1=='%'?CImgDisplay::screen_height():256;
12664             dimw = dimw<0?-1:cimg::round(sep0=='%'?optw*dimw/100:dimw);
12665             dimh = dimh<0?-1:cimg::round(sep1=='%'?opth*dimh/100:dimh);
12666 
12667             const bool is_move = !cimg::type<float>::is_inf(posx) && !cimg::type<float>::is_inf(posy);
12668             CImgDisplay &disp = display_window(wind);
12669 
12670             if (!dimw || !dimh) { // Close
12671               print(images,0,"Close display window [%d].",
12672                     wind);
12673               disp.assign();
12674             } else {
12675               if (disp) { // Update
12676                 if (!selection) disp.show();
12677                 disp.resize(dimw>0?(int)dimw:disp.window_width(),
12678                             dimh>0?(int)dimh:disp.window_height(),
12679                             false);
12680                 if (is_move) {
12681                   if (sepx=='%') posx*=(CImgDisplay::screen_width() - disp.window_width())/100.f;
12682                   if (sepy=='%') posy*=(CImgDisplay::screen_height() - disp.window_height())/100.f;
12683                   disp.move((int)posx,(int)posy);
12684                 }
12685                 if (norm>=0) disp._normalization = (unsigned int)norm;
12686                 if (*title && std::strcmp(disp.title(),title)) disp.set_title("%s",title);
12687                 if (fullscreen>=0 && (bool)fullscreen!=disp.is_fullscreen()) disp.toggle_fullscreen(false);
12688               } else { // Create
12689                 if (!*title) cimg_snprintf(title,_title.width(),"[G'MIC] Window #%u",wind);
12690                 disp.assign(dimw>0?(int)dimw:optw,
12691                             dimh>0?(int)dimh:opth,
12692                             title,norm<0?3:norm,
12693                             fullscreen<0?false:(bool)fullscreen,
12694                             is_move);
12695                 if (is_move) {
12696                   if (sepx=='%') posx*=(CImgDisplay::screen_width() - disp.window_width())/100.f;
12697                   if (sepy=='%') posy*=(CImgDisplay::screen_height() - disp.window_height())/100.f;
12698                   disp.move((int)posx,(int)posy);
12699                 }
12700                 if (norm==2) {
12701                   if (g_list) disp._max = (float)g_list.max_min(disp._min);
12702                   else { disp._min = 0; disp._max = 255; }
12703                 }
12704               }
12705               if (is_move)
12706                 print(images,0,
12707                       "Display image%s in %dx%d %sdisplay window [%d], "
12708                       "with%snormalization, "
12709                       "%sfullscreen, at position (%s,%s) and title '%s'.",
12710                       gmic_selection.data(),
12711                       disp.width(),disp.height(),disp.is_fullscreen()?"fullscreen ":"",
12712                       wind,
12713                       disp.normalization()==0?"out ":disp.normalization()==1?" ":
12714                       disp.normalization()==2?" 1st-time ":" auto-",
12715                       disp.is_fullscreen()?"":"no ",
12716                       argz,argc,
12717                       disp.title());
12718               else
12719                 print(images,0,
12720                       "Display image%s in %dx%d %sdisplay window [%d], with%snormalization, "
12721                       "%sfullscreen and title '%s'.",
12722                       gmic_selection.data(),
12723                       disp.width(),disp.height(),disp.is_fullscreen()?"fullscreen ":"",
12724                       wind,
12725                       disp.normalization()==0?"out ":disp.normalization()==1?" ":
12726                       disp.normalization()==2?" 1st-time ":" auto-",
12727                       disp.is_fullscreen()?"":"no ",
12728                       disp.title());
12729               if (g_list) { g_list.display(disp); is_change = false; }
12730             }
12731             g_list.assign();
12732           }
12733           continue;
12734         }
12735 
12736         // Warp.
12737         if (!std::strcmp("warp",command)) {
12738           gmic_substitute_args(true);
12739           unsigned int mode = 0;
12740           float nb_frames = 1;
12741           interpolation = 1;
12742           boundary = 1;
12743           sep = 0;
12744           if (((cimg_sscanf(argument,"[%255[a-zA-Z0-9_.%+-]%c%c",
12745                             indices,&sep,&end)==2 && sep==']')||
12746                cimg_sscanf(argument,"[%255[a-zA-Z0-9_.%+-]],%u%c",
12747                            indices,&mode,&end)==2 ||
12748                cimg_sscanf(argument,"[%255[a-zA-Z0-9_.%+-]],%u,%u%c",
12749                            indices,&mode,&interpolation,&end)==3 ||
12750                cimg_sscanf(argument,"[%255[a-zA-Z0-9_.%+-]],%u,%u,%u%c",
12751                            indices,&mode,&interpolation,&boundary,&end)==4 ||
12752                cimg_sscanf(argument,"[%255[a-zA-Z0-9_.%+-]],%u,%u,%u,%f%c",
12753                            indices,&mode,&interpolation,&boundary,&nb_frames,&end)==5) &&
12754               (ind=selection2cimg(indices,images.size(),images_names,"warp")).height()==1 &&
12755               mode<=3 && interpolation<=2 && boundary<=3 && nb_frames>=0.5) {
12756             const CImg<T> warping_field = gmic_image_arg(*ind);
12757             nb_frames = cimg::round(nb_frames);
12758             if (nb_frames==1) {
12759               print(images,0,"Warp image%s with %s-%s displacement field [%u], %s interpolation, "
12760                     "%s boundary conditions.",
12761                     gmic_selection.data(),
12762                     mode<=2?"backward":"forward",(mode%2)?"relative":"absolute",
12763                     *ind,
12764                     interpolation==2?"cubic":interpolation==1?"linear":"nearest-neighbor",
12765                     boundary==0?"dirichlet":boundary==1?"neumann":boundary==2?"periodic":"mirror");
12766               cimg_forY(selection,l) gmic_apply(warp(warping_field,mode,interpolation,boundary));
12767             } else {
12768               print(images,0,"Warp image%s with %s-%s displacement field [%u], %s interpolation, "
12769                     "%s boundary conditions and %d frames.",
12770                     gmic_selection.data(),
12771                     mode<=2?"backward":"forward",(mode%2)?"relative":"absolute",
12772                     *ind,
12773                     interpolation==2?"cubic":interpolation==1?"linear":"nearest-neighbor",
12774                     boundary==0?"dirichlet":boundary==1?"neumann":boundary==2?"periodic":"mirror",
12775                     (int)nb_frames);
12776               unsigned int off = 0;
12777               CImg<T> _warp;
12778               if (!(mode%2)) _warp.assign(warping_field,false);
12779 
12780               cimg_forY(selection,l) {
12781                 const unsigned int _ind = selection[l] + off;
12782                 CImg<T>& img = gmic_check(images[_ind]);
12783                 g_list.assign((int)nb_frames);
12784                 name = images_names[_ind];
12785                 cimglist_for(g_list,t)
12786                   if (mode%2) g_list[t] = img.get_warp(warping_field*(t/(nb_frames - 1)),mode,interpolation,boundary);
12787                   else {
12788                     cimg_forXYZ(_warp,x,y,z) {
12789                       const float fact = t/(nb_frames - 1);
12790                       if (_warp.spectrum()>0) _warp(x,y,z,0) = x + (warping_field(x,y,z,0) - x)*fact;
12791                       if (_warp.spectrum()>1) _warp(x,y,z,1) = y + (warping_field(x,y,z,1) - y)*fact;
12792                       if (_warp.spectrum()>2) _warp(x,y,z,2) = z + (warping_field(x,y,z,2) - z)*fact;
12793                     }
12794                     g_list[t] = img.get_warp(_warp,mode,interpolation,boundary);
12795                   }
12796                 if (is_get) {
12797                   images_names.insert((int)nb_frames,name.copymark());
12798                   g_list.move_to(images,~0U);
12799                 } else {
12800                   off+=(int)nb_frames - 1;
12801                   images_names.insert((int)nb_frames - 1,name.get_copymark(),_ind);
12802                   images.remove(_ind); g_list.move_to(images,_ind);
12803                 }
12804               }
12805               g_list.assign();
12806             }
12807           } else arg_error("warp");
12808           is_change = true; ++position; continue;
12809         }
12810 
12811         // Watershed transform.
12812         if (!std::strcmp("watershed",command)) {
12813           gmic_substitute_args(true);
12814           is_high_connectivity = 1;
12815           sep = *indices = 0;
12816           if (((cimg_sscanf(argument,"[%255[a-zA-Z0-9_.%+-]%c%c",indices,&sep,&end)==2 &&
12817                 sep==']') ||
12818                cimg_sscanf(argument,"[%255[a-zA-Z0-9_.%+-]],%u%c",
12819                            indices,&is_high_connectivity,&end)==2) &&
12820               (ind=selection2cimg(indices,images.size(),images_names,"watershed")).height()==1 &&
12821               is_high_connectivity<=1) {
12822             print(images,0,"Compute watershed transform of image%s with priority map [%u] and "
12823                   "%s connectivity.",
12824                   gmic_selection.data(),*ind,is_high_connectivity?"high":"low");
12825             const CImg<T> priority = gmic_image_arg(*ind);
12826             cimg_forY(selection,l) gmic_apply(watershed(priority,(bool)is_high_connectivity));
12827           } else arg_error("watershed");
12828           is_change = true; ++position; continue;
12829         }
12830 
12831         // Wait for a given delay of for user events on display window.
12832         if (!is_get && !std::strcmp("wait",command)) {
12833           gmic_substitute_args(false);
12834           if (!is_selection)
12835             CImg<unsigned int>::vector(0,1,2,3,4,5,6,7,8,9).move_to(selection);
12836           float delay = 0;
12837           if (cimg_sscanf(argument,"%f%c",
12838                           &delay,&end)==1) ++position;
12839           else delay = 0;
12840 
12841           if (!is_display_available) {
12842             if (!delay)
12843               print(images,0,
12844                     "Wait for user events on display window%s (skipped, no display support).",
12845                     gmic_selection.data());
12846             else {
12847               delay = cimg::round(delay);
12848               print(images,0,
12849                     "%s for %g milliseconds according to display window%s.",
12850                     delay<0?"Sleep":"Wait",delay,
12851                     gmic_selection.data());
12852               if (delay<0) cimg::sleep((unsigned int)-delay);
12853               else cimg::wait((unsigned int)delay);
12854             }
12855           } else {
12856             if (!delay) {
12857               print(images,0,"Wait for user events on display window%s.",
12858                     gmic_selection.data());
12859               switch (selection.height()) {
12860               case 1 : CImgDisplay::wait(display_window(selection[0])); break;
12861               case 2 : CImgDisplay::wait(display_window(selection[0]),display_window(selection[1])); break;
12862               case 3 : CImgDisplay::wait(display_window(selection[0]),display_window(selection[1]),
12863                                          display_window(selection[2]));
12864                 break;
12865               case 4 : CImgDisplay::wait(display_window(selection[0]),display_window(selection[1]),
12866                                          display_window(selection[2]),display_window(selection[3]));
12867                 break;
12868               case 5 : CImgDisplay::wait(display_window(selection[0]),display_window(selection[1]),
12869                                          display_window(selection[2]),display_window(selection[3]),
12870                                          display_window(selection[4]));
12871                 break;
12872               case 6 : CImgDisplay::wait(display_window(selection[0]),display_window(selection[1]),
12873                                          display_window(selection[2]),display_window(selection[3]),
12874                                          display_window(selection[4]),display_window(selection[5]));
12875                 break;
12876               case 7 : CImgDisplay::wait(display_window(selection[0]),display_window(selection[1]),
12877                                          display_window(selection[2]),display_window(selection[3]),
12878                                          display_window(selection[4]),display_window(selection[5]),
12879                                          display_window(selection[6]));
12880                 break;
12881               case 8 : CImgDisplay::wait(display_window(selection[0]),display_window(selection[1]),
12882                                          display_window(selection[2]),display_window(selection[3]),
12883                                          display_window(selection[4]),display_window(selection[5]),
12884                                          display_window(selection[6]),display_window(selection[7]));
12885                 break;
12886               case 9 : CImgDisplay::wait(display_window(selection[0]),display_window(selection[1]),
12887                                          display_window(selection[2]),display_window(selection[3]),
12888                                          display_window(selection[4]),display_window(selection[5]),
12889                                          display_window(selection[6]),display_window(selection[7]),
12890                                          display_window(selection[8]));
12891                 break;
12892               case 10 : CImgDisplay::wait(display_window(selection[0]),display_window(selection[1]),
12893                                           display_window(selection[2]),display_window(selection[3]),
12894                                           display_window(selection[4]),display_window(selection[5]),
12895                                           display_window(selection[6]),display_window(selection[7]),
12896                                           display_window(selection[8]),display_window(selection[9]));
12897                 break;
12898               }
12899             } else if (delay<0) {
12900               delay = cimg::round(-delay);
12901               print(images,0,
12902                     "Flush display events of display window%s and wait for %g milliseconds.",
12903                     gmic_selection.data(),delay);
12904               cimg_forY(selection,l) display_window(selection[l]).flush();
12905               if (selection && display_window(selection[0]))
12906                 display_window(selection[0]).wait((unsigned int)delay);
12907               else cimg::wait((unsigned int)delay);
12908             } else {
12909               delay = cimg::round(delay);
12910               print(images,0,"Wait for %g milliseconds according to display window%s.",
12911                     delay,
12912                     gmic_selection.data());
12913               if (selection && display_window(selection[0]))
12914                 display_window(selection[0]).wait((unsigned int)delay);
12915               else cimg::sleep((unsigned int)delay);
12916             }
12917           }
12918           continue;
12919         }
12920 
12921         goto gmic_commands_others;
12922 
12923         //-----------------------------
12924         // Commands starting by 'x...'
12925         //-----------------------------
12926       gmic_commands_x :
12927 
12928         // Bitwise xor.
12929         gmic_arithmetic_command("xor",
12930                                 operator^=,
12931                                 "Compute bitwise XOR of image%s by %g%s",
12932                                 gmic_selection.data(),value,ssep,Tlong,
12933                                 operator^=,
12934                                 "Compute bitwise XOR of image%s by image [%d]",
12935                                 gmic_selection.data(),ind[0],
12936                                 operator_xoreq,
12937                                 "Compute bitwise XOR of image%s by expression %s",
12938                                 gmic_selection.data(),gmic_argument_text_printed(),
12939                                 "Compute sequential bitwise XOR of image%s");
12940 
12941         goto gmic_commands_others;
12942 
12943         //----------------------------
12944         // Other (special) commands.
12945         //----------------------------
12946       gmic_commands_others :
12947 
12948         // If...[elif]...[else]...endif.
12949         if (!is_get && (!std::strcmp("if",item) || (!std::strcmp("elif",item) && check_elif))) {
12950           gmic_substitute_args(false);
12951           is_cond = check_cond(argument,images,*item=='i'?"if":"elif");
12952           check_elif = false;
12953           if (*item=='i') {
12954             if (is_debug_info && debug_line!=~0U) {
12955               cimg_snprintf(argx,_argx.width(),"*if#%u",debug_line);
12956               CImg<char>::string(argx).move_to(callstack);
12957             } else CImg<char>::string("*if").move_to(callstack);
12958             if (is_very_verbose) print(images,0,"Start 'if...endif' block -> condition '%s' %s.",
12959                                        gmic_argument_text_printed(),
12960                                        is_cond?"holds":"does not hold");
12961           } else if (is_very_verbose) print(images,0,"Reach 'elif' block -> condition '%s' %s.",
12962                                             gmic_argument_text_printed(),
12963                                             is_cond?"holds":"does not hold");
12964           if (!is_cond) {
12965             for (int nb_ifs = 1; nb_ifs && position<commands_line.size(); ++position) {
12966               const char *it = commands_line[position].data();
12967               if (*it==1 &&
12968                   cimg_sscanf(commands_line[position].data() + 1,"%x,%x",&_debug_line,&(_debug_filename=0))>0) {
12969                 is_debug_info = true; next_debug_line = _debug_line; next_debug_filename = _debug_filename;
12970               } else {
12971                 it+=*it=='-';
12972                 if (!std::strcmp("if",it)) ++nb_ifs;
12973                 else if (!std::strcmp("endif",it) || !std::strcmp("fi",it)) { --nb_ifs; if (!nb_ifs) --position; }
12974                 else if (nb_ifs==1) {
12975                   if (!std::strcmp("else",it)) --nb_ifs;
12976                   else if (!std::strcmp("elif",it)) { --nb_ifs; check_elif = true; --position; }
12977                 }
12978               }
12979             }
12980             continue;
12981           }
12982           ++position; continue;
12983         }
12984 
12985         // Break and continue.
12986         bool is_continue = false;
12987         if (!is_get && (!std::strcmp("break",item) ||
12988                         (!std::strcmp("continue",item) && (is_continue=true)==true))) {
12989           const char
12990 	    *const com = is_continue?"continue":"break",
12991 	    *const Com = is_continue?"Continue":"Break";
12992           unsigned int callstack_repeat = 0, callstack_do = 0, callstack_for = 0, callstack_local = 0;
12993           for (unsigned int l = callstack.size() - 1; l; --l) {
12994             const char *const s = callstack[l].data();
12995             if (s[0]=='*' && s[1]=='r') { callstack_repeat = l; break; }
12996             else if (s[0]=='*' && s[1]=='d') { callstack_do = l; break; }
12997             else if (s[0]=='*' && s[1]=='f') { callstack_for = l; break; }
12998             else if (s[0]=='*' && s[1]=='l') { callstack_local = l; break; }
12999             else if (s[0]!='*' || s[1]!='i') break;
13000           }
13001           const char *stb = 0, *ste = 0;
13002           unsigned int callstack_ind = 0;
13003           int level = 0;
13004           if (callstack_repeat) {
13005             print(images,0,"%s %scurrent 'repeat...done' block.",
13006                   Com,is_continue?"to next iteration of ":"");
13007             for (level = 1; level && position<commands_line.size(); ++position) {
13008               const char *it = commands_line[position].data();
13009               it+=*it=='-';
13010               if (!std::strcmp("repeat",it) || !std::strcmp("for",it)) ++level;
13011               else if (!std::strcmp("done",it)) --level;
13012             }
13013             callstack_ind = callstack_repeat;
13014             stb = "repeat"; ste = "done";
13015           } else if (callstack_do) {
13016             print(images,0,"%s %scurrent 'do...while' block.",
13017                   Com,is_continue?"to next iteration of ":"");
13018             for (level = 1; level && position<commands_line.size(); ++position) {
13019               const char *it = commands_line[position].data();
13020               it+=*it=='-';
13021               if (!std::strcmp("do",it)) ++level;
13022               else if (!std::strcmp("while",it)) --level;
13023             }
13024             callstack_ind = callstack_do;
13025             stb = "do"; ste = "while";
13026           } else if (callstack_for) {
13027             print(images,0,"%s %scurrent 'for...done' block.",
13028                   Com,is_continue?"to next iteration of ":"");
13029             for (level = 1; level && position<commands_line.size(); ++position) {
13030               const char *it = commands_line[position].data();
13031               it+=*it=='-';
13032               if (!std::strcmp("repeat",it) || !std::strcmp("for",it)) ++level;
13033               else if (!std::strcmp("done",it)) --level;
13034             }
13035             callstack_ind = callstack_for;
13036             stb = "for"; ste = "done";
13037           } else if (callstack_local) {
13038             print(images,0,"%s %scurrent local environment.",
13039                   Com,is_continue?"to end of ":"");
13040             for (level = 1; level && position<commands_line.size(); ++position) {
13041               const char *it = commands_line[position].data();
13042               const bool _is_get = *it=='+' || (*it=='-' && it[1]=='-');
13043               it+=(*it=='+' || *it=='-') + (*it=='-' && it[1]=='-');
13044               if (!std::strcmp("local",it) || !std::strcmp("l",it) ||
13045                   !std::strncmp("local.",it,6) || !std::strncmp("l.",it,2) ||
13046                   !std::strncmp("local[",it,6) || !std::strncmp("l[",it,2)) ++level;
13047               else if (!_is_get && (!std::strcmp("endlocal",it) || !std::strcmp("endl",it))) --level;
13048             }
13049             callstack_ind = callstack_local;
13050             stb = "local"; ste = "endlocal";
13051           } else {
13052             print(images,0,"%s",Com);
13053             error(true,images,0,0,
13054                   "Command '%s': There are no loops or local environment to %s.",com,com);
13055             continue;
13056           }
13057           if (level && position>=commands_line.size())
13058             error(true,images,0,0,
13059                   "Command '%s': Missing associated '%s' command.",stb,ste);
13060           if (is_continue || callstack_local) {
13061 	    if (callstack_ind<callstack.size() - 1) callstack.remove(callstack_ind + 1,callstack.size() - 1);
13062 	    --position;
13063 	  } else {
13064             callstack.remove(callstack_ind,callstack.size() - 1);
13065             if (callstack_do) { --nb_dowhiles; ++position; }
13066             else if (callstack_repeat) --nb_repeatdones;
13067             else --nb_fordones;
13068           }
13069           continue;
13070         }
13071 
13072         // Compute direct or inverse FFT.
13073         const bool inv_fft = !std::strcmp("ifft",command);
13074         if (!std::strcmp("fft",command) || inv_fft) {
13075           gmic_substitute_args(false);
13076           bool is_valid_argument = *argument!=0;
13077           if (is_valid_argument) for (const char *s = argument; *s; ++s) {
13078               const char _s = *s;
13079               if (_s!='x' && _s!='y' && _s!='z') { is_valid_argument = false; break; }
13080             }
13081           if (is_valid_argument) {
13082             print(images,0,"Compute %sfourier transform of image%s along the '%s'-ax%cs with complex pair%s",
13083                   inv_fft?"inverse ":"",
13084                   gmic_selection.data(),
13085                   gmic_argument_text_printed(),
13086                   std::strlen(argument)>1?'e':'i',
13087                   selection.height()>2?"s":selection.height()>=1?"":"().");
13088             ++position;
13089           } else
13090             print(images,0,"Compute %sfourier transform of image%s with complex pair%s",
13091                   inv_fft?"inverse ":"",
13092                   gmic_selection.data(),
13093                   selection.height()>2?"s":selection.height()>=1?"":" ().");
13094           cimg_forY(selection,l) {
13095             const unsigned int
13096 	      uind0 = selection[l],
13097 	      uind1 = l + 1<selection.height()?selection[l + 1]:~0U;
13098             CImg<T> &img0 = gmic_check(images[uind0]),
13099                     &img1 = uind1!=~0U?gmic_check(images[uind1]):CImg<T>::empty();
13100             name = images_names[uind0];
13101             if (uind1!=~0U) { // Complex transform
13102               if (is_verbose) {
13103                 cimg::mutex(29);
13104                 std::fprintf(cimg::output()," ([%u],[%u])%c",uind0,uind1,
13105 			     l>=selection.height() - 2?'.':',');
13106                 std::fflush(cimg::output());
13107                 cimg::mutex(29,0);
13108               }
13109               if (is_get) {
13110                 g_list.assign(img0,img1);
13111                 if (is_valid_argument) for (const char *s = argument; *s; ++s) g_list.FFT(*s,inv_fft);
13112                 else g_list.FFT(inv_fft);
13113                 g_list.move_to(images,~0U);
13114                 images_names.insert(2,name.copymark());
13115               } else {
13116                 g_list.assign(2);
13117                 g_list[0].swap(img0);
13118                 g_list[1].swap(img1);
13119                 if (is_valid_argument) for (const char *s = argument; *s; ++s) g_list.FFT(*s,inv_fft);
13120                 else g_list.FFT(inv_fft);
13121                 g_list[0].swap(img0);
13122                 g_list[1].swap(img1);
13123                 name.get_copymark().move_to(images_names[uind1]);
13124                 name.move_to(images_names[uind0]);
13125               }
13126               ++l;
13127             } else { // Real transform
13128               if (is_verbose) {
13129                 cimg::mutex(29);
13130                 std::fprintf(cimg::output()," ([%u],0)%c",uind0,
13131 			     l>=selection.height() - 2?'.':',');
13132                 std::fflush(cimg::output());
13133                 cimg::mutex(29,0);
13134               }
13135               if (is_get) {
13136                 g_list.assign(img0);
13137                 CImg<T>(g_list[0].width(),g_list[0].height(),g_list[0].depth(),g_list[0].spectrum(),(T)0).
13138                   move_to(g_list);
13139                 if (is_valid_argument) for (const char *s = argument; *s; ++s) g_list.FFT(*s,inv_fft);
13140                 else g_list.FFT(inv_fft);
13141                 g_list.move_to(images,~0U);
13142                 images_names.insert(2,name.copymark());
13143               } else {
13144                 g_list.assign(1);
13145                 g_list[0].swap(img0);
13146                 CImg<T>(g_list[0].width(),g_list[0].height(),g_list[0].depth(),g_list[0].spectrum(),(T)0).
13147                   move_to(g_list);
13148                 if (is_valid_argument) for (const char *s = argument; *s; ++s) g_list.FFT(*s,inv_fft);
13149                 else g_list.FFT(inv_fft);
13150                 g_list[0].swap(img0);
13151                 g_list[1].move_to(images,uind0 + 1);
13152                 name.get_copymark().move_to(images_names,uind0 + 1);
13153                 name.move_to(images_names[uind0]);
13154               }
13155             }
13156           }
13157           g_list.assign();
13158           is_change = true; continue;
13159         }
13160 
13161         // Rescale a 3D object (* or /).
13162         const bool divide3d = !std::strcmp("div3d",command);
13163         if (!std::strcmp("mul3d",command) || divide3d) {
13164           gmic_substitute_args(false);
13165           float sx = 0, sy = 1, sz = 1;
13166           if ((cimg_sscanf(argument,"%f%c",
13167                            &sx,&end)==1 && ((sz=sy=sx),1)) ||
13168               cimg_sscanf(argument,"%f,%f%c",
13169                           &sx,&sy,&end)==2 ||
13170               cimg_sscanf(argument,"%f,%f,%f%c",
13171                           &sx,&sy,&sz,&end)==3) {
13172             if (divide3d)
13173               print(images,0,"Scale 3D object%s with factors (1/%g,1/%g,1/%g).",
13174                     gmic_selection.data(),
13175                     sx,sy,sz);
13176             else
13177               print(images,0,"Scale 3D object%s with factors (%g,%g,%g).",
13178                     gmic_selection.data(),
13179                     sx,sy,sz);
13180             cimg_forY(selection,l) {
13181               const unsigned int uind = selection[l];
13182               CImg<T>& img = images[uind];
13183               try {
13184                 if (divide3d) { gmic_apply(scale_CImg3d(1/sx,1/sy,1/sz)); }
13185                 else gmic_apply(scale_CImg3d(sx,sy,sz));
13186               } catch (CImgException&) {
13187                 if (!img.is_CImg3d(true,&(*message=0)))
13188                   error(true,images,0,0,
13189                         "Command '%s3d': Invalid 3D object [%d], in selected image%s (%s).",
13190                         divide3d?"div":"mul",uind,gmic_selection_err.data(),message.data());
13191                 else throw;
13192               }
13193             }
13194           } else { if (divide3d) arg_error("div3d"); else arg_error("mul3d"); }
13195           is_change = true; ++position; continue;
13196         }
13197 
13198         // Execute custom command.
13199         if (!is_command_input && is_command) {
13200           if (hash_custom==~0U) { // A --builtin_command not supporting double hyphen (e.g. --v)
13201             hash_custom = hashcode(command,false);
13202             is_command = search_sorted(command,commands_names[hash_custom],
13203                                        commands_names[hash_custom].size(),ind_custom);
13204           }
13205 
13206           if (is_command) {
13207             bool has_arguments = false, _is_noarg = false;
13208             CImg<char> substituted_command(1024);
13209             char *ptr_sub = substituted_command.data();
13210             const char
13211               *const command_code = commands[hash_custom][ind_custom].data(),
13212               *const command_code_back = &commands[hash_custom][ind_custom].back();
13213 
13214             if (is_debug) {
13215               CImg<char> command_code_text(264);
13216               const unsigned int ls = (unsigned int)std::strlen(command_code);
13217               if (ls>=264) {
13218                 std::memcpy(command_code_text.data(),command_code,128);
13219                 std::memcpy(command_code_text.data() + 128,"(...)",5);
13220                 std::memcpy(command_code_text.data() + 133,command_code + ls - 130,131);
13221               } else std::strcpy(command_code_text.data(),command_code);
13222               for (char *ptrs = command_code_text, *ptrd = ptrs; *ptrs || (bool)(*ptrd=0);
13223                    ++ptrs)
13224                 if (*ptrs==1) while (*ptrs!=' ') ++ptrs; else *(ptrd++) = *ptrs;
13225               debug(images,"Found custom command '%s: %s' (%s).",
13226                     command,command_code_text.data(),
13227                     commands_has_arguments[hash_custom](ind_custom,0)?"takes arguments":
13228                     "takes no arguments");
13229             }
13230 
13231             CImgList<char> arguments(32);
13232             // Set $0 to be the command name.
13233             CImg<char>::string(command).move_to(arguments[0]);
13234             unsigned int nb_arguments = 0;
13235 
13236             if (commands_has_arguments[hash_custom](ind_custom,0)) { // Command takes arguments
13237               gmic_substitute_args(false);
13238 
13239               // Extract possible command arguments.
13240               for (const char *ss = argument, *_ss = ss; _ss; ss =_ss + 1)
13241                 if ((_ss=std::strchr(ss,','))!=0) {
13242                   if (ss==_ss) ++nb_arguments;
13243                   else {
13244                     if (++nb_arguments>=arguments.size())
13245                       arguments.insert(2 + 2*nb_arguments - arguments.size());
13246                     CImg<char> arg_item(ss,(unsigned int)(_ss - ss + 1));
13247                     arg_item.back() = 0;
13248                     arg_item.move_to(arguments[nb_arguments]);
13249                   }
13250                 } else {
13251                   if (*ss) {
13252                     if (++nb_arguments>=arguments.size())
13253                       arguments.insert(1 + nb_arguments - arguments.size());
13254                     if (*ss!=',') CImg<char>::string(ss).move_to(arguments[nb_arguments]);
13255                   }
13256                   break;
13257                 }
13258 
13259               if (is_debug) {
13260                 debug(images,"Found %d given argument%s for command '%s'%s",
13261                       nb_arguments,nb_arguments!=1?"s":"",
13262                       command,nb_arguments>0?":":".");
13263                 for (unsigned int i = 1; i<=nb_arguments; ++i)
13264                   if (arguments[i]) debug(images,"  $%d = '%s'",i,arguments[i].data());
13265                   else debug(images,"  $%d = (undefined)",i);
13266               }
13267             }
13268 
13269             // Substitute arguments in custom command expression.
13270             CImg<char> inbraces;
13271 
13272             for (const char *nsource = command_code; *nsource;)
13273               if (*nsource!='$') {
13274 
13275                 // If not starting with '$'.
13276                 const char *const nsource0 = nsource;
13277                 nsource = std::strchr(nsource0,'$');
13278                 if (!nsource) nsource = command_code_back;
13279                 CImg<char>(nsource0,(unsigned int)(nsource - nsource0),1,1,1,true).
13280                   append_string_to(substituted_command,ptr_sub);
13281               } else { // '$' expression found
13282                 CImg<char> substr(324);
13283                 inbraces.assign(1,1,1,1,0);
13284                 int iind = 0, iind1 = 0, l_inbraces = 0;
13285                 bool is_braces = false;
13286                 sep = 0;
13287 
13288                 if (nsource[1]=='{') {
13289                   const char *const ptr_beg = nsource + 2, *ptr_end = ptr_beg;
13290                   unsigned int p = 0;
13291                   for (p = 1; p>0 && *ptr_end; ++ptr_end) {
13292                     if (*ptr_end=='{') ++p;
13293                     if (*ptr_end=='}') --p;
13294                   }
13295                   if (p) { CImg<char>::append_string_to(*(nsource++),substituted_command,ptr_sub); continue; }
13296                   l_inbraces = (int)(ptr_end - ptr_beg - 1);
13297                   if (l_inbraces>0) inbraces.assign(ptr_beg,l_inbraces + 1).back() = 0;
13298                   is_braces = true;
13299                 }
13300 
13301                 // Substitute $# -> maximum index of known arguments.
13302                 if (nsource[1]=='#') {
13303                   nsource+=2;
13304                   cimg_snprintf(substr,substr.width(),"%u",nb_arguments);
13305                   CImg<char>(substr.data(),(unsigned int)std::strlen(substr),1,1,1,true).
13306                     append_string_to(substituted_command,ptr_sub);
13307                   has_arguments = true;
13308 
13309                   // Substitute $* -> copy of the specified arguments string.
13310                 } else if (nsource[1]=='*') {
13311                   nsource+=2;
13312                   CImg<char>(argument,(unsigned int)std::strlen(argument),1,1,1,true).
13313                     append_string_to(substituted_command,ptr_sub);
13314                   has_arguments = true;
13315 
13316                   // Substitute $"*" -> copy of the specified "quoted" arguments string.
13317                 } else if (nsource[1]=='\"' && nsource[2]=='*' && nsource[3]=='\"') {
13318                   nsource+=4;
13319                   for (unsigned int i = 1; i<=nb_arguments; ++i) {
13320                     CImg<char>::append_string_to('\"',substituted_command,ptr_sub);
13321                     CImg<char>(arguments[i].data(),arguments[i].width() - 1,1,1,1,true).
13322                       append_string_to(substituted_command,ptr_sub);
13323                     CImg<char>::append_string_to('\"',substituted_command,ptr_sub);
13324                     if (i!=nb_arguments) CImg<char>::append_string_to(',',substituted_command,ptr_sub);
13325                   }
13326                   has_arguments = true;
13327 
13328                   // Substitute $[] -> List of selected image indices.
13329                 } else if (nsource[1]=='[' && nsource[2]==']') {
13330                   nsource+=3;
13331                   cimg_forY(selection,i) {
13332                     cimg_snprintf(substr,substr.width(),"%u,",selection[i]);
13333                     CImg<char>(substr.data(),(unsigned int)std::strlen(substr),1,1,1,true).
13334                       append_string_to(substituted_command,ptr_sub);
13335                   }
13336                   if (selection) --ptr_sub;
13337 
13338                   // Substitute $= -> transfer (quoted) arguments to named variables.
13339                 } else if (nsource[1]=='=' &&
13340                            cimg_sscanf(nsource + 2,"%255[a-zA-Z0-9_]",title)==1 &&
13341                            (*title<'0' || *title>'9')) {
13342                   nsource+=2 + std::strlen(title);
13343                   for (unsigned int i = 0; i<=nb_arguments; ++i) {
13344                     cimg_snprintf(substr,substr.width()," %s%u=\"",title,i);
13345                     CImg<char>(substr.data(),(unsigned int)std::strlen(substr),1,1,1,true).
13346                       append_string_to(substituted_command,ptr_sub);
13347                     CImg<char>(arguments[i].data(),arguments[i].width() - 1,1,1,1,true).
13348                       append_string_to(substituted_command,ptr_sub);
13349                     CImg<char>::append_string_to('\"',substituted_command,ptr_sub);
13350                     CImg<char>::append_string_to(' ',substituted_command,ptr_sub);
13351                   }
13352                   has_arguments = true;
13353 
13354                   // Substitute $i and ${i} -> value of the i^th argument.
13355                 } else if ((cimg_sscanf(nsource,"$%d",&iind)==1 ||
13356                             (cimg_sscanf(nsource,"${%d%c",&iind,&sep)==2 && sep=='}'))) {
13357                   const int niind = iind + (iind<0?(int)nb_arguments + 1:0);
13358                   if ((niind<=0 && iind) || niind>=arguments.width() || !arguments[niind]) {
13359                     error(true,images,0,command,
13360                           "Command '%s': Undefined argument '$%d', in expression '$%s%d%s' "
13361                           "(for %u argument%s specified).",
13362                           command,iind,sep=='}'?"{":"",iind,sep=='}'?"}":"",
13363                           nb_arguments,nb_arguments!=1?"s":"");
13364                   }
13365                   nsource+=cimg_snprintf(substr,substr.width(),"$%d",iind) + (sep=='}'?2:0);
13366                   if (arguments[niind].width()>1)
13367                     CImg<char>(arguments[niind].data(),arguments[niind].width() - 1,1,1,1,true).
13368                       append_string_to(substituted_command,ptr_sub);
13369                   if (niind!=0) has_arguments = true;
13370 
13371                   // Substitute ${i=$j} -> value of the i^th argument, or the default value,
13372                   // i.e. the value of another argument.
13373                 } else if (cimg_sscanf(nsource,"${%d=$%d%c",&iind,&iind1,&sep)==3 && sep=='}' &&
13374                            iind>0) {
13375                   const int niind1 = iind1 + (iind1<0?(int)nb_arguments + 1:0);
13376                   if (niind1<=0 || niind1>=arguments.width() || !arguments[niind1])
13377                     error(true,images,0,command,
13378                           "Command '%s': Undefined argument '$%d', in expression '${%d=$%d}' "
13379                           "(for %u argument%s specified).",
13380                           command,iind1,iind,iind1,
13381                           nb_arguments,nb_arguments!=1?"s":"");
13382                   nsource+=cimg_snprintf(substr,substr.width(),"${%d=$%d}",iind,iind1);
13383                   if (iind>=arguments.width()) arguments.insert(2 + 2*iind - arguments.size());
13384                   if (!arguments[iind]) {
13385                     arguments[iind] = arguments[niind1];
13386                     if (iind>(int)nb_arguments) nb_arguments = (unsigned int)iind;
13387                   }
13388                   if (arguments[iind].width()>1)
13389                     CImg<char>(arguments[iind].data(),arguments[iind].width() - 1,1,1,1,true).
13390                       append_string_to(substituted_command,ptr_sub);
13391                   has_arguments = true;
13392 
13393                   // Substitute ${i=$#} -> value of the i^th argument, or the default value,
13394                   // i.e. the maximum index of known arguments.
13395                 } else if (cimg_sscanf(nsource,"${%d=$#%c",&iind,&sep)==2 && sep=='}' &&
13396                            iind>0) {
13397                   if (iind>=arguments.width()) arguments.insert(2 + 2*iind - arguments.size());
13398                   if (!arguments[iind]) {
13399                     cimg_snprintf(substr,substr.width(),"%u",nb_arguments);
13400                     CImg<char>::string(substr).move_to(arguments[iind]);
13401                     if (iind>(int)nb_arguments) nb_arguments = (unsigned int)iind;
13402                   }
13403                   nsource+=cimg_snprintf(substr,substr.width(),"${%d=$#}",iind);
13404                   if (arguments[iind].width()>1)
13405                     CImg<char>(arguments[iind].data(),arguments[iind].width() - 1,1,1,1,true).
13406                       append_string_to(substituted_command,ptr_sub);
13407                   has_arguments = true;
13408 
13409                   // Substitute ${i=default} -> value of the i^th argument,
13410                   // or the specified default value.
13411                 } else if (cimg_sscanf(inbraces,"%d%c",&iind,&sep)==2 && sep=='=' &&
13412                            iind>0) {
13413                   nsource+=l_inbraces + 3;
13414                   if (iind>=arguments.width()) arguments.insert(2 + 2*iind - arguments.size());
13415                   if (!arguments[iind]) {
13416                     CImg<char>::string(inbraces.data() +
13417                                        cimg_snprintf(substr,substr.width(),"%d=",iind)).
13418                       move_to(arguments[iind]);
13419                     if (iind>(int)nb_arguments) nb_arguments = (unsigned int)iind;
13420                   }
13421                   if (arguments[iind].width()>1)
13422                     CImg<char>(arguments[iind].data(),arguments[iind].width() - 1,1,1,1,true).
13423                       append_string_to(substituted_command,ptr_sub);
13424                   has_arguments = true;
13425 
13426                   // Substitute any other expression starting by '$'.
13427                 } else {
13428 
13429                   // Substitute ${subset} -> values of the selected subset of arguments,
13430                   // separated by ','.
13431                   bool is_valid_subset = false;
13432                   if (is_braces) {
13433                     const char c = *inbraces, nc = c?inbraces[1]:0;
13434                     if (c=='^' || c==':' || c=='.' || (c>='0' && c<='9') ||
13435                         (c=='-' && !((nc>='a' && nc<='z') ||
13436                                      (nc>='A' && nc<='Z') ||
13437                                      nc=='_'))) {
13438 
13439                       CImg<unsigned int> inds;
13440                       status.move_to(o_status); // Save status because 'selection2cimg' can change it
13441                       const int o_verbosity = verbosity;
13442                       const bool o_is_debug = is_debug;
13443                       verbosity = 0;
13444                       is_debug = false;
13445                       try {
13446                         inds = selection2cimg(inbraces,nb_arguments + 1,
13447                                               CImgList<char>::empty(),"",false);
13448                         is_valid_subset = true;
13449                       } catch (...) { inds.assign(); is_valid_subset = false; }
13450                       is_debug = o_is_debug;
13451                       verbosity = o_verbosity;
13452                       o_status.move_to(status);
13453 
13454                       if (is_valid_subset) {
13455                         nsource+=l_inbraces + 3;
13456                         if (inds) {
13457                           cimg_forY(inds,j) {
13458                             const unsigned int uind = inds[j];
13459                             if (uind) has_arguments = true;
13460                             if (!arguments[uind])
13461                               error(true,images,0,command,
13462                                     "Command '%s': Undefined argument '$%d', "
13463                                     "in expression '${%s}'.",
13464                                     command,uind,inbraces.data());
13465                             CImg<char>(arguments[uind],true).append_string_to(substituted_command,ptr_sub);
13466                             *(ptr_sub - 1) = ',';
13467                           }
13468                           --ptr_sub;
13469                           has_arguments = true;
13470                         }
13471                       }
13472                     }
13473                   }
13474                   if (!is_valid_subset) CImg<char>::append_string_to(*(nsource++),substituted_command,ptr_sub);
13475                 }
13476               }
13477             *ptr_sub = 0;
13478 
13479             // Substitute special character codes appearing outside strings.
13480             bool is_dquoted = false, is_escaped = false;
13481             for (char *s = substituted_command.data(); *s; ++s) {
13482               const char c = *s;
13483               if (is_escaped) is_escaped = false;
13484               else if (c=='\\') is_escaped = true;
13485               else if (c=='\"') is_dquoted = !is_dquoted;
13486               if (!is_dquoted) *s = c<' '?(c==gmic_dollar?'$':c==gmic_lbrace?'{':c==gmic_rbrace?'}':
13487                                            c==gmic_comma?',':c==gmic_dquote?'\"':c):c;
13488             }
13489 
13490             if (is_debug) {
13491               CImg<char> command_code_text(264);
13492               const unsigned int l = (unsigned int)std::strlen(substituted_command.data());
13493               if (l>=264) {
13494                 std::memcpy(command_code_text.data(),substituted_command.data(),128);
13495                 std::memcpy(command_code_text.data() + 128,"(...)",5);
13496                 std::memcpy(command_code_text.data() + 133,substituted_command.data() + l - 130,131);
13497               } else std::strcpy(command_code_text.data(),substituted_command.data());
13498               for (char *ptrs = command_code_text, *ptrd = ptrs; *ptrs || (bool)(*ptrd=0);
13499                    ++ptrs)
13500                 if (*ptrs==1) while (*ptrs!=' ') ++ptrs; else *(ptrd++) = *ptrs;
13501               debug(images,"Expand command line for command '%s' to: '%s'.",
13502                     command,command_code_text.data());
13503             }
13504 
13505             const CImgList<char>
13506               ncommands_line = commands_line_to_CImgList(substituted_command.data());
13507             CImg<unsigned int> nvariables_sizes(gmic_varslots);
13508             cimg_forX(nvariables_sizes,l) nvariables_sizes[l] = variables[l]->size();
13509             g_list.assign(selection.height());
13510             g_list_c.assign(selection.height());
13511 
13512             unsigned int nposition = 0;
13513             gmic_exception exception;
13514             const unsigned int
13515               previous_debug_filename = debug_filename,
13516               previous_debug_line = debug_line;
13517             CImg<char>::string(command).move_to(callstack);
13518             if (is_get) {
13519               cimg_forY(selection,l) {
13520                 const unsigned int uind = selection[l];
13521                 g_list[l] = images[uind];
13522                 g_list_c[l] = images_names[uind];
13523               }
13524 
13525               try {
13526                 is_debug_info = false;
13527                 --verbosity;
13528                 _run(ncommands_line,nposition,g_list,g_list_c,images,images_names,nvariables_sizes,&_is_noarg,
13529                      argument,&selection);
13530                 ++verbosity;
13531               } catch (gmic_exception &e) {
13532                 cimg::swap(exception._command,e._command);
13533                 cimg::swap(exception._message,e._message);
13534               }
13535 
13536               g_list.move_to(images,~0U);
13537               cimglist_for(g_list_c,l) g_list_c[l].copymark();
13538               g_list_c.move_to(images_names,~0U);
13539             } else {
13540               cimg::mutex(27);
13541               cimg_forY(selection,l) {
13542                 const unsigned int uind = selection[l];
13543                 if ((images[uind].width() || images[uind].height()) && !images[uind].spectrum()) {
13544                   selection2string(selection,images_names,1,name);
13545                   error(true,images,0,0,
13546                         "Command '%s': Invalid selection%s "
13547                         "(image [%u] is already used in another thread).",
13548                         command,name.data() + (*name=='s'?1:0),uind);
13549                 }
13550                 if (images[uind].is_shared())
13551                   g_list[l].assign(images[uind],false);
13552                 else {
13553                   g_list[l].swap(images[uind]);
13554                   // Small hack to be able to track images of the selection passed to the new environment.
13555                   std::memcpy(&images[uind]._width,&g_list[l]._data,sizeof(void*));
13556                   images[uind]._spectrum = 0;
13557                 }
13558                 g_list_c[l] = images_names[uind]; // Make a copy to be still able to recognize 'pass[label]'
13559               }
13560               cimg::mutex(27,0);
13561 
13562               try {
13563                 is_debug_info = false;
13564                 --verbosity;
13565                 _run(ncommands_line,nposition,g_list,g_list_c,images,images_names,nvariables_sizes,&_is_noarg,
13566                      argument,&selection);
13567                 ++verbosity;
13568               } catch (gmic_exception &e) {
13569                 cimg::swap(exception._command,e._command);
13570                 cimg::swap(exception._message,e._message);
13571               }
13572               if (run_entrypoint) { --verbosity; run_entrypoint = false; }
13573 
13574               const unsigned int nb = std::min((unsigned int)selection.height(),g_list.size());
13575               if (nb>0) {
13576                 for (unsigned int i = 0; i<nb; ++i) {
13577                   const unsigned int uind = selection[i];
13578                   if (images[uind].is_shared()) {
13579                     images[uind] = g_list[i];
13580                     g_list[i].assign();
13581                   } else images[uind].swap(g_list[i]);
13582                   images_names[uind].swap(g_list_c[i]);
13583                 }
13584                 g_list.remove(0,nb - 1);
13585                 g_list_c.remove(0,nb - 1);
13586               }
13587               if (nb<(unsigned int)selection.height())
13588                 remove_images(images,images_names,selection,nb,selection.height() - 1);
13589               else if (g_list) {
13590                 const unsigned int uind0 = selection?selection.back() + 1:images.size();
13591                 g_list_c.move_to(images_names,uind0);
13592                 g_list.move_to(images,uind0);
13593               }
13594             }
13595             for (unsigned int l = 0; l<nvariables_sizes._width - 2; ++l) if (variables[l]->size()>nvariables_sizes[l]) {
13596                 variables_names[l]->remove(nvariables_sizes[l],variables[l]->size() - 1);
13597                 variables[l]->remove(nvariables_sizes[l],variables[l]->size() - 1);
13598               }
13599             callstack.remove();
13600             debug_filename = previous_debug_filename;
13601             debug_line = previous_debug_line;
13602             is_return = false;
13603             g_list.assign(); g_list_c.assign();
13604             if (has_arguments && !_is_noarg) ++position;
13605             if (exception._message) throw exception;
13606             continue;
13607           }
13608         }
13609       } // if (is_command) {
13610 
13611       // Variable assignment.
13612       if (!is_command_input && (*item=='_' || (*item>='a' && *item<='z') || (*item>='A' && *item<='Z'))) {
13613         const char *const s_op_right = std::strchr(item,'=');
13614         if (s_op_right) {
13615           const char *s_op_left = s_op_right;
13616           sep0 = s_op_right>item?*(s_op_right - 1):0;
13617           sep1 = s_op_right>item + 1?*(s_op_right - 2):0;
13618           if ((sep1=='>' || sep1=='<') && sep0==sep1) s_op_left = s_op_right - 2;
13619           else {
13620             sep1 = 0;
13621             if (sep0=='+' || sep0=='-' || sep0=='*' || sep0=='/' || sep0=='.' ||
13622                 sep0=='%' || sep0=='&' || sep0=='|' || sep0=='^') s_op_left = s_op_right - 1;
13623             else sep0 = '=';
13624           }
13625 
13626           // Check validity of variable name(s).
13627           CImgList<char> varnames, varvalues;
13628           bool is_valid_name = true, is_multiarg = false;
13629           const char *s = std::strchr(item,',');
13630           if (!s || s>s_op_right) { // Single variable assignment
13631             is_valid_name = cimg_sscanf(item,"%255[a-zA-Z0-9_]",title)==1 && (*title<'0' || *title>'9');
13632             is_valid_name&=(item + std::strlen(title)==s_op_left);
13633 
13634           } else { // Multi-variable assignment
13635             s = item; // Parse sequence of variable names
13636             while (s<s_op_left) {
13637               const char *ns = std::strchr(s,',');
13638               if (ns==s) { is_valid_name = false; break; }
13639               if (!ns || ns>=s_op_left) ns = s_op_left;
13640               CImg<char>(s,(unsigned int)(ns - s + 1)).move_to(name);
13641               name.back() = 0;
13642               if (cimg_sscanf(name,"%255[a-zA-Z0-9_]%c",title,&sep)==1 && (*title<'0' || *title>'9'))
13643                 name.move_to(varnames);
13644               else { is_valid_name = false; break; }
13645               s = ns + 1;
13646             }
13647 
13648             if (is_valid_name) {
13649               s = s_op_right + 1; // Parse sequence of values
13650               if (!*s) CImg<char>(1,1,1,1,0).move_to(varvalues);
13651               else {
13652                 const char *const s_end = item + std::strlen(item);
13653                 while (s<s_end) {
13654                   const char *ns = std::strchr(s,',');
13655                   if (!ns) ns = s_end;
13656                   CImg<char>(s,(unsigned int)(ns - s + 1)).move_to(name);
13657                   name.back() = 0;
13658                   name.move_to(varvalues);
13659                   s = ns + 1;
13660                 }
13661                 if (*(s_end - 1)==',') CImg<char>(1,1,1,1,0).move_to(varvalues);
13662               }
13663               is_multiarg = varnames.width()==varvalues.width();
13664               is_valid_name&=(is_multiarg || varvalues.width()==1);
13665             }
13666           }
13667 
13668           // Assign or update values of variables.
13669           if (is_valid_name) {
13670             const char *new_value = 0;
13671             if (varnames) { // Multiple variables
13672               cimglist_for(varnames,l) {
13673                 new_value = set_variable(varnames[l],varvalues[is_multiarg?l:0],sep0,variables_sizes);
13674                 if (is_verbose) {
13675                   if (is_multiarg || !l) cimg::strellipsize(varvalues[l],80,true);
13676                   CImg<char>::string(new_value).move_to(name);
13677                   cimg::strellipsize(name,80,true);
13678                   cimg::strellipsize(varnames[l],80,true);
13679                   switch (sep0) {
13680                   case '=' :
13681                     cimg_snprintf(message,message.width(),"'%s=%s', ",
13682                                   varnames[l].data(),varvalues[is_multiarg?l:0].data());
13683                     break;
13684                   case '<' : case '>' :
13685                     cimg_snprintf(message,message.width(),"'%s%c%c=%s'->'%s', ",
13686                                   varnames[l].data(),sep0,sep0,varvalues[is_multiarg?l:0].data(),name.data());
13687                     break;
13688                   default :
13689                     cimg_snprintf(message,message.width(),"'%s%c=%s'->'%s', ",
13690                                   varnames[l].data(),sep0,varvalues[is_multiarg?l:0].data(),name.data());
13691                   }
13692                   CImg<char>::string(message,false).move_to(varnames[l]);
13693                 }
13694               }
13695               if (is_verbose) {
13696                 CImg<char> &last = varnames.back();
13697                 last[last.width() - 2] = 0;
13698                 (varnames>'x').move_to(name);
13699                 cimg::strellipsize(name,80,false);
13700                 print(images,0,"%s multiple variables %s.",
13701                       sep0=='='?"Set":"Update",name.data());
13702               }
13703             } else { // Single variable
13704               new_value = set_variable(title,s_op_right + 1,sep0,variables_sizes);
13705               if (is_verbose) {
13706                 cimg::strellipsize(title,80,true);
13707                 _gmic_argument_text(s_op_right + 1,name.assign(128),is_verbose);
13708                 switch (sep0) {
13709                 case '=' :
13710                   print(images,0,"Set %s variable '%s=%s'.",
13711                         *title=='_'?"global":"local",
13712                         title,name.data());
13713                   break;
13714                 case '<' : case '>' :
13715                   print(images,0,"Update %s variable '%s%c%c=%s'->'%s'.",
13716                         *title=='_'?"global":"local",
13717                         title,sep0,sep0,name.data(),new_value);
13718                   break;
13719                 default :
13720                   print(images,0,"Update %s variable '%s%c=%s'->'%s'.",
13721                         *title=='_'?"global":"local",
13722                         title,sep0,name.data(),new_value);
13723                 }
13724               }
13725             }
13726             continue;
13727           }
13728         }
13729       }
13730 
13731       // Input.
13732       if (is_command_input) ++position;
13733       else {
13734         std::strcpy(command,"input");
13735         argument = item - (is_double_hyphen?2:is_simple_hyphen || is_plus?1:0);
13736         *s_selection = 0;
13737       }
13738       gmic_substitute_args(true);
13739       if (!is_selection || !selection) selection.assign(1,1,1,1,images.size());
13740 
13741       CImg<char> indicesy(256), indicesz(256), indicesc(256);
13742       float dx = 0, dy = 1, dz = 1, dc = 1, nb = 1;
13743       CImg<unsigned int> indx, indy, indz, indc;
13744       sepx = sepy = sepz = sepc = *indices = *indicesy = *indicesz = *indicesc = *argx = *argy = *argz = *argc = 0;
13745 
13746       CImg<char> arg_input(argument,(unsigned int)std::strlen(argument) + 1);
13747       strreplace_fw(arg_input);
13748 
13749       CImg<char> _gmic_selection;
13750       if (is_verbose) selection2string(selection,images_names,0,_gmic_selection);
13751 
13752       if (*arg_input=='0' && !arg_input[1]) {
13753 
13754         // Empty image.
13755         print(images,0,"Input empty image at position%s",
13756               _gmic_selection.data());
13757         g_list.assign(1);
13758         CImg<char>::string("[empty]").move_to(g_list_c);
13759 
13760       } else if ((cimg_sscanf(arg_input,"[%255[a-zA-Z_0-9%.eE%^,:+-]%c%c",indices,&sep,&end)==2 &&
13761                   sep==']') ||
13762                  cimg_sscanf(arg_input,"[%255[a-zA-Z_0-9%.eE%^,:+-]]x%f%c",indices,&nb,&end)==2) {
13763 
13764         // Nb copies of existing images.
13765         nb = cimg::round(nb);
13766         const CImg<unsigned int> inds = selection2cimg(indices,images.size(),images_names,"input");
13767         CImg<char> s_tmp;
13768         if (is_verbose) selection2string(inds,images_names,1,s_tmp);
13769         if (nb<=0) arg_error("input");
13770         if (nb!=1)
13771           print(images,0,"Input %u copies of image%s at position%s",
13772                 (unsigned int)nb,
13773                 s_tmp.data(),
13774                 _gmic_selection.data());
13775         else
13776           print(images,0,"Input copy of image%s at position%s",
13777                 s_tmp.data(),
13778                 _gmic_selection.data());
13779         for (unsigned int i = 0; i<(unsigned int)nb; ++i) cimg_foroff(inds,l) {
13780             g_list.insert(gmic_check(images[inds[l]]));
13781             g_list_c.insert(images_names[inds[l]].get_copymark());
13782           }
13783       } else if ((sep=0,true) &&
13784                  (cimg_sscanf(arg_input,"%255[][a-zA-Z0-9_.eE%+-]%c",
13785                               argx,&end)==1 ||
13786                   cimg_sscanf(arg_input,"%255[][a-zA-Z0-9_.eE%+-],%255[][a-zA-Z0-9_.eE%+-]%c",
13787                               argx,argy,&end)==2 ||
13788                   cimg_sscanf(arg_input,"%255[][a-zA-Z0-9_.eE%+-],%255[][a-zA-Z0-9_.eE%+-],"
13789 			      "%255[][a-zA-Z0-9_.eE%+-]%c",
13790                               argx,argy,argz,&end)==3 ||
13791                   cimg_sscanf(arg_input,"%255[][a-zA-Z0-9_.eE%+-],%255[][a-zA-Z0-9_.eE%+-],"
13792 			      "%255[][a-zA-Z0-9_.eE%+-],%255[][a-zA-Z0-9_.eE%+-]%c",
13793                               argx,argy,argz,argc,&end)==4 ||
13794                   cimg_sscanf(arg_input,"%255[][a-zA-Z0-9_.eE%+-],%255[][a-zA-Z0-9_.eE%+-],"
13795 			      "%255[][a-zA-Z0-9_.eE%+-],%255[][a-zA-Z0-9_.eE%+-],%c",
13796                               argx,argy,argz,argc,&sep)==5) &&
13797                  ((cimg_sscanf(argx,"[%255[a-zA-Z0-9_.%+-]%c%c",indices,&sepx,&end)==2 &&
13798 		   sepx==']' &&
13799                    (indx=selection2cimg(indices,images.size(),images_names,"input")).height()==1) ||
13800                   (cimg_sscanf(argx,"%f%c",&dx,&end)==1 && dx>=1) ||
13801                   (cimg_sscanf(argx,"%f%c%c",&dx,&sepx,&end)==2 && dx>0 && sepx=='%')) &&
13802                  (!*argy ||
13803                   (cimg_sscanf(argy,"[%255[a-zA-Z0-9_.%+-]%c%c",indicesy.data(),&sepy,&end)==2 &&
13804 		   sepy==']' &&
13805                    (indy=selection2cimg(indicesy,images.size(),images_names,"input")).height()==1) ||
13806                   (cimg_sscanf(argy,"%f%c",&dy,&end)==1 && dy>=1) ||
13807                   (cimg_sscanf(argy,"%f%c%c",&dy,&sepy,&end)==2 && dy>0 && sepy=='%')) &&
13808                  (!*argz ||
13809                   (cimg_sscanf(argz,"[%255[a-zA-Z0-9_.%+-]%c%c",indicesz.data(),&sepz,&end)==2 &&
13810 		   sepz==']' &&
13811                    (indz=selection2cimg(indicesz,images.size(),images_names,"input")).height()==1) ||
13812                   (cimg_sscanf(argz,"%f%c",&dz,&end)==1 && dz>=1) ||
13813                   (cimg_sscanf(argz,"%f%c%c",&dz,&sepz,&end)==2 && dz>0 && sepz=='%')) &&
13814                  (!*argc ||
13815                   (cimg_sscanf(argc,"[%255[a-zA-Z0-9_.%+-]%c%c",indicesc.data(),&sepc,&end)==2 &&
13816 		   sepc==']' &&
13817                    (indc=selection2cimg(indicesc,images.size(),images_names,"input")).height()==1) ||
13818                   (cimg_sscanf(argc,"%f%c",&dc,&end)==1 && dc>=1) ||
13819                   (cimg_sscanf(argc,"%f%c%c",&dc,&sepc,&end)==2 && dc>0 && sepc=='%'))) {
13820 
13821         // New image with specified dimensions and optionally values.
13822         if (indx) { dx = (float)gmic_check(images[*indx]).width(); sepx = 0; }
13823         if (indy) { dy = (float)gmic_check(images[*indy]).height(); sepy = 0; }
13824         if (indz) { dz = (float)gmic_check(images[*indz]).depth(); sepz = 0; }
13825         if (indc) { dc = (float)gmic_check(images[*indc]).spectrum(); sepc = 0; }
13826         int idx = 0, idy = 0, idz = 0, idc = 0;
13827         const CImg<T>& img = images.size()?gmic_check(images.back()):CImg<T>::empty();
13828         if (sepx=='%') { idx = (int)cimg::round(dx*img.width()/100); if (!idx) ++idx; }
13829         else idx = (int)cimg::round(dx);
13830         if (sepy=='%') { idy = (int)cimg::round(dy*img.height()/100); if (!idy) ++idy; }
13831         else idy = (int)cimg::round(dy);
13832         if (sepz=='%') { idz = (int)cimg::round(dz*img.depth()/100); if (!idz) ++idz; }
13833         else idz = (int)cimg::round(dz);
13834         if (sepc=='%') { idc = (int)cimg::round(dc*img.spectrum()/100); if (!idc) ++idc; }
13835         else idc = (int)cimg::round(dc);
13836         if (idx<=0 || idy<=0 || idz<=0 || idc<=0) arg_error("input");
13837         CImg<char> s_values;
13838         if (sep) {
13839           const char *_s_values = arg_input.data() + std::strlen(argx) + std::strlen(argy) +
13840             std::strlen(argz) + std::strlen(argc) + 4;
13841           s_values.assign(_s_values,(unsigned int)std::strlen(_s_values) + 1);
13842           cimg::strpare(s_values,'\'',true,false);
13843           strreplace_fw(s_values);
13844           CImg<char> s_values_text(72);
13845           const unsigned int l = (unsigned int)std::strlen(s_values);
13846           if (l>=72) {
13847             std::memcpy(s_values_text.data(),s_values.data(),32);
13848             std::memcpy(s_values_text.data() + 32,"(...)",5);
13849             std::memcpy(s_values_text.data() + 37,s_values.data() + l - 34,35);  // Last '\0' is included
13850           } else std::strcpy(s_values_text,s_values);
13851           print(images,0,"Input image at position%s, with values '%s'",
13852                 _gmic_selection.data(),s_values_text.data());
13853         } else
13854           print(images,0,"Input black image at position%s",
13855                 _gmic_selection.data());
13856         CImg<T> new_image(idx,idy,idz,idc);
13857         if (s_values) {
13858           new_image.fill(s_values.data(),true,true,&images,&images);
13859           cimg_snprintf(title,_title.width(),"[image of '%s']",s_values.data());
13860           CImg<char>::string(title).move_to(g_list_c);
13861         } else { new_image.fill((T)0); CImg<char>::string("[unnamed]").move_to(g_list_c); }
13862         new_image.move_to(g_list);
13863 
13864       } else if (*arg_input=='(' && arg_input[std::strlen(arg_input) - 1]==')') {
13865 
13866         // New IxJxKxL image specified as array.
13867         CImg<bool> au(256,1,1,1,false);
13868         au[(int)'0'] = au[(int)'1'] = au[(int)'2'] = au[(int)'3'] = au[(int)'4'] = au[(int)'5'] = au[(int)'6'] =
13869           au[(int)'7'] = au[(int)'8'] = au[(int)'9'] = au[(int)'.'] = au[(int)'e'] = au[(int)'E'] = au[(int)'i'] =
13870           au[(int)'n'] = au[(int)'f'] = au[(int)'a'] = au[(int)'+'] = au[(int)'-'] = true;
13871         unsigned int l, cx = 0, cy = 0, cz = 0, cc = 0, maxcx = 0, maxcy = 0, maxcz = 0;
13872         const char *nargument = 0;
13873         CImg<char> s_value(256);
13874         char separator = 0;
13875         CImg<T> img;
13876 
13877         for (nargument = arg_input.data() + 1; *nargument; ) {
13878           *s_value = separator = 0;
13879           char *pd = s_value;
13880           // Do something faster than 'scanf("%255[0-9.eEinfa+-]")'.
13881           for (l = 0; l<255 && au((unsigned int)*nargument); ++l) *(pd++) = *(nargument++);
13882           if (l<255) *pd = 0; else arg_error("input");
13883           if (*nargument) separator = *(nargument++);
13884           if ((separator=='^' || separator=='/' || separator==';' || separator==',' || separator==')') &&
13885               cimg_sscanf(s_value,"%lf%c",&value,&end)==1) {
13886             if (cx>maxcx) maxcx = cx;
13887             if (cy>maxcy) maxcy = cy;
13888             if (cz>maxcz) maxcz = cz;
13889             if (cx>=img._width || cy>=img._height || cz>=img._depth || cc>=img._spectrum)
13890               img.resize(cx>=img._width?7*cx/4 + 1:std::max(1U,img._width),
13891                          cy>=img._height?4*cy/4 + 1:std::max(1U,img._height),
13892                          cz>=img._depth?7*cz/4 + 1:std::max(1U,img._depth),
13893                          cc>=img._spectrum?7*cc/4 + 1:std::max(1U,img._spectrum),0);
13894             img(cx,cy,cz,cc) = (T)value;
13895             switch (separator) {
13896             case '^' : cx = cy = cz = 0; ++cc; break;
13897             case '/' : cx = cy = 0; ++cz; break;
13898             case ';' : cx = 0; ++cy; break;
13899             case ',' : ++cx; break;
13900             case ')' : break;
13901             default : arg_error("input");
13902             }
13903           } else arg_error("input");
13904         }
13905         img.resize(maxcx + 1,maxcy + 1,maxcz + 1,cc + 1,0);
13906         print(images,0,"Input image at position%s, with values %s",
13907               _gmic_selection.data(),
13908               gmic_argument_text_printed());
13909         img.move_to(g_list);
13910         arg_input.move_to(g_list_c);
13911 
13912       } else if (*arg_input==gmic_store &&
13913                  cimg_sscanf(arg_input.data() + 1,"*store/%255[a-zA-Z0-9_]%c",&(*argx=0),&end)==1 &&
13914                  (*argx<'0' || *argx>'9')) {
13915 
13916         // Binary-stored variable.
13917         print(images,0,
13918               "Input image from variable '%s', at position%s.",
13919               argx,_gmic_selection.data());
13920         hash = hashcode(argx,true);
13921 
13922         const bool
13923           is_global = *argx=='_',
13924           is_thread_global = is_global && argx[1]=='_';
13925         const int lind = is_global?0:(int)variables_sizes[hash];
13926         int vind = 0;
13927         if (is_thread_global) cimg::mutex(30);
13928         const CImgList<char>
13929           &__variables = *variables[hash],
13930           &__variables_names = *variables_names[hash];
13931         bool is_name_found = false;
13932         for (int l = __variables.width() - 1; l>=lind; --l)
13933           if (!std::strcmp(__variables_names[l],argx)) {
13934             is_name_found = true; vind = l; break;
13935           }
13936         if (is_name_found) {
13937           try {
13938             const char *const zero = (char*)std::memchr(__variables[vind],0,__variables[vind].width());
13939             if (!zero) throw CImgArgumentException(0);
13940             CImgList<T>::get_unserialize(__variables[vind].get_shared_points(zero + 1 - __variables[vind].data(),
13941                                                                              __variables[vind].width() - 1)).
13942               move_to(g_list);
13943           } catch (CImgArgumentException&) {
13944             error(true,images,0,0,
13945                   "Command 'input': Variable '%s' has not been assigned with command 'store'.",
13946                   argx);
13947           }
13948           g_list_c = g_list.back().get_split(CImg<char>::vector(0),0,false);
13949           g_list_c.remove(0);
13950           cimglist_for(g_list_c,q) g_list_c[q].resize(1,g_list_c[q].height() + 1,1,1,0).unroll('x');
13951           if (g_list_c.size()!=g_list.size() - 1)
13952             error(true,images,0,0,
13953                   "Command 'input': Invalid binary encoding of variable '%s'.",
13954                   argx);
13955           else if (g_list_c) g_list.remove();
13956         } else error(true,images,0,0,
13957                      "Command 'input': Variable '%s' has not been assigned.",
13958                      argx);
13959       } else {
13960 
13961         // Input filename.
13962         char cext[12];
13963         CImg<char> _filename(4096), filename_tmp(256), options(256);
13964         *cext = *_filename = *filename_tmp = *options = 0;
13965         bool is_network_file = false;
13966         if (cimg_sscanf(argument,"%11[a-zA-Z]:%4095[^,],%255s",
13967                         cext,_filename.data(),options.data())<2 ||
13968             !cext[1] || // Length of 'ext' must be >=2 (avoid case 'C:\\...' on Windows)
13969             !cimg::strcasecmp(cext,"http") || !cimg::strcasecmp(cext,"https")) {
13970           *cext = *_filename = *options = 0;
13971           if (cimg_sscanf(argument,"%4095[^,],%255s",_filename.data(),options.data())!=2) {
13972             std::strncpy(_filename,argument,_filename.width() - 1);
13973             _filename[_filename.width() - 1] = 0;
13974           }
13975         }
13976         strreplace_fw(_filename);
13977         strreplace_fw(options);
13978         CImg<char> __filename0 = CImg<char>::string(_filename);
13979         const char *const _filename0 = __filename0.data();
13980 
13981         // Test for network file requests.
13982         if (!cimg::strncasecmp(_filename,"http://",7) ||
13983             !cimg::strncasecmp(_filename,"https://",8)) {
13984           try {
13985             cimg::load_network(_filename,filename_tmp);
13986           } catch (CImgIOException&) {
13987             print(images,0,"Input file '%s' at position%s",
13988                   _filename0,
13989                   _gmic_selection.data());
13990             error(true,images,0,0,
13991                   "Unreachable network file '%s'.",
13992                   gmic_argument_text());
13993           }
13994           is_network_file = true;
13995           std::strncpy(_filename,filename_tmp,_filename.width() - 1);
13996           _filename[_filename.width() - 1] = 0;
13997           *filename_tmp = 0;
13998         }
13999 
14000         if (*cext) { // Force input to be read as a '.ext' file : generate random filename
14001           if (*_filename=='-' && (!_filename[1] || _filename[1]=='.')) {
14002             // Simplify filename 'ext:-.foo' as '-.ext'.
14003             cimg_snprintf(_filename,_filename.width(),"-.%s",cext);
14004             *cext = 0;
14005           } else {
14006             std::FILE *file = 0;
14007             do {
14008               cimg_snprintf(filename_tmp,filename_tmp.width(),"%s%c%s.%s",
14009                             cimg::temporary_path(),cimg_file_separator,
14010                             cimg::filenamerand(),cext);
14011               if ((file=cimg::std_fopen(filename_tmp,"rb"))!=0) cimg::fclose(file);
14012             } while (file);
14013 
14014             // Make a temporary copy (or link) of the original file.
14015 #if cimg_OS==1
14016             const char *const _filename_path = realpath(_filename,0);
14017             if (!_filename_path || symlink(_filename_path,filename_tmp))
14018               CImg<unsigned char>::get_load_raw(_filename).save_raw(filename_tmp);
14019             if (_filename_path) std::free((void*)_filename_path);
14020 #else // #if cimg_OS==1
14021             CImg<unsigned char>::get_load_raw(_filename).save_raw(filename_tmp);
14022 #endif // #if cimg_OS==1
14023           }
14024         }
14025 
14026         const char
14027           *const filename = *filename_tmp?filename_tmp:_filename,
14028           *const ext = cimg::split_filename(filename);
14029         const bool is_stdin = *filename=='-' && (!filename[1] || filename[1]=='.');
14030 
14031         const char *file_type = 0;
14032         std::FILE *const file = is_stdin?0:cimg::std_fopen(filename,"rb");
14033         longT _siz = 0;
14034         if (file) {
14035           std::fseek(file,0,SEEK_END);
14036           _siz = std::ftell(file);
14037           std::rewind(file);
14038           file_type = *ext?0:cimg::ftype(file,0);
14039           cimg::fclose(file);
14040         }
14041         if (!is_stdin && file && _siz==0) { // Empty file -> Insert an empty image
14042           g_list_c.insert(__filename0);
14043           g_list.insert(1);
14044         } else if (!cimg::strcasecmp("off",ext) || (file_type && !std::strcmp(file_type,"off"))) {
14045 
14046           // 3D object .off file.
14047           print(images,0,"Input 3D object '%s' at position%s",
14048                 _filename0,_gmic_selection.data());
14049 
14050           if (*options)
14051             error(true,images,0,0,
14052                   "Command 'input': File '%s', format does not take any input options (options '%s' specified).",
14053                   _filename0,options.data());
14054 
14055           CImg<float>::get_load_off(primitives,g_list_f,filename).move_to(vertices);
14056           const CImg<float> opacities(1,primitives.size(),1,1,1);
14057           vertices.object3dtoCImg3d(primitives,g_list_f,opacities,false).move_to(g_list);
14058           primitives.assign();
14059           g_list_f.assign();
14060           g_list_c.insert(__filename0);
14061         } else if (!cimg::strcasecmp(ext,"cimg") && *options) {
14062 
14063           // Part of a .cimg file (non-compressed).
14064           float
14065             n0 = -1, x0 = -1, y0 = -1, z0 = -1, c0 = -1,
14066             n1 = -1, x1 = -1, y1 = -1, z1 = -1, c1 = -1;
14067           if ((cimg_sscanf(options,"%f,%f%c",
14068                            &n0,&n1,&end)==2 ||
14069                cimg_sscanf(options,"%f,%f,%f,%f%c",
14070                            &n0,&n1,&x0,&x1,&end)==4 ||
14071                cimg_sscanf(options,"%f,%f,%f,%f,%f,%f%c",
14072                            &n0,&n1,&x0,&y0,&x1,&y1,&end)==6 ||
14073                cimg_sscanf(options,"%f,%f,%f,%f,%f,%f,%f,%f%c",
14074                            &n0,&n1,&x0,&y0,&z0,&x1,&y1,&z1,&end)==8 ||
14075                cimg_sscanf(options,"%f,%f,%f,%f,%f,%f,%f,%f,%f,%f%c",
14076                            &n0,&n1,&x0,&y0,&z0,&c0,&x1,&y1,&z1,&c1,&end)==10) &&
14077               (n0==-1 || n0>=0) && (n1==-1 || n1>=0) &&
14078               (x0==-1 || x0>=0) && (x1==-1 || x1>=0) &&
14079               (y0==-1 || y0>=0) && (y1==-1 || y1>=0) &&
14080               (z0==-1 || z0>=0) && (z1==-1 || z1>=0) &&
14081               (c0==-1 || c0>=0) && (c1==-1 || c1>=0)) {
14082             n0 = cimg::round(n0); n1 = cimg::round(n1);
14083             x0 = cimg::round(x0); x1 = cimg::round(x1);
14084             y0 = cimg::round(y0); y1 = cimg::round(y1);
14085             z0 = cimg::round(z0); z1 = cimg::round(z1);
14086             c0 = cimg::round(c0); c1 = cimg::round(c1);
14087             if (c0==-1 && c1==-1) {
14088               if (z0==-1 && z1==-1) {
14089                 if (y0==-1 && y1==-1) {
14090                   if (x0==-1 && x1==-1) {
14091                     print(images,0,"Input crop [%d] -> [%d] of file '%s' at position%s",
14092                           (int)n0,(int)n1,
14093                           _filename0,_gmic_selection.data());
14094                     g_list.load_cimg(filename,
14095                                      (unsigned int)n0,(unsigned int)n1,
14096                                      0U,0U,0U,0U,~0U,~0U,~0U,~0U);
14097                   } else {
14098                     print(images,0,"Input crop [%d](%d) -> [%d](%d) of file '%s' at position%s",
14099                           (int)n0,(int)x0,(int)n1,(int)x1,
14100                           _filename0,_gmic_selection.data());
14101                     g_list.load_cimg(filename,
14102                                      (unsigned int)n0,(unsigned int)n1,
14103                                      (unsigned int)x0,0U,0U,0U,
14104                                      (unsigned int)x1,~0U,~0U,~0U);
14105                   }
14106                 } else {
14107                   print(images,0,"Input crop [%d](%d,%d) -> [%d](%d,%d) of file '%s' at position%s",
14108                         (int)n0,(int)n1,(int)x0,(int)y0,(int)x1,(int)y1,
14109                         _filename0,_gmic_selection.data());
14110                   g_list.load_cimg(filename,
14111                                    (unsigned int)n0,(unsigned int)n1,
14112                                    (unsigned int)x0,(unsigned int)y0,0U,0U,
14113                                    (unsigned int)x1,(unsigned int)y1,~0U,~0U);
14114                 }
14115               } else {
14116                 print(images,0,"Input crop [%d](%d,%d,%d) -> [%d](%d,%d,%d) of file '%s' "
14117                       "at position%s",
14118                       (int)n0,(int)n1,(int)x0,(int)y0,(int)z0,(int)x1,(int)y1,(int)z1,
14119                       _filename0,_gmic_selection.data());
14120                 g_list.load_cimg(filename,
14121                                  (unsigned int)n0,(unsigned int)n1,
14122                                  (unsigned int)x0,(unsigned int)y0,(unsigned int)z0,0U,
14123                                  (unsigned int)x1,(unsigned int)y1,(unsigned int)z1,~0U);
14124               }
14125             } else {
14126               print(images,0,"Input crop [%d](%d,%d,%d,%d) -> [%d](%d,%d,%d,%d) of file '%s' "
14127                     "at position%s",
14128                     (int)n0,(int)n1,
14129                     (int)x0,(int)y0,(int)z0,(int)c0,
14130                     (int)x1,(int)y1,(int)z1,(int)c1,
14131                     _filename0,_gmic_selection.data());
14132               g_list.load_cimg(filename,
14133                                (unsigned int)n0,(unsigned int)n1,
14134                                (unsigned int)x0,(unsigned int)y0,
14135                                (unsigned int)z0,(unsigned int)c0,
14136                                (unsigned int)x1,(unsigned int)y1,
14137                                (unsigned int)z1,(unsigned int)c1);
14138             }
14139 
14140             if (g_list) {
14141               g_list_c.insert(__filename0);
14142               if (g_list.size()>1)
14143                 g_list_c.insert(g_list.size() - 1,__filename0.copymark());
14144             }
14145           } else
14146             error(true,images,0,0,
14147                   "Command 'input': .cimg file '%s', invalid file options '%s'.",
14148                   _filename0,options.data());
14149 
14150         } else if (!cimg::strcasecmp(ext,"gmz")) {
14151           print(images,0,"Input file '%s' at position%s",
14152                 _filename0,
14153                 _gmic_selection.data());
14154           g_list.load_cimg(filename);
14155           bool is_gmz = false;
14156           const CImg<char> back = g_list?CImg<char>(g_list.back()):CImg<char>::empty();
14157           if (back.width()==1 && back.depth()==1 && back.spectrum()==1 &&
14158               back[0]=='G' && back[1]=='M' && back[2]=='Z' && !back[3]) {
14159             g_list_c = back.get_split(CImg<char>::vector(0),0,false);
14160             if (g_list_c) {
14161               is_gmz = true;
14162               g_list_c.remove(0);
14163               cimglist_for(g_list_c,l)
14164                 g_list_c[l].resize(1,g_list_c[l].height() + 1,1,1,0).
14165                 unroll('x');
14166               g_list.remove();
14167             }
14168           }
14169           if (!is_gmz)
14170             error(true,images,0,0,"Command 'input': File '%s' is not in .gmz format (magic number not found).",
14171                   _filename0);
14172           if (g_list.size()!=g_list_c.size())
14173             error(true,images,0,0,"Command 'input': File '%s' is not in .gmz format "
14174                   "(numbers of images and names do not match).",
14175                   _filename0);
14176 
14177         } else if (!cimg::strcasecmp(ext,"cimg") || !cimg::strcasecmp(ext,"cimgz")) {
14178           print(images,0,"Input file '%s' at position%s",
14179                 _filename0,
14180                 _gmic_selection.data());
14181           g_list.load_cimg(filename);
14182           if (g_list) {
14183             g_list_c.insert(__filename0);
14184             if (g_list.size()>1)
14185               g_list_c.insert(g_list.size() - 1,__filename0.copymark());
14186           }
14187 
14188         } else if (!cimg::strcasecmp(ext,"avi") ||
14189                    !cimg::strcasecmp(ext,"mov") ||
14190                    !cimg::strcasecmp(ext,"asf") ||
14191                    !cimg::strcasecmp(ext,"divx") ||
14192                    !cimg::strcasecmp(ext,"flv") ||
14193                    !cimg::strcasecmp(ext,"mpg") ||
14194                    !cimg::strcasecmp(ext,"m1v") ||
14195                    !cimg::strcasecmp(ext,"m2v") ||
14196                    !cimg::strcasecmp(ext,"m4v") ||
14197                    !cimg::strcasecmp(ext,"mjp") ||
14198                    !cimg::strcasecmp(ext,"mp4") ||
14199                    !cimg::strcasecmp(ext,"mkv") ||
14200                    !cimg::strcasecmp(ext,"mpe") ||
14201                    !cimg::strcasecmp(ext,"movie") ||
14202                    !cimg::strcasecmp(ext,"ogm") ||
14203                    !cimg::strcasecmp(ext,"ogg") ||
14204                    !cimg::strcasecmp(ext,"qt") ||
14205                    !cimg::strcasecmp(ext,"rm") ||
14206                    !cimg::strcasecmp(ext,"vob") ||
14207                    !cimg::strcasecmp(ext,"wmv") ||
14208                    !cimg::strcasecmp(ext,"xvid") ||
14209                    !cimg::strcasecmp(ext,"mpeg")) {
14210 
14211           // Image sequence file.
14212           float first_frame = 0, last_frame = -1, step = 1;
14213           if ((cimg_sscanf(options,"%f,%f%c",&first_frame,&last_frame,&end)==2 ||
14214               cimg_sscanf(options,"%f,%f,%f%c",&first_frame,&last_frame,&step,&end)==3) &&
14215               first_frame>=0 && (last_frame>=first_frame || last_frame==-1) && step>=0) {
14216 
14217             // Read several frames.
14218             step = cimg::round(step);
14219             const unsigned int
14220               _first_frame = (unsigned int)first_frame,
14221               _last_frame = last_frame>=0?(unsigned int)last_frame:~0U;
14222             if (_last_frame!=~0U)
14223               print(images,0,"Input frames %u...%u:%g of file '%s' at position%s",
14224                     _first_frame,_last_frame,step,
14225                     _filename0,
14226                     _gmic_selection.data());
14227             else
14228               print(images,0,"Input frames %u...(last):%g of file '%s' at position%s",
14229                     _first_frame,step,
14230                     _filename0,
14231                     _gmic_selection.data());
14232 
14233             g_list.load_video(filename,_first_frame,_last_frame,(unsigned int)step);
14234           } else if (cimg_sscanf(options,"%f%c",&first_frame,&end)==1 &&
14235                      first_frame>=0) {
14236             // Read a single frame.
14237             const unsigned int _first_frame = (unsigned int)first_frame;
14238             print(images,0,"Input frame %u of file '%s' at position%s",
14239                   _first_frame,_filename0,
14240                   _gmic_selection.data());
14241             g_list.load_video(filename,_first_frame,_first_frame);
14242           } else if (!*options) {
14243             // Read all frames.
14244             print(images,0,"Input all frames of file '%s' at position%s",
14245                   _filename0,
14246                   _gmic_selection.data());
14247             g_list.load_video(filename);
14248           } else
14249             error(true,images,0,0,
14250                   "Command 'input': video file '%s', invalid file options '%s'.",
14251                   _filename0,options.data());
14252           if (g_list) {
14253             g_list_c.insert(__filename0);
14254             if (g_list.size()>1)
14255               g_list_c.insert(g_list.size() - 1,__filename0.copymark());
14256           }
14257         } else if (!cimg::strcasecmp("raw",ext)) {
14258 
14259           // Raw file.
14260           dx = 0; dy = dz = dc = 1;
14261           uint64T offset = 0;
14262           *argx = 0;
14263           if (!*options ||
14264               cimg_sscanf(options,"%f%c",&dx,&end)==1 ||
14265               cimg_sscanf(options,"%f,%f%c",&dx,&dy,&end)==2 ||
14266               cimg_sscanf(options,"%f,%f,%f%c",&dx,&dy,&dz,&end)==3 ||
14267               cimg_sscanf(options,"%f,%f,%f,%f%c",&dx,&dy,&dz,&dc,&end)==4 ||
14268               cimg_sscanf(options,"%f,%f,%f,%f," cimg_fuint64 "%c",&dx,&dy,&dz,&dc,&offset,&end)==5 ||
14269               cimg_sscanf(options,"%255[a-z64]%c",argx,&end)==1 ||
14270               cimg_sscanf(options,"%255[a-z64],%f%c",argx,&dx,&end)==2 ||
14271               cimg_sscanf(options,"%255[a-z64],%f,%f%c",argx,&dx,&dy,&end)==3 ||
14272               cimg_sscanf(options,"%255[a-z64],%f,%f,%f%c",argx,&dx,&dy,&dz,&end)==4 ||
14273               cimg_sscanf(options,"%255[a-z64],%f,%f,%f,%f%c",argx,&dx,&dy,&dz,&dc,&end)==5 ||
14274               cimg_sscanf(options,"%255[a-z64],%f,%f,%f,%f," cimg_fuint64 "%c",argx,&dx,&dy,&dz,&dc,&offset,
14275                           &end)==6) {
14276             const char *const stype = *argx?argx:cimg::type<T>::string();
14277             dx = cimg::round(dx);
14278             dy = cimg::round(dy);
14279             dz = cimg::round(dz);
14280             dc = cimg::round(dc);
14281             if (dx<0 || dy<=0 || dz<=0 || dc<=0)
14282               error(true,images,0,0,
14283                     "Command 'input': raw file '%s', invalid specified "
14284 		    "dimensions %gx%gx%gx%g.",
14285                     _filename0,dx,dy,dz,dc);
14286 
14287             if (offset)
14288               print(images,0,"Input raw file '%s' (offset: %lu) with type '%s' at position%s",
14289                     _filename0,offset,stype,
14290                     _gmic_selection.data());
14291             else
14292               print(images,0,"Input raw file '%s' with type '%s' at position%s",
14293                     _filename0,stype,
14294                     _gmic_selection.data());
14295 
14296 #define gmic_load_raw(value_type,svalue_type) \
14297             if (!cimg::strcasecmp(stype,svalue_type)) \
14298               CImg<value_type>::get_load_raw(filename, \
14299                                              (unsigned int)dx,(unsigned int)dy, \
14300                                              (unsigned int)dz,(unsigned int)dc,false,false,\
14301                                              (cimg_ulong)offset).move_to(g_list);
14302             gmic_load_raw(unsigned char,"uchar")
14303             else gmic_load_raw(unsigned char,"unsigned char")
14304               else gmic_load_raw(char,"char")
14305                 else gmic_load_raw(unsigned short,"ushort")
14306                   else gmic_load_raw(unsigned short,"unsigned short")
14307                     else gmic_load_raw(short,"short")
14308                       else gmic_load_raw(unsigned int,"uint")
14309                         else gmic_load_raw(unsigned int,"unsigned int")
14310                           else gmic_load_raw(int,"int")
14311                             else gmic_load_raw(uint64T,"uint64")
14312                               else gmic_load_raw(uint64T,"unsigned int64")
14313                                 else gmic_load_raw(int64T,"int64")
14314                                   else gmic_load_raw(float,"float")
14315                                     else gmic_load_raw(double,"double")
14316                                       else error(true,images,0,0,
14317                                                  "Command 'input': raw file '%s', "
14318                                                  "invalid specified pixel type '%s'.\n",
14319                                                  _filename0,stype);
14320             g_list_c.insert(__filename0);
14321           } else
14322             error(true,images,0,0,
14323                   "Command 'input': raw file '%s', invalid file options '%s'.",
14324                   _filename0,options.data());
14325         } else if (!cimg::strcasecmp("yuv",ext)) {
14326 
14327           // YUV file.
14328           float first_frame = 0, last_frame = 0, step = 1, ch = 444;
14329           dx = 0; dy = 1;
14330           if ((err = cimg_sscanf(options,"%f,%f,%f,%f,%f,%f",
14331                                  &dx,&dy,&ch,&first_frame,&last_frame,&step))>=1) {
14332             dx = cimg::round(dx);
14333             dy = cimg::round(dy);
14334             const unsigned int ich = (unsigned int)cimg::round(ch);
14335             if (dx<=0 || dy<=0)
14336               error(true,images,0,0,
14337                     "Command 'input': YUV file '%s', specified dimensions (%g,%g) are invalid.",
14338                     _filename0,dx,dy);
14339             if (ich!=420 && ich!=422 && ich!=444)
14340               error(true,images,0,0,
14341                     "Command 'input': YUV file '%s', specified chroma subsampling '%g' is invalid.",
14342                     _filename0,ch);
14343             first_frame = cimg::round(first_frame);
14344             if (err>4) { // Load multiple frames
14345               last_frame = cimg::round(last_frame);
14346               step = cimg::round(step);
14347               print(images,0,"Input frames %g...%g:%g of YUV-%u:%u:%u file '%s' at position%s",
14348                     first_frame,last_frame,step,
14349                     ich/100,(ich/10)%10,ich%10,
14350                     _filename0,
14351                     _gmic_selection.data());
14352               g_list.load_yuv(filename,(unsigned int)dx,(unsigned int)dy,ich,
14353                               (unsigned int)first_frame,(unsigned int)last_frame,
14354                               (unsigned int)step);
14355             } else if (err==4) { // Load a single frame
14356               print(images,0,"Input frames %g of YUV-%u:%u:%u file '%s' at position%s",
14357                     first_frame,
14358                     ich/100,(ich/10)%10,ich%10,
14359                     _filename0,
14360                     _gmic_selection.data());
14361               g_list.load_yuv(filename,(unsigned int)dx,(unsigned int)dy,ich,
14362                               (unsigned int)first_frame,(unsigned int)first_frame);
14363             } else { // Load all frames
14364               print(images,0,"Input all frames of YUV-%u:%u:%u file '%s' at position%s",
14365                     ich/100,(ich/10)%10,ich%10,
14366                     _filename0,
14367                     _gmic_selection.data());
14368               g_list.load_yuv(filename,(unsigned int)dx,(unsigned int)dy,(unsigned int)ch);
14369             }
14370             if (g_list) {
14371               g_list_c.insert(__filename0);
14372               if (g_list.size()>1)
14373                 g_list_c.insert(g_list.size() - 1,__filename0.copymark());
14374             }
14375           } else
14376             error(true,images,0,0,
14377                   "Command 'input': YUV file '%s', invalid or missing file options '%s'.",
14378                   _filename0,options.data());
14379 
14380         } else if (!cimg::strcasecmp("tif",ext) || !cimg::strcasecmp("tiff",ext) ||
14381                    (file_type && !std::strcmp(file_type,"tif"))) {
14382 
14383           // TIFF file.
14384           float first_frame = 0, last_frame = 0, step = 1;
14385 #ifdef cimg_use_tiff
14386           static const TIFFErrorHandler default_handler = TIFFSetWarningHandler(0);
14387           if (is_very_verbose) TIFFSetWarningHandler(default_handler);
14388           else TIFFSetWarningHandler(0);
14389 #endif // #ifdef cimg_use_tiff
14390           if ((err = cimg_sscanf(options,"%f,%f,%f",&first_frame,&last_frame,&step))>0) {
14391             first_frame = cimg::round(first_frame);
14392             if (err>1) { // Load multiple frames
14393               last_frame = cimg::round(last_frame);
14394               step = cimg::round(step);
14395               print(images,0,"Input frames %g...%g:%g of TIFF file '%s' at position%s",
14396                     first_frame,last_frame,step,
14397                     _filename0,
14398                     _gmic_selection.data());
14399               g_list.load_tiff(filename,(unsigned int)first_frame,(unsigned int)last_frame,
14400                                (unsigned int)step);
14401             } else if (err==1) { // Load a single frame
14402               print(images,0,"Input frames %g of TIFF file '%s' at position%s",
14403                     first_frame,
14404                     _filename0,
14405                     _gmic_selection.data());
14406               g_list.load_tiff(filename,(unsigned int)first_frame,(unsigned int)first_frame);
14407             }
14408           } else { // Load all frames
14409             if (*options) error(true,images,0,0,
14410                                 "Command 'input': TIFF file '%s', "
14411                                 "invalid file options '%s'.",
14412                                 _filename0,options.data());
14413             print(images,0,"Input all frames of TIFF file '%s' at position%s",
14414                   _filename0,
14415                   _gmic_selection.data());
14416             g_list.load_tiff(filename);
14417           }
14418           if (g_list) {
14419             g_list_c.insert(__filename0);
14420             if (g_list.size()>1)
14421               g_list_c.insert(g_list.size() - 1,__filename0.copymark());
14422           }
14423         } else if (!cimg::strcasecmp("gmic",ext)) {
14424 
14425           // G'MIC command file.
14426           const bool add_debug_info = (*options!='0');
14427           print(images,0,"Input custom command file '%s'%s",
14428                 _filename0,!add_debug_info?" without debug info":"");
14429           unsigned int count_new = 0, count_replaced = 0;
14430           std::FILE *const gfile = cimg::fopen(filename,"rb");
14431 
14432           bool is_entrypoint = false, is_add_error = false;
14433           status.move_to(o_status); // Save status because 'add_commands' can change it, with error()
14434           int o_verbosity = verbosity;
14435           const bool o_is_debug = is_debug;
14436           verbosity = 0;
14437           is_debug = false;
14438           try {
14439             add_commands(gfile,add_debug_info?filename:0,&count_new,&count_replaced,
14440                          allow_entrypoint && callstack.size()==1 && !is_command_input?&is_entrypoint:0);
14441           } catch (...) {
14442             is_add_error = true; is_entrypoint = false;
14443           }
14444           is_debug = o_is_debug;
14445           verbosity = o_verbosity;
14446           o_status.move_to(status);
14447           if (is_add_error) {
14448             if (is_network_file)
14449               error(true,images,0,0,
14450                     "Command 'input': Unable to load custom command file '%s' from network.",
14451                     _filename0);
14452             else
14453               error(true,images,0,0,
14454                     "Command 'input': File '%s' is not recognized as a custom command file.",
14455                     _filename0);
14456           }
14457           cimg::fclose(gfile);
14458           if (is_verbose) {
14459             unsigned int count_total = 0;
14460             for (unsigned int l = 0; l<gmic_comslots; ++l) count_total+=commands[l].size();
14461             cimg::mutex(29);
14462             if (count_new && count_replaced)
14463               std::fprintf(cimg::output()," (%u new, %u replaced, total: %u).",
14464                            count_new,count_replaced,count_total);
14465             else if (count_new)
14466               std::fprintf(cimg::output()," (%u new, total: %u).",
14467                            count_new,count_total);
14468             else
14469               std::fprintf(cimg::output()," (%u replaced, total: %u).",
14470                            count_replaced,count_total);
14471             std::fflush(cimg::output());
14472             cimg::mutex(29,0);
14473           }
14474           if (is_entrypoint) { // Tell parser to run '_main_' in next iteration
14475             verbosity++;
14476             --position;
14477             run_entrypoint = true;
14478           }
14479           continue;
14480 
14481         } else { // Other file types.
14482 
14483           // Check if a custom command handling requested file format exists.
14484           cimg_snprintf(formula,_formula.width(),"input_%s",ext);
14485           hash = hashcode(formula,false);
14486           if (search_sorted(formula,commands_names[hash],commands_names[hash].size(),pattern)) { // Command found
14487             cimg_snprintf(formula,_formula.width(),"input_%s[] \"%s\"",ext,_filename0);
14488             const CImgList<char> ncommands_line = commands_line_to_CImgList(formula);
14489             unsigned int nposition = 0;
14490             CImg<char>::string("").move_to(callstack); // Anonymous scope
14491             _run(ncommands_line,nposition,g_list,g_list_c,images,images_names,variables_sizes,0,0,0);
14492             callstack.remove();
14493 
14494           } else { // Not found -> Try generic image loader
14495 
14496             print(images,0,"Input file '%s' at position%s",
14497                   _filename0,
14498                   _gmic_selection.data());
14499             if (*options)
14500               error(true,images,0,0,
14501                     "Command 'input': File '%s', format does not take any input options (options '%s' specified).",
14502                     _filename0,options.data());
14503 
14504             try {
14505               try {
14506                 g_list.load(filename);
14507               } catch (CImgIOException&) {
14508                 if (is_network_file)
14509                   error(true,images,0,0,
14510                         "Command 'input': Unable to load image file '%s' from network.",
14511                         _filename0);
14512                 else throw;
14513               }
14514 
14515               // If .gmz file without extension, process images names anyway.
14516               bool is_gmz = false;
14517               const CImg<char> back = g_list?CImg<char>(g_list.back()):CImg<char>::empty();
14518               if (back.width()==1 && back.depth()==1 && back.spectrum()==1 &&
14519                   back[0]=='G' && back[1]=='M' && back[2]=='Z' && !back[3]) {
14520                 g_list_c = back.get_split(CImg<char>::vector(0),0,false);
14521                 if (g_list_c) {
14522                   is_gmz = true;
14523                   g_list_c.remove(0);
14524                   cimglist_for(g_list_c,l)
14525                     g_list_c[l].resize(1,g_list_c[l].height() + 1,1,1,0).
14526                     unroll('x');
14527                   g_list.remove(g_list.size() - 1);
14528                 }
14529               }
14530 
14531               if (g_list && !is_gmz) {
14532                 g_list_c.insert(__filename0);
14533                 if (g_list.size()>1) {
14534                   g_list_c.insert(g_list.size() - 1,__filename0.copymark());
14535                 }
14536               }
14537             } catch (CImgException&) {
14538               std::FILE *efile = 0;
14539               if (!(efile = cimg::std_fopen(filename,"r"))) {
14540                 if (is_command_input)
14541                   error(true,images,0,0,
14542                         "Unknown filename '%s'.",
14543                         gmic_argument_text());
14544                 else {
14545                   CImg<char>::string(filename).move_to(name);
14546                   const unsigned int foff = (*name=='+' || *name=='-') + (*name=='-' && name[1]=='-');
14547                   const char *misspelled = 0;
14548                   char *const posb = std::strchr(name,'[');
14549                   if (posb) *posb = 0; // Discard selection from the command name
14550                   int dmin = 4;
14551                   // Look for a builtin command
14552                   for (unsigned int l = 0; l<sizeof(builtin_commands_names)/sizeof(char*); ++l) {
14553                     const char *const c = builtin_commands_names[l];
14554                     const int d = levenshtein(c,name.data() + foff);
14555                     if (d<dmin) { dmin = d; misspelled = builtin_commands_names[l]; }
14556                   }
14557                   for (unsigned int i = 0; i<gmic_comslots; ++i) // Look for a custom command
14558                     cimglist_for(commands_names[i],l) {
14559                       const char *const c = commands_names[i][l].data();
14560                       const int d = levenshtein(c,name.data() + foff);
14561                       if (d<dmin) { dmin = d; misspelled = commands_names[i][l].data(); }
14562                     }
14563                   if (misspelled)
14564                     error(true,images,0,0,
14565                           "Unknown command or filename '%s' (did you mean '%s' ?).",
14566                           gmic_argument_text(),misspelled);
14567                   else error(true,images,0,0,
14568                              "Unknown command or filename '%s'.",
14569                              gmic_argument_text());
14570                 }
14571               } else { std::fclose(efile); throw; }
14572             }
14573           }
14574         }
14575 
14576         if (*filename_tmp) std::remove(filename_tmp); // Clean temporary file if used
14577         if (is_network_file) std::remove(_filename);  // Clean temporary file if network input
14578       }
14579 
14580       if (is_verbose) {
14581         cimg::mutex(29);
14582         if (g_list) {
14583           const unsigned int last = g_list.size() - 1;
14584           if (g_list.size()==1) {
14585             if (g_list[0].is_CImg3d(false))
14586               std::fprintf(cimg::output()," (%u vertices, %u primitives).",
14587                            cimg::float2uint((float)g_list(0,6)),
14588                            cimg::float2uint((float)g_list(0,7)));
14589             else
14590               std::fprintf(cimg::output()," (1 image %dx%dx%dx%d).",
14591                            g_list[0].width(),g_list[0].height(),
14592                            g_list[0].depth(),g_list[0].spectrum());
14593           } else
14594             std::fprintf(cimg::output()," (%u images [0] = %dx%dx%dx%d, %s[%u] = %dx%dx%dx%d).",
14595                          g_list.size(),
14596                          g_list[0].width(),g_list[0].height(),
14597                          g_list[0].depth(),g_list[0].spectrum(),
14598                          last==1?"":"(...),",last,
14599                          g_list[last].width(),g_list[last].height(),
14600                          g_list[last].depth(),g_list[last].spectrum());
14601         } else {
14602           std::fprintf(cimg::output()," (no available data).");
14603           g_list_c.assign();
14604         }
14605         std::fflush(cimg::output());
14606         cimg::mutex(29,0);
14607       }
14608 
14609       for (unsigned int l = 0, lsiz = selection.height() - 1U, off = 0; l<=lsiz; ++l) {
14610         const unsigned int uind = selection[l] + off;
14611         off+=g_list.size();
14612         if (l!=lsiz) {
14613           images.insert(g_list,uind);
14614           images_names.insert(g_list_c,uind);
14615         } else {
14616           g_list.move_to(images,uind);
14617           g_list_c.move_to(images_names,uind);
14618         }
14619       }
14620       g_list.assign(); g_list_c.assign();
14621       is_change = true;
14622     } // End main parsing loop of _run()
14623 
14624     // Wait for remaining threads to finish and possibly throw exceptions from threads.
14625     cimglist_for(gmic_threads,k) wait_threads(&gmic_threads[k],true,(T)0);
14626     cimglist_for(gmic_threads,k) cimg_forY(gmic_threads[k],l)
14627       if (gmic_threads(k,l).exception._message) throw gmic_threads(k,l).exception;
14628 
14629     // Post-check global environment consistency.
14630     if (images_names.size()!=images.size())
14631       error(true,images,0,0,
14632             "Internal error: Images (%u) and images names (%u) have different size, "
14633 	    "at return point.",
14634             images_names.size(),images.size());
14635     if (!callstack)
14636       error(true,images,0,0,
14637             "Internal error: Empty call stack at return point.");
14638 
14639     // Post-check local environment consistency.
14640     if (!is_quit && !is_return) {
14641       const CImg<char>& s = callstack.back();
14642       if (s[0]=='*' && (s[1]=='d' || s[1]=='i' || s[1]=='r' || s[1]=='f' || (s[1]=='l' && !is_endlocal))) {
14643         unsigned int reference_line = ~0U;
14644         if (cimg_sscanf(s,"*%*[a-z]#%u",&reference_line)==1)
14645           error(true,images,0,0,
14646                 "A '%s' command is missing (for '%s', line #%u), before return point.",
14647                 s[1]=='d'?"while":s[1]=='i'?"endif":s[1]=='r' || s[1]=='f'?"done":"endlocal",
14648                 s[1]=='d'?"do":s[1]=='i'?"if":s[1]=='r'?"repeat":s[1]=='f'?"for":"local",
14649                 reference_line);
14650         else error(true,images,0,0,
14651               "A '%s' command is missing, before return point.",
14652               s[1]=='d'?"while":s[1]=='i'?"endif":s[1]=='r'?"done":s[1]=='f'?"for":"endlocal");
14653       }
14654     } else if (initial_callstack_size<callstack.size()) callstack.remove(initial_callstack_size,callstack.size() - 1);
14655 
14656     // Post-check validity of shared images.
14657     cimglist_for(images,l) gmic_check(images[l]);
14658 
14659     // Display or print result.
14660     if (verbosity>0 && is_change && !is_quit && !is_return && callstack.size()==1 && images) {
14661       if (!std::strcmp(set_variable("_host","",'.',0),"cli")) {
14662         if (is_display_available) {
14663           CImgList<unsigned int> lselection, lselection3d;
14664           bool is_first3d = false;
14665           display_window(0).assign();
14666           cimglist_for(images,l) {
14667             const bool is_3d = images[l].is_CImg3d(false);
14668             if (!l) is_first3d = is_3d;
14669             CImg<unsigned int>::vector(l).move_to(is_3d?lselection3d:lselection);
14670           }
14671           if (is_first3d) {
14672             display_objects3d(images,images_names,lselection3d>'y',CImg<unsigned char>::empty(),false);
14673             if (lselection)
14674               display_images(images,images_names,lselection>'y',0,false);
14675           } else {
14676             if (lselection)
14677               display_images(images,images_names,lselection>'y',0,false);
14678             if (lselection3d)
14679               display_objects3d(images,images_names,lselection3d>'y',CImg<unsigned char>::empty(),false);
14680           }
14681         } else {
14682           CImg<unsigned int> seq(1,images.width());
14683           cimg_forY(seq,y) seq[y] = y;
14684           print_images(images,images_names,seq,true);
14685         }
14686       }
14687       is_change = false;
14688     }
14689 
14690     if (is_debug) debug(images,"%sExit scope '%s/'.%s\n",
14691                         cimg::t_bold,callstack.back().data(),cimg::t_normal);
14692 
14693     if (callstack.size()==1) {
14694       if (is_quit) {
14695         if (verbosity>=1 || is_debug) {
14696           std::fputc('\n',cimg::output());
14697           std::fflush(cimg::output());
14698         }
14699       } else {
14700         print(images,0,"End G'MIC interpreter.\n");
14701         is_quit = true;
14702       }
14703     }
14704 
14705     verbosity = starting_verbosity;
14706 
14707   } catch (gmic_exception&) {
14708     // Wait for remaining threads to finish.
14709     cimglist_for(gmic_threads,k) wait_threads(&gmic_threads[k],true,(T)0);
14710     throw;
14711 
14712   } catch (CImgAbortException &) { // Special case of abort (abort from a CImg method)
14713     // Wait for remaining threads to finish.
14714     cimglist_for(gmic_threads,k) wait_threads(&gmic_threads[k],true,(T)0);
14715 
14716     // Do the same as for a cancellation point.
14717     const bool is_very_verbose = verbosity>1 || is_debug;
14718     if (is_very_verbose) print(images,0,"Abort G'MIC interpreter (caught abort signal).");
14719     dowhiles.assign(nb_dowhiles = 0);
14720     fordones.assign(nb_fordones = 0);
14721     repeatdones.assign(nb_repeatdones = 0);
14722     position = commands_line.size();
14723     is_change = false;
14724     is_quit = true;
14725 
14726   } catch (CImgException &e) {
14727     // Wait for remaining threads to finish.
14728     cimglist_for(gmic_threads,k) wait_threads(&gmic_threads[k],true,(T)0);
14729 
14730     const char *const e_ptr = e.what() + (!std::strncmp(e.what(),"[gmic_math_parser] ",19)?19:0);
14731     CImg<char> error_message(e_ptr,(unsigned int)std::strlen(e_ptr) + 1);
14732 
14733     const char *const s_fopen = "cimg::fopen(): Failed to open file '";
14734     const unsigned int l_fopen = (unsigned int)std::strlen(s_fopen);
14735     if (!std::strncmp(error_message,s_fopen,l_fopen) &&
14736         !std::strcmp(error_message.end() - 18,"' with mode 'rb'.")) {
14737       error_message[error_message.width() - 18] = 0;
14738       error(true,images,0,0,"Unknown filename '%s'.",error_message.data(l_fopen));
14739     }
14740     for (char *str = std::strstr(error_message,"CImg<"); str; str = std::strstr(str,"CImg<")) {
14741       str[0] = 'g'; str[1] = 'm'; str[2] = 'i'; str[3] = 'c';
14742     }
14743     for (char *str = std::strstr(error_message,"CImgList<"); str; str = std::strstr(str,"CImgList<")) {
14744       str[0] = 'g'; str[1] = 'm'; str[2] = 'i'; str[3] = 'c';
14745     }
14746     for (char *str = std::strstr(error_message,"cimg:"); str; str = std::strstr(str,"cimg:")) {
14747       str[0] = 'g'; str[1] = 'm'; str[2] = 'i'; str[3] = 'c';
14748     }
14749     if (*command) {
14750       const char *em = error_message.data();
14751       if (!std::strncmp("gmic<",em,5)) {
14752         em = std::strstr(em,"(): ");
14753         if (em) em+=4; else em = error_message.data();
14754       }
14755       error(true,images,0,command,"Command '%s': %s",command,em);
14756     } else error(true,images,0,0,"%s",error_message.data());
14757   }
14758   if (!is_endlocal) debug_line = initial_debug_line;
14759   else {
14760     if (next_debug_line!=~0U) { debug_line = next_debug_line; next_debug_line = ~0U; }
14761     if (next_debug_filename!=~0U) { debug_filename = next_debug_filename; next_debug_filename = ~0U; }
14762   }
14763 
14764   // Remove current run from managed list of gmic runs.
14765   cimg::mutex(24);
14766   for (int k = grl.width() - 1; k>=0; --k) {
14767     CImg<void*> &_gr = grl[k];
14768     if (_gr[0]==(void*)this &&
14769         _gr[1]==(void*)&images &&
14770         _gr[2]==(void*)&images_names &&
14771         _gr[3]==(void*)&parent_images &&
14772         _gr[4]==(void*)&parent_images_names &&
14773         _gr[5]==(void*)variables_sizes &&
14774         _gr[6]==(void*)command_selection) {
14775       grl.remove(k); break;
14776     }
14777   }
14778   cimg::mutex(24,0);
14779   return *this;
14780 }
14781 
14782 // Explicitly instantiate constructors and destructor when building the library.
14783 #define gmic_instantiate(pt) \
14784 template gmic::gmic(const char *const commands_line, const char *const custom_commands, \
14785                     const bool include_stdlib, float *const p_progress, bool *const p_is_abort, \
14786                     const pt& pixel_type); \
14787 template gmic::gmic(const char *const commands_line, \
14788                     gmic_list<pt>& images, gmic_list<char>& images_names, \
14789                     const char *const custom_commands, const bool include_stdlib, \
14790                     float *const p_progress, bool *const p_is_abort); \
14791 template gmic& gmic::run(const char *const commands_line, \
14792                          float *const p_progress, bool *const p_is_abort,\
14793                          const pt& pixel_type); \
14794 template gmic& gmic::run(const char *const commands_line, \
14795                          gmic_list<pt> &images, gmic_list<char> &images_names, \
14796                          float *const p_progress, bool *const p_is_abort); \
14797 template CImgList<pt>::~CImgList()
14798 
14799 #ifdef gmic_pixel_type
14800 gmic_instantiate(gmic_pixel_type);
14801 #endif
14802 #ifdef gmic_pixel_type2
14803 gmic_instantiate(gmic_pixel_type2);
14804 #endif
14805 template CImgList<char>::~CImgList();
14806 
14807 #endif // #ifdef cimg_plugin
14808