int jbd2_journal_get_create_access(handle_t *handle, struct buffer_head *bh)
{
	transaction_t *transaction = handle->h_transaction;
	journal_t *journal;
	struct journal_head *jh = jbd2_journal_add_journal_head(bh);
	int err;

	jbd_debug(5, "journal_head %p\n", jh);
	err = -EROFS;
	if (is_handle_aborted(handle))
		goto out;
	journal = transaction->t_journal;
	err = 0;

	JBUFFER_TRACE(jh, "entry");
	/*
	 * The buffer may already belong to this transaction due to pre-zeroing
	 * in the filesystem's new_block code.  It may also be on the previous,
	 * committing transaction's lists, but it HAS to be in Forget state in
	 * that case: the transaction must have deleted the buffer for it to be
	 * reused here.
	 */
	spin_lock(&jh->b_state_lock);
	J_ASSERT_JH(jh, (jh->b_transaction == transaction ||
		jh->b_transaction == NULL ||
		(jh->b_transaction == journal->j_committing_transaction &&
			  jh->b_jlist == BJ_Forget)));

	J_ASSERT_JH(jh, jh->b_next_transaction == NULL);
	J_ASSERT_JH(jh, buffer_locked(jh2bh(jh)));

	if (jh->b_transaction == NULL) {
		/*
		 * Previous jbd2_journal_forget() could have left the buffer
		 * with jbddirty bit set because it was being committed. When
		 * the commit finished, we've filed the buffer for
		 * checkpointing and marked it dirty. Now we are reallocating
		 * the buffer so the transaction freeing it must have
		 * committed and so it's safe to clear the dirty bit.
		 */
		clear_buffer_dirty(jh2bh(jh));
		/* first access by this transaction */
		jh->b_modified = 0;

		JBUFFER_TRACE(jh, "file as BJ_Reserved");
		spin_lock(&journal->j_list_lock);
		__jbd2_journal_file_buffer(jh, transaction, BJ_Reserved);
		spin_unlock(&journal->j_list_lock);
	} else if (jh->b_transaction == journal->j_committing_transaction) {
		/* first access by this transaction */
		jh->b_modified = 0;

		JBUFFER_TRACE(jh, "set next transaction");
		spin_lock(&journal->j_list_lock);
		jh->b_next_transaction = transaction;
		spin_unlock(&journal->j_list_lock);
	}
	spin_unlock(&jh->b_state_lock);

	/*
	 * akpm: I added this.  ext3_alloc_branch can pick up new indirect
	 * blocks which contain freed but then revoked metadata.  We need
	 * to cancel the revoke in case we end up freeing it yet again
	 * and the reallocating as data - this would cause a second revoke,
	 * which hits an assertion error.
	 */
	JBUFFER_TRACE(jh, "cancelling revoke");
	jbd2_journal_cancel_revoke(handle, jh);
out:
	jbd2_journal_put_journal_head(jh);
	return err;
}


    inline char option(const char *const name, const int argc, const char *const *const argv,
                       const char _default, const char *const usage=0) {
      const char *const s = cimg::option(name,argc,argv,(char*)0);
      const char res = s?*s:_default;
      char tmp[8];
      *tmp = res; tmp[1] = 0;
      cimg::option(name,0,0,tmp,usage);
      return res;
    }


      static double _mp_correlate(_cimg_math_parser &mp, bool is_convolve) {
        double *ptrd = &_mp_arg(1) + 1;
        const double *const ptrA = &_mp_arg(2) + 1, *const ptrM = &_mp_arg(7) + 1;
        const unsigned int
          wA = (unsigned int)mp.opcode[3],
          hA = (unsigned int)mp.opcode[4],
          dA = (unsigned int)mp.opcode[5],
          sA = (unsigned int)mp.opcode[6],
          wM = (unsigned int)mp.opcode[8],
          hM = (unsigned int)mp.opcode[9],
          dM = (unsigned int)mp.opcode[10],
          sM = (unsigned int)mp.opcode[11],
          boundary_conditions = (unsigned int)_mp_arg(12),
          channel_mode = (unsigned int)mp.opcode[14];
        const bool
          is_normalized = (bool)_mp_arg(13),
          interpolation_type = (bool)_mp_arg(30);
        const int
          xcenter = mp.opcode[15]!=~0U?(int)_mp_arg(15):(int)(~0U>>1),
          ycenter = mp.opcode[16]!=~0U?(int)_mp_arg(16):(int)(~0U>>1),
          zcenter = mp.opcode[17]!=~0U?(int)_mp_arg(17):(int)(~0U>>1),
          xstart = (int)mp.opcode[18],
          ystart = (int)mp.opcode[19],
          zstart = (int)mp.opcode[20],
          xend = (int)mp.opcode[21],
          yend = (int)mp.opcode[22],
          zend = (int)mp.opcode[23];
        const float
          xstride = (float)_mp_arg(24),
          ystride = (float)_mp_arg(25),
          zstride = (float)_mp_arg(26),
          xdilation = (float)_mp_arg(27),
          ydilation = (float)_mp_arg(28),
          zdilation = (float)_mp_arg(29);
        CImg<doubleT> res;
        if (is_convolve) res = CImg<doubleT>(ptrA,wA,hA,dA,sA,true).
                           get_convolve(CImg<doubleT>(ptrM,wM,hM,dM,sM,true),
                                        boundary_conditions,is_normalized,channel_mode,
                                        xcenter,ycenter,zcenter,
                                        xstart,ystart,zstart,
                                        xend,yend,zend,
                                        xstride,ystride,zstride,
                                        xdilation,ydilation,zdilation,
                                        interpolation_type);
        else res = CImg<doubleT>(ptrA,wA,hA,dA,sA,true).
               get_correlate(CImg<doubleT>(ptrM,wM,hM,dM,sM,true),
                             boundary_conditions,is_normalized,channel_mode,
                             xcenter,ycenter,zcenter,
                             xstart,ystart,zstart,
                             xend,yend,zend,
                             xstride,ystride,zstride,
                             xdilation,ydilation,zdilation,
                             interpolation_type);
        CImg<doubleT>(ptrd,res._width,res._height,res._depth,res._spectrum,true) = res;
        return cimg::type<double>::nan();
      }


    CImg<T>& load_jpeg(std::FILE *const file) {
      return _load_jpeg(file,0);
    }


    static float _cimg_lanczos(const float x) {
      if (x<=-2 || x>=2) return 0;
      const float a = (float)cimg::PI*x, b = 0.5f*a;
      return (float)(x?std::sin(a)*std::sin(b)/(a*b):1);
    }


  inline CImg<T> transpose(const CImg<T>& instance) {
    return instance.get_transpose();
  }


    bool is_event() const {
      return _is_event;
    }


    CImg<T>& draw_point(const int x0, const int y0,
                        const tc *const color, const float opacity=1) {
      return draw_point(x0,y0,0,color,opacity);
    }


    const CImg<T>& save_pnm(std::FILE *const file, const unsigned int bytes_per_pixel=0) const {
      return _save_pnm(file,0,bytes_per_pixel);
    }


    static unsigned int keycode(const char *const keycode) {
#define _cimg_keycode(k) if (!cimg::strcasecmp(keycode,#k)) return cimg::key##k;
      _cimg_keycode(ESC); _cimg_keycode(F1); _cimg_keycode(F2); _cimg_keycode(F3);
      _cimg_keycode(F4); _cimg_keycode(F5); _cimg_keycode(F6); _cimg_keycode(F7);
      _cimg_keycode(F8); _cimg_keycode(F9); _cimg_keycode(F10); _cimg_keycode(F11);
      _cimg_keycode(F12); _cimg_keycode(PAUSE); _cimg_keycode(1); _cimg_keycode(2);
      _cimg_keycode(3); _cimg_keycode(4); _cimg_keycode(5); _cimg_keycode(6);
      _cimg_keycode(7); _cimg_keycode(8); _cimg_keycode(9); _cimg_keycode(0);
      _cimg_keycode(BACKSPACE); _cimg_keycode(INSERT); _cimg_keycode(HOME);
      _cimg_keycode(PAGEUP); _cimg_keycode(TAB); _cimg_keycode(Q); _cimg_keycode(W);
      _cimg_keycode(E); _cimg_keycode(R); _cimg_keycode(T); _cimg_keycode(Y);
      _cimg_keycode(U); _cimg_keycode(I); _cimg_keycode(O); _cimg_keycode(P);
      _cimg_keycode(DELETE); _cimg_keycode(END); _cimg_keycode(PAGEDOWN);
      _cimg_keycode(CAPSLOCK); _cimg_keycode(A); _cimg_keycode(S); _cimg_keycode(D);
      _cimg_keycode(F); _cimg_keycode(G); _cimg_keycode(H); _cimg_keycode(J);
      _cimg_keycode(K); _cimg_keycode(L); _cimg_keycode(ENTER);
      _cimg_keycode(SHIFTLEFT); _cimg_keycode(Z); _cimg_keycode(X); _cimg_keycode(C);
      _cimg_keycode(V); _cimg_keycode(B); _cimg_keycode(N); _cimg_keycode(M);
      _cimg_keycode(SHIFTRIGHT); _cimg_keycode(ARROWUP); _cimg_keycode(CTRLLEFT);
      _cimg_keycode(APPLEFT); _cimg_keycode(ALT); _cimg_keycode(SPACE); _cimg_keycode(ALTGR);
      _cimg_keycode(APPRIGHT); _cimg_keycode(MENU); _cimg_keycode(CTRLRIGHT);
      _cimg_keycode(ARROWLEFT); _cimg_keycode(ARROWDOWN); _cimg_keycode(ARROWRIGHT);
      _cimg_keycode(PAD0); _cimg_keycode(PAD1); _cimg_keycode(PAD2);
      _cimg_keycode(PAD3); _cimg_keycode(PAD4); _cimg_keycode(PAD5);
      _cimg_keycode(PAD6); _cimg_keycode(PAD7); _cimg_keycode(PAD8);
      _cimg_keycode(PAD9); _cimg_keycode(PADADD); _cimg_keycode(PADSUB);
      _cimg_keycode(PADMUL); _cimg_keycode(PADDIV);
      return 0;
    }


    CImg<T>& draw_object3d(LibBoard::Board& board,
                           const float x0, const float y0, const float z0,
                           const CImg<tp>& vertices, const CImgList<tf>& primitives,
                           const CImgList<tc>& colors, const CImg<to>& opacities,
                           const unsigned int render_type=4,
                           const bool is_double_sided=false, const float focale=700,
                           const float lightx=0, const float lighty=0, const float lightz=-5e8,
                           const float specular_lightness=0.2f, const float specular_shininess=0.1f,
                           const float g_opacity=1) {
      return draw_object3d(board,x0,y0,z0,vertices,primitives,colors,opacities,render_type,
                           is_double_sided,focale,lightx,lighty,lightz,
                           specular_lightness,specular_shininess,g_opacity,CImg<floatT>::empty());
    }


    const CImg<T>& _save_png(std::FILE *const file, const char *const filename,
                             const unsigned int bytes_per_pixel=0) const {
      if (!file && !filename)
        throw CImgArgumentException(_cimg_instance
                                    "save_png(): Specified filename is (null).",
                                    cimg_instance);
      if (is_empty()) { cimg::fempty(file,filename); return *this; }

#ifndef cimg_use_png
      cimg::unused(bytes_per_pixel);
      if (!file) return save_other(filename);
      else throw CImgIOException(_cimg_instance
                                 "save_png(): Unable to save data in '(*FILE)' unless libpng is enabled.",
                                 cimg_instance);
#else

#if defined __GNUC__
      const char *volatile nfilename = filename; // Use 'volatile' to avoid (wrong) g++ warning
      std::FILE *volatile nfile = file?file:cimg::fopen(nfilename,"wb");
      volatile double stmin, stmax = (double)max_min(stmin);
#else
      const char *nfilename = filename;
      std::FILE *nfile = file?file:cimg::fopen(nfilename,"wb");
      double stmin, stmax = (double)max_min(stmin);
#endif

      if (_depth>1)
        cimg::warn(_cimg_instance
                   "save_png(): Instance is volumetric, only the first slice will be saved in file '%s'.",
                   cimg_instance,
                   filename);

      if (_spectrum>4)
        cimg::warn(_cimg_instance
                   "save_png(): Instance is multispectral, only the three first channels will be saved in file '%s'.",
                   cimg_instance,
                   filename);

      if (stmin<0 || (bytes_per_pixel==1 && stmax>=256) || stmax>=65536)
        cimg::warn(_cimg_instance
                   "save_png(): Instance has pixel values in [%g,%g], probable type overflow in file '%s'.",
                   cimg_instance,
                   stmin,stmax,filename);

      // Setup PNG structures for write
      png_voidp user_error_ptr = 0;
      png_error_ptr user_error_fn = 0, user_warning_fn = 0;
      png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING,user_error_ptr, user_error_fn,
                                                    user_warning_fn);
      if (!png_ptr){
        if (!file) cimg::fclose(nfile);
        throw CImgIOException(_cimg_instance
                              "save_png(): Failed to initialize 'png_ptr' structure when saving file '%s'.",
                              cimg_instance,
                              nfilename?nfilename:"(FILE*)");
      }
      png_infop info_ptr = png_create_info_struct(png_ptr);
      if (!info_ptr) {
        png_destroy_write_struct(&png_ptr,(png_infopp)0);
        if (!file) cimg::fclose(nfile);
        throw CImgIOException(_cimg_instance
                              "save_png(): Failed to initialize 'info_ptr' structure when saving file '%s'.",
                              cimg_instance,
                              nfilename?nfilename:"(FILE*)");
      }
      if (setjmp(png_jmpbuf(png_ptr))) {
        png_destroy_write_struct(&png_ptr, &info_ptr);
        if (!file) cimg::fclose(nfile);
        throw CImgIOException(_cimg_instance
                              "save_png(): Encountered unknown fatal error in libpng when saving file '%s'.",
                              cimg_instance,
                              nfilename?nfilename:"(FILE*)");
      }
      png_init_io(png_ptr, nfile);

      const int bit_depth = bytes_per_pixel?(bytes_per_pixel*8):(stmax>=256?16:8);

      int color_type;
      switch (spectrum()) {
      case 1 : color_type = PNG_COLOR_TYPE_GRAY; break;
      case 2 : color_type = PNG_COLOR_TYPE_GRAY_ALPHA; break;
      case 3 : color_type = PNG_COLOR_TYPE_RGB; break;
      default : color_type = PNG_COLOR_TYPE_RGB_ALPHA;
      }
      const int interlace_type = PNG_INTERLACE_NONE;
      const int compression_type = PNG_COMPRESSION_TYPE_DEFAULT;
      const int filter_method = PNG_FILTER_TYPE_DEFAULT;
      png_set_IHDR(png_ptr,info_ptr,_width,_height,bit_depth,color_type,interlace_type,compression_type,filter_method);
      png_write_info(png_ptr,info_ptr);
      const int byte_depth = bit_depth>>3;
      const int numChan = spectrum()>4?4:spectrum();
      const int pixel_bit_depth_flag = numChan * (bit_depth - 1);

      // Allocate Memory for Image Save and Fill pixel data
      png_bytep *const imgData = new png_byte*[_height];
      for (unsigned int row = 0; row<_height; ++row) imgData[row] = new png_byte[byte_depth*numChan*_width];
      const T *pC0 = data(0,0,0,0);
      switch (pixel_bit_depth_flag) {
      case 7 :  { // Gray 8-bit
        cimg_forY(*this,y) {
          unsigned char *ptrd = imgData[y];
          cimg_forX(*this,x) *(ptrd++) = (unsigned char)*(pC0++);
        }
      } break;
      case 14 : { // Gray w/ Alpha 8-bit
        const T *pC1 = data(0,0,0,1);
        cimg_forY(*this,y) {
          unsigned char *ptrd = imgData[y];
          cimg_forX(*this,x) {
            *(ptrd++) = (unsigned char)*(pC0++);
            *(ptrd++) = (unsigned char)*(pC1++);
          }
        }
      } break;
      case 21 :  { // RGB 8-bit
        const T *pC1 = data(0,0,0,1), *pC2 = data(0,0,0,2);
        cimg_forY(*this,y) {
          unsigned char *ptrd = imgData[y];
          cimg_forX(*this,x) {
            *(ptrd++) = (unsigned char)*(pC0++);
            *(ptrd++) = (unsigned char)*(pC1++);
            *(ptrd++) = (unsigned char)*(pC2++);
          }
        }
      } break;
      case 28 : { // RGB x/ Alpha 8-bit
        const T *pC1 = data(0,0,0,1), *pC2 = data(0,0,0,2), *pC3 = data(0,0,0,3);
        cimg_forY(*this,y){
          unsigned char *ptrd = imgData[y];
          cimg_forX(*this,x){
            *(ptrd++) = (unsigned char)*(pC0++);
            *(ptrd++) = (unsigned char)*(pC1++);
            *(ptrd++) = (unsigned char)*(pC2++);
            *(ptrd++) = (unsigned char)*(pC3++);
          }
        }
      } break;
      case 15 : { // Gray 16-bit
        cimg_forY(*this,y){
          unsigned short *ptrd = (unsigned short*)(imgData[y]);
          cimg_forX(*this,x) *(ptrd++) = (unsigned short)*(pC0++);
          if (!cimg::endianness()) cimg::invert_endianness((unsigned short*)imgData[y],_width);
        }
      } break;
      case 30 : { // Gray w/ Alpha 16-bit
        const T *pC1 = data(0,0,0,1);
        cimg_forY(*this,y){
          unsigned short *ptrd = (unsigned short*)(imgData[y]);
          cimg_forX(*this,x) {
            *(ptrd++) = (unsigned short)*(pC0++);
            *(ptrd++) = (unsigned short)*(pC1++);
          }
          if (!cimg::endianness()) cimg::invert_endianness((unsigned short*)imgData[y],2*_width);
        }
      } break;
      case 45 : { // RGB 16-bit
        const T *pC1 = data(0,0,0,1), *pC2 = data(0,0,0,2);
        cimg_forY(*this,y) {
          unsigned short *ptrd = (unsigned short*)(imgData[y]);
          cimg_forX(*this,x) {
            *(ptrd++) = (unsigned short)*(pC0++);
            *(ptrd++) = (unsigned short)*(pC1++);
            *(ptrd++) = (unsigned short)*(pC2++);
          }
          if (!cimg::endianness()) cimg::invert_endianness((unsigned short*)imgData[y],3*_width);
        }
      } break;
      case 60 : { // RGB w/ Alpha 16-bit
        const T *pC1 = data(0,0,0,1), *pC2 = data(0,0,0,2), *pC3 = data(0,0,0,3);
        cimg_forY(*this,y) {
          unsigned short *ptrd = (unsigned short*)(imgData[y]);
          cimg_forX(*this,x) {
            *(ptrd++) = (unsigned short)*(pC0++);
            *(ptrd++) = (unsigned short)*(pC1++);
            *(ptrd++) = (unsigned short)*(pC2++);
            *(ptrd++) = (unsigned short)*(pC3++);
          }
          if (!cimg::endianness()) cimg::invert_endianness((unsigned short*)imgData[y],4*_width);
        }
      } break;
      default :
        if (!file) cimg::fclose(nfile);
        throw CImgIOException(_cimg_instance
                              "save_png(): Encountered unknown fatal error in libpng when saving file '%s'.",
                              cimg_instance,
                              nfilename?nfilename:"(FILE*)");
      }
      png_write_image(png_ptr,imgData);
      png_write_end(png_ptr,info_ptr);
      png_destroy_write_struct(&png_ptr, &info_ptr);

      // Deallocate Image Write Memory
      cimg_forY(*this,n) delete[] imgData[n];
      delete[] imgData;

      if (!file) cimg::fclose(nfile);
      return *this;
#endif
    }


    CImg<T>& RGBtosRGB() {
      if (is_empty()) return *this;
      cimg_pragma_openmp(parallel for cimg_openmp_if_size(size(),32))
      cimg_rofoff(*this,off) {
        const Tfloat
          val = (Tfloat)_data[off]/255,
          sval = (Tfloat)(val<=0.0031308f?val*12.92f:1.055f*std::pow(val,0.416667f) - 0.055f);
        _data[off] = (T)cimg::cut(sval*255,0,255);
      }
      return *this;
    }


Mutex_static& Mutex_attr() { static Mutex_static val; return val; }


        \param is_shared Tells if the inserted image is a shared copy of \c img or not.
    **/
    template<typename t>
    CImgList<T>& insert(const CImg<t>& img, const unsigned int pos=~0U, const bool is_shared=false) {
      const unsigned int npos = pos==~0U?_width:pos;
      if (npos>_width)
        throw CImgArgumentException(_cimglist_instance
                                    "insert(): Invalid insertion request of specified image (%u,%u,%u,%u,%p) "
                                    "at position %u.",
                                    cimglist_instance,
                                    img._width,img._height,img._depth,img._spectrum,img._data,npos);
      if (is_shared)
        throw CImgArgumentException(_cimglist_instance
                                    "insert(): Invalid insertion request of specified shared image "
                                    "CImg<%s>(%u,%u,%u,%u,%p) at position %u (pixel types are different).",
                                    cimglist_instance,
                                    img.pixel_type(),img._width,img._height,img._depth,img._spectrum,img._data,npos);

      CImg<T> *const new_data = (++_width>_allocated_width)?new CImg<T>[_allocated_width?(_allocated_width<<=1):
                                                                        (_allocated_width=16)]:0;
      if (!_data) { // Insert new element into empty list
        _data = new_data;
        *_data = img;
      } else {
        if (new_data) { // Insert with re-allocation
          if (npos) std::memcpy((void*)new_data,(void*)_data,sizeof(CImg<T>)*npos);
          if (npos!=_width - 1)
            std::memcpy((void*)(new_data + npos + 1),(void*)(_data + npos),sizeof(CImg<T>)*(_width - 1 - npos));
          std::memset((void*)_data,0,sizeof(CImg<T>)*(_width - 1));
          delete[] _data;
          _data = new_data;
        } else if (npos!=_width - 1) // Insert without re-allocation
          std::memmove((void*)(_data + npos + 1),(void*)(_data + npos),sizeof(CImg<T>)*(_width - 1 - npos));
        _data[npos]._width = _data[npos]._height = _data[npos]._depth = _data[npos]._spectrum = 0;
        _data[npos]._data = 0;
        _data[npos] = img;


    static bool _is_CImg3d(const T val, const char c) {
      return val>=(T)c && val<(T)(c + 1);
    }


CImgArgumentException(const char *const format, ...) { _cimg_exception_err("CImgArgumentException",true); }


static bool is_finite(const short) { return true; }


    const CImg<T> get_shared_channel(const unsigned int c0) const {
      return get_shared_channels(c0,c0);
    }


    const T* data(const unsigned int x, const unsigned int y=0, const unsigned int z=0, const unsigned int c=0) const {
      return const_cast<CImg<T>*>(this)->data(x,y,z,c);
    }


    inline unsigned int nb_cpus() {
      unsigned int res = 1;
#if cimg_OS==2
      SYSTEM_INFO sysinfo;
      GetSystemInfo(&sysinfo);
      res = (unsigned int)sysinfo.dwNumberOfProcessors;
#elif cimg_OS == 1
      res = (unsigned int)sysconf(_SC_NPROCESSORS_ONLN);
#endif
      return res?res:1U;
    }


    static int screen_height() {
      _no_display_exception();
      return 0;
    }


    inline half rol(const half a, const unsigned int n=1) {
      return (half)rol((int)a,n);
    }


    /**
     \param filename Filename to read data from.
    **/
    CImgList<T>& load(const char *const filename) {
      if (!filename)
        throw CImgArgumentException(_cimglist_instance
                                    "load(): Specified filename is (null).",
                                    cimglist_instance);

      if (!cimg::strncasecmp(filename,"http://",7) || !cimg::strncasecmp(filename,"https://",8)) {
        CImg<charT> filename_local(256);
        load(cimg::load_network(filename,filename_local));
        std::remove(filename_local);
        return *this;
      }

      const bool is_stdin = *filename=='-' && (!filename[1] || filename[1]=='.');
      const char *const ext = cimg::split_filename(filename);
      const unsigned int omode = cimg::exception_mode();
      cimg::exception_mode(0);
      bool is_loaded = true;
      try {
#ifdef cimglist_load_plugin
        cimglist_load_plugin(filename);
#endif
#ifdef cimglist_load_plugin1
        cimglist_load_plugin1(filename);
#endif
#ifdef cimglist_load_plugin2
        cimglist_load_plugin2(filename);
#endif
#ifdef cimglist_load_plugin3
        cimglist_load_plugin3(filename);
#endif
#ifdef cimglist_load_plugin4
        cimglist_load_plugin4(filename);
#endif
#ifdef cimglist_load_plugin5
        cimglist_load_plugin5(filename);
#endif
#ifdef cimglist_load_plugin6
        cimglist_load_plugin6(filename);
#endif
#ifdef cimglist_load_plugin7
        cimglist_load_plugin7(filename);
#endif
#ifdef cimglist_load_plugin8
        cimglist_load_plugin8(filename);
#endif
        if (!cimg::strcasecmp(ext,"tif") ||
            !cimg::strcasecmp(ext,"tiff")) load_tiff(filename);
        else if (!cimg::strcasecmp(ext,"gif")) load_gif_external(filename);
        else if (!cimg::strcasecmp(ext,"cimg") ||
                 !cimg::strcasecmp(ext,"cimgz") ||
                 !*ext) load_cimg(filename);
        else if (!cimg::strcasecmp(ext,"rec") ||
                 !cimg::strcasecmp(ext,"par")) load_parrec(filename);
        else if (!cimg::strcasecmp(ext,"avi") ||
                 !cimg::strcasecmp(ext,"mov") ||
                 !cimg::strcasecmp(ext,"asf") ||
                 !cimg::strcasecmp(ext,"divx") ||
                 !cimg::strcasecmp(ext,"flv") ||
                 !cimg::strcasecmp(ext,"mpg") ||
                 !cimg::strcasecmp(ext,"m1v") ||
                 !cimg::strcasecmp(ext,"m2v") ||
                 !cimg::strcasecmp(ext,"m4v") ||
                 !cimg::strcasecmp(ext,"mjp") ||
                 !cimg::strcasecmp(ext,"mp4") ||
                 !cimg::strcasecmp(ext,"mkv") ||
                 !cimg::strcasecmp(ext,"mpe") ||
                 !cimg::strcasecmp(ext,"movie") ||
                 !cimg::strcasecmp(ext,"ogm") ||
                 !cimg::strcasecmp(ext,"ogg") ||
                 !cimg::strcasecmp(ext,"ogv") ||
                 !cimg::strcasecmp(ext,"qt") ||
                 !cimg::strcasecmp(ext,"rm") ||
                 !cimg::strcasecmp(ext,"vob") ||
                 !cimg::strcasecmp(ext,"webm") ||
                 !cimg::strcasecmp(ext,"wmv") ||
                 !cimg::strcasecmp(ext,"xvid") ||
                 !cimg::strcasecmp(ext,"mpeg")) load_video(filename);
        else if (!cimg::strcasecmp(ext,"gz")) load_gzip_external(filename);
        else is_loaded = false;
      } catch (CImgIOException&) { is_loaded = false; }

      // If nothing loaded, try to guess file format from magic number in file.
      if (!is_loaded && !is_stdin) {
        std::FILE *const file = cimg::std_fopen(filename,"rb");
        if (!file) {
          cimg::exception_mode(omode);
          throw CImgIOException(_cimglist_instance
                                "load(): Failed to open file '%s'.",
                                cimglist_instance,
                                filename);
        }

        const char *const f_type = cimg::ftype(file,filename);
        cimg::fclose(file);
        is_loaded = true;
        try {
          if (!cimg::strcasecmp(f_type,"gif")) load_gif_external(filename);
          else if (!cimg::strcasecmp(f_type,"tif") &&
                   cimg::strcasecmp(ext,"nef") &&
                   cimg::strcasecmp(ext,"dng")) load_tiff(filename);
          else is_loaded = false;
        } catch (CImgIOException&) { is_loaded = false; }
      }

      // If nothing loaded, try to load file as a single image.
      if (!is_loaded) {
        assign(1);
        try {
          _data->load(filename);
        } catch (CImgIOException&) {
          cimg::exception_mode(omode);
          throw CImgIOException(_cimglist_instance
                                "load(): Failed to recognize format of file '%s'.",
                                cimglist_instance,
                                filename);
        }
      }


    inline half ror(const half a, const unsigned int n=1) {
      return (half)ror((int)a,n);
    }


      static double mp_isvarname(_cimg_math_parser& mp) {
        const unsigned int siz = (unsigned int)mp.opcode[3];
        const double *ptrs = &_mp_arg(2) + (siz?1:0);
        if (!siz) {
          const char c = (char)*ptrs;
          return (c>='a' && c<='z') || (c>='A' && c<='Z') || c=='_';
        }
        if (*ptrs>='0' && *ptrs<='9') return 0;
        for (unsigned int k = 0; k<siz; ++k) if (!is_varchar((char)ptrs[k])) return 0;
        return 1;
      }


      static double mp_sum(_cimg_math_parser& mp) {
        const unsigned int i_end = (unsigned int)mp.opcode[2];
        double val = _mp_arg(3);
        for (unsigned int i = 4; i<i_end; ++i) val+=_mp_arg(i);
        return val;
      }


    const CImg<T>& display(const char *const title=0, const bool display_info=true, unsigned int *const XYZ=0,
                           const bool exit_on_anykey=false) const {
      CImgDisplay disp;
      return _display(disp,title,display_info,XYZ,exit_on_anykey,false);
    }


    CImg<T>& maxabs(const CImg<t>& img) {
      const ulongT siz = size(), isiz = img.size();
      if (siz && isiz) {
        if (is_overlapped(img)) return maxabs(+img);
        T *ptrd = _data, *const ptre = _data + siz;
        if (siz>isiz) for (ulongT n = siz/isiz; n; --n)
          for (const t *ptrs = img._data, *ptrs_end = ptrs + isiz; ptrs<ptrs_end; ++ptrd)
            *ptrd = cimg::maxabs((T)*(ptrs++),*ptrd);
        for (const t *ptrs = img._data; ptrd<ptre; ++ptrd) *ptrd = cimg::maxabs((T)*(ptrs++),*ptrd);
      }
      return *this;
    }


    CImgDisplay():
      _width(0),_height(0),_normalization(0),
      _min(0),_max(0),
      _is_fullscreen(false),
      _title(0),
      _window_width(0),_window_height(0),_button(0),
      _keys(new unsigned int[128]),_released_keys(new unsigned int[128]),
      _window_x(cimg::type<int>::min()),_window_y(cimg::type<int>::min()),
      _mouse_x(-1),_mouse_y(-1),_wheel(0),
      _is_closed(true),_is_resized(false),_is_moved(false),_is_event(false) {
      assign();
    }


      static double mp_joff(_cimg_math_parser& mp) {
        const unsigned int
          boundary_conditions = (unsigned int)_mp_arg(3);
        const int
          ox = (int)mp.mem[_cimg_mp_slot_x], oy = (int)mp.mem[_cimg_mp_slot_y],
          oz = (int)mp.mem[_cimg_mp_slot_z], oc = (int)mp.mem[_cimg_mp_slot_c];
        const CImg<T> &img = mp.imgin;
        const longT
          off = img.offset(ox,oy,oz,oc) + (longT)_mp_arg(2),
          whds = (longT)img.size();
        if (off>=0 && off<whds) return (double)img[off];
        if (img._data) switch (boundary_conditions) {
          case 3 : { // Mirror
            const longT whds2 = 2*whds, moff = cimg::mod(off,whds2);
            return (double)img[moff<whds?moff:whds2 - moff - 1];
          }
          case 2 : // Periodic
            return (double)img[cimg::mod(off,whds)];
          case 1 : // Neumann
            return (double)img[off<0?0:whds - 1];
          default : // Dirichlet
            return 0;
          }
        return 0;
      }


    CImg(const CImg<t>& img, const char *const dimensions):
      _width(0),_height(0),_depth(0),_spectrum(0),_is_shared(false),_data(0) {
      assign(img,dimensions);
    }


    CImg<T>& sharpen(const float amplitude, const bool sharpen_type=false, const float edge=1,
                     const float alpha=0, const float sigma=0) {
      if (is_empty()) return *this;
      T val_min, val_max = max_min(val_min);
      const float nedge = edge/2;
      CImg<Tfloat> velocity(_width,_height,_depth,_spectrum), _veloc_max(_spectrum);

      if (_depth>1) { // 3D
        if (sharpen_type) { // Shock filters
          CImg<Tfloat> G = (alpha>0?get_blur(alpha).get_structure_tensors():get_structure_tensors());
          if (sigma>0) G.blur(sigma);
          cimg_pragma_openmp(parallel for cimg_openmp_collapse(2) cimg_openmp_if(_width>=(cimg_openmp_sizefactor)*32 &&
                                                                     _height*_depth>=16))
          cimg_forYZ(G,y,z) {
            Tfloat *ptrG0 = G.data(0,y,z,0), *ptrG1 = G.data(0,y,z,1),
              *ptrG2 = G.data(0,y,z,2), *ptrG3 = G.data(0,y,z,3);
            CImg<Tfloat> val, vec;
            cimg_forX(G,x) {
              G.get_tensor_at(x,y,z).symmetric_eigen(val,vec);
              if (val[0]<0) val[0] = 0;
              if (val[1]<0) val[1] = 0;
              if (val[2]<0) val[2] = 0;
              *(ptrG0++) = vec(0,0);
              *(ptrG1++) = vec(0,1);
              *(ptrG2++) = vec(0,2);
              *(ptrG3++) = 1 - (Tfloat)std::pow(1 + val[0] + val[1] + val[2],-(Tfloat)nedge);
            }
          }
          cimg_pragma_openmp(parallel for cimg_openmp_if(_width*_height*_depth>=(cimg_openmp_sizefactor)*512 &&
                                                         _spectrum>=2))
          cimg_forC(*this,c) {
            Tfloat *ptrd = velocity.data(0,0,0,c), veloc_max = 0;
            CImg_3x3x3(I,Tfloat);
            cimg_for3x3x3(*this,x,y,z,c,I,Tfloat) {
              const Tfloat
                u = G(x,y,z,0),
                v = G(x,y,z,1),
                w = G(x,y,z,2),
                amp = G(x,y,z,3),
                ixx = Incc + Ipcc - 2*Iccc,
                ixy = (Innc + Ippc - Inpc - Ipnc)/4,
                ixz = (Incn + Ipcp - Incp - Ipcn)/4,
                iyy = Icnc + Icpc - 2*Iccc,
                iyz = (Icnn + Icpp - Icnp - Icpn)/4,
                izz = Iccn + Iccp - 2*Iccc,
                ixf = Incc - Iccc,
                ixb = Iccc - Ipcc,
                iyf = Icnc - Iccc,
                iyb = Iccc - Icpc,
                izf = Iccn - Iccc,
                izb = Iccc - Iccp,
                itt = u*u*ixx + v*v*iyy + w*w*izz + 2*u*v*ixy + 2*u*w*ixz + 2*v*w*iyz,
                it = u*cimg::minmod(ixf,ixb) + v*cimg::minmod(iyf,iyb) + w*cimg::minmod(izf,izb),
                veloc = -amp*cimg::sign(itt)*cimg::abs(it);
              *(ptrd++) = veloc;
              if (veloc>veloc_max) veloc_max = veloc; else if (-veloc>veloc_max) veloc_max = -veloc;
            }
            _veloc_max[c] = veloc_max;
          }
        } else  // Inverse diffusion
          cimg_pragma_openmp(parallel for cimg_openmp_if(_width*_height*_depth>=(cimg_openmp_sizefactor)*512 &&
                                                         _spectrum>=2))
          cimg_forC(*this,c) {
            Tfloat *ptrd = velocity.data(0,0,0,c), veloc_max = 0;
            CImg_3x3x3(I,Tfloat);
            cimg_for3x3x3(*this,x,y,z,c,I,Tfloat) {
              const Tfloat veloc = -Ipcc - Incc - Icpc - Icnc - Iccp - Iccn + 6*Iccc;
              *(ptrd++) = veloc;
              if (veloc>veloc_max) veloc_max = veloc; else if (-veloc>veloc_max) veloc_max = -veloc;
            }
            _veloc_max[c] = veloc_max;
          }
      } else { // 2D
        if (sharpen_type) { // Shock filters
          CImg<Tfloat> G = (alpha>0?get_blur(alpha).get_structure_tensors():get_structure_tensors());
          if (sigma>0) G.blur(sigma);
          cimg_pragma_openmp(parallel for cimg_openmp_if(_width>=(cimg_openmp_sizefactor)*32 &&
                                                         _height>=(cimg_openmp_sizefactor)*16))
          cimg_forY(G,y) {
            CImg<Tfloat> val, vec;
            Tfloat *ptrG0 = G.data(0,y,0,0), *ptrG1 = G.data(0,y,0,1), *ptrG2 = G.data(0,y,0,2);
            cimg_forX(G,x) {
              G.get_tensor_at(x,y).symmetric_eigen(val,vec);
              if (val[0]<0) val[0] = 0;
              if (val[1]<0) val[1] = 0;
              *(ptrG0++) = vec(0,0);
              *(ptrG1++) = vec(0,1);
              *(ptrG2++) = 1 - (Tfloat)std::pow(1 + val[0] + val[1],-(Tfloat)nedge);
            }
          }
          cimg_pragma_openmp(parallel for cimg_openmp_if(_width*_height>=(cimg_openmp_sizefactor)*512 &&
                                                         _spectrum>=2))
          cimg_forC(*this,c) {
            Tfloat *ptrd = velocity.data(0,0,0,c), veloc_max = 0;
            CImg_3x3(I,Tfloat);
            cimg_for3x3(*this,x,y,0,c,I,Tfloat) {
              const Tfloat
                u = G(x,y,0),
                v = G(x,y,1),
                amp = G(x,y,2),
                ixx = Inc + Ipc - 2*Icc,
                ixy = (Inn + Ipp - Inp - Ipn)/4,
                iyy = Icn + Icp - 2*Icc,
                ixf = Inc - Icc,
                ixb = Icc - Ipc,
                iyf = Icn - Icc,
                iyb = Icc - Icp,
                itt = u*u*ixx + v*v*iyy + 2*u*v*ixy,
                it = u*cimg::minmod(ixf,ixb) + v*cimg::minmod(iyf,iyb),
                veloc = -amp*cimg::sign(itt)*cimg::abs(it);
              *(ptrd++) = veloc;
              if (veloc>veloc_max) veloc_max = veloc; else if (-veloc>veloc_max) veloc_max = -veloc;
            }
            _veloc_max[c] = veloc_max;
          }
        } else // Inverse diffusion
          cimg_pragma_openmp(parallel for cimg_openmp_if(_width*_height>=(cimg_openmp_sizefactor)*512 &&
                                                         _spectrum>=2))
          cimg_forC(*this,c) {
            Tfloat *ptrd = velocity.data(0,0,0,c), veloc_max = 0;
            CImg_3x3(I,Tfloat);
            cimg_for3x3(*this,x,y,0,c,I,Tfloat) {
              const Tfloat veloc = -Ipc - Inc - Icp - Icn + 4*Icc;
              *(ptrd++) = veloc;
              if (veloc>veloc_max) veloc_max = veloc; else if (-veloc>veloc_max) veloc_max = -veloc;
            }
            _veloc_max[c] = veloc_max;
          }
      }
      const Tfloat veloc_max = _veloc_max.max();
      if (veloc_max<=0) return *this;
      return ((velocity*=amplitude/veloc_max)+=*this).cut(val_min,val_max).move_to(*this);
    }


    CImg<T>& draw_triangle(int x0, int y0,
                           int x1, int y1,
                           int x2, int y2,
                           const CImg<tc>& texture,
                           int tx0, int ty0,
                           int tx1, int ty1,
                           int tx2, int ty2,
                           const float opacity=1,
                           const float brightness=1) {
      if (is_empty()) return *this;
      if (texture._depth>1 || texture._spectrum<_spectrum)
        throw CImgArgumentException(_cimg_instance
                                    "draw_triangle(): Invalid specified texture (%u,%u,%u,%u,%p).",
                                    cimg_instance,
                                    texture._width,texture._height,texture._depth,texture._spectrum,texture._data);
      if (is_overlapped(texture))
        return draw_triangle(x0,y0,x1,y1,x2,y2,+texture,tx0,ty0,tx1,ty1,tx2,ty2,opacity,brightness);

      if (y0>y1) cimg::swap(x0,x1,y0,y1,tx0,tx1,ty0,ty1);
      if (y0>y2) cimg::swap(x0,x2,y0,y2,tx0,tx2,ty0,ty2);
      if (y1>y2) cimg::swap(x1,x2,y1,y2,tx1,ty1,tx2,ty2);
      if (y2<0 || y0>=height() || cimg::min(x0,x1,x2)>=width() || cimg::max(x0,x1,x2)<0 || !opacity) return *this;

      const int
        w1 = width() - 1, h1 = height() - 1,
        dx01 = x1 - x0, dx02 = x2 - x0, dx12 = x2 - x1,
        dy01 = std::max(1,y1 - y0), dy02 = std::max(1,y2 - y0), dy12 = std::max(1,y2 - y1),
        cy0 = cimg::cut(y0,0,h1), cy2 = cimg::cut(y2,0,h1),
        hdy01 = dy01*cimg::sign(dx01)/2, hdy02 = dy02*cimg::sign(dx02)/2, hdy12 = dy12*cimg::sign(dx12)/2,
        dtx01 = tx1 - tx0, dtx02 = tx2 - tx0, dtx12 = tx2 - tx1,
        dty01 = ty1 - ty0, dty02 = ty2 - ty0, dty12 = ty2 - ty1,
        hdy01tx = dy01*cimg::sign(dtx01)/2, hdy02tx = dy02*cimg::sign(dtx02)/2, hdy12tx = dy12*cimg::sign(dtx12)/2,
        hdy01ty = dy01*cimg::sign(dty01)/2, hdy02ty = dy02*cimg::sign(dty02)/2, hdy12ty = dy12*cimg::sign(dty12)/2;
      const ulongT twhd = (ulongT)texture._width*texture._height*texture._depth;
      const float cbs = cimg::cut(brightness,0,2);
      cimg_init_scanline(opacity);

      for (int y = cy0; y<=cy2; ++y) {
        const int yy0 = y - y0, yy1 = y - y1;
        int
          xm = y<y1?x0 + (dx01*yy0 + hdy01)/dy01:x1 + (dx12*yy1 + hdy12)/dy12,
          xM = x0 + (dx02*yy0 + hdy02)/dy02,
          txm = y<y1?tx0 + (dtx01*yy0 + hdy01tx)/dy01:tx1 + (dtx12*yy1 + hdy12tx)/dy12,
          txM = tx0 + (dtx02*yy0 + hdy02tx)/dy02,
          tym = y<y1?ty0 + (dty01*yy0 + hdy01ty)/dy01:ty1 + (dty12*yy1 + hdy12ty)/dy12,
          tyM = ty0 + (dty02*yy0 + hdy02ty)/dy02;
        if (xm>xM) cimg::swap(xm,xM,txm,txM,tym,tyM);
        if (xM>=0 && xm<=w1) {
          const int
            cxm = cimg::cut(xm,0,w1),
            cxM = cimg::cut(xM,0,w1);
          T *ptrd = data(cxm,y);
          const int
            dxmM = std::max(1,xM - xm), hdxmM = dxmM/2,
            dtxmM = txM - txm, dtymM = tyM - tym;

          for (int x = cxm; x<=cxM; ++x) {
            const int
              xxm = x - xm,
              tx = (txm*dxmM + dtxmM*xxm + hdxmM)/dxmM,
              ty = (tym*dxmM + dtymM*xxm + hdxmM)/dxmM;
            const tc *const color = &texture._atXY(tx,ty);
            cimg_forC(*this,c) {
              const Tfloat val = cbs<=1?color[c*twhd]*cbs:(2 - cbs)*color[c*twhd] + (cbs - 1)*_sc_maxval;
              ptrd[c*_sc_whd] = (T)(opacity>=1?val:val*_sc_nopacity + ptrd[c*_sc_whd]*_sc_copacity);
            }
            ++ptrd;
          }
        }
      }
      return *this;
    }


    CImg<ucharT> get_serialize(const bool is_compressed=false, const unsigned int header_size=0) const {
      return CImgList<T>(*this,true).get_serialize(is_compressed,header_size);
    }


    CImg<T> get_sort(CImg<t>& permutations, const bool is_increasing=true) const {
      return (+*this).sort(permutations,is_increasing);
    }


static cimg_uint64 inf() { return max(); }


    T _cubic_atX_pc(const float fx, const int y, const int z, const int c) const {
      return cimg::type<T>::cut(_cubic_atX_p(fx,y,z,c));
    }


      static double mp_vector_print(_cimg_math_parser& mp) {
        const bool print_string = (bool)mp.opcode[4];
        cimg_pragma_openmp(critical(mp_vector_print))
        {
          CImg<charT> _expr(mp.opcode[2] - 5);
          const ulongT *ptrs = mp.opcode._data + 5;
          cimg_for(_expr,ptrd,char) *ptrd = (char)*(ptrs++);
          cimg::strellipsize(_expr);
          unsigned int
            ptr = (unsigned int)mp.opcode[1] + 1,
            siz0 = (unsigned int)mp.opcode[3],
            siz = siz0;
          cimg::mutex(6);
          std::fprintf(cimg::output(),"\n[" cimg_appname "_math_parser] %s = [ ",_expr._data);
          unsigned int count = 0;
          while (siz-->0) {
            if (count>=64 && siz>=64) {
              std::fprintf(cimg::output(),"...,");
              ptr = (unsigned int)mp.opcode[1] + 1 + siz0 - 64;
              siz = 64;
            } else std::fprintf(cimg::output(),"%.17g%s",mp.mem[ptr++],siz?",":"");
            ++count;
          }
          if (print_string) {
            CImg<charT> str(siz0 + 1);
            ptr = (unsigned int)mp.opcode[1] + 1;
            for (unsigned int k = 0; k<siz0; ++k) str[k] = (char)mp.mem[ptr++];
            str[siz0] = 0;
            cimg::strellipsize(str,1024,false);
            std::fprintf(cimg::output()," ] = '%s' (size: %u)",str._data,siz0);
          } else std::fprintf(cimg::output()," ] (size: %u)",siz0);
          std::fflush(cimg::output());
          cimg::mutex(6,0);
        }
        return cimg::type<double>::nan();
      }


    }

    //! Display a simple dialog box, and wait for the user's response \specialization.
    inline int dialog(const char *const title, const char *const msg,
                      const char *const button1_label, const char *const button2_label,
                      const char *const button3_label, const char *const button4_label,
                      const char *const button5_label, const char *const button6_label,
                      const bool is_centered) {


      static double mp_da_insert_or_push(_cimg_math_parser& mp) {
        const char *const s_op = mp.opcode[3]==~0U?"da_push":"da_insert";
        mp_check_list(mp,s_op);
        const unsigned int
          dim = (unsigned int)mp.opcode[4],
          _dim = std::max(1U,dim),
          nb_elts = (unsigned int)mp.opcode[5] - 6,
          ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.imglist.width());
        CImg<T> &img = mp.imglist[ind];
        const int
          siz = img?(int)img[img._height - 1]:0,
          pos0 = mp.opcode[3]==~0U?siz:(int)_mp_arg(3),
          pos = pos0<0?pos0 + siz:pos0;

        if (img && _dim!=img._spectrum)
          throw CImgArgumentException("[" cimg_appname "_math_parser] CImg<%s>: Function '%s()': "
                                      "Element to insert has invalid size %u (should be %u).",
                                      mp.imgout.pixel_type(),s_op,_dim,img._spectrum);
        if (img && (img._width!=1 || img._depth!=1 || siz<0 || siz>img.height() - 1))
          throw CImgArgumentException("[" cimg_appname "_math_parser] CImg<%s>: Function '%s()': "
                                      "Specified image (%d,%d,%d,%d) cannot be used as dynamic array%s.",
                                      mp.imgout.pixel_type(),s_op,img.width(),img.height(),img.depth(),img.spectrum(),
                                      img._width==1 && img._depth==1?"":" (contains invalid element counter)");
        if (pos<0 || pos>siz)
          throw CImgArgumentException("[" cimg_appname "_math_parser] CImg<%s>: Function '%s()': "
                                      "Invalid position %d (not in range -%d...%d).",
                                      mp.imgout.pixel_type(),s_op,pos0,siz,siz);

        if (siz + nb_elts + 1>=img._height) // Increase size of dynamic array, if necessary
          img.resize(1,2*siz + nb_elts + 1,1,_dim,0);

        if (pos!=siz) // Move existing data in dynamic array
          cimg_forC(img,c) std::memmove(img.data(0,pos + nb_elts,0,c),img.data(0,pos,0,c),(siz - pos)*sizeof(T));

        if (!dim) // Scalar or vector1() elements
          for (unsigned int k = 0; k<nb_elts; ++k) img[pos + k] = (T)_mp_arg(6 + k);
        else // vectorN() elements, with N>1
          for (unsigned int k = 0; k<nb_elts; ++k) {
            double *ptrs = &_mp_arg(6 + k) + 1;
            cimg_forC(img,c) img(0,pos + k,0,c) = ptrs[c];
          }
        img[img._height - 1] = (T)(siz + nb_elts);
        return cimg::type<double>::nan();
      }


    T at(const int offset, const T& out_value) const {
      return (offset<0 || offset>=(int)size())?out_value:(*this)[offset];
    }


    CImg<T>& resize_object3d(const float sx, const float sy=-100, const float sz=-100) {
      if (_height!=3 || _depth>1 || _spectrum>1)
        throw CImgInstanceException(_cimg_instance
                                    "resize_object3d(): Instance is not a set of 3D vertices.",
                                    cimg_instance);

      CImg<T> xcoords = get_shared_row(0), ycoords = get_shared_row(1), zcoords = get_shared_row(2);
      float
        xm, xM = (float)xcoords.max_min(xm),
        ym, yM = (float)ycoords.max_min(ym),
        zm, zM = (float)zcoords.max_min(zm);
      if (xm<xM) { if (sx>0) xcoords*=sx/(xM-xm); else xcoords*=-sx/100; }
      if (ym<yM) { if (sy>0) ycoords*=sy/(yM-ym); else ycoords*=-sy/100; }
      if (zm<zM) { if (sz>0) zcoords*=sz/(zM-zm); else zcoords*=-sz/100; }
      return *this;
    }


    CImg<T> get_diagonal() const {
      if (is_empty()) return *this;
      const unsigned int siz = (unsigned int)size();
      CImg<T> res(siz,siz,1,1,0);
      cimg_foroff(*this,off) res((unsigned int)off,(unsigned int)off) = (*this)[off];
      return res;
    }


      static double mp_lerp(_cimg_math_parser& mp) {
        const double t = _mp_arg(4);
        return _mp_arg(2)*(1-t) + _mp_arg(3)*t;
      }


    static CImg<T> get_load_pdf_external(const char *const filename, const unsigned int resolution=400) {
      return CImg<T>().load_pdf_external(filename,resolution);
    }


    CImgDisplay& resize(const CImgDisplay& disp, const bool force_redraw=true) {
      return resize(disp.width(),disp.height(),force_redraw);
    }


    static CImg<T> row_vector(const T& a0, const T& a1) {
      CImg<T> r(2,1);
      r[0] = a0; r[1] = a1;
      return r;
    }


    CImg<Tuchar> get_HSVtoRGB() const {
      return CImg<Tuchar>(*this,false).HSVtoRGB();
    }


    CImg<T> get_invert_endianness() const {
      return (+*this).invert_endianness();
    }


    CImg<floatT> get_displacement(const CImg<T>& source,
                                  const float smoothness=0.1f, const float precision=5.f,
                                  const unsigned int nb_scales=0, const unsigned int iteration_max=10000,
                                  const bool is_backward=false,
                                  const CImg<floatT>& guide=CImg<floatT>::const_empty()) const {
      if (is_empty() || !source) return +*this;
      if (!is_sameXYZC(source))
        throw CImgArgumentException(_cimg_instance
                                    "displacement(): Instance and source image (%u,%u,%u,%u,%p) have "
                                    "different dimensions.",
                                    cimg_instance,
                                    source._width,source._height,source._depth,source._spectrum,source._data);
      if (precision<0)
        throw CImgArgumentException(_cimg_instance
                                    "displacement(): Invalid specified precision %g "
                                    "(should be >=0)",
                                    cimg_instance,
                                    precision);

      const bool is_3d = source._depth>1;
      const unsigned int constraint = is_3d?3:2;

      if (guide &&
          (guide._width!=_width || guide._height!=_height || guide._depth!=_depth || guide._spectrum<constraint))
        throw CImgArgumentException(_cimg_instance
                                    "displacement(): Specified guide (%u,%u,%u,%u,%p) "
                                    "has invalid dimensions.",
                                    cimg_instance,
                                    guide._width,guide._height,guide._depth,guide._spectrum,guide._data);

      const unsigned int
        mins = is_3d?cimg::min(_width,_height,_depth):std::min(_width,_height),
        _nb_scales = nb_scales>0?nb_scales:
        (unsigned int)cimg::round(std::log(mins/8.)/std::log(1.5),1,1);

      const float _precision = (float)std::pow(10.,-(double)precision);
      float sm, sM = source.max_min(sm), tm, tM = max_min(tm);
      const float sdelta = sm==sM?1:(sM - sm), tdelta = tm==tM?1:(tM - tm);

      CImg<floatT> U, V;
      floatT bound = 0;
      for (int scale = (int)_nb_scales - 1; scale>=0; --scale) {
        const float factor = (float)std::pow(1.5,(double)scale);
        const unsigned int
          _sw = (unsigned int)(_width/factor), sw = _sw?_sw:1,
          _sh = (unsigned int)(_height/factor), sh = _sh?_sh:1,
          _sd = (unsigned int)(_depth/factor), sd = _sd?_sd:1;
        if (sw<5 && sh<5 && (!is_3d || sd<5)) continue;  // Skip too small scales
        const CImg<Tfloat>
          I1 = (source.get_resize(sw,sh,sd,-100,2)-=sm)/=sdelta,
          I2 = (get_resize(I1,2)-=tm)/=tdelta;
        if (guide._spectrum>constraint) guide.get_resize(I2._width,I2._height,I2._depth,-100,1).move_to(V);
        if (U) (U*=1.5f).resize(I2._width,I2._height,I2._depth,-100,3);
        else {
          if (guide)
            guide.get_shared_channels(0,is_3d?2:1).get_resize(I2._width,I2._height,I2._depth,-100,2).move_to(U);
          else U.assign(I2._width,I2._height,I2._depth,is_3d?3:2,0);
        }

        float dt = 2, energy = cimg::type<float>::max();
        const CImgList<Tfloat> dI = is_backward?I1.get_gradient():I2.get_gradient();
        cimg_abort_init;

        for (unsigned int iteration = 0; iteration<iteration_max; ++iteration) {
          cimg_abort_test;
          float _energy = 0;

          if (is_3d) { // 3D version
            if (smoothness>=0) // Isotropic regularization
              cimg_pragma_openmp(parallel for cimg_openmp_collapse(2)
                                 cimg_openmp_if(_height*_depth>=(cimg_openmp_sizefactor)*8 &&
                                                _width>=(cimg_openmp_sizefactor)*16)
                                 reduction(+:_energy))
              cimg_forYZ(U,y,z) {
                const int
                  _p1y = y?y - 1:0, _n1y = y<U.height() - 1?y + 1:y,
                  _p1z = z?z - 1:0, _n1z = z<U.depth() - 1?z + 1:z;
                cimg_for3X(U,x) {
                  const float
                    X = is_backward?x - U(x,y,z,0):x + U(x,y,z,0),
                    Y = is_backward?y - U(x,y,z,1):y + U(x,y,z,1),
                    Z = is_backward?z - U(x,y,z,2):z + U(x,y,z,2);
                  float delta_I = 0, _energy_regul = 0;
                  if (is_backward) cimg_forC(I2,c) delta_I+=(float)(I1._linear_atXYZ(X,Y,Z,c) - I2(x,y,z,c));
                  else cimg_forC(I2,c) delta_I+=(float)(I1(x,y,z,c) - I2._linear_atXYZ(X,Y,Z,c));
                  cimg_forC(U,c) {
                    const float
                      Ux = 0.5f*(U(_n1x,y,z,c) - U(_p1x,y,z,c)),
                      Uy = 0.5f*(U(x,_n1y,z,c) - U(x,_p1y,z,c)),
                      Uz = 0.5f*(U(x,y,_n1z,c) - U(x,y,_p1z,c)),
                      Uxx = U(_n1x,y,z,c) + U(_p1x,y,z,c),
                      Uyy = U(x,_n1y,z,c) + U(x,_p1y,z,c),
                      Uzz = U(x,y,_n1z,c) + U(x,y,_p1z,c);
                    U(x,y,z,c) = (float)(U(x,y,z,c) + dt*(delta_I*dI[c]._linear_atXYZ(X,Y,Z) +
                                                          smoothness* ( Uxx + Uyy + Uzz)))/(1 + 6*smoothness*dt);
                    _energy_regul+=Ux*Ux + Uy*Uy + Uz*Uz;
                  }
                  if (is_backward) { // Constraint displacement vectors to stay in image
                    if (U(x,y,z,0)>x) U(x,y,z,0) = (float)x;
                    if (U(x,y,z,1)>y) U(x,y,z,1) = (float)y;
                    if (U(x,y,z,2)>z) U(x,y,z,2) = (float)z;
                    bound = (float)x - _width; if (U(x,y,z,0)<=bound) U(x,y,z,0) = bound;
                    bound = (float)y - _height; if (U(x,y,z,1)<=bound) U(x,y,z,1) = bound;
                    bound = (float)z - _depth; if (U(x,y,z,2)<=bound) U(x,y,z,2) = bound;
                  } else {
                    if (U(x,y,z,0)<-x) U(x,y,z,0) = -(float)x;
                    if (U(x,y,z,1)<-y) U(x,y,z,1) = -(float)y;
                    if (U(x,y,z,2)<-z) U(x,y,z,2) = -(float)z;
                    bound = (float)_width - x; if (U(x,y,z,0)>=bound) U(x,y,z,0) = bound;
                    bound = (float)_height - y; if (U(x,y,z,1)>=bound) U(x,y,z,1) = bound;
                    bound = (float)_depth - z; if (U(x,y,z,2)>=bound) U(x,y,z,2) = bound;
                  }
                  _energy+=delta_I*delta_I + smoothness*_energy_regul;
                }
                if (V) cimg_forXYZ(V,_x,_y,_z) if (V(_x,_y,_z,3)) { // Apply constraints
                    U(_x,_y,_z,0) = V(_x,_y,_z,0)/factor;
                    U(_x,_y,_z,1) = V(_x,_y,_z,1)/factor;
                    U(_x,_y,_z,2) = V(_x,_y,_z,2)/factor;
                  }
              } else { // Anisotropic regularization
              const float nsmoothness = -smoothness;
              cimg_pragma_openmp(parallel for cimg_openmp_collapse(2)
                                 cimg_openmp_if(_height*_depth>=(cimg_openmp_sizefactor)*8 &&
                                                _width>=(cimg_openmp_sizefactor)*16)
                                 reduction(+:_energy))
              cimg_forYZ(U,y,z) {
                const int
                  _p1y = y?y - 1:0, _n1y = y<U.height() - 1?y + 1:y,
                  _p1z = z?z - 1:0, _n1z = z<U.depth() - 1?z + 1:z;
                cimg_for3X(U,x) {
                  const float
                    X = is_backward?x - U(x,y,z,0):x + U(x,y,z,0),
                    Y = is_backward?y - U(x,y,z,1):y + U(x,y,z,1),
                    Z = is_backward?z - U(x,y,z,2):z + U(x,y,z,2);
                  float delta_I = 0, _energy_regul = 0;
                  if (is_backward) cimg_forC(I2,c) delta_I+=(float)(I1._linear_atXYZ(X,Y,Z,c) - I2(x,y,z,c));
                  else cimg_forC(I2,c) delta_I+=(float)(I1(x,y,z,c) - I2._linear_atXYZ(X,Y,Z,c));
                  cimg_forC(U,c) {
                    const float
                      Ux = 0.5f*(U(_n1x,y,z,c) - U(_p1x,y,z,c)),
                      Uy = 0.5f*(U(x,_n1y,z,c) - U(x,_p1y,z,c)),
                      Uz = 0.5f*(U(x,y,_n1z,c) - U(x,y,_p1z,c)),
                      N2 = Ux*Ux + Uy*Uy + Uz*Uz,
                      N = std::sqrt(N2),
                      N3 = 1e-5f + N2*N,
                      coef_a = (1 - Ux*Ux/N2)/N,
                      coef_b = -2*Ux*Uy/N3,
                      coef_c = -2*Ux*Uz/N3,
                      coef_d = (1 - Uy*Uy/N2)/N,
                      coef_e = -2*Uy*Uz/N3,
                      coef_f = (1 - Uz*Uz/N2)/N,
                      Uxx = U(_n1x,y,z,c) + U(_p1x,y,z,c),
                      Uyy = U(x,_n1y,z,c) + U(x,_p1y,z,c),
                      Uzz = U(x,y,_n1z,c) + U(x,y,_p1z,c),
                      Uxy = 0.25f*(U(_n1x,_n1y,z,c) + U(_p1x,_p1y,z,c) - U(_n1x,_p1y,z,c) - U(_n1x,_p1y,z,c)),
                      Uxz = 0.25f*(U(_n1x,y,_n1z,c) + U(_p1x,y,_p1z,c) - U(_n1x,y,_p1z,c) - U(_n1x,y,_p1z,c)),
                      Uyz = 0.25f*(U(x,_n1y,_n1z,c) + U(x,_p1y,_p1z,c) - U(x,_n1y,_p1z,c) - U(x,_n1y,_p1z,c));
                    U(x,y,z,c) = (float)(U(x,y,z,c) + dt*(delta_I*dI[c]._linear_atXYZ(X,Y,Z) +
                                                          nsmoothness* ( coef_a*Uxx + coef_b*Uxy +
                                                                         coef_c*Uxz + coef_d*Uyy +
                                                                         coef_e*Uyz + coef_f*Uzz ))
                                         )/(1 + 2*(coef_a + coef_d + coef_f)*nsmoothness*dt);
                    _energy_regul+=N;
                  }
                  if (is_backward) { // Constraint displacement vectors to stay in image
                    if (U(x,y,z,0)>x) U(x,y,z,0) = (float)x;
                    if (U(x,y,z,1)>y) U(x,y,z,1) = (float)y;
                    if (U(x,y,z,2)>z) U(x,y,z,2) = (float)z;
                    bound = (float)x - _width; if (U(x,y,z,0)<=bound) U(x,y,z,0) = bound;
                    bound = (float)y - _height; if (U(x,y,z,1)<=bound) U(x,y,z,1) = bound;
                    bound = (float)z - _depth; if (U(x,y,z,2)<=bound) U(x,y,z,2) = bound;
                  } else {
                    if (U(x,y,z,0)<-x) U(x,y,z,0) = -(float)x;
                    if (U(x,y,z,1)<-y) U(x,y,z,1) = -(float)y;
                    if (U(x,y,z,2)<-z) U(x,y,z,2) = -(float)z;
                    bound = (float)_width - x; if (U(x,y,z,0)>=bound) U(x,y,z,0) = bound;
                    bound = (float)_height - y; if (U(x,y,z,1)>=bound) U(x,y,z,1) = bound;
                    bound = (float)_depth - z; if (U(x,y,z,2)>=bound) U(x,y,z,2) = bound;
                  }
                  _energy+=delta_I*delta_I + nsmoothness*_energy_regul;
                }
                if (V) cimg_forXYZ(V,_x,_y,_z) if (V(_x,_y,_z,3)) { // Apply constraints
                    U(_x,_y,_z,0) = V(_x,_y,_z,0)/factor;
                    U(_x,_y,_z,1) = V(_x,_y,_z,1)/factor;
                    U(_x,_y,_z,2) = V(_x,_y,_z,2)/factor;
                  }
              }
            }
          } else { // 2D version
            if (smoothness>=0) // Isotropic regularization
              cimg_pragma_openmp(parallel for cimg_openmp_if(_height>=(cimg_openmp_sizefactor)*8 &&
                                                             _width>=(cimg_openmp_sizefactor)*16) reduction(+:_energy))
              cimg_forY(U,y) {
                const int _p1y = y?y - 1:0, _n1y = y<U.height() - 1?y + 1:y;
                cimg_for3X(U,x) {
                  const float
                    X = is_backward?x - U(x,y,0):x + U(x,y,0),
                    Y = is_backward?y - U(x,y,1):y + U(x,y,1);
                  float delta_I = 0, _energy_regul = 0;
                  if (is_backward) cimg_forC(I2,c) delta_I+=(float)(I1._linear_atXY(X,Y,c) - I2(x,y,c));
                  else cimg_forC(I2,c) delta_I+=(float)(I1(x,y,c) - I2._linear_atXY(X,Y,c));
                  cimg_forC(U,c) {
                    const float
                      Ux = 0.5f*(U(_n1x,y,c) - U(_p1x,y,c)),
                      Uy = 0.5f*(U(x,_n1y,c) - U(x,_p1y,c)),
                      Uxx = U(_n1x,y,c) + U(_p1x,y,c),
                      Uyy = U(x,_n1y,c) + U(x,_p1y,c);
                    U(x,y,c) = (float)(U(x,y,c) + dt*(delta_I*dI[c]._linear_atXY(X,Y) +
                                                      smoothness*( Uxx + Uyy )))/(1 + 4*smoothness*dt);
                    _energy_regul+=Ux*Ux + Uy*Uy;
                  }
                  if (is_backward) { // Constraint displacement vectors to stay in image
                    if (U(x,y,0)>x) U(x,y,0) = (float)x;
                    if (U(x,y,1)>y) U(x,y,1) = (float)y;
                    bound = (float)x - _width; if (U(x,y,0)<=bound) U(x,y,0) = bound;
                    bound = (float)y - _height; if (U(x,y,1)<=bound) U(x,y,1) = bound;
                  } else {
                    if (U(x,y,0)<-x) U(x,y,0) = -(float)x;
                    if (U(x,y,1)<-y) U(x,y,1) = -(float)y;
                    bound = (float)_width - x; if (U(x,y,0)>=bound) U(x,y,0) = bound;
                    bound = (float)_height - y; if (U(x,y,1)>=bound) U(x,y,1) = bound;
                  }
                  _energy+=delta_I*delta_I + smoothness*_energy_regul;
                }
                if (V) cimg_forXY(V,_x,_y) if (V(_x,_y,2)) { // Apply constraints
                    U(_x,_y,0) = V(_x,_y,0)/factor;
                    U(_x,_y,1) = V(_x,_y,1)/factor;
                  }
              } else { // Anisotropic regularization
              const float nsmoothness = -smoothness;
              cimg_pragma_openmp(parallel for cimg_openmp_if(_height>=(cimg_openmp_sizefactor)*8 &&
                                                             _width>=(cimg_openmp_sizefactor)*16) reduction(+:_energy))
              cimg_forY(U,y) {
                const int _p1y = y?y - 1:0, _n1y = y<U.height() - 1?y + 1:y;
                cimg_for3X(U,x) {
                  const float
                    X = is_backward?x - U(x,y,0):x + U(x,y,0),
                    Y = is_backward?y - U(x,y,1):y + U(x,y,1);
                  float delta_I = 0, _energy_regul = 0;
                  if (is_backward) cimg_forC(I2,c) delta_I+=(float)(I1._linear_atXY(X,Y,c) - I2(x,y,c));
                  else cimg_forC(I2,c) delta_I+=(float)(I1(x,y,c) - I2._linear_atXY(X,Y,c));
                  cimg_forC(U,c) {
                    const float
                      Ux = 0.5f*(U(_n1x,y,c) - U(_p1x,y,c)),
                      Uy = 0.5f*(U(x,_n1y,c) - U(x,_p1y,c)),
                      N2 = Ux*Ux + Uy*Uy,
                      N = std::sqrt(N2),
                      N3 = 1e-5f + N2*N,
                      coef_a = Uy*Uy/N3,
                      coef_b = -2*Ux*Uy/N3,
                      coef_c = Ux*Ux/N3,
                      Uxx = U(_n1x,y,c) + U(_p1x,y,c),
                      Uyy = U(x,_n1y,c) + U(x,_p1y,c),
                      Uxy = 0.25f*(U(_n1x,_n1y,c) + U(_p1x,_p1y,c) - U(_n1x,_p1y,c) - U(_n1x,_p1y,c));
                    U(x,y,c) = (float)(U(x,y,c) + dt*(delta_I*dI[c]._linear_atXY(X,Y) +
                                                      nsmoothness*( coef_a*Uxx + coef_b*Uxy + coef_c*Uyy )))/
                      (1 + 2*(coef_a + coef_c)*nsmoothness*dt);
                    _energy_regul+=N;
                  }
                  if (is_backward) { // Constraint displacement vectors to stay in image
                    if (U(x,y,0)>x) U(x,y,0) = (float)x;
                    if (U(x,y,1)>y) U(x,y,1) = (float)y;
                    bound = (float)x - _width; if (U(x,y,0)<=bound) U(x,y,0) = bound;
                    bound = (float)y - _height; if (U(x,y,1)<=bound) U(x,y,1) = bound;
                  } else {
                    if (U(x,y,0)<-x) U(x,y,0) = -(float)x;
                    if (U(x,y,1)<-y) U(x,y,1) = -(float)y;
                    bound = (float)_width - x; if (U(x,y,0)>=bound) U(x,y,0) = bound;
                    bound = (float)_height - y; if (U(x,y,1)>=bound) U(x,y,1) = bound;
                  }
                  _energy+=delta_I*delta_I + nsmoothness*_energy_regul;
                }
                if (V) cimg_forXY(V,_x,_y) if (V(_x,_y,2)) { // Apply constraints
                    U(_x,_y,0) = V(_x,_y,0)/factor;
                    U(_x,_y,1) = V(_x,_y,1)/factor;
                  }
              }
            }
          }
          const float d_energy = (_energy - energy)/(sw*sh*sd);
          if (d_energy<=0 && -d_energy<_precision) break;
          if (d_energy>0) dt*=0.5f;
          energy = _energy;
        }
      }
      return U;
    }


    static CImg<T> get_load_cimg(const char *const filename,
                                 const unsigned int n0, const unsigned int n1,
                                 const unsigned int x0, const unsigned int y0,
                                 const unsigned int z0, const unsigned int c0,
                                 const unsigned int x1, const unsigned int y1,
                                 const unsigned int z1, const unsigned int c1,
                                 const char axis='z', const float align=0) {
      return CImg<T>().load_cimg(filename,n0,n1,x0,y0,z0,c0,x1,y1,z1,c1,axis,align);
    }


    CImg<T> operator--(int) {
      const CImg<T> copy(*this,false);
      --*this;
      return copy;
    }


    CImg<T> get_crop(const int x0, const int y0, const int z0,
                     const int x1, const int y1, const int z1,
                     const unsigned int boundary_conditions=0) const {
      return get_crop(x0,y0,z0,0,x1,y1,z1,_spectrum - 1,boundary_conditions);
    }


    static CImg<T>& empty() {
      static CImg<T> _empty;
      return _empty.assign();
    }


      float operator()(const float x, const float y, const float z) const {
        return (float)ref._linear_atXYZ(x,y,z);
      }


       \param n Number of empty images to insert.
       \param pos Index of the insertion.
    **/
    CImgList<T>& insert(const unsigned int n, const unsigned int pos=~0U) {
      CImg<T> empty;
      if (!n) return *this;
      const unsigned int npos = pos==~0U?_width:pos;


    inline int option(const char *const name, const int argc, const char *const *const argv,
                      const int _default, const char *const usage=0) {
      const char *const s = cimg::option(name,argc,argv,(char*)0);
      const int res = s?std::atoi(s):_default;
      char *const tmp = new char[256];
      cimg_snprintf(tmp,256,"%d",res);
      cimg::option(name,0,0,tmp,usage);
      delete[] tmp;
      return res;
    }


    CImg<T>& resize_halfXY() {
      return get_resize_halfXY().move_to(*this);
    }


    static CImg<T> row_vector(const T& a0, const T& a1, const T& a2, const T& a3,
                              const T& a4, const T& a5, const T& a6) {
      CImg<T> r(7,1);
      r[0] = a0; r[1] = a1; r[2] = a2; r[3] = a3; r[4] = a4; r[5] = a5; r[6] = a6;
      return r;
    }


      static double mp_polygon(_cimg_math_parser& mp) {
        const unsigned int i_end = (unsigned int)mp.opcode[2];
        unsigned int ind = (unsigned int)mp.opcode[3];
        if (ind!=~0U) {
          if (!mp.imglist.width()) return cimg::type<double>::nan();
          ind = (unsigned int)cimg::mod((int)_mp_arg(3),mp.imglist.width());
        }
        CImg<T> &img = ind==~0U?mp.imgout:mp.imglist[ind];
        bool is_invalid_arguments = i_end<=4, is_outlined = false;
        if (!is_invalid_arguments) {
          int nbv = (int)_mp_arg(4);
          if (!nbv) is_invalid_arguments = true;
          else {
            if (nbv<0) { nbv = -nbv; is_outlined = true; }
            CImg<intT> points(nbv,2,1,1,0);
            CImg<T> color(img._spectrum,1,1,1,0);
            float opacity = 1;
            unsigned int i = 5, pattern=~0U;
            cimg_foroff(points,k) if (i<i_end) points(k/2,k%2) = (int)cimg::round(_mp_arg(i++));
            else { is_invalid_arguments = true; break; }
            if (!is_invalid_arguments) {
              if (i<i_end) opacity = (float)_mp_arg(i++);
              if (is_outlined && i<i_end) pattern = (unsigned int)_mp_arg(i++);
              cimg_forX(color,k) if (i<i_end) color[k] = (T)_mp_arg(i++);
              else { color.resize(k,1,1,1,-1); break; }
              color.resize(img._spectrum,1,1,1,0,2);
              if (is_outlined) img.draw_polygon(points,color._data,opacity,pattern);
              else img.draw_polygon(points,color._data,opacity);
            }
          }
        }
        if (is_invalid_arguments) {
          CImg<doubleT> args(i_end - 4);
          cimg_forX(args,k) args[k] = _mp_arg(4 + k);
          if (ind==~0U)
            throw CImgArgumentException("[" cimg_appname "_math_parser] CImg<%s>: Function 'polygon()': "
                                        "Invalid arguments '%s'. ",
                                        mp.imgin.pixel_type(),args.value_string()._data);
          else
            throw CImgArgumentException("[" cimg_appname "_math_parser] CImg<%s>: Function 'polygon()': "
                                        "Invalid arguments '#%u%s%s'. ",
                                        mp.imgin.pixel_type(),ind,args._width?",":"",args.value_string()._data);
        }
        return cimg::type<double>::nan();
      }


    const CImg<T>& display_object3d(const char *const title,
                                    const CImg<tp>& vertices,
                                    const CImgList<tf>& primitives,
                                    const bool centering=true,
                                    const int render_static=4, const int render_motion=1,
                                    const bool is_double_sided=true, const float focale=700,
                                    const float light_x=0, const float light_y=0, const float light_z=-5e8f,
                                    const float specular_lightness=0.2f, const float specular_shininess=0.1f,
                                    const bool display_axes=true, float *const pose_matrix=0,
                                    const bool exit_on_anykey=false) const {
      return display_object3d(title,vertices,primitives,CImgList<T>(),centering,
                              render_static,render_motion,is_double_sided,focale,
                              light_x,light_y,light_z,specular_lightness,specular_shininess,
                              display_axes,pose_matrix,exit_on_anykey);
    }


    CImg<T>& slice(const int z0) {
      return slices(z0,z0);
    }


    CImg<T>& operator=(const T& value) {
      return fill(value);
    }


    inline int fdate(const char *const path, T *attr, const unsigned int nb_attr) {
#define _cimg_fdate_err() for (unsigned int i = 0; i<nb_attr; ++i) attr[i] = (T)-1
      int res = -1;
      if (!path || !*path) { _cimg_fdate_err(); return -1; }
      cimg::mutex(6);
#if cimg_OS==2
      HANDLE file = CreateFileA(path,GENERIC_READ,0,0,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,0);
      if (file!=INVALID_HANDLE_VALUE) {
        FILETIME _ft;
        SYSTEMTIME ft;
        if (GetFileTime(file,0,0,&_ft) && FileTimeToSystemTime(&_ft,&ft)) {
          for (unsigned int i = 0; i<nb_attr; ++i) {
            res = (int)(attr[i]==0?ft.wYear:attr[i]==1?ft.wMonth:attr[i]==2?ft.wDay:
                        attr[i]==3?ft.wDayOfWeek:attr[i]==4?ft.wHour:attr[i]==5?ft.wMinute:
                        attr[i]==6?ft.wSecond:-1);
            attr[i] = (T)res;
          }
        } else _cimg_fdate_err();
        CloseHandle(file);
      } else _cimg_fdate_err();
#elif cimg_OS==1
      struct stat st_buf;
      if (!stat(path,&st_buf)) {
        const time_t _ft = st_buf.st_mtime;
        const struct tm& ft = *std::localtime(&_ft);
        for (unsigned int i = 0; i<nb_attr; ++i) {
          res = (int)(attr[i]==0?ft.tm_year + 1900:attr[i]==1?ft.tm_mon + 1:attr[i]==2?ft.tm_mday:
                      attr[i]==3?ft.tm_wday:attr[i]==4?ft.tm_hour:attr[i]==5?ft.tm_min:
                      attr[i]==6?ft.tm_sec:-1);
          attr[i] = (T)res;
        }
      } else _cimg_fdate_err();
#endif
      cimg::mutex(6,0);
      return res;
    }


      static double mp_linear_add(_cimg_math_parser& mp) {
        return _mp_arg(2)*_mp_arg(3) + _mp_arg(4);
      }


_functor4d_int(const CImg<T>& pref):ref(pref) {}


int trylock(const unsigned int n) { return pthread_mutex_trylock(&mutex[n]); }


       \param fps Number of desired frames per second.
       \param nb_loops Number of loops (\c 0 for infinite looping).
    **/
    const CImgList<T>& save_gif_external(const char *const filename, const float fps=25,
                                         const unsigned int nb_loops=0) {
      CImg<charT> command(1024), filename_tmp(256), filename_tmp2(256);
      CImgList<charT> filenames;
      std::FILE *file = 0;

#ifdef cimg_use_png
#define _cimg_save_gif_extension "png"
#else
#define _cimg_save_gif_extension "ppm"
#endif

      do {
        cimg_snprintf(filename_tmp,filename_tmp._width,"%s%c%s",
                      cimg::temporary_path(),cimg_file_separator,cimg::filenamerand());
        cimg_snprintf(filename_tmp2,filename_tmp2._width,"%s_000001." _cimg_save_gif_extension,filename_tmp._data);
        if ((file=cimg::std_fopen(filename_tmp2,"rb"))!=0) cimg::fclose(file);
      } while (file);
      cimglist_for(*this,l) {
        cimg_snprintf(filename_tmp2,filename_tmp2._width,"%s_%.6u." _cimg_save_gif_extension,filename_tmp._data,l + 1);
        CImg<charT>::string(filename_tmp2).move_to(filenames);
        if (_data[l]._depth>1 || _data[l]._spectrum!=3) _data[l].get_resize(-100,-100,1,3).save(filename_tmp2);
        else _data[l].save(filename_tmp2);
      }
      cimg_snprintf(command,command._width,"\"%s\" -delay %u -loop %u",
                    cimg::imagemagick_path(),(unsigned int)std::max(0.f,cimg::round(100/fps)),nb_loops);
      CImg<ucharT>::string(command).move_to(filenames,0);
      cimg_snprintf(command,command._width,"\"%s\"",
                    CImg<charT>::string(filename)._system_strescape().data());
      CImg<ucharT>::string(command).move_to(filenames);
      CImg<charT> _command = filenames>'x';
      cimg_for(_command,p,char) if (!*p) *p = ' ';
      _command.back() = 0;

      cimg::system(_command, cimg::imagemagick_path());
      file = cimg::std_fopen(filename,"rb");
      if (!file)
        throw CImgIOException(_cimglist_instance
                              "save_gif_external(): Failed to save file '%s' with external command 'magick/convert'.",
                              cimglist_instance,
                              filename);
      else cimg::fclose(file);


    CImg<T>& load_cimg(std::FILE *const file,
                       const unsigned int n0, const unsigned int n1,
                       const unsigned int x0, const unsigned int y0,
                       const unsigned int z0, const unsigned int c0,
                       const unsigned int x1, const unsigned int y1,
                       const unsigned int z1, const unsigned int c1,
                       const char axis='z', const float align=0) {
      CImgList<T> list;
      list.load_cimg(file,n0,n1,x0,y0,z0,c0,x1,y1,z1,c1);
      if (list._width==1) return list[0].move_to(*this);
      return assign(list.get_append(axis,align));
    }


    CImg<T> operator<<(const CImg<t>& img) const {
      return (+*this)<<=img;
    }


    CImg<T>& fillY(const unsigned int x, const unsigned int z, const unsigned int c, const double a0, ...) {
      if (x<_width && z<_depth && c<_spectrum) _cimg_fill1(x,0,z,c,_width,_height,double);
      return *this;
    }


    CImg<T>& operator|=(const char *const expression) {
      return *this|=(+*this)._fill(expression,true,1,0,0,"operator|=",this);
    }


    CImg<T>& solve(const CImg<t>& A, const bool use_LU=false) {
      if (_depth!=1 || _spectrum!=1 || _height!=A._height || A._depth!=1 || A._spectrum!=1)
        throw CImgArgumentException(_cimg_instance
                                    "solve(): Instance and specified matrix (%u,%u,%u,%u,%p) have "
                                    "incompatible dimensions.",
                                    cimg_instance,
                                    A._width,A._height,A._depth,A._spectrum,A._data);
      typedef _cimg_Ttfloat Ttfloat;

      if (A.size()==1) return (*this)/=A[0];
      if (A._width==2 && A._height==2 && _height==2) { // 2x2 linear system
        const double a = (double)A[0], b = (double)A[1], c = (double)A[2], d = (double)A[3],
          fa = std::fabs(a), fb = std::fabs(b), fc = std::fabs(c), fd = std::fabs(d),
          det = a*d - b*c, fM = cimg::max(fa,fb,fc,fd);
        if (fM==fa)
          cimg_pragma_openmp(parallel for cimg_openmp_if(_width>=256))
          cimg_forX(*this,k) {
            const double u = (double)(*this)(k,0), v = (double)(*this)(k,1), y = (a*v - c*u)/det;
            (*this)(k,0) = (T)((u - b*y)/a); (*this)(k,1) = (T)y;
          } else if (fM==fc)
          cimg_pragma_openmp(parallel for cimg_openmp_if(_width>=256))
          cimg_forX(*this,k) {
            const double u = (double)(*this)(k,0), v = (double)(*this)(k,1), y = (a*v - c*u)/det;
            (*this)(k,0) = (T)((v - d*y)/c); (*this)(k,1) = (T)y;
          } else if (fM==fb)
          cimg_pragma_openmp(parallel for cimg_openmp_if(_width>=256))
          cimg_forX(*this,k) {
            const double u = (double)(*this)(k,0), v = (double)(*this)(k,1), x = (d*u - b*v)/det;
            (*this)(k,0) = (T)x; (*this)(k,1) = (T)((u - a*x)/b);
          } else
          cimg_pragma_openmp(parallel for cimg_openmp_if(_width>=256))
          cimg_forX(*this,k) {
            const double u = (double)(*this)(k,0), v = (double)(*this)(k,1), x = (d*u - b*v)/det;
            (*this)(k,0) = (T)x; (*this)(k,1) = (T)((v - c*x)/d);
          }
        return *this;
      }

      if (A._width==A._height) { // Square linear system
#ifdef cimg_use_lapack
        char TRANS = 'N';
        int INFO, N = _height, LWORK = 4*N, *const IPIV = new int[N];
        Ttfloat
          *const lapA = new Ttfloat[N*N],
          *const lapB = new Ttfloat[N],
          *const WORK = new Ttfloat[LWORK];
        cimg_forXY(A,k,l) lapA[k*N + l] = (Ttfloat)(A(k,l));
        cimg_forX(*this,i) {
          cimg_forY(*this,j) lapB[j] = (Ttfloat)((*this)(i,j));
          cimg::getrf(N,lapA,IPIV,INFO);
          if (INFO)
            cimg::warn(_cimg_instance
                       "solve(): LAPACK library function dgetrf_() returned error code %d.",
                       cimg_instance,
                       INFO);
          else {
            cimg::getrs(TRANS,N,lapA,IPIV,lapB,INFO);
            if (INFO)
              cimg::warn(_cimg_instance
                         "solve(): LAPACK library function dgetrs_() returned error code %d.",
                         cimg_instance,
                         INFO);
          }
          if (!INFO) cimg_forY(*this,j) (*this)(i,j) = (T)(lapB[j]); else cimg_forY(*this,j) (*this)(i,j) = (T)0;
        }
        delete[] IPIV; delete[] lapA; delete[] lapB; delete[] WORK;
#else
        CImg<Ttfloat> lu(A,false);
        CImg<Ttfloat> indx;
        bool d;
        lu._LU(indx,d);
        CImg<T> res(_width,A._width);
        cimg_pragma_openmp(parallel for cimg_openmp_if_size(_width*_height,16))
          cimg_forX(*this,i) res.draw_image(i,get_column(i)._solve(lu,indx));
        res.move_to(*this);
#endif
      } else { // Least-square solution for non-square systems

#ifdef cimg_use_lapack
        char TRANS = 'N';
        int INFO, N = A._width, M = A._height, LWORK = -1, LDA = M, LDB = M, NRHS = _width;
        Ttfloat WORK_QUERY;
        Ttfloat
          * const lapA = new Ttfloat[M*N],
          * const lapB = new Ttfloat[M*NRHS];
        cimg::sgels(TRANS, M, N, NRHS, lapA, LDA, lapB, LDB, &WORK_QUERY, LWORK, INFO);
        LWORK = (int) WORK_QUERY;
        Ttfloat *const WORK = new Ttfloat[LWORK];
        cimg_forXY(A,k,l) lapA[k*M + l] = (Ttfloat)(A(k,l));
        cimg_forXY(*this,k,l) lapB[k*M + l] = (Ttfloat)((*this)(k,l));
        cimg::sgels(TRANS, M, N, NRHS, lapA, LDA, lapB, LDB, WORK, LWORK, INFO);
        if (INFO != 0)
          cimg::warn(_cimg_instance
                     "solve(): LAPACK library function sgels() returned error code %d.",
                     cimg_instance,
                     INFO);
        assign(NRHS, N);
        if (!INFO) cimg_forXY(*this,k,l) (*this)(k,l) = (T)lapB[k*M + l];
        else (A.get_pseudoinvert(use_LU)*(*this)).move_to(*this);
        delete[] lapA; delete[] lapB; delete[] WORK;
#else
        (A.get_pseudoinvert(use_LU)*(*this)).move_to(*this);
#endif
      }
      return *this;
    }


    CImg<T>& projections2d(const unsigned int x0, const unsigned int y0, const unsigned int z0) {
      if (_depth<2) return *this;
      return get_projections2d(x0,y0,z0).move_to(*this);
    }


    bool is_sameXZ(const CImg<t>& img) const {
      return is_sameXZ(img._width,img._depth);
    }


    CImg<T>& assign(const T *const values, const unsigned int size_x, const unsigned int size_y=1,
                    const unsigned int size_z=1, const unsigned int size_c=1) {
      const size_t siz = safe_size(size_x,size_y,size_z,size_c);
      if (!values || !siz) return assign();
      const size_t curr_siz = (size_t)size();
      if (values==_data && siz==curr_siz) return assign(size_x,size_y,size_z,size_c);
      if (_is_shared || values + siz<_data || values>=_data + size()) {
        assign(size_x,size_y,size_z,size_c);
        if (_is_shared) std::memmove((void*)_data,(void*)values,siz*sizeof(T));
        else std::memcpy((void*)_data,(void*)values,siz*sizeof(T));
      } else {
        T *new_data = 0;
        try { new_data = new T[siz]; } catch (...) {
          _width = _height = _depth = _spectrum = 0; _data = 0;
          throw CImgInstanceException(_cimg_instance
                                      "assign(): Failed to allocate memory (%s) for image (%u,%u,%u,%u).",
                                      cimg_instance,
                                      cimg::strbuffersize(sizeof(T)*size_x*size_y*size_z*size_c),
                                      size_x,size_y,size_z,size_c);
        }
        std::memcpy((void*)new_data,(void*)values,siz*sizeof(T));
        delete[] _data; _data = new_data; _width = size_x; _height = size_y; _depth = size_z; _spectrum = size_c;
      }
      return *this;
    }


    CImg<T>& discard(const char axis=0) {
      return get_discard(axis).move_to(*this);
    }


    explicit CImgDisplay(const CImg<T>& img,
                         const char *const title=0, const unsigned int normalization=3,
                         const bool is_fullscreen=false, const bool is_closed=false):
      _width(0),_height(0),_normalization(0),
      _min(0),_max(0),
      _is_fullscreen(false),
      _title(0),
      _window_width(0),_window_height(0),_button(0),
      _keys(new unsigned int[128]),_released_keys(new unsigned int[128]),
      _window_x(cimg::type<int>::min()),_window_y(cimg::type<int>::min()),
      _mouse_x(-1),_mouse_y(-1),_wheel(0),
      _is_closed(true),_is_resized(false),_is_moved(false),_is_event(false) {
      assign(img,title,normalization,is_fullscreen,is_closed);
    }


    inline void getrf(int &N, float *lapA, int *IPIV, int &INFO) {
      sgetrf_(&N,&N,lapA,&N,IPIV,&INFO);
    }


    inline int abs(const unsigned char a) {
      return (int)a;
    }


      float operator()(const float x, const float y, const float z) const {
        return (float)ref((int)x,(int)y,(int)z);
      }


    const T& operator()(const unsigned int x) const {
      return _data[x];
    }


    explicit CImg(const CImgDisplay &disp):_width(0),_height(0),_depth(0),_spectrum(0),_is_shared(false),_data(0) {
      disp.snapshot(*this);
    }


    T _cubic_atX_c(const float fx, const int y, const int z, const int c) const {
      return cimg::type<T>::cut(_cubic_atX(fx,y,z,c));
    }


    CImg<T>& draw_ellipse(const int x0, const int y0, const float r1, const float r2, const float angle,
                          const tc *const color, const float opacity=1) {
      return _draw_ellipse(x0,y0,r1,r2,angle,color,opacity,0U,true);
    }


Mutex_static() { for (unsigned int i = 0; i<32; ++i) pthread_mutex_init(&mutex[i],0); }


    CImg<T>& draw_triangle(int x0, int y0,
                           int x1, int y1,
                           int x2, int y2,
                           const CImg<tc>& texture,
                           int tx0, int ty0,
                           int tx1, int ty1,
                           int tx2, int ty2,
                           float bs0,
                           float bs1,
                           float bs2,
                           const float opacity=1) {
      if (is_empty()) return *this;
      if (texture._depth>1 || texture._spectrum<_spectrum)
        throw CImgArgumentException(_cimg_instance
                                    "draw_triangle(): Invalid specified texture (%u,%u,%u,%u,%p).",
                                    cimg_instance,
                                    texture._width,texture._height,texture._depth,texture._spectrum,texture._data);
      if (is_overlapped(texture))
        return draw_triangle(x0,y0,x1,y1,x2,y2,+texture,tx0,ty0,tx1,ty1,tx2,ty2,
                             bs0,bs1,bs2,opacity);

      if (y0>y1) cimg::swap(x0,x1,y0,y1,tx0,tx1,ty0,ty1,bs0,bs1);
      if (y0>y2) cimg::swap(x0,x2,y0,y2,tx0,tx2,ty0,ty2,bs0,bs2);
      if (y1>y2) cimg::swap(x1,x2,y1,y2,tx1,tx2,ty1,ty2,bs1,bs2);
      if (y2<0 || y0>=height() || cimg::min(x0,x1,x2)>=width() || cimg::max(x0,x1,x2)<0 || !opacity) return *this;

      const int
        w1 = width() - 1, h1 = height() - 1,
        dx01 = x1 - x0, dx02 = x2 - x0, dx12 = x2 - x1,
        dy01 = std::max(1,y1 - y0), dy02 = std::max(1,y2 - y0), dy12 = std::max(1,y2 - y1),
        cy0 = cimg::cut(y0,0,h1), cy2 = cimg::cut(y2,0,h1),
        hdy01 = dy01*cimg::sign(dx01)/2, hdy02 = dy02*cimg::sign(dx02)/2, hdy12 = dy12*cimg::sign(dx12)/2,
        dtx01 = tx1 - tx0, dtx02 = tx2 - tx0, dtx12 = tx2 - tx1,
        dty01 = ty1 - ty0, dty02 = ty2 - ty0, dty12 = ty2 - ty1,
        hdy01tx = dy01*cimg::sign(dtx01)/2, hdy02tx = dy02*cimg::sign(dtx02)/2, hdy12tx = dy12*cimg::sign(dtx12)/2,
        hdy01ty = dy01*cimg::sign(dty01)/2, hdy02ty = dy02*cimg::sign(dty02)/2, hdy12ty = dy12*cimg::sign(dty12)/2;
      const float dbs01 = bs1 - bs0, dbs02 = bs2 - bs0, dbs12 = bs2 - bs1;

      const ulongT twhd = (ulongT)texture._width*texture._height*texture._depth;
      cimg_init_scanline(opacity);

      for (int y = cy0; y<=cy2; ++y) {
        const int yy0 = y - y0, yy1 = y - y1;
        int
          xm = y<y1?x0 + (dx01*yy0 + hdy01)/dy01:x1 + (dx12*yy1 + hdy12)/dy12,
          xM = x0 + (dx02*yy0 + hdy02)/dy02,
          txm = y<y1?tx0 + (dtx01*yy0 + hdy01tx)/dy01:tx1 + (dtx12*yy1 + hdy12tx)/dy12,
          txM = tx0 + (dtx02*yy0 + hdy02tx)/dy02,
          tym = y<y1?ty0 + (dty01*yy0 + hdy01ty)/dy01:ty1 + (dty12*yy1 + hdy12ty)/dy12,
          tyM = ty0 + (dty02*yy0 + hdy02ty)/dy02;
        float
          bsm = y<y1?(bs0 + dbs01*yy0/dy01):(bs1 + dbs12*yy1/dy12),
          bsM = bs0 + dbs02*yy0/dy02;
        if (xm>xM) cimg::swap(xm,xM,txm,txM,tym,tyM,bsm,bsM);
        if (xM>=0 && xm<=w1) {
          const int
            cxm = cimg::cut(xm,0,w1),
            cxM = cimg::cut(xM,0,w1);
          T *ptrd = data(cxm,y);
          const int
            dxmM = std::max(1,xM - xm), hdxmM = dxmM/2,
            dtxmM = txM - txm, dtymM = tyM - tym;
          const float dbsmM = bsM - bsm;

          for (int x = cxm; x<=cxM; ++x) {
            const int
              xxm = x - xm,
              tx = (txm*dxmM + dtxmM*xxm + hdxmM)/dxmM,
              ty = (tym*dxmM + dtymM*xxm + hdxmM)/dxmM;
            const float cbs = cimg::cut(bsm + dbsmM*xxm/dxmM,0,2);
            const tc *const color = &texture._atXY(tx,ty);
            cimg_forC(*this,c) {
              const tc col = color[c*twhd];
              const Tfloat val = cbs<=1?cbs*col:(2 - cbs)*col + (cbs - 1)*_sc_maxval;
              ptrd[c*_sc_whd] = (T)(opacity>=1?val:val*_sc_nopacity + ptrd[c*_sc_whd]*_sc_copacity);
            }
            ++ptrd;
          }
        }
      }
      return *this;
    }


    CImg<T>& draw_axes(const CImg<tx>& values_x, const CImg<ty>& values_y,
                       const tc *const color, const float opacity=1,
                       const unsigned int pattern_x=~0U, const unsigned int pattern_y=~0U,
                       const unsigned int font_height=13, const bool allow_zero=true,
                       const float round_x=0, const float round_y=0) {
      if (is_empty()) return *this;
      const CImg<tx> nvalues_x(values_x._data,values_x.size(),1,1,1,true);
      const int sizx = (int)values_x.size() - 1, wm1 = width() - 1;
      if (sizx>=0) {
        float ox = (float)*nvalues_x;
        for (unsigned int x = sizx?1U:0U; x<_width; ++x) {
          const float nx = (float)nvalues_x._linear_atX((float)x*sizx/wm1);
          if (nx*ox<=0) {
            draw_axis(nx==0?x:x - 1,values_y,color,opacity,pattern_y,font_height,allow_zero,round_y);
            break;
          }
          ox = nx;
        }
      }
      const CImg<ty> nvalues_y(values_y._data,values_y.size(),1,1,1,true);
      const int sizy = (int)values_y.size() - 1, hm1 = height() - 1;
      if (sizy>0) {
        float oy = (float)nvalues_y[0];
        for (unsigned int y = sizy?1U:0U; y<_height; ++y) {
          const float ny = (float)nvalues_y._linear_atX((float)y*sizy/hm1);
          if (ny*oy<=0) {
            draw_axis(values_x,ny==0?y:y - 1,color,opacity,pattern_x,font_height,allow_zero,round_x);
            break;
          }
          oy = ny;
        }
      }
      return *this;
    }


    static void wait(CImgDisplay& disp1, CImgDisplay& disp2, CImgDisplay& disp3, CImgDisplay& disp4) {
      disp1._is_event = disp2._is_event = disp3._is_event = disp4._is_event = false;
      while ((!disp1._is_closed || !disp2._is_closed || !disp3._is_closed || !disp4._is_closed) &&
             !disp1._is_event && !disp2._is_event && !disp3._is_event && !disp4._is_event) wait_all();
    }


    CImg<T>& autocrop(const T& value, const char *const axes="czyx") {
      if (is_empty()) return *this;
      for (const char *s = axes; *s; ++s) {
        const char axis = cimg::lowercase(*s);
        const CImg<intT> coords = _autocrop(value,axis);
        if (coords[0]==-1 && coords[1]==-1) return assign(); // Image has only 'value' pixels
        else switch (axis) {
        case 'x' : {
          const int x0 = coords[0], x1 = coords[1];
          if (x0>=0 && x1>=0) crop(x0,x1);
        } break;
        case 'y' : {
          const int y0 = coords[0], y1 = coords[1];
          if (y0>=0 && y1>=0) crop(0,y0,_width - 1,y1);
        } break;
        case 'z' : {
          const int z0 = coords[0], z1 = coords[1];
          if (z0>=0 && z1>=0) crop(0,0,z0,_width - 1,_height - 1,z1);
        } break;
        default : {
          const int c0 = coords[0], c1 = coords[1];
          if (c0>=0 && c1>=0) crop(0,0,0,c0,_width - 1,_height - 1,_depth - 1,c1);
        }
        }
      }
      return *this;
    }


    CImgDisplay& assign() {
      return flush();
    }


      static double mp_ellipse(_cimg_math_parser& mp) {
        mp_check_list(mp,"ellipse");
        const unsigned int i_end = (unsigned int)mp.opcode[2];
        unsigned int ind = (unsigned int)mp.opcode[3];
        if (ind!=~0U) {
          if (!mp.imglist.width()) return cimg::type<double>::nan();
          ind = (unsigned int)cimg::mod((int)_mp_arg(3),mp.imglist.width());
        }
        CImg<T> &img = ind==~0U?mp.imgout:mp.imglist[ind];
        CImg<T> color(img._spectrum,1,1,1,0);
        bool is_invalid_arguments = false, is_outlined = false;
        float r1 = 0, r2 = 0, angle = 0, opacity = 1;
        unsigned int i = 4, pattern = ~0U;
        int x0 = 0, y0 = 0;
        if (i>=i_end) is_invalid_arguments = true;
        else {
          x0 = (int)cimg::round(_mp_arg(i++));
          if (i>=i_end) is_invalid_arguments = true;
          else {
            y0 = (int)cimg::round(_mp_arg(i++));
            if (i>=i_end) is_invalid_arguments = true;
            else {
              r1 = (float)_mp_arg(i++);
              if (i>=i_end) r2 = r1;
              else {
                r2 = (float)_mp_arg(i++);
                if (i<i_end) {
                  angle = (float)(_mp_arg(i++)*180/cimg::PI);
                  if (i<i_end) {
                    opacity = (float)_mp_arg(i++);
                    if (r1<0 && r2<0) {
                      pattern = (unsigned int)_mp_arg(i++);
                      is_outlined = true;
                      r1 = -r1; r2 = -r2;
                    }
                    if (i<i_end) {
                      cimg_forX(color,k) if (i<i_end) color[k] = (T)_mp_arg(i++);
                      else { color.resize(k,1,1,1,-1); break; }
                      color.resize(img._spectrum,1,1,1,0,2);
                    }
                  }
                }
              }
            }
          }
        }
        if (!is_invalid_arguments) {
          if (is_outlined) img.draw_ellipse(x0,y0,r1,r2,angle,color._data,opacity,pattern);
          else img.draw_ellipse(x0,y0,r1,r2,angle,color._data,opacity);
        } else {
          CImg<doubleT> args(i_end - 4);
          cimg_forX(args,k) args[k] = _mp_arg(4 + k);
          if (ind==~0U)
            throw CImgArgumentException("[" cimg_appname "_math_parser] CImg<%s>: Function 'ellipse()': "
                                        "Invalid arguments '%s'. ",
                                        mp.imgin.pixel_type(),args.value_string()._data);
          else
            throw CImgArgumentException("[" cimg_appname "_math_parser] CImg<%s>: Function 'ellipse()': "
                                        "Invalid arguments '#%u%s%s'. ",
                                        mp.imgin.pixel_type(),ind,args._width?",":"",args.value_string()._data);
        }
        return cimg::type<double>::nan();
      }


      static double mp_vector_map_sv(_cimg_math_parser& mp) { // Operator(scalar,vector)
        unsigned int
          siz = (unsigned int)mp.opcode[2],
          ptrs = (unsigned int)mp.opcode[5] + 1;
        double *ptrd = &_mp_arg(1) + 1;
        mp_func op = (mp_func)mp.opcode[3];
        CImg<ulongT> l_opcode(4);
        l_opcode[2] = mp.opcode[4]; // Scalar argument1
        l_opcode.swap(mp.opcode);
        ulongT &argument2 = mp.opcode[3];
        while (siz-->0) { argument2 = ptrs++; *(ptrd++) = (*op)(mp); }
        l_opcode.swap(mp.opcode);
        return cimg::type<double>::nan();
      }


    CImg<T>& load_bmp(std::FILE *const file) {
      return _load_bmp(file,0);
    }


      static double mp_complex_pow_vs(_cimg_math_parser& mp) {
        const double *ptr1 = &_mp_arg(2) + 1, val2 = _mp_arg(3);
        double *ptrd = &_mp_arg(1) + 1;
        _mp_complex_pow(ptr1[0],ptr1[1],val2,0,ptrd);
        return cimg::type<double>::nan();
      }


    CImg<T>& fill(const T& val0, const T& val1, const T& val2, const T& val3, const T& val4, const T& val5,
                  const T& val6, const T& val7, const T& val8, const T& val9, const T& val10, const T& val11) {
      if (is_empty()) return *this;
      T *ptrd, *ptre = end() - 11;
      for (ptrd = _data; ptrd<ptre; ) {
        *(ptrd++) = val0; *(ptrd++) = val1; *(ptrd++) = val2; *(ptrd++) = val3; *(ptrd++) = val4; *(ptrd++) = val5;
        *(ptrd++) = val6; *(ptrd++) = val7; *(ptrd++) = val8; *(ptrd++) = val9; *(ptrd++) = val10; *(ptrd++) = val11;
      }
      ptre+=11;
      switch (ptre - ptrd) {
      case 11 : *(--ptre) = val10; // fallthrough
      case 10 : *(--ptre) = val9; // fallthrough
      case 9 : *(--ptre) = val8; // fallthrough
      case 8 : *(--ptre) = val7; // fallthrough
      case 7 : *(--ptre) = val6; // fallthrough
      case 6 : *(--ptre) = val5; // fallthrough
      case 5 : *(--ptre) = val4; // fallthrough
      case 4 : *(--ptre) = val3; // fallthrough
      case 3 : *(--ptre) = val2; // fallthrough
      case 2 : *(--ptre) = val1; // fallthrough
      case 1 : *(--ptre) = val0; // fallthrough
      }
      return *this;
    }


      static double mp_image_h(_cimg_math_parser& mp) {
        unsigned int ind = (unsigned int)mp.opcode[2];
        if (ind!=~0U) {
          if (!mp.imglist.width()) return cimg::type<double>::nan();
          ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.imglist.width());
        }
        const CImg<T> &img = ind==~0U?mp.imgout:mp.imglist[ind];
        return (double)img.height();
      }


    CImg<T>& xyYtoRGB(const bool use_D65=true) {
      return xyYtoXYZ().XYZtoRGB(use_D65);
    }


    inline t minabs(const t& a, const t& b, const t& abs_b) {
      return abs_b<cimg::abs(a)?b:a;
    }


      static double mp_self_bitwise_and(_cimg_math_parser& mp) {
        double &val = _mp_arg(1);
        return val = (double)((longT)val & (longT)_mp_arg(2));
      }


    CImg<T>& assign(const CImgDisplay &disp) {
      disp.snapshot(*this);
      return *this;
    }


    CImg<T>& draw_image(const int x0, const int y0,
                        const CImg<t>& sprite, const float opacity=1) {
      return draw_image(x0,y0,0,sprite,opacity);
    }